From 44975d58a7702c03aadbf362608390f5fe74f859 Mon Sep 17 00:00:00 2001 From: Katie Pearson Date: Tue, 17 Sep 2024 07:07:21 -0700 Subject: [PATCH] Update weevil portal with newest code (#1722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bugfix/3.1/map search polygon snapping (#1314) * adds condition to snap leaflet map if shape exists before snapping to points so that likely hood of point in view increases * added min zoom to leafelt map so that if no bounds are specified the view is better * Pencil fix (#1335) * Fix spacing issue * Fix size of pencil icon * Hotfix 2024-05-17 DwC-A Publishing - Fix issue with file handler being closed prematurely * Media API development - Convert Media POST and PUT data input from parameter values to a data object passed within the body - Some improvement with response documentation - Fix duplicate operationId values - Rebuild Swagger API documentation * Coloring of Non Observation Points with Direct Updating (#1337) * uses leaflet path method to set color so that it will update when group is not re drawn closes 1319 * close #1320, addes check for catnum * Bugfix/collection meta data status fix (#1334) * adds check for duplicate collectionCode / institutionalCode key when updating collection meta data and gives status msg upon failure closes #1311 * changes wording of duplicate error * link error to new tab * fixes lang tag error for collmetadata.php title * closes #1326, fixes snapping on map with and without circle (#1338) * Update searchform.js (#1336) * Update searchform.js Fix sessionStorage.querystring bug. Javascript was not handling setting db (collections) checkbox array from user sessionStorage. * Update searchform.js Correcting variable name * Revert "Update searchform.js (#1336)" (#1342) This reverts commit 79e2a1b0da2d03e29d2f3658a936279252edce6b. * removed overarching form tag because it wasn't on both sponsorship forms closes #1329 (#1341) * Fix Occur Editor Nav visiblity with no results closes #1333 (#1343) * cloFix Occur Editor Nav visiblity with no results closes #1333 Shifts conditional hiding search form down a level so it only blocks tabs that way navigation is still possible when there is no query results * shifts formating to correct from one less conditional * occurrence editor images tab (#1350) The image info was being pushed down to the baseline of the displayed image. This fix creates a new class that resets the cell alignment to top aligned. * Update ProfileManager.php (#1347) * Update ProfileManager.php Confirms if 'accessibilityPref' is set in user's dynamic properties and sets AccessibilityPreference to false by default if not. * Fits Table to row size when Smaller than 80vh closes #1340 (#1348) # Issue 1340 # Summary This should fit the bottom scroll bar into view when their is few results. * [3.1] Font unification (#1301) * remove some font-family references * remove in-line styling for font-family in all files not .scss, .css, vendor, .js, .less, .html, or .map * remove from one of the fieldset-likes * add reference to font-family in reset.css * remove unecessary font-size restriction on fields-available chips * 3.1 search page flow bugfix (#1351) * fix the page flow bugs but confirm existing collection persistance issues * display which collections are to be queried in harvest params * solve the collection selection for the new search page * clean up and subfunctionalize * slight refactor to improve readability * add minor aesthetic improvement * add styling class * remove collection display in harvest params * remove translations that are now cruft * Occurrence Dataset Refactor * Revert "Occurrence Dataset Refactor" This reverts commit 6580a7a57919f986f24890ebe559fb7eeedc5e17. * othercatalogNumber parsing bug fix - Parse tags and values found within omoccurrences:otherCatalogNumbers field and display within additional identifiers table within the occurrence editor. If form is saved, parsed values are saved within omoccuridentiers table. * Taxonomy remap bug - Fix issue with taxon remapping/merging into another taxon failing due to key violations within taxstatus (relationship already exists). This is another issue with PHP 8.2 throwing fatal error for mysql warnings. Adding an IGNORE to statement solves the issue since already existing relationships don't need to be transferred, and will be deleted when secondary taxon is deleted. * 3.1 hamburger menu fix (#1363) * Update header.css Center hamburger menu and flow menu content into body so that menu content is scrollable on small screens. Remove transitions Reduce padding around hamburger to unblock 'Home' menu link * order alphabetically and then by string length (#1358) * Merge info button (#1368) * Add dialog box with info about merging records * Remove redundant lang tags Discovered when finding lang tags corresponding to "more information" via dialog box code that I copied * Change dialog box to a link to Symbiota Docs Seems more consistent with where we're going now * Update defaultinvoice.php (#1370) $loanType is a string - was being sanitized as a Int. Variable is not used in any output to client, so should be safe to just use for comparison without filtering. * Taxonomy Editor format adjustment - Minor adjustment to display of parent taxon to fix issue with lack of space between scientific name and author, with additional minor improvements - Minor improvement to only italicize taxa at genus rank or above - Move htmlspecailchars and formatting into class assignment so that rankid can be used to decide formating * Update ImageLocalProcessor.php (#1372) php current() expects an array while parse_url with PHP_URL_PATH paramter should return a string or false. This changes were also committed to hotfix-2024-05-22 * Hotfix 2024 05 22 (#1374) * Hotfix 2024-05-22 - Checklist management: -- Adjust taxon merge function to accommodate PHP v8.1 fatal exceptions during foreign key conflicts within SQL update statements, which previously simply returned false value with a warning - Image processing: -- Bug resolution to avoid fatal error where an array function is incorrectly used on a string. Resolves issue https://github.com/BioKIC/Symbiota/issues/1366 - Occurrence Mapping: -- Fix bug interfering with the exclusion of protected occurrences within the mapping tools when user is not approved to view these records. Associated with issue: https://github.com/BioKIC/Symbiota/issues/1375 Co-authored-by: Greg Post * 3.1-upload-bug - Avoid fatal error that are now thrown by PHP v8.1+ instead of the previous warning * Bugfix/3.1/collection list map clustering (#1373) * reroute map result to map search b/c this is fully featured * closes #1345 add add reference marker functionality to map/index so that no funtionality is lost * inversing switch so logic works out * default clustering off * moved cluster switch on by default from needing param and it just defaults to no clustering * oFixes Map Search Auto searching with no Parameters (#1388) # Summary Only adds lat long data restrictions if something is present in the `sqlWhere` property. That way if checks to sqlWhere don't fire with everything searches ever. * Bugfix/3.1/record search contrast (#1386) * add important to screen reader only class * fixing contrast adding styling class for the insert border labels * collection search uses body color instead of grey for better contrast * updating screen-reader-only for base.css * fixing use of data-labels with content to just normal spans * focus now uses medium color for contrast * adds guard to do either 'AND' or 'WHERE' if $sqlWhere is empty (#1391) * adds guard to do either 'AND' or 'WHERE' if $sqlWhere is empty * fix accidental populating of sqlWhere in setGeoSqlWhere --------- Co-authored-by: Mark * [3.1] Merge Duplicate Record bugfix (#1344) * swaps all the merge record queries to be prepared statements (mostly) and puts them in a try catch so blank errors don't occur on the user side * Add $collId as arg for dupManager merge Records since collId would be undefined otherwise and fixed control flow to only navigate you when error occurs * Adds Ignores to mergeRecord and Error Control Flow to deleteOccurrence * Add forgotten lang tags for dupManager * saving determinations work * determination history priortizes source records current determinations * adding quote filtering for taxa and taxonFilter to prevent link onmouse over xss (#1380) * make sure the formats are in explicitly in a whitelist in an attempt to make burpsuite happy (#1394) * [3.1] - burpsuite - emlhandler fix: adds is_numeric checker for collid in collections/datasets/emlhandler… (#1398) * adds is_numeric checker for collid in collections/datasets/emlhandler.php * respond to code review feedback. If it does not pass the is_numeric check off the bat, it probably should not be treated as valid * [3.1] - select collections from the url if present (#1400) * select collections from the url if present * DRY up sanitizedCollectionSource section, which has the same pattern as occurs elsewhere * fix typo * change default to empty array * add better type checking to collIdsFromUrl * Update ImInventories.php * fixed the icons max-width value (#1410) * attempt to fix the injection issues in collections/list.php (#1401) * attempt to fix the injection issues * change setSciNamesByVerns to use prepared statement * remove extra htmlspecialchars call now that prepared statement is in place * removed extra spaces --------- Co-authored-by: Mark * Fixes Geothesaurus Searching Closes #1396 (#1404) # Issue #1396 # Summary Adds Parameter to let rpc call reduce output to distict results Fixes parent dependent search detection so that initial id is used forever even after the parent param changes * sort by lenght so more exact match comes up when typed (#1416) Issue came up when `calif` would show `Baja California` before `California` because it was sorted by alphabetical but it really should be the more exact match ie the smaller one usually first * Enhancement/3.1/mappoint aid better edit (#1417) * pointaid only autoselects tool if point not present and click to edit is now possible with leaflet draw * fixes deletion problem with marker and circle present. Also fixes drag not being possible when click editing * change checklist point aid to just use collection point aid to reduce duplicated effort * fixed problem that occured while in edit mode and changing the error radius * more form enhancements so that circle and marker move with each other during position edits * 3.1 new search to list display (#1411) * add display format radio button and handle case where user navigates back from table display to list display and then clicks breadcrumbs * handle logic flow of list->table->list * add translation and respond to self code review * fix bug where breadcrumbs in collections/listtabledisplay.php were not respecting whether user was coming from new search page * Change from button back to input to upload file (#1415) * htmlentity quote was messing with json_decode closes #1384 (#1419) * changed to prepared statements (#1422) * fixes bug there was a db error because voucher manager was missing during kml download attempt (#1421) * fixes voucher alignment when no search variables are present closes #1378 (#1423) # Issues #1378 # Summary Default css in reset.css removed default table vertical alignment which skewed the inputs in the table * fix types for taxa id-related prepared statements to match sqls expec… (#1426) * fix types for taxa id-related prepared statements to match sqls expectation of integer types as well as make some cosmetic fixes to spacing for code readbility * change result to statement for close * 3.1 schema patch adjustments (#1413) Schema patch adjustments based on running patch through a new portal 3.0 install and a 1.x => 3.0 => 3.1 portal update. Install/update tests were run both through command line and using the schema manager tool. - Remove omoccurrences index keys that are never set within previous schema updates - Temporarily turn off foreign key checks to allow renaming of omoccurrences collid index Schema Manager adjustments - Adjust code to accept and run the 3.1 patch - Add try-catch and other db checks to avoid PHP 8.1 throwing fatal errors when SQL statements fail, where the previous action only involved a return of a NULL result set * 3.1 burpsuite fixes 19 june 2024 (#1433) * address xss vulnerability for taxa in imagelib/search.php * fix sql injection bug for cntperpage in imagelib/search.php * address sql injection of taxa parameter in checklists/checklist.php and, indirectly, /collections/list.php * hopefully resolves sql injection issue with db/searchvar in /collections/map/kmlhandler.php * hopefully address sql injection for downloadhandler.php * [3.1] Minimal header - part 1 (incomplete implementation that defaults as disabled until [3.2] part 2 is done) (#1429) All requested changes made by @MuchQuak have been incorporated into the Part 2 of the ticket. * establish minimal_header_template.php * remove defaults * freeze of current work to address burpsuite stuff * continue making aesthetics improvements at both small and large screen sizes for the minimal header * makes the map header configurable and handles some styling that has to be dynamic depending on presence or absence of map header * adds minimal header optionally to collections/tools/mappointaid.php * adds minimal header optionally to CHECKLISTS/tools/mappointaid.php * move the z-index styling to the css file so that it can be overwritten more easily in collections/georef/georefclone.php. Implements the minimal header in collections/georef/georefclone.php * fix bug where clicking advanced search from coll profile had a parsing error from the db query param in the url * implement minimal header in collections/individual/index.php * remove cruft * handle cass where [db] is not set * make progress on /checklists/checklistmap.php?clid=78&thesfilter=0&taxonfilter= and on http://localhost/Symbiota/checklists/clgmap.php?pid=7 but both are currently buggy but commented out * add default = false; value to symbini_template * change defaults to false * remove absolute and top styling * Remove imported stylesheet (#1441) The hardcoded import of the variables.css breaks portals customized using a css/custom folder. (issues presented in /collections/misc/collprofiles.php ) The import also appeared to be unnecessary / redundant as both the customized and v202209 folder version were being brought in. * Add missing lang file (#1440) * Determinations import fix (#1435) * added checks for sciname, date, identifiedBy, added try catch to skip errors instead of stopping * replaced ERROR with SKIPPED * unified styling for error/skip displaying * fixed the sql errors displaying * go back to original error display * added note at the end and translations * added styling for disabled buttons (#1431) * Taxonomy API Development (#1442) - Change sciName to scientificName alias - Make Taxa searchable by taxon search variable - Support exact, starts with, wildcard, and wholeword search types - Standardize taxa table indexes - Update Swagger documentation with new functionality * Taxonomy Search API Endpoint Dev (#1443) - Fix bug associated with Swagger documentation - Update API title output * Update harvester.php (#1447) Modifies logic to display available boundary tool when no active geographic thesaurus exist in the DB * CSS drop-down menus (#1452) * CSS drop-down menus Add styling to header.css that enables top drop-down menus * Update OmCollections.php (#1455) Errant line of code short circuiting collection meta data update. * Occurrence editor crowdsource adjustments - Add formatting for eventDate2 - Adjust exsicati - Remove from display: additional identifiers, behavior, vitality * occurrence editor crowdsource adjustments - Add vertical-align to crowdsource css to override value set within reset.css - Force refresh of crowdsource.css within client's browsers * closes #1449 which was preventing checklist polyaid from opening (#1458) * crowdsource editor styling issue - resolve vertical-align issue, which persisted when opening in crowdsourcing mode and then switching between long and short forms - Fix fieldset padding issue within image popout * Api media 2024 06 (#1463) - Fix route bug interfering with POST write calls - Add recordID UUID to POST call - Improve error output media object isn't found (400 error) - Improve standardization of all response output - Add sort fields to api documentation - Add function that attempts to determine mime type via file header. Has added benefit of ensuring input file exists. - Update swagger documentation * Media API - Add ability to match images based on recordID UUID within PATCH call * added confirmation windows for project and editor delete icons (#1454) * [3.1] -burpsuite-fix: do not add taxa to query if there are special characters or quotes (#1436) * do not add taxa to query if there are special characters or quotes * cherry pick fix in 6250abde5 where getTaxonWhereFrag was getting un-cleaned taxon string * respond to code review feedback * Occurrence download bug (#1478) - searchvar term is being sanitized prior to being set as a session variable, which is converting & to &, thus interfering with parsing of all terms after the first term. Translating & to & fixes the issue. - The cleanOutStr function santizes output using htmlspecialchars. The htmlspecialchars on line 638 will double sanitize output, thus converting "east & west" to "east &amp; west". - Variable is getting sanitized within index.php with htmlspecialchars twice. Once on line 12 and again on line 205 Addresses GH issue: https://github.com/BioKIC/Symbiota/issues/1470 * increase font size globally (#843) * increase font size globally to 14 instead of 12, and then make improvements to other parts that looked a little weird in light of this change and addressed them as well * Update header_template.php * swap fonts to 1rem so it always matches html font size, 1rem = font-size of html * header and footer font fixing * removing footer font styling * overhauling base/main/variable css files font sizing * fixing head template font-size usage * fixing sitemap fonts * fixing leafletmap fonts * removing fonts from occur editor and adding reactive styles to button element * reactive fonts for occurrencelist from map search page * jquery-ui-icons-fix * map search font fixes * checklist/index.php font fixes * fixing fonts on collections/list * font fixes taxa profile and taxa editor * fixing missing span * fix merge issues on map search * fixing sitemap badges to scale with font via height based on rem * adjusting font styles and solving conflicts with contrast change on col search * saving col profile layout fixes * move quicksearch to side and make sticky * collections/list/spacing * spacing fix for maps * making checklist.php layout reactive so it works with different font sizes * font and layout fixes for checklistadmin * #tabs font size uses body variable * Reactive quicksearch colprofile * layout and font fixes for query form * fieldset-like alteration * updating header template * standardize some spacing in checklists/checklistadminmeta.php * cleanup on unecessary fonts specs * fixing collectiontitle layout * fixing collection group layout with new font size * fixing error from merge * more clean up of unused font-size declarations * add font-size-normalization on tabs ui * fixing checklist "games" font * modify jquery ui classes to normalize font * adjusting searchStyles inner to use rem * cleanup on some old map inlines that are uneeded * removing redundant font-size3 * removing uneeded font-family --------- Co-authored-by: MuchQuak Co-authored-by: Logan Wilt <91149982+MuchQuak@users.noreply.github.com> * Bugfix/3.1/spiderfy fix (#1476) * make random color function produce a color above 40 luminence so that very dark colors are never produced * add custom clustering strategy so for map/index so that clusters fit in with other points better and also don't cluster far less aggressively * init slider for clustering aggressiveness * fix random color gen create rgb values larger than 255 * add cluster to google maps and point oms.js to cdn so it is up to date * cleaning old gen clusters from leaflet section of map/index * Paleo bug fix - Resolve issue interfering with activation of paleo tools * Collmetadata lang tag audit (#1097) * Audit collmetadata lang tags * Minor fix * Fix popup issue * Fix to Spanish translations per Samanta's feedback (#1489) * Shorten Select Language * Change Contact Us * Change sample search language * Search page fix * Change html characters * Fix lang tags * Spacing fix * Es translation fixes * Audit lang tags for guidmapper.php (#1083) * Audit lang tags for guidmapper.php * Fix guidmapper buttons * Fix inputs for different actions * set the initial zoom at 500m (#1493) * Translation fixes from Samanta's feedback, pt 2 (#1499) * Translation edits to collprofiles * List and listtabledisplay translations * Fix table display translations * Add basic translation files for usagepolicy * Small changes to es (#1501) * symbbase.php refactor - Remove no longer needed code fragments that ensure that old configuration variable formats are supported after a minor update * Fix footer to bottom and correct portal login page (#1502) * switch png to svg, switch button classes to icon-button (#1503) * Remap old system variables to current format * Paleo bug fix - Add activate material sample to collection metadata array * Material Sample minor style adjust * Duplicate tool title too long crash fix (#1511) * added a check for the title length * added trim for last name, added IGNORE to sql insert query * Minor bug fix * change font control (#1512) * Hotfix 2024 06 24 (#1527) * Hotfix 2024-06-24 - Image tools -- Return false when file size cannot be determined within getImgDim1 function - Glossary (resolves issue: https://github.com/BioKIC/Symbiota/issues/1526) -- SQL error causing term list display to fail when a taxon group was included in the search -- Change table linkages associated with taxon group condition to LEFT JOINs so that all terms are displayed, including those without linkages to **synonyms** * Null result handle in collections/misc/collstats.php (#1453) * added null result handle, taxon not found alert * remove extra line, return empty array for incorrect taxon * added a check for families and countries * Occurrence Query String Multiple taxa fix (#1520) * Colprofile Lang Fix (#1517) # Issues #1488 # Summary Exchanged hardcoded string to use a string in the language file * MappointAid Error Radius Check closes #1487 (#1516) # Issue #1487 # Summary Added check for error radius input to error radius. If it doesn't exist default to 0 so no circle is drawn nor value sent outbound. * Head template adjustment and base.css into main.css merge (#1514) - Move majority of php coding out of head_template into symbbase.org. Trying to keep code to a minimum within head import file in order to maintain ability for us to modify code algorithms without having to explicitly change head.php files within each portal install. - Move css out of head_template.php into separate css file, to again maintain ability to globally modify these styles within future code versions. - Move header and footer link/import head so that portal administrator can point to different version if they want (e.g. header_button_menu.css) - Remove css definitions from base.css duplicated within main.css - Move unique css definitions from base.css into main.css - Transfer main.css styles that are only used within a single file into target file - Remove unused or duplicated css from checklist.css - Remove collectiontable and categorytitle styles, which do not appear to be used anywhere within code * CSS Refactor - Remove dependence on $CSS_VERSION_RELEASE and v202209 directory path - Move all css files out of v202209 directory one directory down into css base directory - Add $CSS_VERSION to CSS imports within head_template.php, thus allowing force reload of CSS files when that variable is changed within symbbase.php file - Remove deprecated css/base.css file - Remove deprecated files within css/symb/directory - Reduce pixel size of Symbiota icon, which is much larger than a typical icon. Large version of icon is still conserved, but not used as the default icon. * Fix bold on taxon names closes #1490 (#1529) # Issue #1490 # Summary Remove font weight normal from taxon name so that it is going to be bold (as it should be ) * Fixing Data Management Tool Subheader Styles (#1531) # Summary Css rule to make subheaders not display absolute got overruled during css fixes so I made the rule more precise to ensure it doesn't get overridden. * Fix minors reported within Apache log files - Ensure that NULL value are not sent to exif_thumbnail function - Correct a couple language tags used within collections/map/occurrencelist and taxa/index.php - Add EXTERNAL_RECORDS and PORTAL_LIST language tags to map language files - Remove unneeded htmlspecialchars used on controlled language tags * Occurrence Search bug (#1536) * Occurrence Search bug - Fix issue with validation of infraspecific taxon names by allowing abbreviations with periods (e.g. var.) - Allow taxon names to contain hybrid crosses and extinction dagger - Fix issue with NULL being passed to string function strtoupper * CSS modification by Logan * header and footer adjustments - Move javascript code out of footer into separate js file. This will allow us to make adjustments to code without needing individual non-template footer files to be adjusted to code changes. - Remove code out of header_template file in preference to code run within symbbase.php file - Given condition at top of header page testing if language file is imported, individual isset statements for each LANG variable is not needed. * minimal header adjustments - Remap minimalheader_template.php to minimalheader.php in support of the currently supported template system - Add minimalheader_template.php to setup.sh file * Change html entities to symbols * Add temporarily to resolve conflict * Remove dependence on v202209 * fixed the profile accessibility setting (#1534) * Misc adjustments - Bug fix: remove php code out of new js file - Standardize syntax within footer template file - Remove deprecated code within symbini config file * Very minor code adjustment * improved quick search css (#1539) * Fix for Merge Duplicates Failing when no Determinations need Downgrading (#1484) * add conditional check for if there are any determinations to downgrade before trying closes #1451 * adds copies of some functions from occurrenceEditorDeterminations to properly merge determinations records copied functions have been label as such with todos to remove in 3.2 when occurrence editor work to remove latest identification section has been done * removing dead code comments * fix determination merging priority to favor the lasting record * adds temp function to transfer attributes over to omoccuridentifers if none are present before record merge takes place * Increase Symbiota size of icon * Template Lang File Refactor - Move language variables files for template files into /content/lang/templates/ - Remove template from language file names with intention of including the files as part of the base code - Standardize header/footer lang variables to ensure that there is not overlap with other existing page specific variables. This is done by adding a prefix of H_ or F_. - Remove some unused variables from header language file - Remove /content/lang/header.[LANG_TAG].php include statement from non-header files (clgmap.php, mappointaid.php, georefclone.php). - Some code cleaning and input variable sanitation adjustments to non-header files referenced above. * adding variable to control menu font-size for easy customization (#1547) * adding variable to control menu font-size for easy customization * move font size control to be out of media query * Misc adjustments * Remove 202209 again * Add break to collection profile title (#1556) * Template lang file adjustments - Move image contributors language tags out of header lang file into their own file includes * Continue to synchronize header and footer lang files * Update variables.css correct default header background image path. * change the default project to 0 (#1546) * fix typo in request (#1559) * Add header override files * Adjustment to resolve conflicts * minor adjustment * Change exsicatti to exsicattae (#1563) * Reapply adjustments lost during previous dev merge * Change "open" to "toggle" record search form (#1565) * Remove inline styling for nav icon * Create customizations.css (#1567) * Create customizations.css Creates a new file customizations.css that can be used to store overrides of Symbiota's default styling. This file should never be modified by Symbiota Dev's so as to not trample on a portal's changes. * Continued adjustments * More developments - dynamic map bug fix: fix link to page that lists all collections - mappointaid bug fix: fix error importing language files - Add missing LANG variables within collection search pages * Additional styling adjustments - Remove all inline styling of the img elements that were tagged with the "navIcon" attributed. The height of these elements were defined as 15px within the imported css and the inline width defined to 1.3em, thus creating image warping. - Modified img styling within imported css file to width of 1.3em, which is what was defined within the inline for all elements * updated the path for accessibility and condensed css (#1572) * Taxon Description issues - Add condition testing for language file existence, defaulting to English file upon false - Make use of cleanOutStr function, thus avoiding null used within htmlspecialchars string function - Fix bad language keys * created css class for inverted color, switched location of buttons in taxonomydisplay (#1576) * Dynamic Map adjustments - Make use of getDomain() function within the shared Manager class. This function will return the SERVER_HOST value that is set within the symbini config file, or if that variable is not set, generate it dynamically. This reduces the need for this variable to be set within every portal, and centralizes the generation of this so that it can be done safely. - KML failed to validate within dev environments due to Deprecated Code warning thrown due to undeclared class variable used within a local scope. - Sanitation adjustments * Collection Search variable adjustment (#1578) - Improve the translation of collection input variable (e.g. db) to a database query string, which also improves copy link functions. - Exclude commas from uri encoding * Hotfix 2024 07 25 (#1591) - Inventory and Project Management -- Display all checklists that have at least one linked taxon OR is a parent checklist inheriting taxa from a child checklist ([Issue #1532](https://github.com/BioKIC/Symbiota/issues/1532)) -- Removed limit that required checklist to have at least 10 linked taxa -- Order checklists by project and checklists names -- Only display Global symbol within map display option when one or more checklists within a group have lat/long centroids -- Make project title an active link that opens the project within the project details page - DwC-A Publishing -- Fix fatal error when DwC-A path fails to return a data file * Disallow glossary download unless you're an editor (#1589) * Add link to data usage policy to prompt citing glossary (#1590) * Add link to data usage policy to prompt citing glossary * Add placeholder for glossary citation * Make use of getDomin within UtilityFunctions * Minor adjustments to accommodate failed input variables * Adjustment to previous submit * Minor adjustments to checklists lang files * Rename Taxon / Transfer Vouchers fix (#1594) * Rename Taxon / Transfer Vouchers fix Resolves an issue when author is included in the Target Taxon autocomplete suggestions. * Modifications - Add on-change function that nulls tid when data return is null (e.g. invalid name). This clears out tid when one selects a taxon, which populates tid field, but then enters a taxon that is not in the list. Otherwise, the tid would submit the old selected value rather than correctly displaying message that tid is null. - Remove checkScinameExistence function and rpc call, since it is no longer needed * Language and JS logic. --------- Co-authored-by: Edward Gilbert * Button styling fix in collprofiles.php (#1535) * fixed the font styling * change the primary button font color to white * unify all buttons styling with body-font-family variable * Occurrence Editor otherCatNum NULL search - Bug fix within occurrence editor for when user searches otherCatalogNumbers IS NULL - Fatal error fix when searching otherCatalogNumber NOT LIKE Addresses issue: https://github.com/BioKIC/Symbiota/issues/1582 * Hotfix 2024 08 07 (#1606) Taxonomy Editor - Add taxamaps table to data remapping functions when taxon is deleted or remapped. Resolves [issue #1598](https://github.com/BioKIC/Symbiota/issues/1598) - Minor error fixes and adjustments to language file imports Glossary Management - Fix issue associated within new sources failing to save when displaying all terms without a taxon group selected. Resolves [issue #1584](https://github.com/BioKIC/Symbiota/issues/1584) - Display all source definitions when taxon group is not selected - Minor adjustments to language terms and misc code Occurrence Cleaner - duplicate merge tool - Replace catalog numbers as the unique identifier for duplicate cluster with numeric key, thus avoiding interference of target form element due to existence of space or period within catalog number. Resolves [issue #1471](https://github.com/BioKIC/Symbiota/issues/1471) - Internationize first submit button - Add header and footer to cleaning page Specimen Upload - Remove display of Data Versioning Checkbox option for snapshot datasets. Batch import data versioning is only available for live managed collections. * Header / Footer adjustments - Fix incorrect language tag variables within footer file - Remove test of H_WELCOME variable, given that no other variables are present - Comment out js console log output used for debugging * Checklist Add taxon bugfix (#1604) * Checklist Add taxon bugfix see #1602. This fix ties the add taxon feature to a taxonomy.tid (instead of the sciname value returned by the autocomplete. * refactor based on footer_template.php Refactored the language value injection based on the pattern Ed used in footer_template.php * Reset CSS Adjustments - Remove reset.css from head_template.css in preference of pushing back official rollout of a reset stylesheet to 3.2. Ideally, with normalized.slim.css integrated into a single reset - Move normalized out of head.php into main so that once merged into reset, we can delete normalized from main without having to adjust head.php files. - Force refresh of main.css via $CSS_VERSION variable * increased the width of utm div (#1613) * removed extra div from for loop (#1614) * Accessibility adjustments (#1610) - Move accessibility toggle button and associated components out of footer into single contained file that can be added to footer, header, or other location via an include function - Move accessibility files into accessibility directory - Migrate accessibility.footer.css js into accessibilityUtil.js file - Move accessibility language terms into their own lang files * Remove Vertical-align from reset.css closes #1498 (#1518) * Remove Vertical-align from reset.css closes #1498 # Issue #1498 # Summary Causes undesirable before for things that don't have a default vertical align like tables, will expand height for some tds if valign top is used. Also I don't know why reseting the vertical alignment would be needed * adding back vertical align but making it top instead of baseline * fix vertical aligntop for taxa main image * change over remaing valign propties to styles instead * remove gridlike-row class from grouped collections (#1618) * added left margin to heading (#1622) * fixed the headers, added subheader class (#1623) * added display inline, added margin (#1624) * Make SVG icons color controlled by values in variables.css (#1473) * added styling for svg in icon-buttons, removed inline fill for svgs * remove fill attributes from svgs in list and occurrencelist * fixed the css class for icon-button * Geo thesaurus default data (#1627) - Add a default thesaurus to 3.0 fresh install. Note that this data file was not included within the 3.1 data patch. Some databases already have some form of a thesaurus, or may wish to create a thesaurus from transferring data from their lookup tables. Running the insert statements when the table is already populated with data would result in an PK conflict errors. -- Addresses issue https://github.com/BioKIC/Symbiota/issues/1605 - Add data file into /schema/3.0/data/ directory so that it could be run as an optional insert. We may want to add a menu option that allows user to run this install from the management interface, and maybe remove the keys from the insert statement so that data would be appended without duplicate key errors, and unique indexes that ensure that duplicate entries are avoided. And/or add a separate script file that only contains SQL that adds continents and links existing countries as children. - Deactivate links on non-accepted terms - Remove ability to add children to non-accepted terms - Don't display map nor map controls for non-accepted terms - Add function that appends synonym terms to term output and add synonyms to terms details * Snapshot collection menu adjustments (#1628) - Collection admin menu change -- Disactivate certain menu options for snapshot collections. The goal is to further discourage folks from live managing specimen records within a Snapshot dataset, which is happening more often than it should. -- Keep deactivated items listed within the menu with a clear message stated that these items are only available to Live Managed collection. It is important for snapshot collections to know that these options are still available, but they need to go through the correct channels to activate them. - Standardization of language tags (internationalization) -- Add condition test to language file import -- Remove no longer needed term specific conditions with default language terms -- Add missing language terms -- Delete French language file, which only contained English terms. This language file should be created within a separate PR. - Misc adjustments -- Move html output generation out of class file -- Add rights formatting into a static function within the Utility class. There are a number of locations where this functions need to be called (e.g. images), but this will be done within a separate PR. -- Add rights html styling to main.css -- Misc styling tweaks to collection profile page Before: ![image](https://github.com/user-attachments/assets/11051792-84d1-48b0-8959-f7eb105c68b5) After: ![image](https://github.com/user-attachments/assets/0082a9f8-b180-4c0f-b892-788056e2d47e) * Audit of specupload lang tags (#1084) * Fix specupload lang tags * Add home to lang tags * Update specupload.es.php * Update specupload.fr.php * Collection menu adjustment - Comment out code that deactivates certain menu options within snapshot collections, with the goal of better reviewing these changes and reactivating within 3.2 rollout - Remove restrictions to label making options, since there are valid reasons for snapshot to make labels * Update setup.sh (#1630) * Update setup.sh Update script to: - dynamically find all template files - avoid overwriting existing files unless 'force' option is used - 'test' option to show example output - 'verbose' option to give more information about what the script does Update INSTALL/md * Hide h1 titles on popups and pages with duplicate titles (#1633) * added screen-reader-only class * hide dynamic map heading * Update OccurrenceIndividual.php (#1648) * Update OccurrenceIndividual.php Apply html elements after cleaning DB value * Add missing language tag (#1642) * Update geothesaurus (#1636) - Add counties for Mexico and U.S.A. - Add accented letters for Mexican states (and fix some too-long state names) - Remove 3-letter abbreviations for states that were previously just those of the country * add check for clid and default to 0 if it doesn't exist (#1632) * Bugfix/3.1/geothesaurus map fixes (#1635) * Add check for geojson parsing on mapcoordaid that will not parse output into json if there is nothing to parse * if coordaid changes are made but not saved then save them if save and close is selected * add warning of unsaved changes when leaving geothesaurus index after making an edit * removing custom dialog option because they are not allowed * saves and closes coordaid if edits are saved in leaflet plugin * Update header_template.php * Contacts header on collprofiles font fix (#1651) * removed inline styling * merge adjustment * return inline styling on listed colls sections * Change NSF references to US NSF (#1652) * Checklist html tags (#1637) - Fix bug that was html encoding input data prior to be added to the database. All database specific input variables were removed from the view bag in preference to be handled by the class data handlers. -- Addresses issue: https://github.com/BioKIC/Symbiota/issues/1596 - Removed writable database calls from the ChecklistVoucherAdmin class in preference for shared write statements within the ImInventories class. - Add checklist voucher write functions to the shared ImInventories class. - Mics bug fixes to the ImInventories class - Misc style adjustments to improve layout of editor forms * Populated individual Date fields - Add code to the occurrence editor that automatically populates year, month, day, startDayOfYear, and endDayOfYear from eventDate fields whenever they are are modified - Add code to the statistics generator code that populates missing or incorrect individual date fields for a given collection. * fix geographic cleaning tool (#1659) * restore the previous code * restore the badcountrycount functionality * remove lkupcountry * added hidden input for default useThes value (#1657) * Schema Manager adjustments (#1655) Changes include improved error reporting when database does not exist or is misspelled within connection config file, as well as reporting authentication issues associated with portal base users. If user goes to sitemap and database tables do not yet exist, the user will be forwarded directly to schema manager to assist installing base schema. Note that the first 3 commits are only associated with batch code adjustments that remove unnecessary code fragements. Looks directly are the last commit to review modifications that are associated with logistically changes (e.g. https://github.com/BioKIC/Symbiota/commit/c54952e651279da22a7efa953d1ea9364cfc9023). Specific changes: - Add try/catch to db_connection file and connection test within Manager class that reports when connection to database fails (e.g. no connection, authentication issue, etc) - Sitemap adjustments that automatically forwards user to schema manager whenever schema is missing (e.g. fresh install). - Add backward ticks to all table and stored procedure names within schema files so that installation logger is able to properly report table name within logs - Misc adjustments to schema manager to improved output reports - Add message to schema manager that warns user when log directories are not writable by web user (e.g. apache) - Only check users accessibility settings if they are logged in to the portal * Update DwcArchiverCore.php - fix issue when custom where statement gets an extra "where" added when string begins with an empty space Addresses issue: https://github.com/BioKIC/Symbiota/issues/1653 * Dynamic map checklist dev - Include points from child checklists when dynamic map is selected within the checklists page - Add IGNORE to stats INSERT statement in order to avoid fatal error when a duplicate entry it inserted. * Taxon links bug - Remove second declaration of taxonlinks array, which was causing the array to be redefined throughout each record loop. * Fix redundant map (#1663) That also wasn't reading projArr['displayMap'] variable * adjust sort comparator to return explcit numbers so v8 doesn't get co… (#1669) * adjust sort comparator to return explcit numbers so v8 doesn't get confused * add origin linking to tid * add origin linking to google map search * wrapped latitude/longitude in label (#1668) * Add crowdsourcing language tag * Update INSTALL.md Formatting * Update INSTALL.md Additional formatting * Update search.php - Bug fix resolving issue where collection administrators and editors did not have permission to tag images from their collection. Addresses issue https://github.com/BioKIC/Symbiota/issues/1616 * Hotfix 2024 08 22 (#1677) - Minor bug fix that was interfering with saving of locationRemarks when a new record is saved (resolves https://github.com/BioKIC/Symbiota/issues/1445) * 3.1 preparation updates - Move still relevant data files into 3.0 data directory - Update INSTALL.md documentation - Fix misc spelling error - Update version number * Fix typo * Update UPDATE.md (#1681) * Fix Backward Compatiblity Issue involving mysqli_execute_query (#1682) * Adds util function to make use of mysqli_execute_query for older versions of php # Summary `mysqli_execute_query` or `$mysqli->execute_query` function is an 8.2 only function provided by mysqli which could break portals that cannot upgrade to 8.2 from 8 or are running 7.3. Solution is to make a util function with the same api as mysqli_execute_query but will use the php version to decide wether to use mysqli_execute_query or normal statement binding. * removing comment left over from prototyping * usage of taxa thesaurus family over occurrence family (#1675) * usage of taxa thesaurus family over occurrence family * Modifications - "ts.taxauthid = 1" needs to be added to the where condition or the recordset would return duplicate records for each taxonomic thesaurus defined within taxstatus table (e.g. there are 4 defined within SEINet). I suppose not including it would not be a big problem since the occid would be the same for each record, and thus the output data array would just copy over existing data. However, it would iterate through more records than needed. Another solution would be to change the statement to "SELECT DISTINCT...", but this can significantly slows down an ordered query. - Since "ts.taxauthid = 1" is added to the where clause, the getTableJoins function should always add the taxstatus left join, thus we can leave out that test. * Minor fix to previous submit --------- Co-authored-by: Edward Gilbert * bugfix barcodes (#1683) fix label printing barcode 500 error * Update CHANGELOG * Api occurrence 2024 08 (#1688) - Occurrence API endpoint development -- Add collector, collector last name, and collector number as searchable functions -- Develop write functions (insert, update, delete), but not yet activated for public use -- Add new endpoint for loading and processing skeletal occurrence data -- Add swagger documentation for skeletal record processing - Taxonomy API endpoint development -- Refactor to use query builder for defining search terms (bring code in sync NEON taxonomy developments) - Mics -- Add helper functions to Lumen API --------- Co-authored-by: Logan Wilt <91149982+MuchQuak@users.noreply.github.com> Co-authored-by: Edward Gilbert Co-authored-by: Greg Post Co-authored-by: Mark Co-authored-by: NikitaSalikov <86389284+NikitaSalikov@users.noreply.github.com> Co-authored-by: MuchQuak --- .gitignore | 10 +- .vscode/launch.json | 48 + README.md | 1 + accessibility/module.php | 43 + accessibility/rpc/toggle-styles.php | 13 + admin/index.php | 14 +- admin/portalindex.php | 30 +- admin/schemamanager.php | 126 +- agents/agent.php | 6 +- agents/index.php | 11 +- agents/rpc/handler.php | 2 +- agents/rpc/index.php | 9 +- api/app/Helpers/GPoint.php | 560 + api/app/Helpers/Helper.php | 53 + api/app/Helpers/OccurrenceHelper.php | 1103 + api/app/Helpers/TaxonomyHelper.php | 367 + api/app/Http/Controllers/Controller.php | 5 +- api/app/Http/Controllers/MediaController.php | 599 +- .../Http/Controllers/OccurrenceController.php | 431 +- .../Http/Controllers/TaxonomyController.php | 134 +- api/app/Models/Media.php | 15 +- api/app/Models/Occurrence.php | 36 +- api/app/Models/Taxonomy.php | 8 +- api/composer.json | 5 +- api/config/swagger-lume.php | 2 +- api/routes/web.php | 17 +- api/storage/api-docs/api-docs.json | 828 +- api/vendor/composer/autoload_classmap.php | 6 + api/vendor/composer/autoload_files.php | 11 +- api/vendor/composer/autoload_psr4.php | 2 +- api/vendor/composer/autoload_static.php | 219 +- .../src/Report/Html/Facade.php | 2 +- .../src/Util/TestDox/HtmlResultPrinter.php | 2 +- checklists/checklist.php | 773 +- checklists/checklistadmin.php | 165 +- checklists/checklistadminchildren.php | 88 +- checklists/checklistadminmeta.php | 184 +- checklists/checklistmap.php | 389 +- checklists/clgmap.php | 188 +- checklists/clsppeditor.php | 267 +- checklists/dynamicchecklist.php | 29 +- checklists/dynamicmap.php | 334 +- checklists/externalvouchers.php | 156 + checklists/imgvouchertab.php | 11 +- checklists/index.php | 59 +- checklists/mswordexport.php | 13 +- checklists/nonvoucheredtab.php | 51 +- checklists/rpc/gettid.php | 25 - checklists/rpc/index.php | 16 +- checklists/rpc/linkvoucher.php | 22 +- checklists/tools/checklistloader.php | 71 +- checklists/tools/index.php | 10 +- checklists/tools/mappointaid.php | 235 +- checklists/tools/mappolyaid.php | 440 +- checklists/vaconflicts.php | 40 +- checklists/vamissingtaxa.php | 85 +- checklists/voucheradmin.php | 150 +- checklists/voucherreporthandler.php | 5 - classes/APITaxonomy.php | 1 + classes/ActionManager.php | 4 +- classes/AgentManager.php | 10 +- classes/ChecklistAdmin.php | 151 +- classes/ChecklistLoaderManager.php | 5 +- classes/ChecklistManager.php | 135 +- classes/ChecklistVoucherAdmin.php | 144 +- classes/ChecklistVoucherManager.php | 197 +- classes/ChecklistVoucherPensoft.php | 3 + classes/ChecklistVoucherReport.php | 54 +- classes/CollectionMetadata.php | 111 + classes/DatasetsMetadata.php | 55 + classes/DwcArchiverBaseManager.php | 18 +- classes/DwcArchiverCore.php | 184 +- classes/DwcArchiverOccurrence.php | 276 +- classes/DwcArchiverPublisher.php | 36 +- classes/EOLManager.php | 20 +- classes/EOLUtilities.php | 19 +- classes/GamesManager.php | 4 +- classes/GeographicThesaurus.php | 828 +- classes/GlossaryManager.php | 42 +- classes/GlossaryUpload.php | 48 +- classes/ImInventories.php | 593 +- classes/ImageBatchProcessor.php | 14 +- classes/ImageCleaner.php | 10 +- classes/ImageDetailManager.php | 4 +- classes/ImageExplorer.php | 154 +- classes/ImageImport.php | 5 +- classes/ImageLibraryBrowser.php | 10 +- classes/ImageLibrarySearch.php | 243 +- classes/ImageLocalProcessor.php | 268 +- classes/ImageProcessor.php | 9 +- classes/ImageShared.php | 183 +- classes/InstitutionManager.php | 311 +- classes/KeyCharAdmin.php | 24 +- classes/KeyDataManager.php | 14 +- classes/KeyMatrixEditor.php | 8 +- classes/Manager.php | 42 +- classes/MapSupport.php | 218 +- classes/MediaResolutionTools.php | 4 +- classes/ObservationSubmitManager.php | 25 +- classes/OccurrenceAccessStats.php | 28 +- classes/OccurrenceAssociations.php | 2 +- classes/OccurrenceAttributeSearch.php | 16 +- classes/OccurrenceAttributes.php | 4 +- classes/OccurrenceChecklistManager.php | 5 +- classes/OccurrenceCleaner.php | 196 +- classes/OccurrenceCollectionProfile.php | 127 +- classes/OccurrenceDownload.php | 54 +- classes/OccurrenceDuplicate.php | 26 +- classes/OccurrenceEditorDeterminations.php | 42 +- classes/OccurrenceEditorImages.php | 10 +- classes/OccurrenceEditorManager.php | 689 +- classes/OccurrenceEditorMaterialSample.php | 142 - classes/OccurrenceEditorResource.php | 148 +- classes/OccurrenceExsiccatae.php | 16 +- classes/OccurrenceGeorefTools.php | 26 +- classes/OccurrenceImport.php | 449 + classes/OccurrenceIndividual.php | 1144 +- classes/OccurrenceLabel.php | 19 +- classes/OccurrenceListManager.php | 9 +- classes/OccurrenceLoans.php | 2 +- classes/OccurrenceMaintenance.php | 86 +- classes/OccurrenceManager.php | 248 +- classes/OccurrenceMapManager.php | 167 +- classes/OccurrenceSearchSupport.php | 229 +- classes/OccurrenceSesar.php | 5 +- classes/OccurrenceSkeletal.php | 8 +- classes/OccurrenceTaxaManager.php | 84 +- classes/OccurrenceUtilities.php | 136 + classes/OmAssociations.php | 286 + classes/OmCollections.php | 44 +- classes/OmDeterminations.php | 188 + classes/OmMaterialSample.php | 177 + classes/OpenIdProfileManager.php | 119 + classes/PermissionsManager.php | 353 +- classes/Person.php | 14 +- classes/PluginsManager.php | 13 +- classes/ProfileManager.php | 134 +- classes/RpcOccurrenceEditor.php | 61 + classes/RpcTaxonomy.php | 223 +- classes/SchemaManager.php | 96 +- classes/SiteMapManager.php | 43 +- classes/SpecProcNlpHandler.php | 8 +- classes/SpecProcNlpLbcc.php | 84 +- classes/SpecProcNlpParser.php | 59 +- classes/SpecProcNlpSalix.php | 402 +- classes/SpecProcNlpUtilities.php | 30 +- classes/SpecProcessorManager.php | 25 +- classes/SpecProcessorNEVP.php | 4 +- classes/SpecProcessorOcr.php | 12 +- classes/SpecUpload.php | 32 +- classes/SpecUploadBase.php | 486 +- classes/SpecUploadDwca.php | 12 +- classes/SpecUploadFile.php | 1 - classes/TPDescEditorManager.php | 39 +- classes/TPImageEditorManager.php | 5 +- classes/TaxonProfile.php | 15 +- classes/TaxonomyCleaner.php | 8 +- classes/TaxonomyDisplayManager.php | 252 +- classes/TaxonomyEditorManager.php | 174 +- classes/TaxonomyHarvester.php | 46 +- classes/TaxonomyUpload.php | 30 +- classes/TraitBarPlot.php | 1 + classes/UtilitiesFileImport.php | 220 + classes/UtilityFunctions.php | 28 + classes/WordCloud.php | 13 +- classes/index.php | 10 +- collections/admin/guidmapper.php | 48 +- collections/admin/igsnmanagement.php | 83 +- collections/admin/igsnmapper.php | 82 +- collections/admin/igsnverification.php | 80 +- collections/admin/importextended.php | 356 + collections/admin/index.php | 10 +- collections/admin/logs/index.php | 16 +- collections/admin/restorebackup.php | 38 +- collections/admin/specupload.php | 73 +- collections/admin/specuploadmanagement.php | 166 +- collections/admin/specuploadmap.php | 231 +- collections/admin/specuploadprocessor.php | 140 +- collections/admin/uploadreviewer.php | 30 +- collections/checklist.php | 8 +- collections/cleaning/coordinatevalidator.php | 47 +- collections/cleaning/duplicatesearch.php | 28 +- collections/cleaning/fieldstandardization.php | 77 +- collections/cleaning/imagerecycler.php | 29 +- collections/cleaning/index.php | 117 +- collections/cleaning/politicalunits.php | 51 +- collections/cleaning/taxonomycleaner.php | 86 +- collections/datasets/datapublisher.php | 181 +- collections/datasets/datasetmanager.php | 78 +- collections/datasets/duplicatemanager.php | 49 +- collections/datasets/emlhandler.php | 7 +- collections/datasets/index.php | 64 +- collections/datasets/occurharvester.php | 42 +- collections/datasets/public.php | 24 +- collections/datasets/publiclist.php | 18 +- collections/datasets/rsshandler.php | 3 + collections/download/downloadhandler.php | 25 +- collections/download/index.php | 135 +- collections/editor/assocsppaid.php | 12 +- collections/editor/batchdeterminations.php | 77 +- collections/editor/dev/index.php | 9 +- collections/editor/dev/occurdataentry.php | 41 +- collections/editor/dupesearch.php | 76 +- collections/editor/editProcessor.php | 4 +- collections/editor/editreviewer.php | 78 +- collections/editor/imageoccursubmit.php | 71 +- collections/editor/includes/admintab.php | 6 +- .../config/occurVarColl1_template.php | 2 +- .../config/occurVarDefault_template.php | 2 +- .../config/occurVarGenObsDefault_template.php | 2 +- .../config/occureditorcrowdsource.css | 51 +- .../editor/includes/determinationtab.php | 10 +- collections/editor/includes/imagetab.php | 40 +- collections/editor/includes/imgprocessor.php | 6 +- collections/editor/includes/index.php | 9 +- .../editor/includes/materialsampleinclude.php | 32 +- collections/editor/includes/paleoinclude.php | 44 +- collections/editor/includes/queryform.php | 231 +- collections/editor/includes/resourcetab.php | 516 +- collections/editor/includes/traittab.php | 10 +- collections/editor/index.php | 11 +- collections/editor/observationsubmit.php | 376 +- collections/editor/occurrenceeditor.php | 2039 +- collections/editor/occurrencetabledisplay.php | 185 +- collections/editor/resourcehandler.php | 24 +- collections/editor/rpc/dupelist.php | 33 +- collections/editor/rpc/getGeography.php | 14 + collections/editor/rpc/getnewdetitem.php | 6 +- collections/editor/rpc/index.php | 9 +- .../editor/rpc/localitysecuritycheck.php | 36 +- collections/editor/rpc/lookupCountry.php | 30 - collections/editor/rpc/lookupCounty.php | 35 - collections/editor/rpc/lookupMunicipality.php | 32 - collections/editor/rpc/lookupState.php | 35 - collections/editor/skeletalsubmit.php | 147 +- collections/editor/tools/coordformatter.php | 111 +- collections/editor/tools/index.php | 9 +- collections/exsiccati/batchimport.php | 18 +- collections/exsiccati/index.php | 128 +- collections/georef/batchgeoreftool.php | 407 +- collections/georef/geolocate.php | 4 +- collections/georef/geolocatetools.php | 18 +- collections/georef/georefclone.php | 307 +- collections/georef/index.php | 9 +- collections/harvestparams.php | 228 +- collections/index.php | 114 +- .../individual/domManipulationUtils.js | 31 + collections/individual/index.php | 484 +- collections/individual/linkedresources.php | 117 +- collections/list.php | 124 +- collections/listtabledisplay.php | 216 +- collections/loans/exchange.php | 142 +- collections/loans/exchangetab.php | 65 +- collections/loans/incoming.php | 143 +- collections/loans/index.php | 166 +- collections/loans/outgoing.php | 135 +- collections/loans/reports/defaultenvelope.php | 10 +- collections/loans/reports/defaultinvoice.php | 48 +- .../loans/reports/defaultmailinglabel.php | 10 +- .../loans/reports/defaultspecimenlist.php | 10 +- collections/loans/reportsinclude.php | 29 +- collections/loans/specimentab.php | 168 +- collections/loans/specnoteseditor.php | 33 +- collections/map/googlemap.php | 54 +- collections/map/index.php | 3325 +- collections/map/leafletmap.php | 443 + collections/map/mapbox.php | 65 +- collections/map/occurrencelist.php | 109 +- collections/map/portalSelector.php | 88 + .../map/rpc/206161_heatmap_1695410561.png | Bin 0 -> 498700 bytes collections/map/rpc/getCoordinates.php | 18 + collections/map/rpc/getTaxa.php | 34 + collections/map/rpc/postMap.php | 18 + collections/map/rpc/searchCollections.php | 101 + collections/map/staticmaphandler.php | 522 + collections/misc/assocmanagement.php | 16 +- collections/misc/colladdress.php | 26 +- collections/misc/collbackup.php | 65 +- collections/misc/collectionproperties.php | 37 +- collections/misc/collmetadata.php | 412 +- collections/misc/collmetaresources.php | 27 +- collections/misc/collorderstats.php | 16 +- collections/misc/collpermissions.php | 251 +- collections/misc/collprofiles.php | 963 +- collections/misc/collprofilestats.php | 8 +- collections/misc/collstats.php | 1199 +- collections/misc/collyearstats.php | 12 +- collections/misc/commentlist.php | 158 +- collections/misc/index.php | 9 +- collections/misc/institutioneditor.php | 246 +- collections/misc/occurrencesearch.php | 69 +- collections/misc/protectedspecies.php | 141 +- collections/misc/rpc/index.php | 9 +- collections/reports.php | 5 +- collections/reports/accessreport.php | 20 +- collections/reports/annotationmanager.php | 22 +- collections/reports/barcodes.php | 16 +- collections/reports/defaultannotations.php | 9 +- .../reports/defaultannotationsword.php | 3 + collections/reports/index.php | 7 +- collections/reports/labeldynamic.php | 14 +- collections/reports/labeljsongui.php | 18 +- collections/reports/labelmanager.php | 197 +- collections/reports/labelprofile.php | 22 +- collections/reports/labels.php | 37 +- collections/reports/labelsword.php | 24 +- collections/rpc/index.php | 9 +- collections/search/collectionContent.php | 87 + .../collectionGroupSubcollectionList.php | 25 + collections/search/css/searchStyles.css | 919 + collections/search/css/searchStylesInner.css | 771 + collections/search/css/tables.css | 109 + collections/search/index.php | 612 + collections/search/js/alerts.js | 27 + collections/search/js/searchform.js | 951 + .../search/sass/0-plugins/_plugins-dir.sass | 3 + collections/search/sass/1-base/_base-dir.sass | 10 + collections/search/sass/1-base/_base.sass | 25 + collections/search/sass/1-base/_common.sass | 17 + collections/search/sass/1-base/_fonts.sass | 34 + collections/search/sass/1-base/_footer.sass | 1 + collections/search/sass/1-base/_main.sass | 3 + collections/search/sass/1-base/_nav.sass | 1 + collections/search/sass/1-base/_section.sass | 2 + .../search/sass/2-layouts/_layouts-dir.sass | 4 + .../search/sass/2-layouts/_search-form.sass | 127 + .../search/sass/3-modules/_accordion.sass | 59 + collections/search/sass/3-modules/_btn.sass | 43 + collections/search/sass/3-modules/_chip.sass | 54 + .../search/sass/3-modules/_color-box.sass | 18 + .../search/sass/3-modules/_input-text.sass | 105 + collections/search/sass/3-modules/_modal.sass | 23 + .../search/sass/3-modules/_modules-dir.sass | 13 + .../search/sass/3-modules/_select.sass | 30 + .../search/sass/3-modules/_text-area.sass | 78 + .../search/sass/3-modules/_utilities.sass | 26 + collections/search/sass/_mixins.sass | 6 + collections/search/sass/_variables.sass | 29 + collections/search/sass/app.sass | 15 + .../search/singleCollectionGroupDetails.php | 50 + ...singleCollectionWithoutCategoryDetails.php | 51 + .../search/singleSubcollectionDetails.php | 46 + .../crowdsource/controlpanel.php | 31 +- .../specprocessor/crowdsource/index.php | 36 +- .../specprocessor/crowdsource/review.php | 48 +- .../specprocessor/duplicateharvest.php | 59 +- .../specprocessor/dwcaexpeditionhandler.php | 15 +- collections/specprocessor/exporter.php | 55 +- collections/specprocessor/geolocate.php | 34 +- collections/specprocessor/imageprocessor.php | 344 +- collections/specprocessor/index.php | 40 +- collections/specprocessor/nlpprocessor.php | 16 +- collections/specprocessor/nlpprofiles.php | 23 +- collections/specprocessor/ocrprocessor.php | 108 +- collections/specprocessor/processor.php | 12 +- collections/specprocessor/reports.php | 92 +- .../specprocessor/rpc/coge_build_dwca.php | 4 +- .../specprocessor/rpc/coge_getCount.php | 4 +- .../specprocessor/salix/salixhandler.php | 67 +- .../standalone_scripts/ImageBatchHandler.php | 2 +- .../standalone_scripts/dwca_parser.php | 1 - .../standalone_scripts/index.php | 9 +- .../standalone_scripts/media_scripts.php | 29 +- .../specprocessor/wordcloudhandler.php | 8 +- collections/tools/index.php | 7 +- collections/tools/mapcoordaid.php | 634 +- collections/tools/mappointaid.php | 641 +- collections/tools/mappolyaid.php | 25 +- collections/tools/wikistatementparser.php | 337 + collections/traitattr/attributemining.php | 94 +- collections/traitattr/index.php | 9 +- collections/traitattr/occurattributes.php | 574 +- collections/traitattr/traitmanagment.php | 13 +- composer.json | 3 +- composer.lock | 273 +- config/auth_config_template.php | 10 + config/dbconnection_template.php | 19 +- config/index.php | 9 +- config/schema/1.0/data/index.php | 9 +- config/schema/1.0/index.php | 9 +- config/schema/1.0/misc/index.php | 9 +- config/schema/1.0/patches/index.php | 9 +- config/schema/2.0/index.php | 9 +- .../{1.0 => 3.0}/data/extended_taxonrank.sql | 0 config/schema/3.0/data/geothesaurus.sql | 5 + .../{1.0 => 3.0}/data/taxa_upper_lichens.csv | 0 .../{1.0 => 3.0}/data/taxa_upper_plants.csv | 0 config/schema/3.0/db_schema-3.0.sql | 10 +- config/schema/3.0/dev/index.php | 9 +- config/schema/3.0/dev/proposed_tables.sql | 26 - config/schema/3.0/index.php | 9 +- .../3.0/patches/db_schema_patch-3.1.sql | 275 + config/schema/3.0/patches/index.php | 9 +- config/schema/index.php | 9 +- config/setup.bash | 191 + config/{setup.sh => setup_pre_4.4.bash} | 12 +- config/setup_win.ps1 | 1 - config/symbbase.php | 87 +- config/symbini_template.php | 42 +- .../reports/labeljson_template.php | 10 +- content/index.php | 9 +- content/lang/admin/langmanager.php | 10 +- content/lang/checklists/checklist.en.php | 33 +- content/lang/checklists/checklist.es.php | 79 +- content/lang/checklists/checklist.fr.php | 85 +- content/lang/checklists/checklist.pt.php | 112 + content/lang/checklists/checklistadmin.en.php | 30 +- content/lang/checklists/checklistadmin.es.php | 91 +- content/lang/checklists/checklistadmin.fr.php | 85 +- .../checklists/checklistadminchildren.en.php | 28 +- .../checklists/checklistadminchildren.es.php | 30 +- .../checklists/checklistadminchildren.fr.php | 47 +- .../lang/checklists/checklistadminmeta.en.php | 1 + .../lang/checklists/checklistadminmeta.es.php | 3 + .../lang/checklists/checklistadminmeta.fr.php | 1 + .../lang/checklists/checklistloader.en.php | 47 + .../lang/checklists/checklistloader.es.php | 47 + .../lang/checklists/checklistloader.fr.php | 47 + content/lang/checklists/checklistmap.en.php | 4 +- content/lang/checklists/checklistmap.es.php | 5 + content/lang/checklists/checklistmap.fr.php | 3 + content/lang/checklists/clgmap.en.php | 13 + content/lang/checklists/clgmap.es.php | 15 + content/lang/checklists/clgmap.fr.php | 15 + content/lang/checklists/clsppeditor.en.php | 16 +- content/lang/checklists/clsppeditor.es.php | 18 +- content/lang/checklists/clsppeditor.fr.php | 16 +- content/lang/checklists/dynamicmap.en.php | 3 +- content/lang/checklists/dynamicmap.es.php | 7 +- content/lang/checklists/dynamicmap.fr.php | 1 + content/lang/checklists/index.en.php | 3 + content/lang/checklists/index.es.php | 5 + content/lang/checklists/index.fr.php | 2 + .../lang/checklists/rpc/linkvoucher.en.php | 11 + .../lang/checklists/rpc/linkvoucher.es.php | 11 + .../lang/checklists/rpc/linkvoucher.fr.php | 11 + content/lang/checklists/vaconflicts.en.php | 1 + content/lang/checklists/vaconflicts.es.php | 3 + content/lang/checklists/vaconflicts.fr.php | 9 +- content/lang/checklists/vamissingtaxa.en.php | 1 + content/lang/checklists/vamissingtaxa.es.php | 3 + content/lang/checklists/vamissingtaxa.fr.php | 11 +- content/lang/checklists/voucheradmin.en.php | 25 + content/lang/checklists/voucheradmin.es.php | 69 +- content/lang/checklists/voucheradmin.fr.php | 26 +- content/lang/classes/OccurrenceManager.en.php | 27 + content/lang/classes/OccurrenceManager.es.php | 27 + content/lang/classes/OccurrenceManager.fr.php | 27 + .../lang/classes/TaxonomyEditorManager.en.php | 2 + .../lang/collections/admin/guidmapper.en.php | 7 +- .../lang/collections/admin/guidmapper.es.php | 5 + .../lang/collections/admin/guidmapper.fr.php | 21 + .../collections/admin/igsnmanagement.en.php | 38 + .../collections/admin/igsnmanagement.es.php | 40 + .../collections/admin/igsnmanagement.fr.php | 40 + .../lang/collections/admin/igsnmapper.en.php | 42 + .../lang/collections/admin/igsnmapper.es.php | 44 + .../lang/collections/admin/igsnmapper.fr.php | 44 + .../collections/admin/igsnverification.en.php | 38 + .../collections/admin/igsnverification.es.php | 40 + .../collections/admin/igsnverification.fr.php | 40 + .../collections/admin/importextended.en.php | 62 + .../collections/admin/importextended.es.php | 62 + .../collections/admin/importextended.fr.php | 62 + .../lang/collections/admin/specupload.en.php | 16 +- .../lang/collections/admin/specupload.es.php | 17 +- .../lang/collections/admin/specupload.fr.php | 16 +- .../admin/specuploadmanagement.en.php | 3 + .../admin/specuploadmanagement.es.php | 4 + content/lang/collections/associations.en.php | 49 + content/lang/collections/associations.es.php | 49 + content/lang/collections/associations.fr.php | 49 + content/lang/collections/checklist.es.php | 2 +- .../cleaning/fieldstandardization.en.php | 20 + .../cleaning/fieldstandardization.es.php | 21 + .../cleaning/fieldstandardization.fr.php | 20 + .../collections/cleaning/imagerecycler.en.php | 16 + .../collections/cleaning/imagerecycler.es.php | 18 + .../collections/cleaning/imagerecycler.fr.php | 18 + .../lang/collections/cleaning/index.en.php | 33 + .../lang/collections/cleaning/index.es.php | 33 + .../lang/collections/cleaning/index.fr.php | 33 + .../cleaning/politicalunits.fr.php | 45 + .../cleaning/taxonomycleaner.en.php | 1 + .../cleaning/taxonomycleaner.es.php | 11 +- .../cleaning/taxonomycleaner.fr.php | 49 + .../lang/collections/customsearchtype.en.php | 19 + .../lang/collections/customsearchtype.es.php | 21 + .../lang/collections/customsearchtype.fr.php | 19 + .../collections/datasets/datapublisher.en.php | 1 + .../collections/datasets/datapublisher.es.php | 2 +- .../collections/datasets/datapublisher.fr.php | 1 + .../datasets/datasetmanager.en.php | 3 +- .../datasets/datasetmanager.es.php | 71 + .../datasets/datasetmanager.fr.php | 71 + .../datasets/duplicatemanager.es.php | 40 + .../lang/collections/datasets/index.en.php | 25 + .../lang/collections/datasets/index.es.php | 25 + .../lang/collections/datasets/index.fr.php | 25 + .../datasets/occurharvester.en.php | 21 + .../datasets/occurharvester.es.php | 24 + .../datasets/occurharvester.fr.php | 25 + .../lang/collections/datasets/public.en.php | 15 + .../lang/collections/datasets/public.es.php | 17 + .../lang/collections/datasets/public.fr.php | 17 + .../collections/datasets/publiclist.en.php | 10 + .../collections/datasets/publiclist.es.php | 10 + .../collections/datasets/publiclist.fr.php | 10 + .../lang/collections/download/index.en.php | 49 + .../lang/collections/download/index.es.php | 49 + .../lang/collections/download/index.fr.php | 48 + .../collections/editor/assocsppaid.es.php | 2 + .../editor/batchdeterminations.en.php | 4 +- .../editor/batchdeterminations.es.php | 6 +- .../editor/batchdeterminations.fr.php | 6 +- .../lang/collections/editor/dupesearch.en.php | 7 +- .../lang/collections/editor/dupesearch.es.php | 7 +- .../lang/collections/editor/dupesearch.fr.php | 7 +- .../collections/editor/editreviewer.en.php | 6 + .../collections/editor/editreviewer.es.php | 6 + .../editor/imageoccursubmit.en.php | 2 + .../editor/imageoccursubmit.es.php | 6 +- .../editor/imageoccursubmit.fr.php | 46 + .../editor/includes/admintab.es.php | 2 + .../editor/includes/determinationtab.en.php | 2 - .../editor/includes/geotools.es.php | 4 +- .../editor/includes/imagetab.es.php | 4 +- .../editor/includes/imgprocessor.es.php | 4 +- .../includes/materialsampleinclude.en.php | 15 + .../includes/materialsampleinclude.es.php | 15 + .../includes/materialsampleinclude.fr.php | 15 + .../editor/includes/queryform.en.php | 30 +- .../editor/includes/queryform.es.php | 34 +- .../editor/includes/queryform.fr.php | 36 +- .../editor/includes/resourcetab.en.php | 35 +- .../editor/includes/resourcetab.es.php | 39 +- .../editor/includes/resourcetab.fr.php | 35 +- .../editor/includes/traittab.es.php | 4 +- .../editor/observationsubmit.en.php | 28 +- .../editor/observationsubmit.es.php | 34 +- .../editor/observationsubmit.fr.php | 81 + .../editor/occurrenceeditor.en.php | 35 +- .../editor/occurrenceeditor.es.php | 35 +- .../editor/occurrenceeditor.fr.php | 36 +- .../editor/occurrencetabledisplay.en.php | 11 +- .../editor/occurrencetabledisplay.es.php | 15 +- .../editor/occurrencetabledisplay.fr.php | 11 +- .../collections/editor/rpc/dupelist.en.php | 21 + .../collections/editor/rpc/dupelist.es.php | 23 + .../collections/editor/rpc/dupelist.fr.php | 23 + .../collections/editor/skeletalsubmit.en.php | 5 +- .../collections/editor/skeletalsubmit.es.php | 9 +- .../collections/editor/skeletalsubmit.fr.php | 5 +- .../editor/tools/coordformatter.en.php | 61 + .../editor/tools/coordformatter.es.php | 59 + .../editor/tools/coordformatter.fr.php | 59 + .../fieldterms/occurrenceterms.en.php | 92 + .../fieldterms/occurrenceterms.es.php | 92 + .../fieldterms/occurrenceterms.fr.php | 92 + .../collections/georef/batchgeoreftool.en.php | 25 + .../collections/georef/batchgeoreftool.es.php | 29 +- .../collections/georef/batchgeoreftool.fr.php | 25 + .../collections/georef/georefclone.en.php | 19 + .../collections/georef/georefclone.es.php | 19 + .../collections/georef/georefclone.fr.php | 19 + content/lang/collections/harvestparams.en.php | 16 +- content/lang/collections/harvestparams.es.php | 42 +- content/lang/collections/harvestparams.fr.php | 16 +- content/lang/collections/harvestparams.pt.php | 70 + content/lang/collections/index.es.php | 2 +- content/lang/collections/index.pt.php | 18 + .../lang/collections/individual/index.en.php | 19 +- .../lang/collections/individual/index.es.php | 99 +- .../lang/collections/individual/index.fr.php | 17 +- content/lang/collections/list.en.php | 35 +- content/lang/collections/list.es.php | 36 +- content/lang/collections/list.fr.php | 35 +- content/lang/collections/list.pt.php | 74 + .../lang/collections/listtabledisplay.en.php | 10 +- .../lang/collections/listtabledisplay.es.php | 11 +- .../lang/collections/listtabledisplay.fr.php | 9 +- .../lang/collections/listtabledisplay.pt.php | 35 + .../lang/collections/loans/loan_langs.en.php | 227 + .../lang/collections/loans/loan_langs.es.php | 227 + .../lang/collections/loans/loan_langs.fr.php | 232 + content/lang/collections/map/index.en.php | 28 +- content/lang/collections/map/index.es.php | 38 +- content/lang/collections/map/index.fr.php | 70 + content/lang/collections/map/index.pt.php | 69 + .../lang/collections/map/leafletmap.en.php | 24 + .../lang/collections/map/leafletmap.es.php | 24 + .../lang/collections/map/leafletmap.fr.php | 25 + content/lang/collections/map/mapbox.en.php | 40 + content/lang/collections/map/mapbox.es.php | 41 + content/lang/collections/map/mapbox.fr.php | 41 + content/lang/collections/map/mapshared.en.php | 15 + content/lang/collections/map/mapshared.es.php | 16 + content/lang/collections/map/mapshared.fr.php | 16 + content/lang/collections/map/simplemap.en.php | 18 + content/lang/collections/map/simplemap.es.php | 18 + content/lang/collections/map/simplemap.fr.php | 18 + .../collections/map/staticmaphandler.en.php | 39 + .../collections/map/staticmaphandler.es.php | 40 + .../collections/map/staticmaphandler.fr.php | 39 + .../collections/misc/assocmanagement.es.php | 4 +- .../lang/collections/misc/colladdress.es.php | 4 +- .../lang/collections/misc/collbackup.en.php | 9 +- .../lang/collections/misc/collbackup.es.php | 11 +- .../lang/collections/misc/collbackup.fr.php | 9 +- .../misc/collectionproperties.en.php | 24 + .../misc/collectionproperties.es.php | 26 + .../misc/collectionproperties.fr.php | 25 + .../lang/collections/misc/collmetadata.en.php | 5 + .../lang/collections/misc/collmetadata.es.php | 130 + .../lang/collections/misc/collmetadata.fr.php | 5 + .../collections/misc/collmetaresources.es.php | 4 +- .../collections/misc/collorderstats.es.php | 4 +- .../collections/misc/collpermissions.en.php | 4 + .../collections/misc/collpermissions.es.php | 12 +- .../collections/misc/collpermissions.fr.php | 8 + .../lang/collections/misc/collprofiles.en.php | 14 + .../lang/collections/misc/collprofiles.es.php | 108 +- .../lang/collections/misc/collprofiles.fr.php | 14 + .../lang/collections/misc/collstats.en.php | 17 +- .../lang/collections/misc/collstats.es.php | 20 +- .../lang/collections/misc/collstats.fr.php | 14 +- .../lang/collections/misc/commentlist.en.php | 6 +- .../lang/collections/misc/commentlist.es.php | 5 + .../lang/collections/misc/commentlist.fr.php | 3 + .../collections/misc/institutioneditor.en.php | 52 + .../collections/misc/institutioneditor.es.php | 54 + .../collections/misc/institutioneditor.fr.php | 54 + .../collections/misc/occurrencesearch.en.php | 31 + .../collections/misc/occurrencesearch.es.php | 34 + .../collections/misc/occurrencesearch.fr.php | 36 + .../collections/misc/protectedspecies.en.php | 28 + .../collections/misc/protectedspecies.es.php | 28 + .../collections/misc/protectedspecies.fr.php | 28 + .../lang/collections/portalSelector.en.php | 10 + .../lang/collections/portalSelector.es.php | 10 + .../lang/collections/portalSelector.fr.php | 10 + .../lang/collections/portalSelector.pt.php | 10 + .../reports/annotationmanager.es.php | 33 + .../reports/annotationmanager.fr.php | 33 + .../collections/reports/labelmanager.en.php | 73 + .../collections/reports/labelmanager.es.php | 73 + .../collections/reports/labelmanager.fr.php | 73 + content/lang/collections/search/index.en.php | 116 + content/lang/collections/search/index.es.php | 119 + content/lang/collections/search/index.fr.php | 120 + content/lang/collections/sharedterms.en.php | 14 +- content/lang/collections/sharedterms.es.php | 14 +- content/lang/collections/sharedterms.fr.php | 14 +- content/lang/collections/sharedterms.pt.php | 34 + .../crowdsource/controlpanel.en.php | 2 +- .../crowdsource/controlpanel.es.php | 24 +- .../crowdsource/controlpanel.fr.php | 42 + .../specprocessor/crowdsource/index.en.php | 6 + .../specprocessor/crowdsource/index.es.php | 30 +- .../specprocessor/crowdsource/index.fr.php | 36 + .../specprocessor/crowdsource/review.es.php | 24 +- .../specprocessor/crowdsource/review.fr.php | 36 + .../collections/specprocessor/exporter.es.php | 80 + .../collections/specprocessor/exporter.fr.php | 78 + .../specprocessor/geolocate.en.php | 4 + .../specprocessor/geolocate.es.php | 45 + .../specprocessor/geolocate.fr.php | 41 + .../collections/specprocessor/index.en.php | 18 + .../collections/specprocessor/index.es.php | 18 + .../collections/specprocessor/index.fr.php | 18 + .../specprocessor/salix/salixhandler.en.php | 21 + .../specprocessor/salix/salixhandler.es.php | 21 + .../specprocessor/salix/salixhandler.fr.php | 21 + .../specprocessor/specprocessor_tools.en.php | 222 + .../specprocessor/specprocessor_tools.es.php | 223 + .../specprocessor/specprocessor_tools.fr.php | 224 + content/lang/collections/tools/mapaids.en.php | 21 +- content/lang/collections/tools/mapaids.es.php | 25 +- content/lang/collections/tools/mapaids.fr.php | 46 + .../traitattr/attributemining.en.php | 56 + .../traitattr/attributemining.es.php | 55 + .../traitattr/attributemining.fr.php | 56 + .../traitattr/occurattributes.en.php | 69 + .../traitattr/occurattributes.es.php | 74 + .../traitattr/occurattributes.fr.php | 70 + content/lang/geothesaurus/harvester.en.php | 51 + content/lang/geothesaurus/harvester.es.php | 53 + content/lang/geothesaurus/harvester.fr.php | 53 + content/lang/geothesaurus/index.en.php | 58 + content/lang/geothesaurus/index.es.php | 58 + content/lang/geothesaurus/index.fr.php | 58 + content/lang/glossary/addterm.en.php | 1 + content/lang/glossary/addterm.es.php | 1 + content/lang/glossary/index.en.php | 5 +- content/lang/glossary/index.es.php | 5 +- content/lang/glossary/individual.en.php | 1 + content/lang/glossary/individual.es.php | 1 + content/lang/glossary/termdetails.en.php | 1 + content/lang/glossary/termdetails.es.php | 1 + content/lang/ident/index.en.php | 28 +- content/lang/ident/index.es.php | 36 +- content/lang/ident/index.fr.php | 39 + content/lang/ident/key.en.php | 35 +- content/lang/ident/key.es.php | 41 +- content/lang/ident/tools/matrixeditor.en.php | 24 + content/lang/ident/tools/matrixeditor.es.php | 23 + content/lang/ident/tools/matrixeditor.fr.php | 24 + .../lang/imagelib/admin/imageloader.en.php | 1 + .../lang/imagelib/admin/imageloader.es.php | 37 + .../lang/imagelib/admin/imageloader.fr.php | 37 + .../imagelib/admin/thumbnailbuilder.es.php | 41 + .../imagelib/admin/thumbnailbuilder.fr.php | 41 + content/lang/imagelib/contributors.en.php | 14 + content/lang/imagelib/contributors.es.php | 14 + content/lang/imagelib/contributors.fr.php | 14 + content/lang/imagelib/contributors.pt.php | 14 + content/lang/imagelib/imgdetails.en.php | 63 + content/lang/imagelib/imgdetails.es.php | 65 + content/lang/imagelib/imgdetails.fr.php | 64 + content/lang/imagelib/index.es.php | 24 +- content/lang/imagelib/search.en.php | 47 +- content/lang/imagelib/search.es.php | 48 +- content/lang/imagelib/search.fr.php | 48 +- .../lang/misc/aboutproject.en_template.php | 1 + .../lang/misc/aboutproject.es_template.php | 1 + content/lang/profile/authCallback.en.php | 14 + content/lang/profile/authCallback.es.php | 16 + content/lang/profile/authCallback.fr.php | 16 + content/lang/profile/index.en.php | 2 + content/lang/profile/index.es.php | 34 +- content/lang/profile/index.fr.php | 3 +- content/lang/profile/newprofile.en.php | 3 + content/lang/profile/newprofile.es.php | 4 +- content/lang/profile/newprofile.fr.php | 4 +- content/lang/profile/occurrencemenu.en.php | 1 + content/lang/profile/occurrencemenu.es.php | 1 + content/lang/profile/occurrencemenu.fr.php | 1 + .../lang/profile/personalspecbackup.en.php | 2 +- .../lang/profile/personalspecbackup.es.php | 2 +- .../lang/profile/personalspecbackup.fr.php | 2 +- content/lang/profile/taxoninterests.fr.php | 2 +- content/lang/profile/usermanagement.en.php | 6 +- content/lang/profile/usermanagement.es.php | 4 + content/lang/profile/usermanagement.fr.php | 6 +- content/lang/profile/userprofile.en.php | 5 + content/lang/profile/userprofile.es.php | 5 +- content/lang/profile/userprofile.fr.php | 5 +- .../lang/profile/usertaxonomymanager.en.php | 3 +- .../lang/profile/usertaxonomymanager.es.php | 1 + .../lang/profile/usertaxonomymanager.fr.php | 2 +- content/lang/projects/index.en.php | 24 + content/lang/projects/index.es.php | 62 +- content/lang/projects/index.fr.php | 24 + content/lang/sitemap.en.php | 14 +- content/lang/sitemap.es.php | 129 +- content/lang/sitemap.fr.php | 15 +- content/lang/sitemap.pt.php | 134 + content/lang/taxa/index.en.php | 5 + content/lang/taxa/index.es.php | 34 +- content/lang/taxa/index.fr.php | 4 + content/lang/taxa/index.pt.php | 44 + content/lang/taxa/profile/tpdesceditor.en.php | 40 + content/lang/taxa/profile/tpdesceditor.es.php | 42 + content/lang/taxa/profile/tpdesceditor.fr.php | 42 + content/lang/taxa/profile/tpdesceditor.pt.php | 42 + content/lang/taxa/profile/tpeditor.en.php | 2 + content/lang/taxa/profile/tpeditor.es.php | 51 + content/lang/taxa/profile/tpeditor.fr.php | 2 + content/lang/taxa/profile/tpeditor.pt.php | 53 + .../lang/taxa/profile/tpimageeditor.en.php | 3 + .../lang/taxa/profile/tpimageeditor.es.php | 43 + .../lang/taxa/profile/tpimageeditor.fr.php | 43 + .../lang/taxa/profile/tpimageeditor.pt.php | 43 + content/lang/taxa/taxonomy/batchloader.en.php | 2 +- content/lang/taxa/taxonomy/batchloader.es.php | 2 +- content/lang/taxa/taxonomy/taxoneditor.en.php | 16 +- content/lang/taxa/taxonomy/taxoneditor.es.php | 12 +- .../lang/taxa/taxonomy/taxonomydelete.en.php | 5 +- .../lang/taxa/taxonomy/taxonomydelete.es.php | 3 + .../lang/taxa/taxonomy/taxonomydisplay.en.php | 5 + .../lang/taxa/taxonomy/taxonomydisplay.es.php | 6 + .../lang/taxa/taxonomy/taxonomydisplay.fr.php | 34 + .../lang/taxa/taxonomy/taxonomyloader.en.php | 5 + .../lang/taxa/taxonomy/taxonomyloader.es.php | 4 + content/lang/templates/accessibility.en.php | 15 + content/lang/templates/accessibility.es.php | 15 + content/lang/templates/accessibility.fr.php | 15 + content/lang/templates/accessibility.pt.php | 15 + content/lang/templates/header.en.override.php | 14 + content/lang/templates/header.en.php | 69 + content/lang/templates/header.es.override.php | 14 + .../header.es.php} | 26 +- content/lang/templates/header.fr.override.php | 14 + content/lang/templates/header.fr.php | 69 + content/lang/templates/header.pt.override.php | 14 + content/lang/templates/header.pt.php | 69 + content/lang/templates/index.en.php | 17 + content/lang/templates/index.es.php | 17 + content/lang/templates/index.fr.php | 17 + content/lang/templates/index.pt.php | 17 + content/lang/templates/usagepolicy.en.php | 11 + content/lang/templates/usagepolicy.es.php | 11 + content/lang/templates/usagepolicy.fr.php | 11 + content/lang/templates/usagepolicy.pt.php | 11 + content/logs/cyverse/index.php | 9 +- content/logs/gbif/index.php | 9 +- content/logs/idigbio/index.php | 9 +- content/logs/igsn/index.php | 9 +- content/logs/imgProccessing/index.php | 9 +- content/logs/index.php | 9 +- content/logs/install/index.php | 9 +- content/logs/iplant/index.php | 9 +- content/logs/occurImport/index.php | 9 +- content/logs/processing/index.php | 9 +- content/logs/stats/index.php | 9 +- css/base.css | 399 - css/index.php | 9 +- {js/jquery-ui => css}/jquery-ui.css | 107 +- .../jquery-ui.min.css | 6 +- css/jquery.mobile-1.4.0.min.css | 683 - css/leafletMap.css | 45 + css/occureditor.css | 53 +- css/symb/index.php | 32 - css/symb/main.css | 237 - css/symb/tooltips.css | 4 - css/symbiota/accessibility-compliant.css | 19 + css/symbiota/accessibility-controls.css | 22 + css/symbiota/checklists/checklist.css | 177 + .../symbiota/checklists/index.php | 7 +- .../collections/editor}/index.php | 7 +- .../editor/occureditormaterialsample.css | 13 - .../collections/editor/occurrenceeditor.css | 155 +- .../symbiota/collections/index.php | 7 +- css/symbiota/collections/individual/index.css | 153 + .../collections/individual}/index.php | 7 +- .../symbiota/collections/individual/popup.css | 3 - .../symbiota/collections/listdisplay.css | 19 - .../collections/reports}/index.php | 7 +- .../collections/reports}/labelhelpers.css | 0 .../collections/reports}/lichenpacket.css | 0 .../collections/sharedCollectionStyling.css | 191 + css/symbiota/condensed.css | 15 + css/symbiota/customizations.css | 5 + css/{v202209 => }/symbiota/footer.css | 1 - css/{v202209 => }/symbiota/header.css | 148 +- css/{v202209 => }/symbiota/index.php | 9 +- css/symbiota/main.css | 683 + css/{v202209 => }/symbiota/normalize.slim.css | 9 - css/symbiota/reset.css | 130 + css/{v202209 => }/symbiota/sitemap.css | 20 +- css/{v202209 => }/symbiota/taxa/index.css | 4 +- css/{v202209 => }/symbiota/taxa/index.php | 9 +- css/{v202209 => }/symbiota/taxa/traitplot.css | 0 css/symbiota/variables.css | 25 + css/v202209/alerts.css | 43 - css/v202209/index.php | 32 - css/v202209/jquery-ui.css | 7 - css/v202209/ol.css | 1 - css/v202209/quicksearch.css | 27 - css/v202209/slideshowstyle.css | 178 - css/v202209/symbiota/base.css | 301 - css/v202209/symbiota/checklists/checklist.css | 324 - .../symbiota/collections/individual/index.css | 155 - .../collections/reports/labelhelpers.css | 985 - .../collections/reports/lichenpacket.css | 92 - css/v202209/symbiota/main.css | 294 - css/v202209/symbiota/variables.css | 32 - docs/CHANGELOG.md | 29 +- docs/CONTRIBUTING.md | 2 +- docs/INSTALL.md | 140 +- docs/UPDATE.md | 25 +- docs/index.php | 9 +- docs/release-protocol.md | 18 +- docs/styleguide.php | 60 + docs/third_party_auth_setup.md | 28 + games/flashcards.php | 32 +- games/index.php | 7 +- games/namegame.php | 66 +- games/ootd/index.php | 25 +- games/ootd/readme.txt | 4 +- games/rpc/index.php | 9 +- games/where/Help.html | 2 +- games/where/WhereInTheWorld.html | 3 +- games/where/index.php | 13 +- {css => games/where}/ol.css | 0 games/where/rpc/WhereInTheWorld.php | 6 +- geothesaurus/harvester.php | 481 +- geothesaurus/index.php | 832 +- geothesaurus/rpc/searchGeothesaurus.php | 15 + glossary/addterm.php | 20 +- glossary/glossaryloader.php | 23 +- glossary/glossdocexport.php | 19 +- glossary/index.php | 125 +- glossary/individual.php | 23 +- glossary/rpc/index.php | 9 +- glossary/sources.php | 42 +- glossary/termdetails.php | 101 +- ident/admin/chardetails.php | 60 +- ident/admin/headingadmin.php | 6 +- ident/admin/index.php | 93 +- ident/admin/taxonomylinkage.php | 12 +- ident/index.php | 17 +- ident/key-v1.php | 31 +- ident/key.php | 102 +- ident/rpc/index.php | 9 +- ident/tools/chardeficit.php | 11 +- ident/tools/editor.php | 20 +- ident/tools/index.php | 9 +- ident/tools/matrixeditor.php | 86 +- imagelib/admin/imageloader.php | 16 +- imagelib/admin/index.php | 9 +- imagelib/admin/thumbnailbuilder.php | 52 +- imagelib/admin/verifypaths.php | 13 +- imagelib/contributors.php | 32 +- imagelib/imgdetails.php | 178 +- imagelib/index.php | 51 +- imagelib/search.php | 489 +- ...cessibility_FILL0_wght400_GRAD0_opsz24.svg | 3 + images/add.png | Bin 367 -> 554 bytes ...ontent_copy_FILL0_wght400_GRAD0_opsz24.svg | 1 + images/cross-out.png | Bin 49185 -> 584 bytes images/css/images/index.php | 9 +- images/dataset-white.png | Bin 0 -> 7934 bytes images/dataset.png | Bin 3761 -> 223 bytes images/del.png | Bin 49831 -> 217 bytes images/dl.png | Bin 313 -> 262 bytes images/dl2-white.png | Bin 0 -> 3294 bytes images/dl2.png | Bin 1139 -> 262 bytes images/download-white.svg | 1 + .../download_FILL0_wght400_GRAD0_opsz24.svg | 1 + images/draggable.png | Bin 14265 -> 320 bytes images/drop.png | Bin 311 -> 217 bytes images/edit.png | Bin 451 -> 275 bytes images/edit_off.png | Bin 395 -> 412 bytes images/editadmin.png | Bin 50167 -> 7959 bytes images/editplus.png | Bin 3781 -> 228 bytes images/editspp.png | Bin 829 -> 8888 bytes images/editsquare.png | Bin 0 -> 321 bytes images/editvoucher.png | Bin 50132 -> 8131 bytes images/find.png | Bin 51094 -> 471 bytes images/games/games.png | Bin 49816 -> 440 bytes images/games/ootd/qmark.png | Bin 57613 -> 0 bytes images/icons/inaturalist.png | Bin 0 -> 18545 bytes images/index.php | 9 +- images/info.png | Bin 1127 -> 684 bytes images/info_deprecated.png | Bin 0 -> 554 bytes images/key.png | Bin 2382 -> 340 bytes images/layout/index.php | 9 +- images/layout/logo_symbiota.png | Bin 603287 -> 16305 bytes images/layout/logo_symbiota_lg.png | Bin 0 -> 603287 bytes images/link-white.svg | 1 + images/link.png | Bin 830 -> 311 bytes images/link2.png | Bin 2459 -> 311 bytes images/list_FILL0_wght400_GRAD0_opsz24.svg | 1 + images/map.png | Bin 3238 -> 486 bytes images/minus.png | Bin 48577 -> 215 bytes images/newwin.png | Bin 54681 -> 277 bytes images/plus.png | Bin 48550 -> 228 bytes images/print.png | Bin 452 -> 256 bytes images/qmark.png | Bin 492 -> 684 bytes images/table-white.png | Bin 0 -> 4689 bytes images/tochild.png | Bin 53349 -> 615 bytes images/toparent.png | Bin 54969 -> 604 bytes images/triangledown.png | Bin 2340 -> 219 bytes images/triangleright.png | Bin 47448 -> 196 bytes images/triangleup.png | Bin 267 -> 218 bytes images/undo.png | Bin 50421 -> 293 bytes images/voucheradd.png | Bin 48941 -> 268 bytes images/world.png | Bin 52078 -> 575 bytes includes/citationcollection_template.php | 5 +- includes/citationgbif_template.php | 3 + includes/footer_template.php | 29 +- includes/googleMap.php | 18 + includes/head_template.php | 16 +- includes/head_w_tooltips_template.php | 28 - includes/header_template.php | 105 +- includes/index.php | 9 +- includes/leafletMap.php | 65 + includes/leftmenu_template.php | 87 - includes/minimalheader_template.php | 100 + includes/quicksearch.php | 2 +- includes/styleguide_template.php | 37 - includes/usagepolicy_template.php | 28 +- index_template.php | 19 +- js/.DS_Store | Bin 0 -> 8196 bytes js/Leaflet.markercluster-1.4.1/.DS_Store | Bin 0 -> 6148 bytes js/Leaflet.markercluster-1.4.1/.gitignore | 7 + js/Leaflet.markercluster-1.4.1/.travis.yml | 20 + js/Leaflet.markercluster-1.4.1/CHANGELOG.md | 242 + .../CONTRIBUTING.md | 70 + .../ISSUE_TEMPLATE.md | 23 + js/Leaflet.markercluster-1.4.1/Jakefile.js | 86 + .../MIT-LICENCE.txt} | 25 +- js/Leaflet.markercluster-1.4.1/README.md | 292 + js/Leaflet.markercluster-1.4.1/bower.json | 27 + .../build/hintrc.js | 37 + .../build/rollup-config.js | 41 + .../dist/MarkerCluster.Default.css | 60 + .../dist/MarkerCluster.css | 14 + .../dist/WhereAreTheJavascriptFiles.txt | 5 + .../dist/leaflet.markercluster-src.js | 2690 ++ .../dist/leaflet.markercluster-src.js.map | 1 + .../dist/leaflet.markercluster.js | 3 + .../dist/leaflet.markercluster.js.map | 1 + .../example/geojson-sample.js | 53 + .../example/geojson.html | 54 + .../example/map.png | Bin 0 -> 389888 bytes .../example/marker-clustering-convexhull.html | 81 + .../example/marker-clustering-custom.html | 114 + .../example/marker-clustering-dragging.html | 83 + .../example/marker-clustering-everything.html | 80 + .../example/marker-clustering-geojson.html | 70 + .../example/marker-clustering-pane.html | 104 + ...rker-clustering-realworld-maxzoom.388.html | 45 + ...arker-clustering-realworld-mobile.388.html | 44 + .../marker-clustering-realworld.10000.html | 46 + .../marker-clustering-realworld.388.html | 45 + .../marker-clustering-realworld.50000.html | 78 + .../marker-clustering-singlemarkermode.html | 60 + .../example/marker-clustering-spiderfier.html | 60 + .../marker-clustering-zoomtobounds.html | 60 + .../marker-clustering-zoomtoshowlayer.html | 59 + .../example/marker-clustering.html | 88 + .../example/mobile.css | 6 + .../example/old-bugs/add-1000-after.html | 83 + .../old-bugs/add-markers-offscreen.html | 52 + .../old-bugs/add-remove-before-addtomap.html | 62 + .../example/old-bugs/animationless-zoom.html | 47 + .../click-cluster-at-screen-edge.html | 59 + .../disappearing-marker-from-spider.html | 106 + ...doesnt-update-cluster-on-bottom-level.html | 69 + .../old-bugs/drag-with-spiderfying.html | 75 + .../old-bugs/remove-add-clustering.html | 74 + .../old-bugs/remove-when-spiderfied.html | 65 + .../removelayer-after-remove-from-map.html | 69 + .../old-bugs/setView-doesnt-remove.html | 69 + .../zoomtoshowlayer-doesnt-need-to-zoom.html | 63 + ...oshowlayer-doesnt-zoom-if-centered-on.html | 56 + .../example/realworld.10000.js | 10004 +++++ .../example/realworld.388.js | 393 + .../example/realworld.50000.1.js | 25006 ++++++++++++ .../example/realworld.50000.2.js | 25000 ++++++++++++ .../remove-geoJSON-when-spiderfied.html | 83 + .../example/screen.css | 28 + js/Leaflet.markercluster-1.4.1/package.json | 43 + js/Leaflet.markercluster-1.4.1/spec/after.js | 2 + js/Leaflet.markercluster-1.4.1/spec/expect.js | 1253 + .../spec/index.html | 76 + .../spec/karma.conf.js | 96 + js/Leaflet.markercluster-1.4.1/spec/sinon.js | 4223 ++ .../spec/suites/AddLayer.MultipleSpec.js | 125 + .../spec/suites/AddLayer.SingleSpec.js | 118 + .../spec/suites/AddLayersSpec.js | 123 + .../suites/ChildChangingIconSupportSpec.js | 56 + .../spec/suites/CircleMarkerSupportSpec.js | 147 + .../spec/suites/CircleSupportSpec.js | 144 + .../spec/suites/DistanceGridSpec.js | 41 + .../spec/suites/LeafletSpec.js | 6 + .../spec/suites/NonPointSpec.js | 240 + .../spec/suites/PaneSpec.js | 63 + .../spec/suites/QuickHullSpec.js | 52 + .../spec/suites/RefreshSpec.js | 435 + .../spec/suites/RememberOpacity.js | 162 + .../spec/suites/RemoveLayerSpec.js | 204 + .../spec/suites/SpecHelper.js | 30 + .../spec/suites/animateOptionSpec.js | 117 + .../spec/suites/clearLayersSpec.js | 54 + .../suites/disableClusteringAtZoomSpec.js | 63 + .../spec/suites/eachLayerSpec.js | 65 + .../spec/suites/eventsSpec.js | 373 + .../spec/suites/getBoundsSpec.js | 128 + .../spec/suites/getLayersSpec.js | 75 + .../spec/suites/getVisibleParentSpec.js | 72 + .../spec/suites/markerMoveSupportSpec.js | 77 + .../spec/suites/nonIntegerZoomSpec.js | 52 + .../spec/suites/onAddSpec.js | 65 + .../spec/suites/onRemoveSpec.js | 52 + .../spec/suites/removeLayersSpec.js | 195 + .../suites/removeOutsideVisibleBoundsSpec.js | 248 + .../spec/suites/singleMarkerModeSpec.js | 74 + .../spec/suites/spiderfySpec.js | 344 + .../spec/suites/supportNegativeZoomSpec.js | 89 + .../spec/suites/unspiderfySpec.js | 139 + .../spec/suites/zoomAnimationSpec.js | 382 + .../src/DistanceGrid.js | 118 + .../src/MarkerCluster.QuickHull.js | 165 + .../src/MarkerCluster.Spiderfier.js | 475 + .../src/MarkerCluster.js | 406 + .../src/MarkerClusterGroup.Refresh.js | 110 + .../src/MarkerClusterGroup.js | 1374 + .../src/MarkerOpacity.js | 22 + js/Leaflet.markercluster-1.4.1/src/index.js | 8 + js/autocomplete-input.js | 246 + js/datatables/DataTables-1.11.5/css/index.php | 9 +- .../DataTables-1.11.5/images/index.php | 9 +- js/datatables/DataTables-1.11.5/index.php | 9 +- js/datatables/DataTables-1.11.5/js/index.php | 9 +- .../FixedColumns-4.0.2/css/index.php | 9 +- js/datatables/FixedColumns-4.0.2/index.php | 9 +- js/datatables/FixedColumns-4.0.2/js/index.php | 9 +- js/datatables/index.php | 9 +- js/dojo-1.17.3/dojo-1.14.1/claro-1.14.1.css | 5043 --- js/dojo-1.17.3/dojo-1.14.1/dojo.js | 247 - js/dojo-1.17.3/dojo/package-lock.json | 41 +- js/dojo-1.17.3/dojo/package.json | 2 +- js/dom-to-image/dist/dom-to-image.min.js | 2 + js/dropzone-5.7.0-min/index.php | 9 +- js/heatmap/google-heatmap.js | 280 + js/heatmap/heatmap.js | 9 + js/heatmap/leaflet-heatmap.js | 246 + js/index.php | 9 +- js/jquery-1.10.2.min.js | 6 - js/jquery-1.10.2.min.map | 1 - js/jquery-1.9.1.js | 9597 ----- js/jquery-3.2.1.min.js | 4 - js/jquery-3.7.1.min.js | 2 + js/jquery-ui-1.10.4.js | 15008 ------- js/jquery-ui-1.12.1/AUTHORS.txt | 333 - js/jquery-ui-1.12.1/images/index.php | 33 - .../ui-bg_highlight-soft_65_f3f3f3_1x100.png | Bin 274 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 280 -> 0 bytes .../images/ui-icons_222222_256x240.png | Bin 6922 -> 0 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 4549 -> 0 bytes .../images/ui-icons_454545_256x240.png | Bin 6992 -> 0 bytes .../images/ui-icons_888888_256x240.png | Bin 6999 -> 0 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 4549 -> 0 bytes js/jquery-ui-1.12.1/index.php | 33 - js/jquery-ui-1.12.1/jquery-ui.css | 1312 - js/jquery-ui-1.12.1/jquery-ui.js | 18706 --------- js/jquery-ui-1.12.1/jquery-ui.min.css | 7 - js/jquery-ui-1.12.1/jquery-ui.min.js | 13 - js/jquery-ui-1.12.1/jquery-ui.structure.css | 886 - .../jquery-ui.structure.min.css | 5 - js/jquery-ui-1.12.1/jquery-ui.theme.css | 443 - js/jquery-ui-1.12.1/jquery-ui.theme.min.css | 5 - js/jquery-ui-1.12.1/package.json | 74 - js/jquery-ui.js | 7 - js/jquery-ui.min.js | 6 + js/jquery-ui/AUTHORS.txt | 367 - js/jquery-ui/images/index.php | 33 - .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 393 -> 0 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 265 -> 0 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 323 -> 0 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 324 -> 0 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 390 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 325 -> 0 bytes .../images/ui-icons_222222_256x240.png | Bin 7025 -> 0 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 4618 -> 0 bytes .../images/ui-icons_454545_256x240.png | Bin 7090 -> 0 bytes .../images/ui-icons_888888_256x240.png | Bin 7111 -> 0 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 4618 -> 0 bytes js/jquery-ui/index.php | 33 - js/jquery-ui/jquery-ui.js | 19057 --------- js/jquery-ui/jquery-ui.min.css | 7 - js/jquery-ui/jquery-ui.min.js | 6 - js/jquery-ui/jquery-ui.structure.css | 886 - js/jquery-ui/jquery-ui.theme.css | 446 - js/jquery-ui/jquery-ui.theme.min.css | 5 - js/jquery-ui/package.json | 74 - js/jquery-ui/theme.css | 446 - js/jquery.js | 9597 ----- js/jquery.mobile-1.4.0.min.js | 11 - js/jquery.mobile-1.4.0.min.map | 1 - js/jscolor/index.php | 9 +- js/leaflet-draw-drag/README.md | 96 + .../dist/Leaflet.draw.drag-src.js | 1103 + .../dist/Leaflet.draw.drag.js | 12 + js/leaflet-draw-drag/index.js | 11 + .../node_modules/leaflet-draw/CHANGELOG.md | 200 + .../node_modules/leaflet-draw/README.md | 174 + .../leaflet-draw/dist/images/layers-2x.png | Bin 0 -> 1259 bytes .../leaflet-draw/dist/images/layers.png | Bin 0 -> 696 bytes .../dist/images/marker-icon-2x.png | Bin 0 -> 2586 bytes .../leaflet-draw/dist/images/marker-icon.png | Bin 0 -> 1466 bytes .../dist/images/marker-shadow.png | Bin 0 -> 618 bytes .../dist/images/spritesheet-2x.png | Bin 0 -> 3581 bytes .../leaflet-draw/dist/images/spritesheet.png | Bin 0 -> 1906 bytes .../leaflet-draw/dist/images/spritesheet.svg | 156 + .../leaflet-draw/dist/leaflet.draw-src.css | 325 + .../leaflet-draw/dist/leaflet.draw-src.js | 4791 +++ .../leaflet-draw/dist/leaflet.draw-src.map | 1 + .../leaflet-draw/dist/leaflet.draw.css | 10 + .../leaflet-draw/dist/leaflet.draw.js | 10 + .../leaflet-draw/docs/css/main.css | 1087 + .../leaflet-draw/docs/css/normalize.css | 426 + .../docs/examples-0.7.x/basic.html | 108 + .../docs/examples-0.7.x/edithandlers.html | 79 + .../docs/examples-0.7.x/full.html | 85 + .../libs/Leaflet.draw.drag-src.js | 894 + .../examples-0.7.x/libs/images/layers-2x.png | Bin 0 -> 2898 bytes .../examples-0.7.x/libs/images/layers.png | Bin 0 -> 1502 bytes .../libs/images/marker-icon-2x.png | Bin 0 -> 4033 bytes .../libs/images/marker-icon.png | Bin 0 -> 1747 bytes .../libs/images/marker-icon@2x.png | Bin 0 -> 4033 bytes .../libs/images/marker-shadow.png | Bin 0 -> 797 bytes .../docs/examples-0.7.x/libs/leaflet-src.js | 9169 +++++ .../docs/examples-0.7.x/libs/leaflet.css | 478 + .../libs/leaflet.geometryutil.js | 682 + .../docs/examples-0.7.x/libs/leaflet.snap.js | 360 + .../docs/examples-0.7.x/libs/spectrum.css | 519 + .../docs/examples-0.7.x/libs/spectrum.js | 2080 + .../docs/examples-0.7.x/snapping.html | 171 + .../leaflet-draw/docs/examples/basic.html | 108 + .../docs/examples/edithandlers.html | 79 + .../leaflet-draw/docs/examples/full.html | 85 + .../examples/libs/Leaflet.draw.drag-src.js | 894 + .../docs/examples/libs/images/layers-2x.png | Bin 0 -> 1259 bytes .../docs/examples/libs/images/layers.png | Bin 0 -> 696 bytes .../examples/libs/images/marker-icon-2x.png | Bin 0 -> 2586 bytes .../docs/examples/libs/images/marker-icon.png | Bin 0 -> 1466 bytes .../examples/libs/images/marker-shadow.png | Bin 0 -> 618 bytes .../docs/examples/libs/leaflet-src.js | 13044 ++++++ .../docs/examples/libs/leaflet-src.map | 1 + .../docs/examples/libs/leaflet.css | 623 + .../examples/libs/leaflet.geometryutil.js | 682 + .../docs/examples/libs/leaflet.snap.js | 360 + .../docs/examples/libs/spectrum.css | 519 + .../docs/examples/libs/spectrum.js | 2080 + .../leaflet-draw/docs/examples/popup.html | 145 + .../leaflet-draw/docs/examples/snapping.html | 171 + .../leaflet-draw/docs/highlight/LICENSE | 24 + .../docs/highlight/highlight.pack.js | 1 + .../docs/highlight/styles/github-gist.css | 211 + .../leaflet-draw/docs/images/favicon.ico | Bin 0 -> 32988 bytes .../leaflet-draw/docs/images/forum-round.png | Bin 0 -> 1822 bytes .../leaflet-draw/docs/images/github-round.png | Bin 0 -> 2132 bytes .../leaflet-draw/docs/images/sprite.png | Bin 0 -> 2018 bytes .../leaflet-draw/docs/images/sprite.svg | 75 + .../docs/images/twitter-round.png | Bin 0 -> 2177 bytes .../leaflet-draw/docs/images/twitter.png | Bin 0 -> 444 bytes .../node_modules/leaflet-draw/docs/js/docs.js | 52 + .../docs/leaflet-draw-0.4.12.html | 2597 ++ .../docs/leaflet-draw-0.4.14.html | 2602 ++ .../leaflet-draw/docs/leaflet-draw-0.4.2.html | 2243 + .../leaflet-draw/docs/leaflet-draw-0.4.3.html | 2265 + .../leaflet-draw/docs/leaflet-draw-0.4.4.html | 2268 + .../leaflet-draw/docs/leaflet-draw-0.4.5.html | 2330 ++ .../leaflet-draw/docs/leaflet-draw-0.4.7.html | 2330 ++ .../leaflet-draw/docs/leaflet-draw-0.4.9.html | 2485 ++ .../docs/leaflet-draw-latest.html | 2602 ++ .../node_modules/leaflet-draw/package.json | 98 + js/leaflet-draw-drag/package.json | 44 + js/leaflet-draw-drag/src/Edit.Circle.Drag.js | 108 + js/leaflet-draw-drag/src/Edit.Poly.Drag.js | 218 + .../src/Edit.Rectangle.Drag.js | 121 + .../src/Edit.SimpleShape.Drag.js | 42 + js/leaflet-draw-drag/src/EditToolbar.Edit.js | 34 + js/leaflet-draw-drag/src/footer.js | 1 + js/leaflet-draw-drag/src/prelude.js | 14 + js/leaflet-draw/.DS_Store | Bin 0 -> 6148 bytes js/leaflet-draw/CHANGELOG.md | 200 + js/leaflet-draw/README.md | 174 + js/leaflet-draw/dist/.DS_Store | Bin 0 -> 6148 bytes js/leaflet-draw/dist/images/layers-2x.png | Bin 0 -> 1259 bytes js/leaflet-draw/dist/images/layers.png | Bin 0 -> 696 bytes .../dist/images/marker-icon-2x.png | Bin 0 -> 2586 bytes js/leaflet-draw/dist/images/marker-icon.png | Bin 0 -> 1466 bytes js/leaflet-draw/dist/images/marker-shadow.png | Bin 0 -> 618 bytes .../dist/images/spritesheet-2x.png | Bin 0 -> 3581 bytes js/leaflet-draw/dist/images/spritesheet.png | Bin 0 -> 1906 bytes js/leaflet-draw/dist/images/spritesheet.svg | 156 + js/leaflet-draw/dist/leaflet.draw-src.css | 325 + js/leaflet-draw/dist/leaflet.draw-src.js | 4774 +++ js/leaflet-draw/dist/leaflet.draw-src.map | 1 + js/leaflet-draw/dist/leaflet.draw.css | 10 + js/leaflet-draw/dist/leaflet.draw.js | 10 + js/leaflet-draw/docs/css/main.css | 1087 + js/leaflet-draw/docs/css/normalize.css | 426 + .../docs/examples-0.7.x/basic.html | 108 + .../docs/examples-0.7.x/edithandlers.html | 79 + js/leaflet-draw/docs/examples-0.7.x/full.html | 85 + .../libs/Leaflet.draw.drag-src.js | 894 + .../examples-0.7.x/libs/images/layers-2x.png | Bin 0 -> 2898 bytes .../examples-0.7.x/libs/images/layers.png | Bin 0 -> 1502 bytes .../libs/images/marker-icon-2x.png | Bin 0 -> 4033 bytes .../libs/images/marker-icon.png | Bin 0 -> 1747 bytes .../libs/images/marker-icon@2x.png | Bin 0 -> 4033 bytes .../libs/images/marker-shadow.png | Bin 0 -> 797 bytes .../docs/examples-0.7.x/libs/leaflet-src.js | 9169 +++++ .../docs/examples-0.7.x/libs/leaflet.css | 478 + .../libs/leaflet.geometryutil.js | 682 + .../docs/examples-0.7.x/libs/leaflet.snap.js | 360 + .../docs/examples-0.7.x/libs/spectrum.css | 519 + .../docs/examples-0.7.x/libs/spectrum.js | 2080 + .../docs/examples-0.7.x/snapping.html | 171 + js/leaflet-draw/docs/examples/basic.html | 108 + .../docs/examples/edithandlers.html | 79 + js/leaflet-draw/docs/examples/full.html | 85 + .../examples/libs/Leaflet.draw.drag-src.js | 894 + .../docs/examples/libs/images/layers-2x.png | Bin 0 -> 1259 bytes .../docs/examples/libs/images/layers.png | Bin 0 -> 696 bytes .../examples/libs/images/marker-icon-2x.png | Bin 0 -> 2586 bytes .../docs/examples/libs/images/marker-icon.png | Bin 0 -> 1466 bytes .../examples/libs/images/marker-shadow.png | Bin 0 -> 618 bytes .../docs/examples/libs/leaflet-src.js | 13802 +++++++ .../docs/examples/libs/leaflet-src.map | 1 + .../docs/examples/libs/leaflet.css | 636 + .../examples/libs/leaflet.geometryutil.js | 682 + .../docs/examples/libs/leaflet.snap.js | 360 + .../docs/examples/libs/spectrum.css | 519 + .../docs/examples/libs/spectrum.js | 2080 + js/leaflet-draw/docs/examples/popup.html | 145 + js/leaflet-draw/docs/examples/snapping.html | 171 + js/leaflet-draw/docs/highlight/LICENSE | 24 + .../docs/highlight/highlight.pack.js | 1 + .../docs/highlight/styles/github-gist.css | 211 + js/leaflet-draw/docs/images/favicon.ico | Bin 0 -> 32988 bytes js/leaflet-draw/docs/images/forum-round.png | Bin 0 -> 1822 bytes js/leaflet-draw/docs/images/github-round.png | Bin 0 -> 2132 bytes js/leaflet-draw/docs/images/sprite.png | Bin 0 -> 2018 bytes js/leaflet-draw/docs/images/sprite.svg | 75 + js/leaflet-draw/docs/images/twitter-round.png | Bin 0 -> 2177 bytes js/leaflet-draw/docs/images/twitter.png | Bin 0 -> 444 bytes js/leaflet-draw/docs/js/docs.js | 52 + js/leaflet-draw/docs/leaflet-draw-0.4.12.html | 2597 ++ js/leaflet-draw/docs/leaflet-draw-0.4.14.html | 2602 ++ js/leaflet-draw/docs/leaflet-draw-0.4.2.html | 2243 + js/leaflet-draw/docs/leaflet-draw-0.4.3.html | 2265 + js/leaflet-draw/docs/leaflet-draw-0.4.4.html | 2268 + js/leaflet-draw/docs/leaflet-draw-0.4.5.html | 2330 ++ js/leaflet-draw/docs/leaflet-draw-0.4.7.html | 2330 ++ js/leaflet-draw/docs/leaflet-draw-0.4.9.html | 2485 ++ js/leaflet-draw/docs/leaflet-draw-1.0.0.html | 2602 ++ js/leaflet-draw/docs/leaflet-draw-1.0.1.html | 2602 ++ js/leaflet-draw/docs/leaflet-draw-latest.html | 2602 ++ js/leaflet-draw/package.json | 99 + js/leaflet-path-drag/.editorconfig | 19 + js/leaflet-path-drag/.eslintrc | 58 + js/leaflet-path-drag/.npmignore | 31 + js/leaflet-path-drag/README.md | 56 + js/leaflet-path-drag/circle.yml | 3 + js/leaflet-path-drag/dist/L.Path.Drag-src.js | 565 + js/leaflet-path-drag/dist/L.Path.Drag.js | 6 + js/leaflet-path-drag/example/css/style.css | 46 + .../example/css/topcoat-desktop-light.css | 3509 ++ js/leaflet-path-drag/example/font/LICENSE.txt | 93 + .../example/font/SourceCodePro-Black.otf | Bin 0 -> 88472 bytes .../example/font/SourceCodePro-Bold.otf | Bin 0 -> 92248 bytes .../example/font/SourceCodePro-ExtraLight.otf | Bin 0 -> 84636 bytes .../example/font/SourceCodePro-Light.otf | Bin 0 -> 88152 bytes .../example/font/SourceCodePro-Regular.otf | Bin 0 -> 89600 bytes .../example/font/SourceCodePro-Semibold.otf | Bin 0 -> 89576 bytes .../example/font/SourceSansPro-Black.otf | Bin 0 -> 98472 bytes .../example/font/SourceSansPro-BlackIt.otf | Bin 0 -> 96536 bytes .../example/font/SourceSansPro-Bold.otf | Bin 0 -> 104072 bytes .../example/font/SourceSansPro-BoldIt.otf | Bin 0 -> 101848 bytes .../example/font/SourceSansPro-ExtraLight.otf | Bin 0 -> 94560 bytes .../font/SourceSansPro-ExtraLightIt.otf | Bin 0 -> 92880 bytes .../example/font/SourceSansPro-It.otf | Bin 0 -> 100556 bytes .../example/font/SourceSansPro-Light.otf | Bin 0 -> 98972 bytes .../example/font/SourceSansPro-LightIt.otf | Bin 0 -> 97352 bytes .../example/font/SourceSansPro-Regular.otf | Bin 0 -> 101820 bytes .../example/font/SourceSansPro-Semibold.otf | Bin 0 -> 101772 bytes .../example/font/SourceSansPro-SemiboldIt.otf | Bin 0 -> 100000 bytes js/leaflet-path-drag/example/index.html | 33 + js/leaflet-path-drag/example/js/app.js | 240 + js/leaflet-path-drag/example/js/bundle.js | 14416 +++++++ js/leaflet-path-drag/index.html | 5 + js/leaflet-path-drag/index.js | 7 + js/leaflet-path-drag/package.json | 50 + js/leaflet-path-drag/src/Canvas.js | 83 + js/leaflet-path-drag/src/Path.Drag.js | 360 + js/leaflet-path-drag/src/Path.Transform.js | 45 + js/leaflet-path-drag/src/SVG.VML.js | 57 + js/leaflet-path-drag/src/SVG.js | 20 + js/leaflet-path-drag/test/L.Path.Drag.test.js | 303 + js/symb/MapShapeHelper.js | 87 + js/symb/accessibilityUtils.js | 80 + js/symb/api.taxonomy.taxasuggest.js | 11 +- js/symb/checklists.checklist.js | 349 +- js/symb/checklists.externalserviceapi.js | 135 + js/symb/collections.editor.autocomplete.js | 46 + js/symb/collections.editor.main.js | 1002 +- js/symb/collections.editor.query.js | 61 +- js/symb/collections.editor.skeletal.js | 23 - js/symb/collections.editor.table.js | 12 +- js/symb/collections.editor.tools.js | 13 +- js/symb/collections.harvestparams.js | 225 +- js/symb/collections.imageoccursubmit.js | 25 - js/symb/collections.index.js | 291 +- js/symb/collections.list.js | 34 +- js/symb/collections.map.index.js | 1070 +- js/symb/collections.misc.collmetadata.js | 18 + js/symb/geolocate.js | 6 +- js/symb/googleMap.js | 309 + js/symb/ident.tools.matrixeditor.js | 29 + js/symb/imagelib.search.js | 30 +- js/symb/index.php | 9 +- js/symb/leafletMap.js | 656 + js/symb/localitySuggest.js | 44 + js/symb/selectUtilities.js | 31 + js/symb/taxa.index.js | 6 +- js/symb/taxa.taxonomyloader.js | 268 +- js/symb/wktpolygontools.js | 33 + js/tinymce/README.md | 73 + js/tinymce/icons/default/icons.js | 182 + js/tinymce/icons/default/icons.min.js | 1 + js/tinymce/index.php | 33 - js/tinymce/jquery.tinymce.min.js | 81 +- js/tinymce/langs/es.js | 261 - js/tinymce/langs/index.php | 33 - js/tinymce/plugins/advlist/plugin.js | 261 + js/tinymce/plugins/advlist/plugin.min.js | 4 +- js/tinymce/plugins/anchor/plugin.js | 216 + js/tinymce/plugins/anchor/plugin.min.js | 4 +- js/tinymce/plugins/autolink/plugin.js | 213 + js/tinymce/plugins/autolink/plugin.min.js | 4 +- js/tinymce/plugins/autoresize/plugin.js | 184 + js/tinymce/plugins/autoresize/plugin.min.js | 4 +- js/tinymce/plugins/autosave/plugin.js | 212 + js/tinymce/plugins/autosave/plugin.min.js | 4 +- js/tinymce/plugins/bbcode/plugin.js | 99 + js/tinymce/plugins/bbcode/plugin.min.js | 4 +- js/tinymce/plugins/charmap/plugin.js | 1696 + js/tinymce/plugins/charmap/plugin.min.js | 4 +- js/tinymce/plugins/code/plugin.js | 91 + js/tinymce/plugins/code/plugin.min.js | 4 +- js/tinymce/plugins/codesample/plugin.js | 2462 ++ js/tinymce/plugins/codesample/plugin.min.js | 4 +- js/tinymce/plugins/colorpicker/plugin.js | 21 + js/tinymce/plugins/colorpicker/plugin.min.js | 4 +- js/tinymce/plugins/contextmenu/plugin.js | 21 + js/tinymce/plugins/contextmenu/plugin.min.js | 4 +- js/tinymce/plugins/directionality/plugin.js | 453 + .../plugins/directionality/plugin.min.js | 4 +- .../plugins/emoticons/js/emojiimages.js | 9424 +++++ .../plugins/emoticons/js/emojiimages.min.js | 3 + js/tinymce/plugins/emoticons/js/emojis.js | 18400 +++++---- js/tinymce/plugins/emoticons/js/emojis.min.js | 4 +- js/tinymce/plugins/emoticons/plugin.js | 636 + js/tinymce/plugins/emoticons/plugin.min.js | 4 +- js/tinymce/plugins/fullpage/plugin.js | 544 + js/tinymce/plugins/fullpage/plugin.min.js | 4 +- js/tinymce/plugins/fullscreen/plugin.js | 1347 + js/tinymce/plugins/fullscreen/plugin.min.js | 4 +- js/tinymce/plugins/help/plugin.js | 852 + js/tinymce/plugins/help/plugin.min.js | 4 +- js/tinymce/plugins/hr/plugin.js | 45 + js/tinymce/plugins/hr/plugin.min.js | 4 +- js/tinymce/plugins/image/plugin.js | 1666 + js/tinymce/plugins/image/plugin.min.js | 4 +- js/tinymce/plugins/imagetools/plugin.js | 1531 + js/tinymce/plugins/imagetools/plugin.min.js | 4 +- js/tinymce/plugins/importcss/plugin.js | 342 + js/tinymce/plugins/importcss/plugin.min.js | 4 +- js/tinymce/plugins/index.php | 33 - js/tinymce/plugins/insertdatetime/plugin.js | 182 + .../plugins/insertdatetime/plugin.min.js | 4 +- js/tinymce/plugins/legacyoutput/plugin.js | 199 + js/tinymce/plugins/legacyoutput/plugin.min.js | 4 +- js/tinymce/plugins/link/plugin.js | 1293 + js/tinymce/plugins/link/plugin.min.js | 4 +- js/tinymce/plugins/lists/plugin.js | 2313 ++ js/tinymce/plugins/lists/plugin.min.js | 4 +- js/tinymce/plugins/media/plugin.js | 1359 + js/tinymce/plugins/media/plugin.min.js | 4 +- js/tinymce/plugins/nonbreaking/plugin.js | 100 + js/tinymce/plugins/nonbreaking/plugin.min.js | 4 +- js/tinymce/plugins/noneditable/plugin.js | 117 + js/tinymce/plugins/noneditable/plugin.min.js | 4 +- js/tinymce/plugins/pagebreak/plugin.js | 104 + js/tinymce/plugins/pagebreak/plugin.min.js | 4 +- js/tinymce/plugins/paste/plugin.js | 1803 + js/tinymce/plugins/paste/plugin.min.js | 4 +- js/tinymce/plugins/preview/plugin.js | 126 + js/tinymce/plugins/preview/plugin.min.js | 4 +- js/tinymce/plugins/print/plugin.js | 52 + js/tinymce/plugins/print/plugin.min.js | 4 +- js/tinymce/plugins/quickbars/plugin.js | 474 + js/tinymce/plugins/quickbars/plugin.min.js | 4 +- js/tinymce/plugins/save/plugin.js | 120 + js/tinymce/plugins/save/plugin.min.js | 4 +- js/tinymce/plugins/searchreplace/plugin.js | 1166 + .../plugins/searchreplace/plugin.min.js | 4 +- js/tinymce/plugins/spellchecker/plugin.js | 730 + js/tinymce/plugins/spellchecker/plugin.min.js | 4 +- js/tinymce/plugins/tabfocus/plugin.js | 130 + js/tinymce/plugins/tabfocus/plugin.min.js | 4 +- js/tinymce/plugins/table/plugin.js | 11510 ++++++ js/tinymce/plugins/table/plugin.min.js | 4 +- js/tinymce/plugins/template/plugin.js | 591 + js/tinymce/plugins/template/plugin.min.js | 4 +- js/tinymce/plugins/textcolor/plugin.js | 21 + js/tinymce/plugins/textcolor/plugin.min.js | 4 +- js/tinymce/plugins/textpattern/plugin.js | 1374 + js/tinymce/plugins/textpattern/plugin.min.js | 4 +- js/tinymce/plugins/toc/plugin.js | 238 + js/tinymce/plugins/toc/plugin.min.js | 4 +- js/tinymce/plugins/visualblocks/plugin.js | 103 + js/tinymce/plugins/visualblocks/plugin.min.js | 4 +- js/tinymce/plugins/visualchars/plugin.js | 526 + js/tinymce/plugins/visualchars/plugin.min.js | 4 +- js/tinymce/plugins/wordcount/plugin.js | 425 + js/tinymce/plugins/wordcount/plugin.min.js | 4 +- js/tinymce/skins/content/dark/content.css | 72 + js/tinymce/skins/content/dark/content.min.css | 8 + .../skins/content/dark/content.min.css.map | 1 + js/tinymce/skins/content/default/content.css | 67 + .../skins/content/default/content.min.css | 3 +- .../skins/content/default/content.min.css.map | 1 + js/tinymce/skins/content/document/content.css | 72 + .../skins/content/document/content.min.css | 3 +- .../content/document/content.min.css.map | 1 + js/tinymce/skins/content/index.php | 33 - js/tinymce/skins/content/writer/content.css | 68 + .../skins/content/writer/content.min.css | 3 +- .../skins/content/writer/content.min.css.map | 1 + js/tinymce/skins/index.php | 33 - js/tinymce/skins/ui/index.php | 33 - js/tinymce/skins/ui/oxide-dark/content.css | 714 + .../skins/ui/oxide-dark/content.inline.css | 726 + .../ui/oxide-dark/content.inline.min.css | 3 +- .../ui/oxide-dark/content.inline.min.css.map | 1 + .../skins/ui/oxide-dark/content.min.css | 3 +- .../skins/ui/oxide-dark/content.min.css.map | 1 + .../skins/ui/oxide-dark/content.mobile.css | 29 + .../ui/oxide-dark/content.mobile.min.css | 8 + .../ui/oxide-dark/content.mobile.min.css.map | 1 + .../ui/oxide-dark/fonts/tinymce-mobile.woff | Bin 0 -> 4624 bytes js/tinymce/skins/ui/oxide-dark/skin.css | 3047 ++ js/tinymce/skins/ui/oxide-dark/skin.min.css | 3 +- .../skins/ui/oxide-dark/skin.min.css.map | 1 + .../skins/ui/oxide-dark/skin.mobile.css | 673 + .../skins/ui/oxide-dark/skin.mobile.min.css | 8 + .../ui/oxide-dark/skin.mobile.min.css.map | 1 + .../skins/ui/oxide-dark/skin.shadowdom.css | 37 + .../ui/oxide-dark/skin.shadowdom.min.css | 8 + .../ui/oxide-dark/skin.shadowdom.min.css.map | 1 + js/tinymce/skins/ui/oxide/content.css | 732 + js/tinymce/skins/ui/oxide/content.inline.css | 726 + .../skins/ui/oxide/content.inline.min.css | 3 +- .../skins/ui/oxide/content.inline.min.css.map | 1 + js/tinymce/skins/ui/oxide/content.min.css | 3 +- js/tinymce/skins/ui/oxide/content.min.css.map | 1 + js/tinymce/skins/ui/oxide/content.mobile.css | 29 + .../skins/ui/oxide/content.mobile.min.css | 9 +- .../skins/ui/oxide/content.mobile.min.css.map | 1 + js/tinymce/skins/ui/oxide/skin.css | 3047 ++ js/tinymce/skins/ui/oxide/skin.min.css | 3 +- js/tinymce/skins/ui/oxide/skin.min.css.map | 1 + js/tinymce/skins/ui/oxide/skin.mobile.css | 673 + js/tinymce/skins/ui/oxide/skin.mobile.min.css | 9 +- .../skins/ui/oxide/skin.mobile.min.css.map | 1 + js/tinymce/skins/ui/oxide/skin.shadowdom.css | 37 + .../skins/ui/oxide/skin.shadowdom.min.css | 8 + .../skins/ui/oxide/skin.shadowdom.min.css.map | 1 + js/tinymce/themes/index.php | 33 - js/tinymce/themes/mobile/index.php | 33 - js/tinymce/themes/mobile/theme.js | 13130 ++++++ js/tinymce/themes/mobile/theme.min.js | 4 +- js/tinymce/themes/silver/index.php | 33 - js/tinymce/themes/silver/theme.js | 34075 ++++++++++++++++ js/tinymce/themes/silver/theme.min.js | 4 +- js/tinymce/tinymce.d.ts | 3039 ++ js/tinymce/tinymce.js | 30142 ++++++++++++++ js/tinymce/tinymce.min.js | 4 +- misc/aboutproject.php | 44 + misc/aboutproject_template.php | 9 +- misc/charencodingclean.php | 4 +- misc/contacts.php | 35 + misc/contacts_template.php | 7 +- misc/general_template.php | 12 +- misc/generalsimple_template.php | 7 +- misc/index.php | 9 +- misc/partners_template.php | 9 +- plugins/index.php | 9 +- plugins/quicksearch/index.php | 9 +- plugins/slideshow/index.php | 9 +- profile/authCallback.php | 79 + profile/imagesforid.php | 4 +- profile/index.php | 181 +- profile/newprofile.php | 312 +- profile/occurrencemenu.php | 297 +- profile/openIdAuth.php | 43 + profile/personalspecbackup.php | 11 +- profile/rpc/displayimagesforid.php | 66 +- profile/rpc/index.php | 9 +- profile/specimenstoid.php | 4 +- profile/usermanagement.php | 181 +- profile/userprofile.php | 42 +- profile/usertaxonomymanager.php | 32 +- profile/viewprofile.php | 22 +- projects/checklisttab.php | 15 +- projects/index.php | 203 +- projects/managertab.php | 15 +- references/authoreditor.php | 15 +- references/index.php | 13 +- references/refdetails.php | 25 +- references/rpc/authormanager.php | 12 +- references/rpc/index.php | 9 +- requests/index.php | 13 +- requests/rpc/index.php | 9 +- rpc/crossPortalHeaders.php | 31 + rpc/taxasuggest.php | 17 +- sitemap.php | 387 +- taxa/index.php | 182 +- taxa/profile/eolmapper.php | 9 +- taxa/profile/index.php | 17 +- taxa/profile/tpdesceditor.php | 110 +- taxa/profile/tpeditor.php | 79 +- taxa/profile/tpimageeditor.php | 656 +- taxa/resourcetab.php | 8 +- taxa/taxonomy/batchloader.php | 307 +- taxa/taxonomy/index.php | 16 +- taxa/taxonomy/quickload.php | 29 +- taxa/taxonomy/rpc/getdynamicchildren.php | 15 +- taxa/taxonomy/rpc/index.php | 9 +- taxa/taxonomy/taxoneditor.php | 243 +- taxa/taxonomy/taxonomychildren.php | 4 +- taxa/taxonomy/taxonomycleaner.php | 67 +- taxa/taxonomy/taxonomydelete.php | 130 +- taxa/taxonomy/taxonomydisplay.php | 139 +- taxa/taxonomy/taxonomydynamicdisplay.php | 156 +- taxa/taxonomy/taxonomyloader.php | 191 +- taxa/taxonomy/taxonomymaintenance.php | 35 +- temp/index.php | 9 +- themes/index.php | 9 +- utilities/SymbUtil.php | 34 + vendor/composer/autoload_classmap.php | 2 + vendor/composer/autoload_files.php | 33 +- vendor/composer/autoload_psr4.php | 2 + vendor/composer/autoload_static.php | 48 +- vendor/composer/installed.json | 281 + vendor/composer/installed.php | 40 +- .../.github/PULL_REQUEST_TEMPLATE.md | 2 + .../.github/workflows/build.yml | 38 + .../jumbojett/openid-connect-php/.gitignore | 4 + .../jumbojett/openid-connect-php/CHANGELOG.md | 185 + vendor/jumbojett/openid-connect-php/LICENSE | 201 + vendor/jumbojett/openid-connect-php/README.md | 238 + .../openid-connect-php/client_example.php | 56 + .../openid-connect-php/composer.json | 24 + .../openid-connect-php/phpunit.xml.dist | 28 + .../src/OpenIDConnectClient.php | 2219 + .../tests/OpenIDConnectClientTest.php | 91 + .../tests/TokenVerificationTest.php | 34 + .../tests/data/jwks-ps256.json | 12 + .../constant_time_encoding/LICENSE.txt | 48 + .../constant_time_encoding/README.md | 84 + .../constant_time_encoding/composer.json | 56 + .../constant_time_encoding/src/Base32.php | 519 + .../constant_time_encoding/src/Base32Hex.php | 111 + .../constant_time_encoding/src/Base64.php | 314 + .../src/Base64DotSlash.php | 88 + .../src/Base64DotSlashOrdered.php | 82 + .../src/Base64UrlSafe.php | 95 + .../constant_time_encoding/src/Binary.php | 90 + .../src/EncoderInterface.php | 52 + .../constant_time_encoding/src/Encoding.php | 262 + .../constant_time_encoding/src/Hex.php | 146 + .../constant_time_encoding/src/RFC4648.php | 186 + vendor/paragonie/random_compat/LICENSE | 22 + vendor/paragonie/random_compat/build-phar.sh | 5 + vendor/paragonie/random_compat/composer.json | 34 + .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + vendor/paragonie/random_compat/lib/random.php | 32 + .../random_compat/other/build_phar.php | 57 + .../random_compat/psalm-autoload.php | 9 + vendor/paragonie/random_compat/psalm.xml | 19 + .../src/PhpSpreadsheet/Worksheet/Iterator.php | 2 +- .../src/PhpSpreadsheet/Writer/Html.php | 4 +- .../phpword/src/PhpWord/Shared/Text.php | 2 +- .../phpword/src/PhpWord/Shared/XMLWriter.php | 2 +- vendor/phpseclib/phpseclib/AUTHORS | 7 + vendor/phpseclib/phpseclib/BACKERS.md | 16 + .../phpseclib/phpseclib/LICENSE | 27 +- vendor/phpseclib/phpseclib/README.md | 97 + vendor/phpseclib/phpseclib/composer.json | 84 + .../phpseclib/Common/Functions/Strings.php | 505 + .../phpseclib/phpseclib/Crypt/AES.php | 116 + .../phpseclib/phpseclib/Crypt/Blowfish.php | 918 + .../phpseclib/phpseclib/Crypt/ChaCha20.php | 799 + .../phpseclib/Crypt/Common/AsymmetricKey.php | 581 + .../phpseclib/Crypt/Common/BlockCipher.php | 24 + .../Crypt/Common/Formats/Keys/JWK.php | 69 + .../Crypt/Common/Formats/Keys/OpenSSH.php | 220 + .../Crypt/Common/Formats/Keys/PKCS.php | 72 + .../Crypt/Common/Formats/Keys/PKCS1.php | 209 + .../Crypt/Common/Formats/Keys/PKCS8.php | 724 + .../Crypt/Common/Formats/Keys/PuTTY.php | 374 + .../Crypt/Common/Formats/Signature/Raw.php | 60 + .../phpseclib/Crypt/Common/PrivateKey.php | 31 + .../phpseclib/Crypt/Common/PublicKey.php | 25 + .../phpseclib/Crypt/Common/StreamCipher.php | 54 + .../phpseclib/Crypt/Common/SymmetricKey.php | 3396 ++ .../Crypt/Common/Traits/Fingerprint.php | 57 + .../Crypt/Common/Traits/PasswordProtected.php | 46 + .../phpseclib/phpseclib/Crypt/DES.php | 1392 + .../phpseclib/phpseclib/Crypt/DH.php | 405 + .../phpseclib/Crypt/DH/Formats/Keys/PKCS1.php | 77 + .../phpseclib/Crypt/DH/Formats/Keys/PKCS8.php | 132 + .../phpseclib/Crypt/DH/Parameters.php | 36 + .../phpseclib/Crypt/DH/PrivateKey.php | 75 + .../phpseclib/Crypt/DH/PublicKey.php | 49 + .../phpseclib/phpseclib/Crypt/DSA.php | 337 + .../Crypt/DSA/Formats/Keys/OpenSSH.php | 118 + .../Crypt/DSA/Formats/Keys/PKCS1.php | 143 + .../Crypt/DSA/Formats/Keys/PKCS8.php | 146 + .../Crypt/DSA/Formats/Keys/PuTTY.php | 109 + .../phpseclib/Crypt/DSA/Formats/Keys/Raw.php | 85 + .../phpseclib/Crypt/DSA/Formats/Keys/XML.php | 132 + .../Crypt/DSA/Formats/Signature/ASN1.php | 62 + .../Crypt/DSA/Formats/Signature/Raw.php | 25 + .../Crypt/DSA/Formats/Signature/SSH2.php | 74 + .../phpseclib/Crypt/DSA/Parameters.php | 36 + .../phpseclib/Crypt/DSA/PrivateKey.php | 152 + .../phpseclib/Crypt/DSA/PublicKey.php | 86 + .../phpseclib/phpseclib/Crypt/EC.php | 480 + .../phpseclib/Crypt/EC/BaseCurves/Base.php | 218 + .../phpseclib/Crypt/EC/BaseCurves/Binary.php | 373 + .../Crypt/EC/BaseCurves/KoblitzPrime.php | 335 + .../Crypt/EC/BaseCurves/Montgomery.php | 279 + .../phpseclib/Crypt/EC/BaseCurves/Prime.php | 785 + .../Crypt/EC/BaseCurves/TwistedEdwards.php | 215 + .../phpseclib/Crypt/EC/Curves/Curve25519.php | 81 + .../phpseclib/Crypt/EC/Curves/Curve448.php | 92 + .../phpseclib/Crypt/EC/Curves/Ed25519.php | 333 + .../phpseclib/Crypt/EC/Curves/Ed448.php | 273 + .../Crypt/EC/Curves/brainpoolP160r1.php | 34 + .../Crypt/EC/Curves/brainpoolP160t1.php | 47 + .../Crypt/EC/Curves/brainpoolP192r1.php | 34 + .../Crypt/EC/Curves/brainpoolP192t1.php | 34 + .../Crypt/EC/Curves/brainpoolP224r1.php | 34 + .../Crypt/EC/Curves/brainpoolP224t1.php | 34 + .../Crypt/EC/Curves/brainpoolP256r1.php | 34 + .../Crypt/EC/Curves/brainpoolP256t1.php | 34 + .../Crypt/EC/Curves/brainpoolP320r1.php | 40 + .../Crypt/EC/Curves/brainpoolP320t1.php | 40 + .../Crypt/EC/Curves/brainpoolP384r1.php | 58 + .../Crypt/EC/Curves/brainpoolP384t1.php | 58 + .../Crypt/EC/Curves/brainpoolP512r1.php | 58 + .../Crypt/EC/Curves/brainpoolP512t1.php | 58 + .../phpseclib/Crypt/EC/Curves/nistb233.php | 18 + .../phpseclib/Crypt/EC/Curves/nistb409.php | 18 + .../phpseclib/Crypt/EC/Curves/nistk163.php | 18 + .../phpseclib/Crypt/EC/Curves/nistk233.php | 18 + .../phpseclib/Crypt/EC/Curves/nistk283.php | 18 + .../phpseclib/Crypt/EC/Curves/nistk409.php | 18 + .../phpseclib/Crypt/EC/Curves/nistp192.php | 18 + .../phpseclib/Crypt/EC/Curves/nistp224.php | 18 + .../phpseclib/Crypt/EC/Curves/nistp256.php | 18 + .../phpseclib/Crypt/EC/Curves/nistp384.php | 18 + .../phpseclib/Crypt/EC/Curves/nistp521.php | 18 + .../phpseclib/Crypt/EC/Curves/nistt571.php | 18 + .../phpseclib/Crypt/EC/Curves/prime192v1.php | 18 + .../phpseclib/Crypt/EC/Curves/prime192v2.php | 34 + .../phpseclib/Crypt/EC/Curves/prime192v3.php | 34 + .../phpseclib/Crypt/EC/Curves/prime239v1.php | 34 + .../phpseclib/Crypt/EC/Curves/prime239v2.php | 34 + .../phpseclib/Crypt/EC/Curves/prime239v3.php | 34 + .../phpseclib/Crypt/EC/Curves/prime256v1.php | 18 + .../phpseclib/Crypt/EC/Curves/secp112r1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp112r2.php | 35 + .../phpseclib/Crypt/EC/Curves/secp128r1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp128r2.php | 35 + .../phpseclib/Crypt/EC/Curves/secp160k1.php | 46 + .../phpseclib/Crypt/EC/Curves/secp160r1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp160r2.php | 35 + .../phpseclib/Crypt/EC/Curves/secp192k1.php | 45 + .../phpseclib/Crypt/EC/Curves/secp192r1.php | 78 + .../phpseclib/Crypt/EC/Curves/secp224k1.php | 45 + .../phpseclib/Crypt/EC/Curves/secp224r1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp256k1.php | 49 + .../phpseclib/Crypt/EC/Curves/secp256r1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp384r1.php | 52 + .../phpseclib/Crypt/EC/Curves/secp521r1.php | 46 + .../phpseclib/Crypt/EC/Curves/sect113r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect113r2.php | 34 + .../phpseclib/Crypt/EC/Curves/sect131r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect131r2.php | 34 + .../phpseclib/Crypt/EC/Curves/sect163k1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect163r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect163r2.php | 34 + .../phpseclib/Crypt/EC/Curves/sect193r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect193r2.php | 34 + .../phpseclib/Crypt/EC/Curves/sect233k1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect233r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect239k1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect283k1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect283r1.php | 34 + .../phpseclib/Crypt/EC/Curves/sect409k1.php | 38 + .../phpseclib/Crypt/EC/Curves/sect409r1.php | 38 + .../phpseclib/Crypt/EC/Curves/sect571k1.php | 42 + .../phpseclib/Crypt/EC/Curves/sect571r1.php | 42 + .../Crypt/EC/Formats/Keys/Common.php | 549 + .../phpseclib/Crypt/EC/Formats/Keys/JWK.php | 189 + .../EC/Formats/Keys/MontgomeryPrivate.php | 101 + .../EC/Formats/Keys/MontgomeryPublic.php | 71 + .../Crypt/EC/Formats/Keys/OpenSSH.php | 209 + .../phpseclib/Crypt/EC/Formats/Keys/PKCS1.php | 194 + .../phpseclib/Crypt/EC/Formats/Keys/PKCS8.php | 234 + .../phpseclib/Crypt/EC/Formats/Keys/PuTTY.php | 138 + .../phpseclib/Crypt/EC/Formats/Keys/XML.php | 485 + .../Crypt/EC/Formats/Keys/libsodium.php | 116 + .../Crypt/EC/Formats/Signature/ASN1.php | 62 + .../Crypt/EC/Formats/Signature/Raw.php | 25 + .../Crypt/EC/Formats/Signature/SSH2.php | 94 + .../phpseclib/Crypt/EC/Parameters.php | 36 + .../phpseclib/Crypt/EC/PrivateKey.php | 256 + .../phpseclib/Crypt/EC/PublicKey.php | 172 + .../phpseclib/phpseclib/Crypt/Hash.php | 1455 + .../phpseclib/Crypt/PublicKeyLoader.php | 111 + .../phpseclib/phpseclib/Crypt/RC2.php | 640 + .../phpseclib/phpseclib/Crypt/RC4.php | 280 + .../phpseclib/phpseclib/Crypt/RSA.php | 933 + .../phpseclib/Crypt/RSA/Formats/Keys/JWK.php | 142 + .../Crypt/RSA/Formats/Keys/MSBLOB.php | 228 + .../Crypt/RSA/Formats/Keys/OpenSSH.php | 132 + .../Crypt/RSA/Formats/Keys/PKCS1.php | 160 + .../Crypt/RSA/Formats/Keys/PKCS8.php | 122 + .../phpseclib/Crypt/RSA/Formats/Keys/PSS.php | 238 + .../Crypt/RSA/Formats/Keys/PuTTY.php | 121 + .../phpseclib/Crypt/RSA/Formats/Keys/Raw.php | 184 + .../phpseclib/Crypt/RSA/Formats/Keys/XML.php | 171 + .../phpseclib/Crypt/RSA/PrivateKey.php | 530 + .../phpseclib/Crypt/RSA/PublicKey.php | 513 + .../phpseclib/phpseclib/Crypt/Random.php | 222 + .../phpseclib/phpseclib/Crypt/Rijndael.php | 1036 + .../phpseclib/phpseclib/Crypt/Salsa20.php | 526 + .../phpseclib/phpseclib/Crypt/TripleDES.php | 436 + .../phpseclib/phpseclib/Crypt/Twofish.php | 816 + .../Exception/BadConfigurationException.php | 23 + .../Exception/BadDecryptionException.php | 23 + .../phpseclib/Exception/BadModeException.php | 23 + .../Exception/ConnectionClosedException.php | 23 + .../Exception/FileNotFoundException.php | 23 + .../Exception/InconsistentSetupException.php | 23 + .../Exception/InsufficientSetupException.php | 23 + .../Exception/NoKeyLoadedException.php | 23 + .../NoSupportedAlgorithmsException.php | 23 + .../Exception/UnableToConnectException.php | 23 + .../UnsupportedAlgorithmException.php | 23 + .../Exception/UnsupportedCurveException.php | 23 + .../Exception/UnsupportedFormatException.php | 23 + .../UnsupportedOperationException.php | 23 + .../phpseclib/phpseclib/File/ANSI.php | 551 + .../phpseclib/phpseclib/File/ASN1.php | 1508 + .../phpseclib/phpseclib/File/ASN1/Element.php | 43 + .../File/ASN1/Maps/AccessDescription.php | 32 + .../ASN1/Maps/AdministrationDomainName.php | 36 + .../File/ASN1/Maps/AlgorithmIdentifier.php | 35 + .../phpseclib/File/ASN1/Maps/AnotherName.php | 37 + .../phpseclib/File/ASN1/Maps/Attribute.php | 37 + .../File/ASN1/Maps/AttributeType.php | 26 + .../File/ASN1/Maps/AttributeTypeAndValue.php | 32 + .../File/ASN1/Maps/AttributeValue.php | 26 + .../phpseclib/File/ASN1/Maps/Attributes.php | 31 + .../ASN1/Maps/AuthorityInfoAccessSyntax.php | 31 + .../File/ASN1/Maps/AuthorityKeyIdentifier.php | 45 + .../phpseclib/File/ASN1/Maps/BaseDistance.php | 26 + .../File/ASN1/Maps/BasicConstraints.php | 39 + .../Maps/BuiltInDomainDefinedAttribute.php | 32 + .../Maps/BuiltInDomainDefinedAttributes.php | 31 + .../ASN1/Maps/BuiltInStandardAttributes.php | 67 + .../phpseclib/File/ASN1/Maps/CPSuri.php | 26 + .../File/ASN1/Maps/CRLDistributionPoints.php | 31 + .../phpseclib/File/ASN1/Maps/CRLNumber.php | 26 + .../phpseclib/File/ASN1/Maps/CRLReason.php | 41 + .../phpseclib/File/ASN1/Maps/CertPolicyId.php | 26 + .../phpseclib/File/ASN1/Maps/Certificate.php | 33 + .../File/ASN1/Maps/CertificateIssuer.php | 24 + .../File/ASN1/Maps/CertificateList.php | 33 + .../File/ASN1/Maps/CertificatePolicies.php | 31 + .../ASN1/Maps/CertificateSerialNumber.php | 26 + .../File/ASN1/Maps/CertificationRequest.php | 33 + .../ASN1/Maps/CertificationRequestInfo.php | 41 + .../File/ASN1/Maps/Characteristic_two.php | 36 + .../phpseclib/File/ASN1/Maps/CountryName.php | 36 + .../phpseclib/File/ASN1/Maps/Curve.php | 36 + .../phpseclib/File/ASN1/Maps/DHParameter.php | 38 + .../phpseclib/File/ASN1/Maps/DSAParams.php | 33 + .../File/ASN1/Maps/DSAPrivateKey.php | 36 + .../phpseclib/File/ASN1/Maps/DSAPublicKey.php | 26 + .../phpseclib/File/ASN1/Maps/DigestInfo.php | 34 + .../File/ASN1/Maps/DirectoryString.php | 35 + .../phpseclib/File/ASN1/Maps/DisplayText.php | 34 + .../File/ASN1/Maps/DistributionPoint.php | 45 + .../File/ASN1/Maps/DistributionPointName.php | 40 + .../phpseclib/File/ASN1/Maps/DssSigValue.php | 32 + .../phpseclib/File/ASN1/Maps/ECParameters.php | 45 + .../phpseclib/File/ASN1/Maps/ECPoint.php | 26 + .../phpseclib/File/ASN1/Maps/ECPrivateKey.php | 48 + .../phpseclib/File/ASN1/Maps/EDIPartyName.php | 42 + .../File/ASN1/Maps/EcdsaSigValue.php | 32 + .../File/ASN1/Maps/EncryptedData.php | 26 + .../ASN1/Maps/EncryptedPrivateKeyInfo.php | 32 + .../File/ASN1/Maps/ExtKeyUsageSyntax.php | 31 + .../phpseclib/File/ASN1/Maps/Extension.php | 43 + .../File/ASN1/Maps/ExtensionAttribute.php | 42 + .../File/ASN1/Maps/ExtensionAttributes.php | 31 + .../phpseclib/File/ASN1/Maps/Extensions.php | 33 + .../phpseclib/File/ASN1/Maps/FieldElement.php | 26 + .../phpseclib/File/ASN1/Maps/FieldID.php | 35 + .../phpseclib/File/ASN1/Maps/GeneralName.php | 80 + .../phpseclib/File/ASN1/Maps/GeneralNames.php | 31 + .../File/ASN1/Maps/GeneralSubtree.php | 42 + .../File/ASN1/Maps/GeneralSubtrees.php | 31 + .../File/ASN1/Maps/HashAlgorithm.php | 24 + .../File/ASN1/Maps/HoldInstructionCode.php | 26 + .../File/ASN1/Maps/InvalidityDate.php | 26 + .../File/ASN1/Maps/IssuerAltName.php | 24 + .../ASN1/Maps/IssuingDistributionPoint.php | 68 + .../File/ASN1/Maps/KeyIdentifier.php | 26 + .../phpseclib/File/ASN1/Maps/KeyPurposeId.php | 26 + .../phpseclib/File/ASN1/Maps/KeyUsage.php | 39 + .../File/ASN1/Maps/MaskGenAlgorithm.php | 24 + .../phpseclib/File/ASN1/Maps/Name.php | 31 + .../File/ASN1/Maps/NameConstraints.php | 40 + .../File/ASN1/Maps/NetworkAddress.php | 26 + .../File/ASN1/Maps/NoticeReference.php | 37 + .../File/ASN1/Maps/NumericUserIdentifier.php | 26 + .../phpseclib/File/ASN1/Maps/ORAddress.php | 33 + .../File/ASN1/Maps/OneAsymmetricKey.php | 48 + .../File/ASN1/Maps/OrganizationName.php | 26 + .../ASN1/Maps/OrganizationalUnitNames.php | 31 + .../File/ASN1/Maps/OtherPrimeInfo.php | 34 + .../File/ASN1/Maps/OtherPrimeInfos.php | 32 + .../phpseclib/File/ASN1/Maps/PBEParameter.php | 34 + .../phpseclib/File/ASN1/Maps/PBES2params.php | 34 + .../phpseclib/File/ASN1/Maps/PBKDF2params.php | 41 + .../phpseclib/File/ASN1/Maps/PBMAC1params.php | 34 + .../phpseclib/File/ASN1/Maps/PKCS9String.php | 32 + .../phpseclib/File/ASN1/Maps/Pentanomial.php | 33 + .../phpseclib/File/ASN1/Maps/PersonalName.php | 54 + .../File/ASN1/Maps/PolicyInformation.php | 38 + .../File/ASN1/Maps/PolicyMappings.php | 37 + .../File/ASN1/Maps/PolicyQualifierId.php | 26 + .../File/ASN1/Maps/PolicyQualifierInfo.php | 32 + .../File/ASN1/Maps/PostalAddress.php | 32 + .../phpseclib/File/ASN1/Maps/Prime_p.php | 26 + .../File/ASN1/Maps/PrivateDomainName.php | 32 + .../phpseclib/File/ASN1/Maps/PrivateKey.php | 26 + .../File/ASN1/Maps/PrivateKeyInfo.php | 41 + .../File/ASN1/Maps/PrivateKeyUsagePeriod.php | 40 + .../phpseclib/File/ASN1/Maps/PublicKey.php | 26 + .../File/ASN1/Maps/PublicKeyAndChallenge.php | 32 + .../File/ASN1/Maps/PublicKeyInfo.php | 35 + .../File/ASN1/Maps/RC2CBCParameter.php | 37 + .../phpseclib/File/ASN1/Maps/RDNSequence.php | 38 + .../File/ASN1/Maps/RSAPrivateKey.php | 44 + .../phpseclib/File/ASN1/Maps/RSAPublicKey.php | 32 + .../File/ASN1/Maps/RSASSA_PSS_params.php | 58 + .../phpseclib/File/ASN1/Maps/ReasonFlags.php | 39 + .../ASN1/Maps/RelativeDistinguishedName.php | 37 + .../File/ASN1/Maps/RevokedCertificate.php | 35 + .../ASN1/Maps/SignedPublicKeyAndChallenge.php | 33 + .../File/ASN1/Maps/SpecifiedECDomain.php | 45 + .../File/ASN1/Maps/SubjectAltName.php | 24 + .../ASN1/Maps/SubjectDirectoryAttributes.php | 31 + .../ASN1/Maps/SubjectInfoAccessSyntax.php | 31 + .../File/ASN1/Maps/SubjectPublicKeyInfo.php | 32 + .../phpseclib/File/ASN1/Maps/TBSCertList.php | 54 + .../File/ASN1/Maps/TBSCertificate.php | 65 + .../File/ASN1/Maps/TerminalIdentifier.php | 26 + .../phpseclib/File/ASN1/Maps/Time.php | 32 + .../phpseclib/File/ASN1/Maps/Trinomial.php | 26 + .../File/ASN1/Maps/UniqueIdentifier.php | 26 + .../phpseclib/File/ASN1/Maps/UserNotice.php | 38 + .../phpseclib/File/ASN1/Maps/Validity.php | 32 + .../File/ASN1/Maps/netscape_ca_policy_url.php | 26 + .../File/ASN1/Maps/netscape_cert_type.php | 40 + .../File/ASN1/Maps/netscape_comment.php | 26 + .../phpseclib/phpseclib/File/X509.php | 4006 ++ .../phpseclib/phpseclib/Math/BigInteger.php | 893 + .../Math/BigInteger/Engines/BCMath.php | 697 + .../Math/BigInteger/Engines/BCMath/Base.php | 110 + .../BigInteger/Engines/BCMath/BuiltIn.php | 40 + .../Engines/BCMath/DefaultEngine.php | 25 + .../BigInteger/Engines/BCMath/OpenSSL.php | 25 + .../Engines/BCMath/Reductions/Barrett.php | 187 + .../Engines/BCMath/Reductions/EvalBarrett.php | 108 + .../Math/BigInteger/Engines/Engine.php | 1285 + .../phpseclib/Math/BigInteger/Engines/GMP.php | 694 + .../BigInteger/Engines/GMP/DefaultEngine.php | 40 + .../Math/BigInteger/Engines/OpenSSL.php | 68 + .../phpseclib/Math/BigInteger/Engines/PHP.php | 1343 + .../Math/BigInteger/Engines/PHP/Base.php | 143 + .../BigInteger/Engines/PHP/DefaultEngine.php | 25 + .../BigInteger/Engines/PHP/Montgomery.php | 89 + .../Math/BigInteger/Engines/PHP/OpenSSL.php | 25 + .../Engines/PHP/Reductions/Barrett.php | 281 + .../Engines/PHP/Reductions/Classic.php | 42 + .../Engines/PHP/Reductions/EvalBarrett.php | 484 + .../Engines/PHP/Reductions/Montgomery.php | 126 + .../Engines/PHP/Reductions/MontgomeryMult.php | 76 + .../Engines/PHP/Reductions/PowerOfTwo.php | 59 + .../Math/BigInteger/Engines/PHP32.php | 371 + .../Math/BigInteger/Engines/PHP64.php | 372 + .../phpseclib/phpseclib/Math/BinaryField.php | 194 + .../phpseclib/Math/BinaryField/Integer.php | 516 + .../phpseclib/Math/Common/FiniteField.php | 22 + .../Math/Common/FiniteField/Integer.php | 44 + .../phpseclib/phpseclib/Math/PrimeField.php | 118 + .../phpseclib/Math/PrimeField/Integer.php | 419 + .../phpseclib/phpseclib/Net/SFTP.php | 3546 ++ .../phpseclib/phpseclib/Net/SFTP/Stream.php | 756 + .../phpseclib/phpseclib/Net/SSH2.php | 5337 +++ .../phpseclib/phpseclib/System/SSH/Agent.php | 286 + .../phpseclib/System/SSH/Agent/Identity.php | 320 + .../System/SSH/Common/Traits/ReadBytes.php | 37 + .../phpseclib/phpseclib/bootstrap.php | 22 + .../phpseclib/phpseclib/phpseclib/openssl.cnf | 6 + webservices/dataentryactivity.php | 15 +- webservices/dwc/dwcaingesthandler.php | 6 +- webservices/dwc/dwcapubhandler.php | 6 +- webservices/dwc/index.php | 9 +- webservices/dwc/rss.xml | 2 +- webservices/index.php | 9 +- 2009 files changed, 502112 insertions(+), 118370 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 accessibility/module.php create mode 100644 accessibility/rpc/toggle-styles.php create mode 100644 api/app/Helpers/GPoint.php create mode 100644 api/app/Helpers/Helper.php create mode 100644 api/app/Helpers/OccurrenceHelper.php create mode 100644 api/app/Helpers/TaxonomyHelper.php create mode 100644 checklists/externalvouchers.php delete mode 100644 checklists/rpc/gettid.php create mode 100644 classes/CollectionMetadata.php create mode 100644 classes/DatasetsMetadata.php delete mode 100644 classes/OccurrenceEditorMaterialSample.php create mode 100644 classes/OccurrenceImport.php create mode 100644 classes/OmAssociations.php create mode 100644 classes/OmDeterminations.php create mode 100644 classes/OmMaterialSample.php create mode 100644 classes/OpenIdProfileManager.php create mode 100644 classes/UtilitiesFileImport.php create mode 100644 collections/admin/importextended.php create mode 100644 collections/editor/rpc/getGeography.php delete mode 100644 collections/editor/rpc/lookupCountry.php delete mode 100644 collections/editor/rpc/lookupCounty.php delete mode 100644 collections/editor/rpc/lookupMunicipality.php delete mode 100644 collections/editor/rpc/lookupState.php create mode 100644 collections/individual/domManipulationUtils.js create mode 100644 collections/map/leafletmap.php create mode 100644 collections/map/portalSelector.php create mode 100644 collections/map/rpc/206161_heatmap_1695410561.png create mode 100644 collections/map/rpc/getCoordinates.php create mode 100644 collections/map/rpc/getTaxa.php create mode 100644 collections/map/rpc/postMap.php create mode 100644 collections/map/rpc/searchCollections.php create mode 100644 collections/map/staticmaphandler.php create mode 100644 collections/search/collectionContent.php create mode 100644 collections/search/collectionGroupSubcollectionList.php create mode 100644 collections/search/css/searchStyles.css create mode 100644 collections/search/css/searchStylesInner.css create mode 100644 collections/search/css/tables.css create mode 100644 collections/search/index.php create mode 100644 collections/search/js/alerts.js create mode 100644 collections/search/js/searchform.js create mode 100644 collections/search/sass/0-plugins/_plugins-dir.sass create mode 100644 collections/search/sass/1-base/_base-dir.sass create mode 100644 collections/search/sass/1-base/_base.sass create mode 100644 collections/search/sass/1-base/_common.sass create mode 100644 collections/search/sass/1-base/_fonts.sass create mode 100644 collections/search/sass/1-base/_footer.sass create mode 100644 collections/search/sass/1-base/_main.sass create mode 100644 collections/search/sass/1-base/_nav.sass create mode 100644 collections/search/sass/1-base/_section.sass create mode 100644 collections/search/sass/2-layouts/_layouts-dir.sass create mode 100644 collections/search/sass/2-layouts/_search-form.sass create mode 100644 collections/search/sass/3-modules/_accordion.sass create mode 100644 collections/search/sass/3-modules/_btn.sass create mode 100644 collections/search/sass/3-modules/_chip.sass create mode 100644 collections/search/sass/3-modules/_color-box.sass create mode 100644 collections/search/sass/3-modules/_input-text.sass create mode 100644 collections/search/sass/3-modules/_modal.sass create mode 100644 collections/search/sass/3-modules/_modules-dir.sass create mode 100644 collections/search/sass/3-modules/_select.sass create mode 100644 collections/search/sass/3-modules/_text-area.sass create mode 100644 collections/search/sass/3-modules/_utilities.sass create mode 100644 collections/search/sass/_mixins.sass create mode 100644 collections/search/sass/_variables.sass create mode 100644 collections/search/sass/app.sass create mode 100644 collections/search/singleCollectionGroupDetails.php create mode 100644 collections/search/singleCollectionWithoutCategoryDetails.php create mode 100644 collections/search/singleSubcollectionDetails.php create mode 100644 collections/tools/wikistatementparser.php create mode 100644 config/auth_config_template.php rename config/schema/{1.0 => 3.0}/data/extended_taxonrank.sql (100%) create mode 100644 config/schema/3.0/data/geothesaurus.sql rename config/schema/{1.0 => 3.0}/data/taxa_upper_lichens.csv (100%) rename config/schema/{1.0 => 3.0}/data/taxa_upper_plants.csv (100%) create mode 100644 config/schema/3.0/patches/db_schema_patch-3.1.sql create mode 100644 config/setup.bash rename config/{setup.sh => setup_pre_4.4.bash} (70%) create mode 100644 content/lang/checklists/checklist.pt.php create mode 100644 content/lang/checklists/checklistloader.en.php create mode 100644 content/lang/checklists/checklistloader.es.php create mode 100644 content/lang/checklists/checklistloader.fr.php create mode 100644 content/lang/checklists/clgmap.en.php create mode 100644 content/lang/checklists/clgmap.es.php create mode 100644 content/lang/checklists/clgmap.fr.php create mode 100644 content/lang/checklists/rpc/linkvoucher.en.php create mode 100644 content/lang/checklists/rpc/linkvoucher.es.php create mode 100644 content/lang/checklists/rpc/linkvoucher.fr.php create mode 100644 content/lang/classes/OccurrenceManager.en.php create mode 100644 content/lang/classes/OccurrenceManager.es.php create mode 100644 content/lang/classes/OccurrenceManager.fr.php create mode 100644 content/lang/collections/admin/guidmapper.fr.php create mode 100644 content/lang/collections/admin/igsnmanagement.en.php create mode 100644 content/lang/collections/admin/igsnmanagement.es.php create mode 100644 content/lang/collections/admin/igsnmanagement.fr.php create mode 100644 content/lang/collections/admin/igsnmapper.en.php create mode 100644 content/lang/collections/admin/igsnmapper.es.php create mode 100644 content/lang/collections/admin/igsnmapper.fr.php create mode 100644 content/lang/collections/admin/igsnverification.en.php create mode 100644 content/lang/collections/admin/igsnverification.es.php create mode 100644 content/lang/collections/admin/igsnverification.fr.php create mode 100644 content/lang/collections/admin/importextended.en.php create mode 100644 content/lang/collections/admin/importextended.es.php create mode 100644 content/lang/collections/admin/importextended.fr.php create mode 100644 content/lang/collections/associations.en.php create mode 100644 content/lang/collections/associations.es.php create mode 100644 content/lang/collections/associations.fr.php create mode 100644 content/lang/collections/cleaning/fieldstandardization.en.php create mode 100644 content/lang/collections/cleaning/fieldstandardization.es.php create mode 100644 content/lang/collections/cleaning/fieldstandardization.fr.php create mode 100644 content/lang/collections/cleaning/imagerecycler.en.php create mode 100644 content/lang/collections/cleaning/imagerecycler.es.php create mode 100644 content/lang/collections/cleaning/imagerecycler.fr.php create mode 100644 content/lang/collections/cleaning/index.en.php create mode 100644 content/lang/collections/cleaning/index.es.php create mode 100644 content/lang/collections/cleaning/index.fr.php create mode 100644 content/lang/collections/cleaning/politicalunits.fr.php create mode 100644 content/lang/collections/cleaning/taxonomycleaner.fr.php create mode 100644 content/lang/collections/customsearchtype.en.php create mode 100644 content/lang/collections/customsearchtype.es.php create mode 100644 content/lang/collections/customsearchtype.fr.php create mode 100644 content/lang/collections/datasets/datasetmanager.es.php create mode 100644 content/lang/collections/datasets/datasetmanager.fr.php create mode 100644 content/lang/collections/datasets/duplicatemanager.es.php create mode 100644 content/lang/collections/datasets/index.en.php create mode 100644 content/lang/collections/datasets/index.es.php create mode 100644 content/lang/collections/datasets/index.fr.php create mode 100644 content/lang/collections/datasets/occurharvester.en.php create mode 100644 content/lang/collections/datasets/occurharvester.es.php create mode 100644 content/lang/collections/datasets/occurharvester.fr.php create mode 100644 content/lang/collections/datasets/public.en.php create mode 100644 content/lang/collections/datasets/public.es.php create mode 100644 content/lang/collections/datasets/public.fr.php create mode 100644 content/lang/collections/datasets/publiclist.en.php create mode 100644 content/lang/collections/datasets/publiclist.es.php create mode 100644 content/lang/collections/datasets/publiclist.fr.php create mode 100644 content/lang/collections/download/index.en.php create mode 100644 content/lang/collections/download/index.es.php create mode 100644 content/lang/collections/download/index.fr.php create mode 100644 content/lang/collections/editor/imageoccursubmit.fr.php create mode 100644 content/lang/collections/editor/includes/materialsampleinclude.en.php create mode 100644 content/lang/collections/editor/includes/materialsampleinclude.es.php create mode 100644 content/lang/collections/editor/includes/materialsampleinclude.fr.php create mode 100644 content/lang/collections/editor/observationsubmit.fr.php create mode 100644 content/lang/collections/editor/rpc/dupelist.en.php create mode 100644 content/lang/collections/editor/rpc/dupelist.es.php create mode 100644 content/lang/collections/editor/rpc/dupelist.fr.php create mode 100644 content/lang/collections/editor/tools/coordformatter.en.php create mode 100644 content/lang/collections/editor/tools/coordformatter.es.php create mode 100644 content/lang/collections/editor/tools/coordformatter.fr.php create mode 100644 content/lang/collections/fieldterms/occurrenceterms.en.php create mode 100644 content/lang/collections/fieldterms/occurrenceterms.es.php create mode 100644 content/lang/collections/fieldterms/occurrenceterms.fr.php create mode 100644 content/lang/collections/georef/georefclone.en.php create mode 100644 content/lang/collections/georef/georefclone.es.php create mode 100644 content/lang/collections/georef/georefclone.fr.php create mode 100644 content/lang/collections/harvestparams.pt.php create mode 100644 content/lang/collections/index.pt.php create mode 100644 content/lang/collections/list.pt.php create mode 100644 content/lang/collections/listtabledisplay.pt.php create mode 100644 content/lang/collections/loans/loan_langs.en.php create mode 100644 content/lang/collections/loans/loan_langs.es.php create mode 100644 content/lang/collections/loans/loan_langs.fr.php create mode 100644 content/lang/collections/map/index.fr.php create mode 100644 content/lang/collections/map/index.pt.php create mode 100644 content/lang/collections/map/leafletmap.en.php create mode 100644 content/lang/collections/map/leafletmap.es.php create mode 100644 content/lang/collections/map/leafletmap.fr.php create mode 100644 content/lang/collections/map/mapbox.en.php create mode 100644 content/lang/collections/map/mapbox.es.php create mode 100644 content/lang/collections/map/mapbox.fr.php create mode 100644 content/lang/collections/map/mapshared.en.php create mode 100644 content/lang/collections/map/mapshared.es.php create mode 100644 content/lang/collections/map/mapshared.fr.php create mode 100644 content/lang/collections/map/simplemap.en.php create mode 100644 content/lang/collections/map/simplemap.es.php create mode 100644 content/lang/collections/map/simplemap.fr.php create mode 100644 content/lang/collections/map/staticmaphandler.en.php create mode 100644 content/lang/collections/map/staticmaphandler.es.php create mode 100644 content/lang/collections/map/staticmaphandler.fr.php create mode 100644 content/lang/collections/misc/collectionproperties.en.php create mode 100644 content/lang/collections/misc/collectionproperties.es.php create mode 100644 content/lang/collections/misc/collectionproperties.fr.php create mode 100644 content/lang/collections/misc/collmetadata.es.php create mode 100644 content/lang/collections/misc/institutioneditor.en.php create mode 100644 content/lang/collections/misc/institutioneditor.es.php create mode 100644 content/lang/collections/misc/institutioneditor.fr.php create mode 100644 content/lang/collections/misc/occurrencesearch.en.php create mode 100644 content/lang/collections/misc/occurrencesearch.es.php create mode 100644 content/lang/collections/misc/occurrencesearch.fr.php create mode 100644 content/lang/collections/misc/protectedspecies.en.php create mode 100644 content/lang/collections/misc/protectedspecies.es.php create mode 100644 content/lang/collections/misc/protectedspecies.fr.php create mode 100644 content/lang/collections/portalSelector.en.php create mode 100644 content/lang/collections/portalSelector.es.php create mode 100644 content/lang/collections/portalSelector.fr.php create mode 100644 content/lang/collections/portalSelector.pt.php create mode 100644 content/lang/collections/reports/annotationmanager.es.php create mode 100644 content/lang/collections/reports/annotationmanager.fr.php create mode 100644 content/lang/collections/reports/labelmanager.en.php create mode 100644 content/lang/collections/reports/labelmanager.es.php create mode 100644 content/lang/collections/reports/labelmanager.fr.php create mode 100644 content/lang/collections/search/index.en.php create mode 100644 content/lang/collections/search/index.es.php create mode 100644 content/lang/collections/search/index.fr.php create mode 100644 content/lang/collections/sharedterms.pt.php create mode 100644 content/lang/collections/specprocessor/crowdsource/controlpanel.fr.php create mode 100644 content/lang/collections/specprocessor/crowdsource/index.fr.php create mode 100644 content/lang/collections/specprocessor/crowdsource/review.fr.php create mode 100644 content/lang/collections/specprocessor/exporter.es.php create mode 100644 content/lang/collections/specprocessor/exporter.fr.php create mode 100644 content/lang/collections/specprocessor/geolocate.es.php create mode 100644 content/lang/collections/specprocessor/geolocate.fr.php create mode 100644 content/lang/collections/specprocessor/index.en.php create mode 100644 content/lang/collections/specprocessor/index.es.php create mode 100644 content/lang/collections/specprocessor/index.fr.php create mode 100644 content/lang/collections/specprocessor/salix/salixhandler.en.php create mode 100644 content/lang/collections/specprocessor/salix/salixhandler.es.php create mode 100644 content/lang/collections/specprocessor/salix/salixhandler.fr.php create mode 100644 content/lang/collections/specprocessor/specprocessor_tools.en.php create mode 100644 content/lang/collections/specprocessor/specprocessor_tools.es.php create mode 100644 content/lang/collections/specprocessor/specprocessor_tools.fr.php create mode 100644 content/lang/collections/tools/mapaids.fr.php create mode 100644 content/lang/collections/traitattr/attributemining.en.php create mode 100644 content/lang/collections/traitattr/attributemining.es.php create mode 100644 content/lang/collections/traitattr/attributemining.fr.php create mode 100644 content/lang/collections/traitattr/occurattributes.en.php create mode 100644 content/lang/collections/traitattr/occurattributes.es.php create mode 100644 content/lang/collections/traitattr/occurattributes.fr.php create mode 100644 content/lang/geothesaurus/harvester.en.php create mode 100644 content/lang/geothesaurus/harvester.es.php create mode 100644 content/lang/geothesaurus/harvester.fr.php create mode 100644 content/lang/geothesaurus/index.en.php create mode 100644 content/lang/geothesaurus/index.es.php create mode 100644 content/lang/geothesaurus/index.fr.php create mode 100644 content/lang/ident/index.fr.php create mode 100644 content/lang/ident/tools/matrixeditor.en.php create mode 100644 content/lang/ident/tools/matrixeditor.es.php create mode 100644 content/lang/ident/tools/matrixeditor.fr.php create mode 100644 content/lang/imagelib/admin/imageloader.es.php create mode 100644 content/lang/imagelib/admin/imageloader.fr.php create mode 100644 content/lang/imagelib/admin/thumbnailbuilder.es.php create mode 100644 content/lang/imagelib/admin/thumbnailbuilder.fr.php create mode 100644 content/lang/imagelib/contributors.en.php create mode 100644 content/lang/imagelib/contributors.es.php create mode 100644 content/lang/imagelib/contributors.fr.php create mode 100644 content/lang/imagelib/contributors.pt.php create mode 100644 content/lang/imagelib/imgdetails.en.php create mode 100644 content/lang/imagelib/imgdetails.es.php create mode 100644 content/lang/imagelib/imgdetails.fr.php create mode 100644 content/lang/profile/authCallback.en.php create mode 100644 content/lang/profile/authCallback.es.php create mode 100644 content/lang/profile/authCallback.fr.php create mode 100644 content/lang/sitemap.pt.php create mode 100644 content/lang/taxa/index.pt.php create mode 100644 content/lang/taxa/profile/tpdesceditor.en.php create mode 100644 content/lang/taxa/profile/tpdesceditor.es.php create mode 100644 content/lang/taxa/profile/tpdesceditor.fr.php create mode 100644 content/lang/taxa/profile/tpdesceditor.pt.php create mode 100644 content/lang/taxa/profile/tpeditor.es.php create mode 100644 content/lang/taxa/profile/tpeditor.pt.php create mode 100644 content/lang/taxa/profile/tpimageeditor.es.php create mode 100644 content/lang/taxa/profile/tpimageeditor.fr.php create mode 100644 content/lang/taxa/profile/tpimageeditor.pt.php create mode 100644 content/lang/taxa/taxonomy/taxonomydisplay.fr.php create mode 100644 content/lang/templates/accessibility.en.php create mode 100644 content/lang/templates/accessibility.es.php create mode 100644 content/lang/templates/accessibility.fr.php create mode 100644 content/lang/templates/accessibility.pt.php create mode 100644 content/lang/templates/header.en.override.php create mode 100644 content/lang/templates/header.en.php create mode 100644 content/lang/templates/header.es.override.php rename content/lang/{header.es_template.php => templates/header.es.php} (53%) create mode 100644 content/lang/templates/header.fr.override.php create mode 100644 content/lang/templates/header.fr.php create mode 100644 content/lang/templates/header.pt.override.php create mode 100644 content/lang/templates/header.pt.php create mode 100644 content/lang/templates/index.en.php create mode 100644 content/lang/templates/index.es.php create mode 100644 content/lang/templates/index.fr.php create mode 100644 content/lang/templates/index.pt.php create mode 100644 content/lang/templates/usagepolicy.en.php create mode 100644 content/lang/templates/usagepolicy.es.php create mode 100644 content/lang/templates/usagepolicy.fr.php create mode 100644 content/lang/templates/usagepolicy.pt.php delete mode 100644 css/base.css rename {js/jquery-ui => css}/jquery-ui.css (88%) rename js/jquery-ui/jquery-ui.structure.min.css => css/jquery-ui.min.css (50%) delete mode 100644 css/jquery.mobile-1.4.0.min.css create mode 100644 css/leafletMap.css delete mode 100644 css/symb/index.php delete mode 100644 css/symb/main.css delete mode 100644 css/symb/tooltips.css create mode 100644 css/symbiota/accessibility-compliant.css create mode 100644 css/symbiota/accessibility-controls.css create mode 100644 css/symbiota/checklists/checklist.css rename css/{v202209 => }/symbiota/checklists/index.php (84%) rename css/{v202209/symbiota/collections/reports => symbiota/collections/editor}/index.php (84%) rename css/{v202209 => }/symbiota/collections/editor/occureditormaterialsample.css (89%) rename css/{v202209 => }/symbiota/collections/editor/occurrenceeditor.css (92%) rename css/{v202209 => }/symbiota/collections/index.php (84%) create mode 100644 css/symbiota/collections/individual/index.css rename css/{v202209/symbiota/collections/editor => symbiota/collections/individual}/index.php (84%) rename css/{v202209 => }/symbiota/collections/individual/popup.css (70%) rename css/{v202209 => }/symbiota/collections/listdisplay.css (65%) rename css/{v202209/symbiota/collections/individual => symbiota/collections/reports}/index.php (84%) rename css/{symb => symbiota/collections/reports}/labelhelpers.css (100%) rename css/{symb => symbiota/collections/reports}/lichenpacket.css (100%) create mode 100644 css/symbiota/collections/sharedCollectionStyling.css create mode 100644 css/symbiota/condensed.css create mode 100644 css/symbiota/customizations.css rename css/{v202209 => }/symbiota/footer.css (97%) rename css/{v202209 => }/symbiota/header.css (61%) rename css/{v202209 => }/symbiota/index.php (70%) create mode 100644 css/symbiota/main.css rename css/{v202209 => }/symbiota/normalize.slim.css (79%) create mode 100644 css/symbiota/reset.css rename css/{v202209 => }/symbiota/sitemap.css (62%) rename css/{v202209 => }/symbiota/taxa/index.css (99%) rename css/{v202209 => }/symbiota/taxa/index.php (70%) rename css/{v202209 => }/symbiota/taxa/traitplot.css (100%) create mode 100644 css/symbiota/variables.css delete mode 100644 css/v202209/alerts.css delete mode 100644 css/v202209/index.php delete mode 100644 css/v202209/jquery-ui.css delete mode 100644 css/v202209/ol.css delete mode 100644 css/v202209/quicksearch.css delete mode 100644 css/v202209/slideshowstyle.css delete mode 100644 css/v202209/symbiota/base.css delete mode 100644 css/v202209/symbiota/checklists/checklist.css delete mode 100644 css/v202209/symbiota/collections/individual/index.css delete mode 100644 css/v202209/symbiota/collections/reports/labelhelpers.css delete mode 100644 css/v202209/symbiota/collections/reports/lichenpacket.css delete mode 100644 css/v202209/symbiota/main.css delete mode 100644 css/v202209/symbiota/variables.css create mode 100644 docs/styleguide.php create mode 100644 docs/third_party_auth_setup.md rename {css => games/where}/ol.css (100%) create mode 100644 geothesaurus/rpc/searchGeothesaurus.php create mode 100644 images/accessibility_FILL0_wght400_GRAD0_opsz24.svg create mode 100644 images/content_copy_FILL0_wght400_GRAD0_opsz24.svg create mode 100644 images/dataset-white.png create mode 100644 images/dl2-white.png create mode 100644 images/download-white.svg create mode 100644 images/download_FILL0_wght400_GRAD0_opsz24.svg create mode 100644 images/editsquare.png delete mode 100644 images/games/ootd/qmark.png create mode 100644 images/icons/inaturalist.png create mode 100644 images/info_deprecated.png create mode 100644 images/layout/logo_symbiota_lg.png create mode 100644 images/link-white.svg create mode 100644 images/list_FILL0_wght400_GRAD0_opsz24.svg create mode 100644 images/table-white.png create mode 100644 includes/googleMap.php delete mode 100644 includes/head_w_tooltips_template.php create mode 100644 includes/leafletMap.php delete mode 100644 includes/leftmenu_template.php create mode 100644 includes/minimalheader_template.php delete mode 100644 includes/styleguide_template.php create mode 100644 js/.DS_Store create mode 100644 js/Leaflet.markercluster-1.4.1/.DS_Store create mode 100644 js/Leaflet.markercluster-1.4.1/.gitignore create mode 100644 js/Leaflet.markercluster-1.4.1/.travis.yml create mode 100644 js/Leaflet.markercluster-1.4.1/CHANGELOG.md create mode 100644 js/Leaflet.markercluster-1.4.1/CONTRIBUTING.md create mode 100644 js/Leaflet.markercluster-1.4.1/ISSUE_TEMPLATE.md create mode 100644 js/Leaflet.markercluster-1.4.1/Jakefile.js rename js/{jquery-ui-1.12.1/LICENSE.txt => Leaflet.markercluster-1.4.1/MIT-LICENCE.txt} (56%) create mode 100644 js/Leaflet.markercluster-1.4.1/README.md create mode 100644 js/Leaflet.markercluster-1.4.1/bower.json create mode 100644 js/Leaflet.markercluster-1.4.1/build/hintrc.js create mode 100644 js/Leaflet.markercluster-1.4.1/build/rollup-config.js create mode 100644 js/Leaflet.markercluster-1.4.1/dist/MarkerCluster.Default.css create mode 100644 js/Leaflet.markercluster-1.4.1/dist/MarkerCluster.css create mode 100644 js/Leaflet.markercluster-1.4.1/dist/WhereAreTheJavascriptFiles.txt create mode 100644 js/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster-src.js create mode 100644 js/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster-src.js.map create mode 100644 js/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster.js create mode 100644 js/Leaflet.markercluster-1.4.1/dist/leaflet.markercluster.js.map create mode 100644 js/Leaflet.markercluster-1.4.1/example/geojson-sample.js create mode 100644 js/Leaflet.markercluster-1.4.1/example/geojson.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/map.png create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-convexhull.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-custom.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-dragging.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-everything.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-geojson.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-pane.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-realworld-maxzoom.388.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-realworld-mobile.388.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-realworld.10000.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-realworld.388.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-realworld.50000.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-singlemarkermode.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-spiderfier.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-zoomtobounds.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering-zoomtoshowlayer.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/marker-clustering.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/mobile.css create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/add-1000-after.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/add-markers-offscreen.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/add-remove-before-addtomap.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/animationless-zoom.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/click-cluster-at-screen-edge.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/disappearing-marker-from-spider.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/doesnt-update-cluster-on-bottom-level.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/drag-with-spiderfying.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/remove-add-clustering.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/remove-when-spiderfied.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/removelayer-after-remove-from-map.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/setView-doesnt-remove.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/zoomtoshowlayer-doesnt-need-to-zoom.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/old-bugs/zoomtoshowlayer-doesnt-zoom-if-centered-on.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/realworld.10000.js create mode 100644 js/Leaflet.markercluster-1.4.1/example/realworld.388.js create mode 100644 js/Leaflet.markercluster-1.4.1/example/realworld.50000.1.js create mode 100644 js/Leaflet.markercluster-1.4.1/example/realworld.50000.2.js create mode 100644 js/Leaflet.markercluster-1.4.1/example/remove-geoJSON-when-spiderfied.html create mode 100644 js/Leaflet.markercluster-1.4.1/example/screen.css create mode 100644 js/Leaflet.markercluster-1.4.1/package.json create mode 100644 js/Leaflet.markercluster-1.4.1/spec/after.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/expect.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/index.html create mode 100644 js/Leaflet.markercluster-1.4.1/spec/karma.conf.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/sinon.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/AddLayer.MultipleSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/AddLayer.SingleSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/AddLayersSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/ChildChangingIconSupportSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/CircleMarkerSupportSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/CircleSupportSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/DistanceGridSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/LeafletSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/NonPointSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/PaneSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/QuickHullSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/RefreshSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/RememberOpacity.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/RemoveLayerSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/SpecHelper.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/animateOptionSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/clearLayersSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/disableClusteringAtZoomSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/eachLayerSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/eventsSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/getBoundsSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/getLayersSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/getVisibleParentSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/markerMoveSupportSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/nonIntegerZoomSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/onAddSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/onRemoveSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/removeLayersSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/removeOutsideVisibleBoundsSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/singleMarkerModeSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/spiderfySpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/supportNegativeZoomSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/unspiderfySpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/spec/suites/zoomAnimationSpec.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/DistanceGrid.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerCluster.QuickHull.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerCluster.Spiderfier.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerCluster.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerClusterGroup.Refresh.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerClusterGroup.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/MarkerOpacity.js create mode 100644 js/Leaflet.markercluster-1.4.1/src/index.js create mode 100644 js/autocomplete-input.js delete mode 100644 js/dojo-1.17.3/dojo-1.14.1/claro-1.14.1.css delete mode 100644 js/dojo-1.17.3/dojo-1.14.1/dojo.js create mode 100644 js/dom-to-image/dist/dom-to-image.min.js create mode 100644 js/heatmap/google-heatmap.js create mode 100644 js/heatmap/heatmap.js create mode 100644 js/heatmap/leaflet-heatmap.js delete mode 100644 js/jquery-1.10.2.min.js delete mode 100644 js/jquery-1.10.2.min.map delete mode 100644 js/jquery-1.9.1.js delete mode 100644 js/jquery-3.2.1.min.js create mode 100644 js/jquery-3.7.1.min.js delete mode 100644 js/jquery-ui-1.10.4.js delete mode 100644 js/jquery-ui-1.12.1/AUTHORS.txt delete mode 100644 js/jquery-ui-1.12.1/images/index.php delete mode 100644 js/jquery-ui-1.12.1/images/ui-bg_highlight-soft_65_f3f3f3_1x100.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-icons_222222_256x240.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-icons_2e83ff_256x240.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-icons_454545_256x240.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-icons_888888_256x240.png delete mode 100644 js/jquery-ui-1.12.1/images/ui-icons_cd0a0a_256x240.png delete mode 100644 js/jquery-ui-1.12.1/index.php delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.css delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.js delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.min.css delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.min.js delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.structure.css delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.structure.min.css delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.theme.css delete mode 100644 js/jquery-ui-1.12.1/jquery-ui.theme.min.css delete mode 100644 js/jquery-ui-1.12.1/package.json delete mode 100644 js/jquery-ui.js create mode 100644 js/jquery-ui.min.js delete mode 100644 js/jquery-ui/AUTHORS.txt delete mode 100644 js/jquery-ui/images/index.php delete mode 100644 js/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 js/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 js/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 js/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 js/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 js/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 js/jquery-ui/images/ui-icons_222222_256x240.png delete mode 100644 js/jquery-ui/images/ui-icons_2e83ff_256x240.png delete mode 100644 js/jquery-ui/images/ui-icons_454545_256x240.png delete mode 100644 js/jquery-ui/images/ui-icons_888888_256x240.png delete mode 100644 js/jquery-ui/images/ui-icons_cd0a0a_256x240.png delete mode 100644 js/jquery-ui/index.php delete mode 100644 js/jquery-ui/jquery-ui.js delete mode 100644 js/jquery-ui/jquery-ui.min.css delete mode 100644 js/jquery-ui/jquery-ui.min.js delete mode 100644 js/jquery-ui/jquery-ui.structure.css delete mode 100644 js/jquery-ui/jquery-ui.theme.css delete mode 100644 js/jquery-ui/jquery-ui.theme.min.css delete mode 100644 js/jquery-ui/package.json delete mode 100644 js/jquery-ui/theme.css delete mode 100644 js/jquery.js delete mode 100644 js/jquery.mobile-1.4.0.min.js delete mode 100644 js/jquery.mobile-1.4.0.min.map create mode 100644 js/leaflet-draw-drag/README.md create mode 100644 js/leaflet-draw-drag/dist/Leaflet.draw.drag-src.js create mode 100644 js/leaflet-draw-drag/dist/Leaflet.draw.drag.js create mode 100644 js/leaflet-draw-drag/index.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/CHANGELOG.md create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/README.md create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/layers-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/layers.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/marker-icon-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/marker-icon.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/marker-shadow.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/spritesheet-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/spritesheet.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/images/spritesheet.svg create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/leaflet.draw-src.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/leaflet.draw-src.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/leaflet.draw-src.map create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/leaflet.draw.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/dist/leaflet.draw.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/css/main.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/css/normalize.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/basic.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/edithandlers.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/full.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/Leaflet.draw.drag-src.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/layers-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/layers.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon@2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/images/marker-shadow.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/leaflet-src.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/leaflet.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/leaflet.geometryutil.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/leaflet.snap.js create mode 100755 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/spectrum.css create mode 100755 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/libs/spectrum.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples-0.7.x/snapping.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/basic.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/edithandlers.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/full.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/Leaflet.draw.drag-src.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/images/layers-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/images/layers.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/images/marker-icon-2x.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/images/marker-icon.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/images/marker-shadow.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/leaflet-src.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/leaflet-src.map create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/leaflet.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/leaflet.geometryutil.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/leaflet.snap.js create mode 100755 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/spectrum.css create mode 100755 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/libs/spectrum.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/popup.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/examples/snapping.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/highlight/LICENSE create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/highlight/highlight.pack.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/highlight/styles/github-gist.css create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/favicon.ico create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/forum-round.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/github-round.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/sprite.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/sprite.svg create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/twitter-round.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/images/twitter.png create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/js/docs.js create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.12.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.14.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.2.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.3.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.4.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.5.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.7.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-0.4.9.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/docs/leaflet-draw-latest.html create mode 100644 js/leaflet-draw-drag/node_modules/leaflet-draw/package.json create mode 100644 js/leaflet-draw-drag/package.json create mode 100644 js/leaflet-draw-drag/src/Edit.Circle.Drag.js create mode 100644 js/leaflet-draw-drag/src/Edit.Poly.Drag.js create mode 100644 js/leaflet-draw-drag/src/Edit.Rectangle.Drag.js create mode 100644 js/leaflet-draw-drag/src/Edit.SimpleShape.Drag.js create mode 100644 js/leaflet-draw-drag/src/EditToolbar.Edit.js create mode 100644 js/leaflet-draw-drag/src/footer.js create mode 100644 js/leaflet-draw-drag/src/prelude.js create mode 100644 js/leaflet-draw/.DS_Store create mode 100644 js/leaflet-draw/CHANGELOG.md create mode 100644 js/leaflet-draw/README.md create mode 100644 js/leaflet-draw/dist/.DS_Store create mode 100644 js/leaflet-draw/dist/images/layers-2x.png create mode 100644 js/leaflet-draw/dist/images/layers.png create mode 100644 js/leaflet-draw/dist/images/marker-icon-2x.png create mode 100644 js/leaflet-draw/dist/images/marker-icon.png create mode 100644 js/leaflet-draw/dist/images/marker-shadow.png create mode 100644 js/leaflet-draw/dist/images/spritesheet-2x.png create mode 100644 js/leaflet-draw/dist/images/spritesheet.png create mode 100644 js/leaflet-draw/dist/images/spritesheet.svg create mode 100644 js/leaflet-draw/dist/leaflet.draw-src.css create mode 100644 js/leaflet-draw/dist/leaflet.draw-src.js create mode 100644 js/leaflet-draw/dist/leaflet.draw-src.map create mode 100644 js/leaflet-draw/dist/leaflet.draw.css create mode 100644 js/leaflet-draw/dist/leaflet.draw.js create mode 100644 js/leaflet-draw/docs/css/main.css create mode 100644 js/leaflet-draw/docs/css/normalize.css create mode 100644 js/leaflet-draw/docs/examples-0.7.x/basic.html create mode 100644 js/leaflet-draw/docs/examples-0.7.x/edithandlers.html create mode 100644 js/leaflet-draw/docs/examples-0.7.x/full.html create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/Leaflet.draw.drag-src.js create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/layers-2x.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/layers.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon-2x.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/marker-icon@2x.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/images/marker-shadow.png create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/leaflet-src.js create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/leaflet.css create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/leaflet.geometryutil.js create mode 100644 js/leaflet-draw/docs/examples-0.7.x/libs/leaflet.snap.js create mode 100755 js/leaflet-draw/docs/examples-0.7.x/libs/spectrum.css create mode 100755 js/leaflet-draw/docs/examples-0.7.x/libs/spectrum.js create mode 100644 js/leaflet-draw/docs/examples-0.7.x/snapping.html create mode 100644 js/leaflet-draw/docs/examples/basic.html create mode 100644 js/leaflet-draw/docs/examples/edithandlers.html create mode 100644 js/leaflet-draw/docs/examples/full.html create mode 100644 js/leaflet-draw/docs/examples/libs/Leaflet.draw.drag-src.js create mode 100644 js/leaflet-draw/docs/examples/libs/images/layers-2x.png create mode 100644 js/leaflet-draw/docs/examples/libs/images/layers.png create mode 100644 js/leaflet-draw/docs/examples/libs/images/marker-icon-2x.png create mode 100644 js/leaflet-draw/docs/examples/libs/images/marker-icon.png create mode 100644 js/leaflet-draw/docs/examples/libs/images/marker-shadow.png create mode 100644 js/leaflet-draw/docs/examples/libs/leaflet-src.js create mode 100644 js/leaflet-draw/docs/examples/libs/leaflet-src.map create mode 100644 js/leaflet-draw/docs/examples/libs/leaflet.css create mode 100644 js/leaflet-draw/docs/examples/libs/leaflet.geometryutil.js create mode 100644 js/leaflet-draw/docs/examples/libs/leaflet.snap.js create mode 100755 js/leaflet-draw/docs/examples/libs/spectrum.css create mode 100755 js/leaflet-draw/docs/examples/libs/spectrum.js create mode 100644 js/leaflet-draw/docs/examples/popup.html create mode 100644 js/leaflet-draw/docs/examples/snapping.html create mode 100644 js/leaflet-draw/docs/highlight/LICENSE create mode 100644 js/leaflet-draw/docs/highlight/highlight.pack.js create mode 100644 js/leaflet-draw/docs/highlight/styles/github-gist.css create mode 100644 js/leaflet-draw/docs/images/favicon.ico create mode 100644 js/leaflet-draw/docs/images/forum-round.png create mode 100644 js/leaflet-draw/docs/images/github-round.png create mode 100644 js/leaflet-draw/docs/images/sprite.png create mode 100644 js/leaflet-draw/docs/images/sprite.svg create mode 100644 js/leaflet-draw/docs/images/twitter-round.png create mode 100644 js/leaflet-draw/docs/images/twitter.png create mode 100644 js/leaflet-draw/docs/js/docs.js create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.12.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.14.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.2.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.3.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.4.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.5.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.7.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-0.4.9.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-1.0.0.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-1.0.1.html create mode 100644 js/leaflet-draw/docs/leaflet-draw-latest.html create mode 100644 js/leaflet-draw/package.json create mode 100644 js/leaflet-path-drag/.editorconfig create mode 100644 js/leaflet-path-drag/.eslintrc create mode 100644 js/leaflet-path-drag/.npmignore create mode 100644 js/leaflet-path-drag/README.md create mode 100644 js/leaflet-path-drag/circle.yml create mode 100644 js/leaflet-path-drag/dist/L.Path.Drag-src.js create mode 100644 js/leaflet-path-drag/dist/L.Path.Drag.js create mode 100644 js/leaflet-path-drag/example/css/style.css create mode 100755 js/leaflet-path-drag/example/css/topcoat-desktop-light.css create mode 100755 js/leaflet-path-drag/example/font/LICENSE.txt create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-Black.otf create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-Bold.otf create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-ExtraLight.otf create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-Light.otf create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-Regular.otf create mode 100755 js/leaflet-path-drag/example/font/SourceCodePro-Semibold.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-Black.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-BlackIt.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-Bold.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-BoldIt.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-ExtraLight.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-ExtraLightIt.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-It.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-Light.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-LightIt.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-Regular.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-Semibold.otf create mode 100755 js/leaflet-path-drag/example/font/SourceSansPro-SemiboldIt.otf create mode 100644 js/leaflet-path-drag/example/index.html create mode 100644 js/leaflet-path-drag/example/js/app.js create mode 100644 js/leaflet-path-drag/example/js/bundle.js create mode 100644 js/leaflet-path-drag/index.html create mode 100644 js/leaflet-path-drag/index.js create mode 100644 js/leaflet-path-drag/package.json create mode 100644 js/leaflet-path-drag/src/Canvas.js create mode 100644 js/leaflet-path-drag/src/Path.Drag.js create mode 100644 js/leaflet-path-drag/src/Path.Transform.js create mode 100644 js/leaflet-path-drag/src/SVG.VML.js create mode 100644 js/leaflet-path-drag/src/SVG.js create mode 100644 js/leaflet-path-drag/test/L.Path.Drag.test.js create mode 100644 js/symb/MapShapeHelper.js create mode 100644 js/symb/accessibilityUtils.js create mode 100644 js/symb/checklists.externalserviceapi.js create mode 100644 js/symb/collections.editor.autocomplete.js create mode 100644 js/symb/collections.misc.collmetadata.js create mode 100644 js/symb/googleMap.js create mode 100644 js/symb/ident.tools.matrixeditor.js create mode 100644 js/symb/leafletMap.js create mode 100644 js/symb/localitySuggest.js create mode 100644 js/symb/selectUtilities.js create mode 100644 js/tinymce/README.md create mode 100644 js/tinymce/icons/default/icons.js create mode 100644 js/tinymce/icons/default/icons.min.js delete mode 100644 js/tinymce/index.php delete mode 100644 js/tinymce/langs/es.js delete mode 100644 js/tinymce/langs/index.php create mode 100644 js/tinymce/plugins/advlist/plugin.js create mode 100644 js/tinymce/plugins/anchor/plugin.js create mode 100644 js/tinymce/plugins/autolink/plugin.js create mode 100644 js/tinymce/plugins/autoresize/plugin.js create mode 100644 js/tinymce/plugins/autosave/plugin.js create mode 100644 js/tinymce/plugins/bbcode/plugin.js create mode 100644 js/tinymce/plugins/charmap/plugin.js create mode 100644 js/tinymce/plugins/code/plugin.js create mode 100644 js/tinymce/plugins/codesample/plugin.js create mode 100644 js/tinymce/plugins/colorpicker/plugin.js create mode 100644 js/tinymce/plugins/contextmenu/plugin.js create mode 100644 js/tinymce/plugins/directionality/plugin.js create mode 100644 js/tinymce/plugins/emoticons/js/emojiimages.js create mode 100644 js/tinymce/plugins/emoticons/js/emojiimages.min.js create mode 100644 js/tinymce/plugins/emoticons/plugin.js create mode 100644 js/tinymce/plugins/fullpage/plugin.js create mode 100644 js/tinymce/plugins/fullscreen/plugin.js create mode 100644 js/tinymce/plugins/help/plugin.js create mode 100644 js/tinymce/plugins/hr/plugin.js create mode 100644 js/tinymce/plugins/image/plugin.js create mode 100644 js/tinymce/plugins/imagetools/plugin.js create mode 100644 js/tinymce/plugins/importcss/plugin.js delete mode 100644 js/tinymce/plugins/index.php create mode 100644 js/tinymce/plugins/insertdatetime/plugin.js create mode 100644 js/tinymce/plugins/legacyoutput/plugin.js create mode 100644 js/tinymce/plugins/link/plugin.js create mode 100644 js/tinymce/plugins/lists/plugin.js create mode 100644 js/tinymce/plugins/media/plugin.js create mode 100644 js/tinymce/plugins/nonbreaking/plugin.js create mode 100644 js/tinymce/plugins/noneditable/plugin.js create mode 100644 js/tinymce/plugins/pagebreak/plugin.js create mode 100644 js/tinymce/plugins/paste/plugin.js create mode 100644 js/tinymce/plugins/preview/plugin.js create mode 100644 js/tinymce/plugins/print/plugin.js create mode 100644 js/tinymce/plugins/quickbars/plugin.js create mode 100644 js/tinymce/plugins/save/plugin.js create mode 100644 js/tinymce/plugins/searchreplace/plugin.js create mode 100644 js/tinymce/plugins/spellchecker/plugin.js create mode 100644 js/tinymce/plugins/tabfocus/plugin.js create mode 100644 js/tinymce/plugins/table/plugin.js create mode 100644 js/tinymce/plugins/template/plugin.js create mode 100644 js/tinymce/plugins/textcolor/plugin.js create mode 100644 js/tinymce/plugins/textpattern/plugin.js create mode 100644 js/tinymce/plugins/toc/plugin.js create mode 100644 js/tinymce/plugins/visualblocks/plugin.js create mode 100644 js/tinymce/plugins/visualchars/plugin.js create mode 100644 js/tinymce/plugins/wordcount/plugin.js create mode 100644 js/tinymce/skins/content/dark/content.css create mode 100644 js/tinymce/skins/content/dark/content.min.css create mode 100644 js/tinymce/skins/content/dark/content.min.css.map create mode 100644 js/tinymce/skins/content/default/content.css create mode 100644 js/tinymce/skins/content/default/content.min.css.map create mode 100644 js/tinymce/skins/content/document/content.css create mode 100644 js/tinymce/skins/content/document/content.min.css.map delete mode 100644 js/tinymce/skins/content/index.php create mode 100644 js/tinymce/skins/content/writer/content.css create mode 100644 js/tinymce/skins/content/writer/content.min.css.map delete mode 100644 js/tinymce/skins/index.php delete mode 100644 js/tinymce/skins/ui/index.php create mode 100644 js/tinymce/skins/ui/oxide-dark/content.css create mode 100644 js/tinymce/skins/ui/oxide-dark/content.inline.css create mode 100644 js/tinymce/skins/ui/oxide-dark/content.inline.min.css.map create mode 100644 js/tinymce/skins/ui/oxide-dark/content.min.css.map create mode 100644 js/tinymce/skins/ui/oxide-dark/content.mobile.css create mode 100644 js/tinymce/skins/ui/oxide-dark/content.mobile.min.css create mode 100644 js/tinymce/skins/ui/oxide-dark/content.mobile.min.css.map create mode 100644 js/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.css create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.min.css.map create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.mobile.css create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.mobile.min.css create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.mobile.min.css.map create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.shadowdom.css create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css create mode 100644 js/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/content.css create mode 100644 js/tinymce/skins/ui/oxide/content.inline.css create mode 100644 js/tinymce/skins/ui/oxide/content.inline.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/content.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/content.mobile.css create mode 100644 js/tinymce/skins/ui/oxide/content.mobile.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/skin.css create mode 100644 js/tinymce/skins/ui/oxide/skin.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/skin.mobile.css create mode 100644 js/tinymce/skins/ui/oxide/skin.mobile.min.css.map create mode 100644 js/tinymce/skins/ui/oxide/skin.shadowdom.css create mode 100644 js/tinymce/skins/ui/oxide/skin.shadowdom.min.css create mode 100644 js/tinymce/skins/ui/oxide/skin.shadowdom.min.css.map delete mode 100644 js/tinymce/themes/index.php delete mode 100644 js/tinymce/themes/mobile/index.php create mode 100644 js/tinymce/themes/mobile/theme.js delete mode 100644 js/tinymce/themes/silver/index.php create mode 100644 js/tinymce/themes/silver/theme.js create mode 100644 js/tinymce/tinymce.d.ts create mode 100644 js/tinymce/tinymce.js create mode 100644 misc/aboutproject.php create mode 100644 misc/contacts.php create mode 100644 profile/authCallback.php create mode 100644 profile/openIdAuth.php create mode 100644 rpc/crossPortalHeaders.php create mode 100644 utilities/SymbUtil.php create mode 100644 vendor/jumbojett/openid-connect-php/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 vendor/jumbojett/openid-connect-php/.github/workflows/build.yml create mode 100644 vendor/jumbojett/openid-connect-php/.gitignore create mode 100644 vendor/jumbojett/openid-connect-php/CHANGELOG.md create mode 100644 vendor/jumbojett/openid-connect-php/LICENSE create mode 100644 vendor/jumbojett/openid-connect-php/README.md create mode 100644 vendor/jumbojett/openid-connect-php/client_example.php create mode 100644 vendor/jumbojett/openid-connect-php/composer.json create mode 100644 vendor/jumbojett/openid-connect-php/phpunit.xml.dist create mode 100644 vendor/jumbojett/openid-connect-php/src/OpenIDConnectClient.php create mode 100644 vendor/jumbojett/openid-connect-php/tests/OpenIDConnectClientTest.php create mode 100644 vendor/jumbojett/openid-connect-php/tests/TokenVerificationTest.php create mode 100644 vendor/jumbojett/openid-connect-php/tests/data/jwks-ps256.json create mode 100644 vendor/paragonie/constant_time_encoding/LICENSE.txt create mode 100644 vendor/paragonie/constant_time_encoding/README.md create mode 100644 vendor/paragonie/constant_time_encoding/composer.json create mode 100644 vendor/paragonie/constant_time_encoding/src/Base32.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Base32Hex.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Base64.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Base64DotSlash.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Base64UrlSafe.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Binary.php create mode 100644 vendor/paragonie/constant_time_encoding/src/EncoderInterface.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Encoding.php create mode 100644 vendor/paragonie/constant_time_encoding/src/Hex.php create mode 100644 vendor/paragonie/constant_time_encoding/src/RFC4648.php create mode 100644 vendor/paragonie/random_compat/LICENSE create mode 100644 vendor/paragonie/random_compat/build-phar.sh create mode 100644 vendor/paragonie/random_compat/composer.json create mode 100644 vendor/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 vendor/paragonie/random_compat/lib/random.php create mode 100644 vendor/paragonie/random_compat/other/build_phar.php create mode 100644 vendor/paragonie/random_compat/psalm-autoload.php create mode 100644 vendor/paragonie/random_compat/psalm.xml create mode 100644 vendor/phpseclib/phpseclib/AUTHORS create mode 100644 vendor/phpseclib/phpseclib/BACKERS.md rename js/jquery-ui/LICENSE.txt => vendor/phpseclib/phpseclib/LICENSE (52%) create mode 100644 vendor/phpseclib/phpseclib/README.md create mode 100644 vendor/phpseclib/phpseclib/composer.json create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/File/X509.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/bootstrap.php create mode 100644 vendor/phpseclib/phpseclib/phpseclib/openssl.cnf diff --git a/.gitignore b/.gitignore index 3200114ba0..37fe849076 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,10 @@ /config/dbconnection.php /config/symbini.php +/config/auth_config.php /footer.php /header.php /leftmenu.php /index.php -content/lang/header.en.php -content/lang/header.es.php -content/lang/header.fr.php -content/lang/index.en.php -content/lang/index.es.php -content/lang/index.fr.php includes/header.php includes/footer.php includes/head.php @@ -20,6 +15,3 @@ includes/citationgbif.php includes/citationcollection.php includes/citationdataset.php includes/citationportal.php -.project -.settings/org.eclipse.core.resources.prefs -.settings/org.eclipse.php.ui.prefs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..9dc6b4df69 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 0, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + }, + { + "name": "Launch Built-in web server", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-dxdebug.mode=debug", + "-dxdebug.start_with_request=yes", + "-S", + "localhost:0" + ], + "program": "", + "cwd": "${workspaceRoot}", + "port": 9003, + "serverReadyAction": { + "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index e3d1630c1f..a93d2b6136 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Symbiota has been generously funded by the National Science Foundation (DBI-0743 - 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 +- Development and testing preformed using MariaDB. If you are using Oracle MySQL instead, please [report any issues](https://github.com/BioKIC/Symbiota/issues/new). ## INSTALLATION diff --git a/accessibility/module.php b/accessibility/module.php new file mode 100644 index 0000000000..2b733bfdd4 --- /dev/null +++ b/accessibility/module.php @@ -0,0 +1,43 @@ + + +

+

+ +
+ +
+
+ + + + + + + + + + + diff --git a/accessibility/rpc/toggle-styles.php b/accessibility/rpc/toggle-styles.php new file mode 100644 index 0000000000..c623f0a8cf --- /dev/null +++ b/accessibility/rpc/toggle-styles.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php index 333a382d08..7e3ac5d599 100644 --- a/admin/index.php +++ b/admin/index.php @@ -1,11 +1,13 @@ - + - Forbidden + <?php echo $LANG['FORBIDDEN']; ?> @@ -15,13 +17,13 @@ $displayLeftMenu = true; include($SERVER_ROOT.'/includes/header.php'); ?> -
-

Forbidden

+
+

- You don't have permission to access this page. +
- + <?php echo $DEFAULT_TITLE; ?> Portal Index Control Panel - - + + - '; + + '; // include_once($SERVER_ROOT.'/includes/googleanalytics.php'); echo ' - '; + + '; // include_once($SERVER_ROOT.'/includes/googleanalytics.php'); echo ' - + + + - - '; if($pid){ @@ -150,7 +168,7 @@ function changeImageSource(elem){ } else{ echo '' . $LANG['NAV_HOME'] . ' >> '; - echo '' . $clManager->getClName() . ''; + echo '' . $clManager->getClName() . ''; } echo '
'; ?> @@ -162,54 +180,25 @@ function changeImageSource(elem){ ?> -
+

getClName(); ?> -

+ - - -
-
    -
  • - - - -
    - getClName()."&taxonfilter=".$taxonFilter."&showcommon=".$showCommon.($clManager->getThesFilter()?"&thesfilter=".$clManager->getThesFilter():""); - ?> - - -
    -
  • -
-
- '; $argStr = '&clid='.$clid.'&dynclid='.$dynClid.($showCommon?'&showcommon=1':'').($showSynonyms?'&showsynonyms=1':'').($showVouchers?'&showvouchers=1':''); $argStr .= ($showAuthors?'&showauthors=1':'').($clManager->getThesFilter()?'&thesfilter='.$clManager->getThesFilter():''); @@ -219,11 +208,11 @@ function changeImageSource(elem){ //Do not show certain fields if Dynamic Checklist ($dynClid) if($clid){ if($clArray['type'] == 'rarespp'){ - echo '
'.(isset($LANG['SENSITIVE_SPECIES'])?$LANG['SENSITIVE_SPECIES']:'Sensitive species checklist for').': '.$clArray["locality"].'
'; + echo '
' . $LANG['SENSITIVE_SPECIES'] . ': ' . $clArray["locality"] . '
'; if($isEditor && $clArray["locality"]){ include_once($SERVER_ROOT.'/classes/OccurrenceMaintenance.php'); $occurMaintenance = new OccurrenceMaintenance(); - echo '
'.(isset($LANG['NUMBER_PENDING'])?$LANG['NUMBER_PENDING']:'Number of specimens pending protection').': '; + echo '
' . $LANG['NUMBER_PENDING'] . ': '; if($action == 'protectspp'){ $occurMaintenance->protectStateRareSpecies($clid,$clArray["locality"]); echo '0'; @@ -232,14 +221,14 @@ function changeImageSource(elem){ $protectCnt = $occurMaintenance->getStateProtectionCount($clid, $clArray["locality"]); echo $protectCnt; if($protectCnt){ - echo ''; - echo ''; + echo ''; + echo ''; echo ''; } } else{ - echo ''; - echo ''; + echo ''; + echo ''; echo ''; } echo '
'; @@ -247,24 +236,24 @@ function changeImageSource(elem){ } elseif($clArray['type'] == 'excludespp'){ $parentArr = $clManager->getParentChecklist(); - echo '
'.(isset($LANG['EXCLUSION_LIST'])?$LANG['EXCLUSION_LIST']:'Exclusion Species List for').' '.current($parentArr).'
'; + echo '
' . $LANG['EXCLUSION_LIST'] . ' ' . current($parentArr) . '
'; } if($childArr = $clManager->getChildClidArr()){ - echo '
'.(isset($LANG['INCLUDE_TAXA'])?$LANG['INCLUDE_TAXA']:'Includes taxa from following child checklists').':
'; + echo '
' . $LANG['INCLUDE_TAXA'] . ':
'; echo '
'; foreach($childArr as $childClid => $childName){ - echo ''; + echo ''; } echo '
'; } if($exclusionArr = $clManager->getExclusionChecklist()){ - echo '
'.(isset($LANG['TAXA_EXCLUDED'])?$LANG['TAXA_EXCLUDED']:'Taxa explicitly excluded').': '.current($exclusionArr).'
'; + echo '
' . $LANG['TAXA_EXCLUDED'] . ': ' . current($exclusionArr) . '
'; } if($clArray["authors"] && $clArray['type'] != 'excludespp'){ ?>
- : + :
@@ -272,33 +261,33 @@ function changeImageSource(elem){ } if($clArray['publication']){ $pubStr = $clArray['publication']; - if(substr($pubStr,0,4)=='http' && !strpos($pubStr,' ')) $pubStr = ''.$pubStr.''; - echo '
'.(isset($LANG['CITATION'])?$LANG['CITATION']:'Citation').': '.$pubStr.'
'; + if(substr($pubStr,0,4)=='http' && !strpos($pubStr,' ')) $pubStr = '' . $pubStr . ''; + echo '
' . $LANG['CITATION'] . ': ' . $pubStr . '
'; } } if(($clArray["locality"] || ($clid && ($clArray["latcentroid"] || $clArray["abstract"])) || $clArray["notes"])){ ?> -
-
+
+
'.$LANG['LOCALITY'].': '.$locStr.'
'; + echo '
' . $LANG['LOCALITY'] . ': ' . $locStr . '
'; } } if($clid && $clArray["abstract"]){ $abstractTitle = $LANG['ABSTRACT']; if($clArray['type'] == 'excludespp') $abstractTitle = $LANG['COMMENTS']; - echo '
'.$abstractTitle.': '.$clArray['abstract'].'
'; + echo '
' . $abstractTitle . ': ' . $clArray['abstract'] . '
'; } if($clArray['notes']){ - echo '
'.(isset($LANG['NOTES'])?$LANG['NOTES']:'Notes').': '.$clArray['notes'].'
'; + echo '
' . $LANG['NOTES'] . ': ' . $clArray['notes'] . '
'; } ?>
-
+

-
+
+
+
+
+ ' . $LANG['FAMILIES'] . ': '; + echo $clManager->getFamilyCount(); + echo '.'; + ?> +
+
+ ' . $LANG['GENERA'] . ': '; + echo $clManager->getGenusCount(); + echo '.'; + ?> +
+
+ ' . $LANG['SPECIES'] . ': '; + echo $clManager->getSpeciesCount(); + echo '.'; + ?> +
+
+ ' . $LANG['TOTAL_TAXA'] . ': '; + echo $clManager->getTaxaCount(); + echo '.'; + ?> +
+
+
+
+ getImageLimit():$clManager->getTaxaLimit()); + $pageCount = ceil($clManager->getTaxaCount()/$taxaLimit); + if(($pageNumber)>$pageCount) $pageNumber = 1; + echo $LANG['PAGE'] . ' ' . ($pageNumber).' ' . $LANG['OF'] . ' ' . $pageCount . ': '; + for($x=1;$x<=$pageCount;$x++){ + if($x>1) echo " | "; + if(($pageNumber) == $x) echo ''; + else echo ''; + echo ($x); + if(($pageNumber) == $x) echo ''; + else echo ''; + } + if($showImages){ + ?> +
+ : + /> + /> +
+ +
+
+ $sppArr){ + $tu = (array_key_exists('tnurl',$sppArr) ? $sppArr['tnurl'] : ''); + $u = (array_key_exists('url',$sppArr) ? $sppArr['url'] : ''); + $imgSrc = ($tu?$tu:$u); + ?> +
+
+ "; + echo ""; + echo ""; + } + else{ + ?> +
+ ' . $LANG['NOTY'] . '
' . $LANG['AVAIL']; ?>
+
+ +
+
+ '; + echo '' . $sppArr['sciname'] . ''; + echo ''; + ?> + + " . $sppArr["vern"] . "
"; + } + if(!$showAlphaTaxa){ + $family = $sppArr['family']; + if($family != $prevfam){ + ?> +
+ [] +
+ +
+
+ '; + if($clid && $clArray['dynamicProperties']){ + $dynamPropsArr = json_decode($clArray['dynamicProperties'], true); + } + $voucherArr = array(); + $externalVoucherArr = array(); + if($showVouchers) { + $voucherArr = $clManager->getVoucherArr(); + if($clManager->getAssociatedExternalService()) $externalVoucherArr = $clManager->getExternalVoucherArr(); + } + $prevGroup = ''; + $arrForExternalServiceApi = ''; + foreach($taxaArray as $tid => $sppArr){ + $group = $sppArr['taxongroup']; + if($group != $prevGroup){ + $famUrl = '../taxa/index.php?taxauthid=1&taxon=' . strip_tags($group) . '&clid='.$clid; + //Edit family name display style here + ?> +
+ + + + + +
+ '; + //Edit species name display style here + echo '
'; + if(!preg_match('/\ssp\d/',$sppArr["sciname"])) echo ''; + echo '' . $sppArr['sciname'] . ' '; + if(array_key_exists("author",$sppArr)) echo $sppArr["author"]; + if(!preg_match('/\ssp\d/',$sppArr["sciname"])) echo ""; + if(array_key_exists('vern',$sppArr)){ + echo ' - '.$sppArr['vern'] . ''; + } + if($clid && $clArray['dynamicsql']){ + ?> + + + + + '; + echo ''; + echo ''; + } + ?> + + + + + + \n"; + if($showSynonyms && isset($sppArr['syn'])){ + echo '
[' . $sppArr['syn'] . ']
'; + } + if($showVouchers){ + $voucStr = ''; + if(array_key_exists('notes',$sppArr)) $voucStr .= $sppArr['notes'] . '; '; + if(array_key_exists($tid,$voucherArr)){ + $voucCnt = 0; + foreach($voucherArr[$tid] as $occid => $collName){ + if($voucCnt == 4 && !$printMode){ + $voucStr .= '' . $LANG['MORE'] . '...'. + ''; + } + if(isset($externalVoucherArr[$tid])) { + foreach($externalVoucherArr[$tid] as $extVouchArr){ + if(!empty($extVouchArr['display'])){ + if(!empty($extVouchArr['url'])) $voucStr .= '' . $extVouchArr['display'] . ', '; + else $voucStr .= $extVouchArr['display'] . ', '; + } + } + } + $voucStr = trim($voucStr,' ;,'); + if($voucStr) echo '
' . $voucStr . '
'; + } + echo "
\n"; + } + echo '
'; + if(isset($dynamPropsArr['externalservice']) && $dynamPropsArr['externalservice'] == 'inaturalist') { + echo ''; + echo ''; + ?> + + getImageLimit():$clManager->getTaxaLimit()); + if($clManager->getTaxaCount() > (($pageNumber)*$taxaLimit)){ + echo ''; + } + if(!$taxaArray) echo "

" . $LANG['NOTAXA'] . "

"; + ?> +
+
+ + +
+ + + +
+
    +
  • + + Games <?php echo $LANG['GAMES']; ?> + +
    + getClName()).'&taxonfilter='.$taxonFilter.'&showcommon='.$showCommon.($clManager->getThesFilter()?'&thesfilter='.$clManager->getThesFilter():''); + ?> + + +
    +
  • +
+
+
: +
".$LANG['COMMON']."
"; + echo " " . "
"; } ?> - /> + /> +
-
- :
- getTaxonAuthorityList(); foreach($taxonAuthList as $taCode => $taValue){ - echo "\n"; + echo "\n"; } ?>
-
"> - /> - +
"> + /> +
'; - echo " ".$LANG['COMMON'].""; + echo " " . ""; echo '
'; } ?>
- onclick="showImagesChecked(this.form);" /> - + onclick="showImagesChecked(this.form);" /> +
-
"> - /> - +
"> + /> +
-
- /> - +
+ /> +
- /> - + /> +
@@ -393,17 +711,17 @@ function changeImageSource(elem){ '; ?> - +
-
- +
+
-
- +
+
-
- +
+
@@ -416,7 +734,7 @@ function changeImageSource(elem){
setClid($clid); $statusStr = ''; @@ -27,7 +38,7 @@ if($newClid) header('Location: checklist.php?clid='.$newClid); } //If we made it here the user does not have any checklist roles. cancel further execution. - $statusStr = 'You do not have permission to create a Checklist. Please contact an administrator.'; + $statusStr = $LANG['NO_PERMISSIONS']; } $isEditor = 0; @@ -36,36 +47,38 @@ //Submit checklist MetaData edits if($action == 'submitEdit'){ if($clManager->editChecklist($_POST)){ - header('Location: checklist.php?clid='.$clid.'&pid='.$pid); + header('Location: checklist.php?clid=' . $clid . '&pid=' . $pid); } else{ $statusStr = $clManager->getErrorMessage(); } } elseif($action == 'deleteChecklist'){ - if($clManager->deleteChecklist($_POST['delclid'])){ + if($clManager->deleteChecklist($delclid)){ header('Location: ../index.php'); } - else $statusStr = 'ERROR deleting checklist: '.$clManager->getErrorMessage(); + else $statusStr = $LANG['ERR_DELETING_CHECKLIST'] . ': ' . $clManager->getErrorMessage(); } elseif($action == 'addEditor'){ - $statusStr = $clManager->addEditor($_POST['editoruid']); + $statusStr = $clManager->addEditor($editoruid); } elseif(array_key_exists('deleteuid',$_REQUEST)){ $statusStr = $clManager->deleteEditor($_REQUEST['deleteuid']); } elseif($action == 'addToProject'){ - $statusStr = $clManager->addProject($_POST['pid']); + $statusStr = $clManager->addProject($pid); } elseif($action == 'deleteProject'){ - $statusStr = $clManager->deleteProject($_POST['pid']); + $statusStr = $clManager->deleteProject($pid); } elseif($action == 'addPoint'){ - $statusStr = $clManager->addPoint($_POST['pointtid'],$_POST['pointlat'],$_POST['pointlng'],$_POST['notes']); + if(!$clManager->addPoint($pointtid, $pointlat, $pointlng, $notes)){ + $statusStr = $clManager->getErrorMessage(); + } } elseif($action && array_key_exists('clidadd',$_POST)){ - if(!$clManager->addChildChecklist($_POST['clidadd'])){ - $statusStr = 'ERROR adding child checklist link'; + if(!$clManager->addChildChecklist($clidadd)){ + $statusStr = $LANG['ERR_ADDING_CHILD']; } } elseif($action && array_key_exists('cliddel',$_GET)){ @@ -74,37 +87,35 @@ } } elseif($action == 'parseChecklist'){ - $parseTid = 0; - if(array_key_exists('parsetid',$_POST) && is_numeric($_POST['parsetid'])) $parseTid = $_POST['parsetid']; - $taxon = ''; - if(array_key_exists('taxon',$_POST)) $taxon = filter_var($_POST['taxon'], FILTER_SANITIZE_STRING); - $resultArr = $clManager->parseChecklist($parseTid, $taxon, $targetClid, $parentClid, $targetPid, $transferMethod, $copyAttributes); + $resultArr = $clManager->parseChecklist($parsetid, $taxon, $targetClid, $parentClid, $targetPid, $transferMethod, $copyAttributes); if($resultArr){ - $statusStr = '
Checklist parsed successfully!
'; + $statusStr = '
' . $LANG['CHECK_PARSED_SUCCESS'] . '
'; if(isset($resultArr['targetPid'])){ $targetPid = $resultArr['targetPid']; - $statusStr .= ''; + $statusStr .= ''; } - if(isset($resultArr['targetClid'])) $statusStr .= ''; + if(isset($resultArr['targetClid'])) $statusStr .= ''; if(isset($resultArr['parentClid'])){ $parentClid = $resultArr['parentClid']; - $statusStr .= ''; + $statusStr .= ''; } } } } $clArray = $clManager->getMetaData(); +$clArray = $clManager->cleanOutArray($clArray); ?> - + + - - <?php echo $DEFAULT_TITLE.' - '.$LANG['CHECKLIST_ADMIN'];?> - + + <?= $DEFAULT_TITLE . ' - ' . $LANG['CHECKLIST_ADMIN'] ?> + - - + + - -
+
- +
- +
- +
'; + echo '
' . $LANG['NO_PARENTS'] . '
'; } ?> @@ -144,19 +143,20 @@ function validateAddChildForm(f){
- Batch Parse Species List -
Use the following tool to parse a list into multiple children checklists based on taxonomic nodes (Liliopsida, Eudicots, Pinopsida, etc)
+ +
- + +
- - + + $name){ @@ -166,28 +166,30 @@ function validateAddChildForm(f){
- - > transfer taxa - > copy taxa + + > + + > +
- +
- +
- > - + > +
- +
- +
\ No newline at end of file diff --git a/checklists/checklistadminmeta.php b/checklists/checklistadminmeta.php index e91bd67da9..823e2d62fd 100644 --- a/checklists/checklistadminmeta.php +++ b/checklists/checklistadminmeta.php @@ -1,7 +1,8 @@ setClid($clid); $clArray = $clManager->getMetaData($pid); +$clArray = $clManager->cleanOutArray($clArray); $defaultArr = array(); if(isset($clArray['defaultsettings']) && $clArray['defaultsettings']){ $defaultArr = json_decode($clArray['defaultsettings'], true); } +$dynamPropsArr = array(); +if(isset($clArray['dynamicProperties']) && $clArray['dynamicProperties']){ + $dynamPropsArr = json_decode($clArray['dynamicProperties'], true); +} ?>
- +
-
+
-
+
" />
-
+
getUserChecklistArr(); ?> @@ -162,11 +177,11 @@ function openMappingPolyAid() { if($userClArr){ ?> @@ -174,26 +189,43 @@ function openMappingPolyAid() { } ?>
-
-
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
" />
-
+
" />
-
+
-
+
" />
- :
+ :
-
+
-
- " /> +
+ " />
-
- " /> +
+ " />
- +
-
- " /> +
+ " />
-
+
- + ;"> - Click globe to view/edit'); ?> + ;"> - Click globe to create polygon');?> + - - + +
-
+
- +
".(isset($LANG['DISPLAY_SYNONYMS'])?$LANG['DISPLAY_SYNONYMS']:'Display Synonyms'); + echo " " . $LANG['DISPLAY_SYNONYMS']; ?>
".$LANG['COMMON']; + if($DISPLAY_COMMON_NAMES) echo " " . $LANG['COMMON']; ?>
@@ -286,32 +318,32 @@ function openMappingPolyAid() { /> - +
- : - + : +
- : + :
'.(isset($LANG['EDITCHECKLIST'])?$LANG['EDITCHECKLIST']:'Edit Checklist').''; + echo ''; } else{ - echo ''; + echo ''; } ?>
@@ -327,7 +359,7 @@ function openMappingPolyAid() { '.(isset($LANG['ASSIGNED_CHECKLISTS'])?$LANG['ASSIGNED_CHECKLISTS']:'Checklists assigned to your account').'
'; + echo '
' . $LANG['ASSIGNED_CHECKLISTS'] . '
'; $listArr = $clManager->getManagementLists($userId); if(array_key_exists('cl',$listArr)){ $clArr = $listArr['cl']; @@ -337,11 +369,11 @@ function openMappingPolyAid() { foreach($clArr as $kClid => $vName){ ?>
  • - - + + - - + +
  • -
    -
    - +
    +
    +
    '.(isset($LANG['PROJ_ADMIN'])?$LANG['PROJ_ADMIN']:'Inventory Project Administration').'
    '; + echo '
    ' . $LANG['PROJ_ADMIN'] . '
    '; if(array_key_exists('proj',$listArr)){ $projArr = $listArr['proj']; ?> @@ -370,11 +402,11 @@ function openMappingPolyAid() { foreach($projArr as $pid => $projName){ ?>
  • - - + + - - + +
  • '.(isset($LANG['NO_PROJECTS'])?$LANG['NO_PROJECTS']:'There are no Projects for which you have administrative permissions').'
    '; + echo '
    ' . $LANG['NO_PROJECTS'] . '
    '; } } ?> -
    \ No newline at end of file +
    diff --git a/checklists/checklistmap.php b/checklists/checklistmap.php index 05d200f51d..dbfed41686 100644 --- a/checklists/checklistmap.php +++ b/checklists/checklistmap.php @@ -1,12 +1,13 @@ setTaxonFilter($taxonFilter); $coordArr = $clManager->getVoucherCoordinates(); +$clMeta = $clManager->getClMetaData(); +$coordJson = json_encode($coordArr); +$clName = htmlspecialchars($clManager->getClName() ?? 'Unknown Collection', ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE); + +$coords = []; + +foreach($coordArr as $tid => $taxaCoords) { + foreach($taxaCoords as $coord) { + $ll = explode(',',$coord['ll']); + if(count($ll) == 2 && trim($ll[0]) != 0 && trim($ll[1]) != 0) { + array_push($coords, ['lat' => $ll[0], 'lng' => $ll[1], 'occid' => $coord['occid'], 'notes' => $coord['notes']]); + } + } +} +$coordJson = json_encode($coords); +$metaJson = json_encode($clMeta); + +$shouldUseMinimalMapHeader = $SHOULD_USE_MINIMAL_MAP_HEADER ?? false; + ?> - - - <?php echo $DEFAULT_TITLE.' - '.(isset($LANG['COORD_MAP'])?$LANG['COORD_MAP']:'Checklist Coordinate Map'); ?> - - - - - - - + + + <?php echo $DEFAULT_TITLE . ' - ' . $LANG['COORD_MAP']; ?> + + + + + + + + + + -
    - . -
    -
    - . -
    - -
    - - \ No newline at end of file +

    + +

    +
    + . +
    +
    + . +
    + +
    +
    + + diff --git a/checklists/clgmap.php b/checklists/clgmap.php index 9647a82e7a..4d35a1c9bd 100644 --- a/checklists/clgmap.php +++ b/checklists/clgmap.php @@ -1,91 +1,115 @@ setProj($pid); + +$shouldUseMinimalMapHeader = $SHOULD_USE_MINIMAL_MAP_HEADER ?? false; ?> - - - <?php echo $DEFAULT_TITLE.' - '.(isset($LANG['H_INVENTORIES'])?$LANG['H_INVENTORIES']:'Species Checklists'); ?> - - + + + + + <?= $DEFAULT_TITLE . ' - ' . $LANG['TITLE'] ?> + + +

    Checklist Map

    +
    - \ No newline at end of file + diff --git a/checklists/clsppeditor.php b/checklists/clsppeditor.php index eabc0c96d4..be0548bf01 100644 --- a/checklists/clsppeditor.php +++ b/checklists/clsppeditor.php @@ -1,13 +1,14 @@ setTid($tid); $vManager->setClid($clid); $followUpAction = ''; -if($action == 'renameTransfer'){ +if($action == 'remapTaxon'){ $rareLocality = ''; - if($_POST['cltype'] == 'rarespp') $rareLocality = $_POST['locality']; - if($vManager->renameTaxon($_POST['renametid'], $rareLocality)){ - $followUpAction = 'removeTaxon()'; + if(!empty($_POST['cltype']) && $_POST['cltype'] == 'rarespp' && !empty($_POST['locality'])) $rareLocality = $_POST['locality']; + if($vManager->remapTaxon($_POST['renametid'], $rareLocality)){ + $followUpAction = 'reloadParentPage()'; } - else echo $vManager->getErrorMessage(); + else $statusStr = $vManager->getErrorMessage(); } elseif($action == 'editChecklist'){ - $eArr = Array(); - $eArr['habitat'] = $_POST['habitat']; - $eArr['abundance'] = $_POST['abundance']; - $eArr['notes'] = $_POST['notes']; - $eArr['internalnotes'] = $_POST['internalnotes']; - $eArr['source'] = $_POST['source']; - $eArr['familyoverride'] = $_POST['familyoverride']; - $status = $vManager->editClData($eArr); - $followUpAction = 'self.close()'; + if($vManager->editClData($_POST)){ + $followUpAction = 'reloadParentPage()'; + } + else $statusStr = $vManager->getErrorMessage(); } elseif($action == 'deleteTaxon'){ $rareLocality = ''; - if($_POST['cltype'] == 'rarespp') $rareLocality = $_POST['locality']; - $status = $vManager->deleteTaxon($rareLocality); - $followUpAction = 'removeTaxon()'; + if(!empty($_POST['cltype']) && $_POST['cltype'] == 'rarespp' && !empty($_POST['locality'])) $rareLocality = $_POST['locality']; + if($vManager->deleteTaxon($_POST, $rareLocality)){ + $followUpAction = 'reloadParentPage()'; + } + else $statusStr = $vManager->getErrorMessage(); } elseif($action == 'editVoucher'){ - if(!$vManager->editVoucher($_POST['voucherID'], $_POST['notes'], $_POST['editornotes'])){ - $status = $vManager->getErrorMessage(); + $voucherID = filter_var($_POST['voucherID'], FILTER_SANITIZE_NUMBER_INT); + if(!$vManager->editVoucher($voucherID, $_POST['notes'], $_POST['editornotes'])){ + $statusStr = $vManager->getErrorMessage(); } } elseif($action == 'deleteVoucher'){ - if(!$vManager->deleteVoucher($_POST['voucherID'])){ - $status = $vManager->getErrorMessage(); + if(!empty($_POST['voucherID'])){ + if(!$vManager->deleteVoucher($_POST['voucherID'])){ + $statusStr = $vManager->getErrorMessage(); + } } } -elseif($action == 'Add Voucher'){ - //For processing requests sent from /collections/individual/index.php - $status = $vManager->addVoucher($_POST['voccid'],$_POST['vnotes'],$_POST['veditnotes']); -} $clArray = $vManager->getChecklistData(); ?> - + + - <?php echo (isset($LANG['SPEC_DETAILS'])?$LANG['SPEC_DETAILS']:'Species Details'); ?>: <?php echo $vManager->getTaxonName().' of '.$vManager->getClName(); ?> - + <?= $LANG['SPEC_DETAILS'] . ': ' . ($vManager->getTaxonName() ?? $LANG['UNKNOWN_TAXON']) . ' ' . $LANG['OF'] . ' ' . $vManager->getClName() ?? $LANG['UNKNOWN_COLLECTION']; ?> + - - + + - - + + '; + echo '
    ' . $LANG['NO_DATA'] . '
    '; } ?>
    - \ No newline at end of file + diff --git a/checklists/dynamicchecklist.php b/checklists/dynamicchecklist.php index a995fa6b34..6f6e303256 100644 --- a/checklists/dynamicchecklist.php +++ b/checklists/dynamicchecklist.php @@ -1,33 +1,28 @@ getTid($taxa); $dynClid = 0; if($radius) $dynClid = $dynClManager->createChecklist($lat, $lng, $radius, $radiusUnits, $tid); else $dynClid = $dynClManager->createDynamicChecklist($lat, $lng, $dynamicRadius, $tid); - if($dynClid){ if($interface == "key"){ header("Location: ".$CLIENT_ROOT."/ident/key.php?dynclid=".$dynClid."&taxon=All Species"); @@ -36,6 +31,6 @@ header("Location: ".$CLIENT_ROOT."/checklists/checklist.php?dynclid=".$dynClid); } } -else echo 'ERROR generating checklist'; +else echo $LANG['ERROR_GEN_CHECK']; $dynClManager->removeOldChecklists(); -?> \ No newline at end of file +?> diff --git a/checklists/dynamicmap.php b/checklists/dynamicmap.php index 76716588bb..233db12da2 100644 --- a/checklists/dynamicmap.php +++ b/checklists/dynamicmap.php @@ -1,7 +1,8 @@ 40) $zoomInt = 3; } ?> - + + - <?php echo $DEFAULT_TITLE.' - '.(isset($LANG['CHECKLIST_GENERATOR'])?$LANG['CHECKLIST_GENERATOR']:'Dynamic Checklist Generator'); ?> + <?php echo $DEFAULT_TITLE . ' - ' . $LANG['CHECKLIST_GENERATOR']; ?> - - - - - + + - function placeMarker() { - if(currentMarker) currentMarker.setMap(); - if(mapZoom == map.getZoom()){ - var marker = new google.maps.Marker({ - position: startLocation, - map: map - }); - currentMarker = marker; - - var latValue = startLocation.lat(); - var lonValue = startLocation.lng(); - latValue = latValue.toFixed(5);; - lonValue = lonValue.toFixed(5); - document.getElementById("latbox").value = latValue; - document.getElementById("lngbox").value = lonValue; - document.getElementById("latlngspan").innerHTML = latValue + ", " + lonValue; - document.mapForm.buildchecklistbutton.disabled = false; - submitCoord = true; - } - } + + //Map Global Vars from php + let latCent; + let lngCent; + let mapZoom; + + $(document).ready(function() { + $( "#taxa" ).autocomplete({ + source: function( request, response ) { + $.getJSON( "../rpc/taxasuggest.php", { term: request.term, rankhigh: 180 }, response ); + }, + minLength: 2, + autoFocus: true, + select: function( event, ui ) { + if(ui.item){ + $( "#tid" ).val(ui.item.id); + } + } + }); + }); + + function getRadius() { + const radius = document.getElementById('radius').value; + const radiusUnits = document.getElementById('radiusunits').value; + + if(radiusUnits === "km") return radius * 1000; + + const MILES_TO_METERS = 1609.344; + + return radius * MILES_TO_METERS; + } + + function onRadiusChange(eventFunction) { + let radiusInput = document.getElementById('radius'); + if(radiusInput) { + radiusInput.addEventListener('change', eventFunction); + //Need because input clears on focus + radiusInput.addEventListener('focus', eventFunction); + } + + let radiusUnits = document.getElementById('radiusunits'); + if(radiusUnits) { + radiusUnits.addEventListener('change', eventFunction); + } + } + + function leafletInit() { + + let dmOptions = { + zoom: mapZoom, + center: [latCent, lngCent], + }; + + map = new LeafletMap('map_canvas', dmOptions) + + let markerGroup = new L.layerGroup().addTo(map.mapLayer); + let latlng; + + function drawMarker(center) { + //Clear Layers In Between Clicks + if(markerGroup) markerGroup.clearLayers(); + + latlng = center; + + //Render Marker + L.marker(center).addTo(markerGroup); + + //Render Radius if Input + let radius = getRadius(); + if(radius > 0) { + let circle = L.circle(center, radius) + .setStyle(map.DEFAULT_SHAPE_OPTIONS) + .addTo(markerGroup); + } + } + + map.mapLayer.on('click', e => { + drawMarker(e.latlng); + updateMarkerPosition(e.latlng.lat, e.latlng.lng); + }); + + onRadiusChange(e => { + if(latlng) drawMarker(latlng); + }); + } + + function googleInit() { + var dmLatLng = new google.maps.LatLng(latCent, lngCent); + var dmOptions = { + zoom: mapZoom, + center: dmLatLng, + mapTypeId: google.maps.MapTypeId.TERRAIN + }; + + map = new google.maps.Map(document.getElementById("map_canvas"), dmOptions); + + let marker; + let circle; + let latlng; + + google.maps.event.addListener(map, 'click', function(event) { + if(marker) marker.setMap(); + if(circle) circle.setMap(); + latlng = event.latLng; + + marker = new google.maps.Marker({ + position: event.latLng, + map: map + }); + + let radius = getRadius(); + if(radius > 0) { + circle = new google.maps.Circle({ + center: event.latLng, + radius: radius, + clickable: false, + map: map + }); + } + + updateMarkerPosition(event.latLng.lat(), event.latLng.lng()); + }); + + onRadiusChange(e => { + if(circle) circle.setMap(); + if(!latlng) return; + + const new_radius = getRadius(); + if(new_radius > 0) { + circle = new google.maps.Circle({ + center: latlng, + clickable: false, + radius: new_radius, + map: map + }); + } + }); + } + + function initialize(){ + try { + const data = document.getElementById('service-container'); + latCent = parseFloat(data.getAttribute('data-latCen')) + lngCent = parseFloat(data.getAttribute('data-lngCen')) + mapZoom = parseInt(data.getAttribute('data-mapZoom')) + } catch { + alert("Failed to load map centering"); + } + + + leafletInit(); + + googleInit(); + + } + + function updateMarkerPosition(lat, lng) { + lat = lat.toFixed(5); + lng = lng.toFixed(5); + + document.getElementById("latbox").value = lat; + document.getElementById("lngbox").value = lng; + document.getElementById("latlngspan").innerHTML = lat + ", " + lng; + document.mapForm.buildchecklistbutton.disabled = false; + submitCoord = true; + } + + function checkForm(){ + if(submitCoord) return true; + alert(""); + return false; + } + +
    "; - echo "Home > "; + echo "" . $LANG['HOME'] . " > "; echo $checklists_dynamicmapCrumbs; - echo "Dynamic Map"; + echo "" . $LANG['DYNAMIC_MAP'] . ""; echo "
    "; } } else{ ?> -
    -
    - +
    +

    Dynamic Map

    +
    + - +
    -
    -
    -
    -
    - - - - -
    -
    - : - < > +
    + +
    + + + +
    + : + < >
    +
    -
    -
    - : +
    +
    +
    -
    - : - - +
    -
    +
    - \ No newline at end of file + diff --git a/checklists/externalvouchers.php b/checklists/externalvouchers.php new file mode 100644 index 0000000000..1c690cbcc6 --- /dev/null +++ b/checklists/externalvouchers.php @@ -0,0 +1,156 @@ +setClid($clid); + $metaArr = $clManager->getClMetaData(); + if(!empty($metaArr['dynamicProperties'])){ + $dynamPropsArr = json_decode($metaArr['dynamicProperties'], true); + } +} + +$isEditor = 0; +if($IS_ADMIN || (array_key_exists('ClAdmin', $USER_RIGHTS) && in_array($clid, $USER_RIGHTS['ClAdmin']))){ + $isEditor = 1; +} +if($isEditor){ + ?> +
    +
    +
    +
    + +
    + getTaxaList()){ + ?> +
    + + +
    +
    + '.$LANG['SAVEEXTVOUCH'].''; + $prevGroup = ''; + $arrForExternalServiceApi = ''; + $cnt = 1; + foreach($taxaArray as $tid => $sppArr){ + $group = $sppArr['taxongroup']; + if($group != $prevGroup){ + $famUrl = '../taxa/index.php?taxauthid=1&taxon='.strip_tags($group).'&clid='.$clid; + //Edit family name display style here + ?> +
    + +
    + '; + echo ''; + echo ' '; + ?> + + + + + + + + + \n"; + $scinameasid = str_replace(' ', '-', $sppArr['sciname']); + $arrForExternalServiceApi .= ($arrForExternalServiceApi ? ',' : '') . "'" . $scinameasid . "'"; + if($cnt%15 == 0) echo $button; + $cnt++; + } + echo $button; + ?> + + +
    +
    + + + +
    + '.$LANG['EMPTY_LIST'].''; + ?> +
    +
    +
    + \ No newline at end of file diff --git a/checklists/imgvouchertab.php b/checklists/imgvouchertab.php index 63dabfd0b2..8d3dbf457d 100644 --- a/checklists/imgvouchertab.php +++ b/checklists/imgvouchertab.php @@ -1,7 +1,8 @@ $v){ - echo ''; + echo ''; } ?>
    - />
    +
    -
    \ No newline at end of file +
    diff --git a/checklists/index.php b/checklists/index.php index e426f87802..aa4c0a7b31 100644 --- a/checklists/index.php +++ b/checklists/index.php @@ -1,26 +1,43 @@ setProj($pid); ?> - + + - <?php echo $DEFAULT_TITLE; ?> Species Lists + <?php echo $DEFAULT_TITLE . $LANG['SPECIES_INVENTORIES']; ?> + -
    -

    +
    +

    getChecklists(); if($researchArr){ foreach($researchArr as $pid => $projArr){ ?> -

    +

    '; + if($projName == 'Miscellaneous Inventories') $projName = $LANG['MISC_INVENTORIES']; echo $projName; + if($pid) echo ''; + if(!empty($projArr['displayMap'])){ + ?> + '> + <?= $LANG[' /> + + - '> - - -

    -
    '; + else echo '
    ' . $LANG['NO_INVENTORIES'] . '
    '; ?>
    @@ -67,4 +90,4 @@ include($SERVER_ROOT.'/includes/footer.php'); ?> - \ No newline at end of file + diff --git a/checklists/mswordexport.php b/checklists/mswordexport.php index e1f6c05608..318ccc1596 100644 --- a/checklists/mswordexport.php +++ b/checklists/mswordexport.php @@ -117,7 +117,7 @@ $textrun->addTextBreak(1); } } -if(($clArray['locality'] || ($clid && ($clArray['latcentroid'] || $clArray['abstract'])) || $clArray['notes'])){ +if(isset($clArray['locality']) || ($clid && isset ($clArray['latcentroid']) || isset ($clArray['abstract']) || isset($clArray['notes']))){ $locStr = $clManager->cleanOutText($clArray['locality']); if($clid && $clArray['latcentroid']) $locStr .= ' ('.$clArray['latcentroid'].', '.$clArray['longcentroid'].')'; if($locStr){ @@ -125,13 +125,13 @@ $textrun->addText($locStr,'textFont'); $textrun->addTextBreak(1); } - if($clid && $clArray['abstract']){ + if($clid && isset ($clArray['abstract'])){ $abstract = $clManager->cleanOutText($clArray['abstract']); $textrun->addText('Abstract: ', 'topicFont'); $textrun->addText($abstract, 'textFont'); $textrun->addTextBreak(1); } - if($clid && $clArray['notes']){ + if($clid && isset ($clArray['notes'])){ $notes = $clManager->cleanOutText($clArray['notes']); $textrun->addText('Notes: ', 'topicFont'); $textrun->addText($notes, 'textFont'); @@ -165,7 +165,7 @@ $imgSrc = ($tu?$tu:$u); if($imageCnt%4 == 1) $table->addRow(); if($imgSrc){ - $imgSrc = (array_key_exists('imageDomain',$GLOBALS)&&substr($imgSrc,0,4)!='http'?$GLOBALS['imageDomain']:'').$imgSrc; + $imgSrc = (array_key_exists('IMAGE_DOMAIN', $GLOBALS) && substr($imgSrc, 0, 4) != 'http' ? $GLOBALS['IMAGE_DOMAIN'] : '') . $imgSrc; $cell = $table->addCell(null,$imageCellStyle); $textrun = $cell->addTextRun('imagePara'); $textrun->addImage($imgSrc,array('width'=>160,'height'=>160)); @@ -242,12 +242,15 @@ } } } -$fileName = str_replace(array(' ', '/', '.'), '_', $clManager->getClName()); +$fileName = str_replace(array(' ', '/', '.'), '_', $clManager->getClName() ?? ""); $fileName = preg_replace('/[^0-9A-Za-z\-]/', '', $fileName); if(strlen($fileName) > 30) $fileName = substr($fileName, 0, 30); $targetFile = $SERVER_ROOT.'/temp/report/'.$fileName.'_'.date('Y-m-d').'.docx'; $phpWord->save($targetFile, 'Word2007'); +ob_start(); +ob_clean(); +ob_end_flush(); header('Content-Description: File Transfer'); header('Content-type: application/force-download'); header('Content-Disposition: attachment; filename='.basename($targetFile)); diff --git a/checklists/nonvoucheredtab.php b/checklists/nonvoucheredtab.php index d098c27964..b3b1e126d5 100644 --- a/checklists/nonvoucheredtab.php +++ b/checklists/nonvoucheredtab.php @@ -1,7 +1,8 @@
    - : + getChildClidArr()){ - echo ' (excludes taxa from children checklists)'; + echo ' ' . $LANG['EXCLUDES_CHILDREN_TAXA']; } ?> - +
    getNewVouchers($startPos,$displayMode)){ + if($specArr = $clManager->getNewVouchers($startPos, $displayMode)){ ?>
    - +
    - - - + + + $occArr){ foreach($occArr as $occid => $oArr){ echo ''; echo ''; - echo ''; + echo ''; echo ''; - echo ''; + echo ''; echo ''; } } @@ -99,12 +100,12 @@
    - + '.$LANG['NOVOUCHLOCA'].''; + echo '
    ' . $LANG['NOVOUCHLOCA'] . '
    '; } ?> @@ -121,16 +122,16 @@ getNonVoucheredTaxa($startPos)){ foreach($nonVoucherArr as $family => $tArr){ - echo '
    '.strtoupper($family).'
    '; + echo '
    ' . strtoupper($family) . '
    '; echo '
    '; foreach($tArr as $clTaxaID => $taxaArr){ $tid = $taxaArr['t']; - $sciname = htmlspecialchars($taxaArr['s'], HTML_SPECIAL_CHARS_FLAGS); + $sciname = $clManager->cleanOutStr($taxaArr['s']); ?>
    - - - + + +
    100){ echo '
    '; - if($startPos > 0) echo ''; - echo '<< '.$LANG['PREVIOUS'].''; + if($startPos > 0) echo ''; + echo '<< ' . $LANG['PREVIOUS'] . ''; if($startPos > 0) echo ''; - echo ' || '.$startPos.'-'.($startPos+($arrCnt<100?$arrCnt:100)).''.$LANG['RECORDS'].' || '; - if(($startPos + 100) <= $nonVoucherCnt) echo ''; - echo ''.$LANG['NEXT'].' >>'; + echo ' || ' . $startPos . '-' . ($startPos+($arrCnt<100?$arrCnt:100)) . '' . $LANG['RECORDS'] . ' || '; + if(($startPos + 100) <= $nonVoucherCnt) echo ''; + echo '' . $LANG['NEXT'] . ' >>'; if(($startPos + 100) <= $nonVoucherCnt) echo ''; echo '
    '; } } else{ - echo '

    '.$LANG['ALLTAXACONTAINVOUCH'].'

    '; + echo '

    ' . $LANG['ALLTAXACONTAINVOUCH'] . '

    '; } ?>
    diff --git a/checklists/rpc/gettid.php b/checklists/rpc/gettid.php deleted file mode 100644 index bf87eb01a5..0000000000 --- a/checklists/rpc/gettid.php +++ /dev/null @@ -1,25 +0,0 @@ -real_escape_string($_REQUEST["sciname"]); - -$sql = "SELECT t.tid FROM taxa t ". - "WHERE (t.sciname = '".$sciName."')"; -$result = $con->query($sql); -if($row = $result->fetch_object()){ - $responseStr = $row->tid; -} -$result->close(); -if(!($con === false)) $con->close(); - -//output the response -echo $responseStr; -?> \ No newline at end of file diff --git a/checklists/rpc/index.php b/checklists/rpc/index.php index eae7091dad..dfa0f5affb 100644 --- a/checklists/rpc/index.php +++ b/checklists/rpc/index.php @@ -1,11 +1,15 @@ - + - Forbidden + <?php echo $LANG['FORBIDDEN']; ?> @@ -16,13 +20,13 @@ include($SERVER_ROOT.'/includes/header.php'); ?> -
    -

    Forbidden

    +
    +

    - You don't have permission to access this page. +
    setClid($clid); - if($clManager->linkVoucher($taxon, $occid)) echo 'Success! Voucher added to checklist.'; - else echo 'Unable to link voucher: '.$clManager->getErrorMessage(); + if($clManager->linkVoucher($taxon, $occid)) echo $LANG['VOUCHER_ADDED']; + else{ + $errStr = $clManager->getErrorMessage(); + if($errStr == 'voucherAlreadyLinked') $errStr = $LANG['VOUCHER_ALREADY_LINKED']; + echo $LANG['UNABLE_TO_LINK'] . ': ' . $errStr; + } } } ?> \ No newline at end of file diff --git a/checklists/tools/checklistloader.php b/checklists/tools/checklistloader.php index 80c68faa80..54203fe9a5 100644 --- a/checklists/tools/checklistloader.php +++ b/checklists/tools/checklistloader.php @@ -1,6 +1,11 @@ - + - <?php echo $DEFAULT_TITLE; ?> Species Checklist Loader + <?php echo $DEFAULT_TITLE . " " . $LANG['SPEC_CHECKLOAD']?> @@ -28,12 +33,12 @@ function validateUploadForm(thisForm){ var testStr = document.getElementById("uploadfile").value; if(testStr == ""){ - alert("Please select a file to upload"); + alert(""); return false; } testStr = testStr.toLowerCase(); if(testStr.indexOf(".csv") == -1 && testStr.indexOf(".CSV") == -1){ - alert("Document "+document.getElementById("uploadfile").value+" must be a CSV file (with a .csv extension)"); + alert("" + document.getElementById("uploadfile").value + ""); return false; } return true; @@ -51,17 +56,17 @@ function displayErrors(clickObj){ include($SERVER_ROOT.'/includes/header.php'); ?> -
    +

    - +

    @@ -74,38 +79,38 @@ function displayErrors(clickObj){ ?>
      -
    • Loading checklist...
    • +
    • uploadCsvList($thesId); $statusStr = $clLoaderManager->getErrorMessage(); if(!$cnt && $statusStr){ echo '
      '; echo '
      '.$statusStr.'
      '; - echo '
      Return to Loader and make sure the input file matches requirements within instructions
      '; + echo '
      ' . $LANG['RETURN_LOADER'] . '' . $LANG['INPUT_MATCH'] . '
      '; echo '
      '; exit; } $probCnt = count($clLoaderManager->getProblemTaxa()); $errorArr = $clLoaderManager->getWarningArr(); ?> -
    • Upload status...
    • -
    • Taxa successfully loaded:
    • -
    • Problematic Taxa:
    • -
    • General errors:
    • -
    • Upload Complete! Proceed to Checklists
    • +
    • +
    • +
    • +
    • +
    '; - echo 'Problematic Taxa Resolution'; + echo '' . $LANG['TAXA_RESOLUTION'] . ''; $clLoaderManager->resolveProblemTaxa(); echo ''; } if($errorArr){ ?>
    - General Errors - Display general errors + +
    '.$oArr['sciname'].'' . $clManager->cleanOutStr($oArr['sciname']) . ''; echo $oArr['recordedby'].' '.$oArr['recordnumber'].'
    '; if($oArr['eventdate']) echo $oArr['eventdate'].'
    '; - echo ''; + echo ''; echo $oArr['collcode']; echo ''; echo '
    '.$oArr['locality'].'' . $oArr['locality'] . '
    +
    - - - - + + + + $vArr){ @@ -70,15 +70,15 @@ function validateBatchConflictForm(f){ @@ -95,20 +95,20 @@ function validateBatchConflictForm(f){
    - + '.(isset($LANG['FROM_CHILD'])?$LANG['FROM_CHILD']:'(from child checklists)'); + if($vArr['clid'] != $clid) echo '
    ' . $LANG['FROM_CHILD']; ?>
    - +
    - +
    - : -
    -
    *
    + : +
    +
    *
    '.(isset($LANG['NO_CONFLICTS'])?$LANG['NO_CONFLICTS']:'No conflicts exist').''; + else echo '

    ' . $LANG['NO_CONFLICTS'] . '

    '; ?> -
    \ No newline at end of file + diff --git a/checklists/vamissingtaxa.php b/checklists/vamissingtaxa.php index adabe781a2..4ad9599d78 100644 --- a/checklists/vamissingtaxa.php +++ b/checklists/vamissingtaxa.php @@ -1,12 +1,13 @@ setClid($clid); @@ -23,34 +24,34 @@ elseif($displayMode==2) $missingArr = $vManager->getMissingProblemTaxa(); else $missingArr = $vManager->getMissingTaxa(); ?> -
    +
    getMissingTaxaCount(); ?> - + - - + +
    - : + : @@ -65,20 +66,19 @@ if($displayMode==1){ ?>
    - +
    - - - + + + '; echo ''; - echo ''; + echo ''; echo ''; @@ -104,10 +104,10 @@ ?>
    - +
    '.$sciStr.'' . htmlspecialchars($sciStr, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''; echo $oArr['recordedby'].' '.$oArr['recordnumber'].'
    '; if($oArr['eventdate']) echo $oArr['eventdate'].'
    '; - echo ''; + echo ''; echo $oArr['collcode']; echo ''; echo '
    - +
    - /> + />
    @@ -115,29 +115,25 @@ - +
    '.(isset($LANG['SPEC_COUNT'])?$LANG['SPEC_COUNT']:'Specimen Count').' '.$recCnt.'
    '; - $queryStr = 'tabindex=1&displaymode=1&clid='.$clid.'&pid='.$pid.'&start='.(++$startIndex); - if($recCnt > $limitRange) echo '
    '.(isset($LANG['VIEW_NEXT'])?$LANG['VIEW_NEXT']:'View Next').' '.$limitRange.'
    '; + echo '
    ' . $LANG['SPEC_COUNT'] . ' ' . $recCnt . '
    '; + $queryStr = 'tabindex=1&displaymode=1&clid=' . $clid . '&pid=' . $pid . '&start=' . (++$startIndex); + if($recCnt > $limitRange) echo '
    ' . htmlspecialchars($LANG['VIEW_NEXT'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ' ' . htmlspecialchars($limitRange, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '
    '; } elseif($displayMode==2){ ?>
    - +
    - - - - + + + + '; echo $oArr['recordedby'].' '.$oArr['recordnumber'].'
    '; if($oArr['eventdate']) echo $oArr['eventdate'].'
    '; - echo ''; + echo ''; echo $oArr['collcode']; echo ''; echo ''; @@ -173,17 +169,16 @@ ?>
    -
    $sn){ ?>
    - - - + + +
    linkTaxaVouchers($_POST['occids'], $useCurrentTaxonomy, $linkVouchers); } elseif($action == 'resolveconflicts'){ $clManager->batchTransferConflicts($_POST['occid'], (array_key_exists('removetaxa',$_POST) ? true : false)); } + elseif($action == 'linkExternalVouchers'){ + $clManager->setClid($clid); + $cnt = 0; + foreach($_POST as $key => $value) { + if(substr($key, 0, 2) == 'i-') { + $tid = substr($key, 2); + if(is_numeric($tid) && !empty($_POST[$tid])) { + if($clManager->addExternalVouchers($tid, urldecode($_POST[$tid]))){ + $cnt++; + } + else{ + $statusStr .= $clManager->getErrorMessage().'
    '; + } + } + } + } + if($cnt){ + $statusStr = $cnt . ' ' . $LANG['EXT_VOUCHERS_LINKED']; + } + } } $clManager->setCollectionVariables(); $clMetaArr = $clManager->getClMetadata(); ?> - + + - <?php echo $DEFAULT_TITLE; ?> <?php echo $LANG['CHECKLIST_ADMIN'];?> + <?php echo $DEFAULT_TITLE . ' ' . $LANG['CHECKLIST_ADMIN']; ?> - - + + - @@ -75,23 +102,26 @@ include($SERVER_ROOT.'/includes/header.php'); ?>
    -
    - +

    Voucher Administration

    +

    -
    - +
    +

    getQueryVariableStr(); ?> - + + + <?php echo $LANG['IMG_EDIT'] ?> + +
    - +
    - - @@ -199,7 +233,9 @@ '; $model = $this->model; $dates = "(".$model->getyearofbirth()."-".$model->getyearofdeath().")"; - $returnvalue .= "\n"; + $returnvalue .= "\n"; $returnvalue .= "\n"; $returnvalue .= "\n"; $returnvalue .= ''; diff --git a/classes/ChecklistAdmin.php b/classes/ChecklistAdmin.php index baab69df2c..04967a8d16 100644 --- a/classes/ChecklistAdmin.php +++ b/classes/ChecklistAdmin.php @@ -31,16 +31,16 @@ public function createChecklist($postArr){ $newClid = 0; if($GLOBALS['SYMB_UID'] && isset($postArr['name'])){ $postArr['defaultsettings'] = $this->getDefaultJson($postArr); + $postArr['dynamicProperties'] = $this->getDynamicPropertiesJsonWithExternalServiceDetails($postArr); $inventoryManager = new ImInventories(); - $newClid = $inventoryManager->insertChecklist($postArr); - - if($newClid){ + if($inventoryManager->insertChecklist($postArr)){ + $newClid = $inventoryManager->getPrimaryKey(); //Add permissions to allow creater to be an editor and then reset user permissions stored in browser cache $inventoryManager->insertUserRole($GLOBALS['SYMB_UID'], 'ClAdmin', 'fmchecklists', $newClid, $GLOBALS['SYMB_UID']); $newPManager = new ProfileManager(); - $newPManager->setUserName($GLOBALS['USERNAME']); - $newPManager->authenticate(); + $newPManager->setUid($GLOBALS['SYMB_UID']); + $newPManager->setUserRights(); if($postArr['type'] == 'excludespp' && $postArr['excludeparent']){ //If is an exclusion checklists, link to parent checklist if(!$inventoryManager->insertChildChecklist($postArr['excludeparent'], $newClid, $GLOBALS['SYMB_UID'])){ @@ -48,6 +48,7 @@ public function createChecklist($postArr){ } } } + else echo $inventoryManager->errorMessage; } return $newClid; } @@ -56,7 +57,7 @@ public function editChecklist($postArr){ $status = false; if($GLOBALS['SYMB_UID'] && isset($postArr['name'])){ $postArr['defaultsettings'] = $this->getDefaultJson($postArr); - + $postArr['dynamicProperties'] = $this->getDynamicPropertiesJsonWithExternalServiceDetails($postArr); $inventoryManager = new ImInventories(); $inventoryManager->setClid($this->clid); $status = $inventoryManager->updateChecklist($postArr); @@ -88,6 +89,65 @@ private function getDefaultJson($postArr){ return json_encode($defaultArr); } + private function getDynamicPropertiesJsonWithExternalServiceDetails($postArr){ + $dynpropArr = array(); + if(!empty($postArr['dynamicProperties'])) $dynpropArr = json_decode($postArr['dynamicProperties']); + if(!empty($postArr['externalservice']) && !empty($postArr['externalserviceid'])){ + $dynpropArr['externalservice'] = $postArr['externalservice']; + $dynpropArr['externalserviceid'] = $postArr['externalserviceid']; + if($dynpropArr['externalservice'] == 'inaturalist') { + $dynpropArr['externalserviceiconictaxon'] = array_key_exists('externalserviceiconictaxon',$postArr)?$postArr['externalserviceiconictaxon']:''; + $requestUrl = 'https://api.inaturalist.org/v1/projects/' . $dynpropArr['externalserviceid']; + $inatProjectJson = json_decode(file_get_contents($requestUrl)); + $inatProjectJson = $inatProjectJson->results[0]; + if(!empty($inatProjectJson->place_id)){ + $requestStr = 'https://api.inaturalist.org/v1/places/' . $inatProjectJson->place_id; + $inatPlaceJson = json_decode(file_get_contents($requestStr)); + $inatPlaceCoords = $inatPlaceJson->results[0]->bounding_box_geojson->coordinates[0]; + $minlon = $maxlon = $prevminlon = $prevmaxlon = $inatPlaceCoords[0][0]; + $minlat = $maxlat = $prevminlat = $prevmaxlat = $inatPlaceCoords[0][1]; + for ($k = 1 ; $k < 4; $k++) { + if ($inatPlaceCoords[$k][0] < $prevminlon) { + $minlon = $prevminlon = $inatPlaceCoords[$k][0]; + } + if ($inatPlaceCoords[$k][1] < $prevminlat) { + $minlat = $prevminlat = $inatPlaceCoords[$k][1]; + } + if ($inatPlaceCoords[$k][0] > $prevmaxlon) { + $maxlon = $prevmaxlon = $inatPlaceCoords[$k][0]; + } + if ($inatPlaceCoords[$k][1] > $prevmaxlat) { + $maxlat = $prevmaxlat = $inatPlaceCoords[$k][1]; + } + } + + /* + $maxlondiff = $maxlatdiff = 0; + $bboxloncoords = $bboxlatcoords = array(); + for ($k = 0 ; $k < 4; $k++) { + if(isset($prevzero)) { + $londiff = abs($inatPlaceCoords[$k][0] - $prevzero); + $latdiff = abs($inatPlaceCoords[$k][1] - $prevone); + $maxlondiff = ($londiff > $maxlondiff) ? $londiff : $maxlondiff; + $maxlatdiff = ($latdiff > $maxlatdiff) ? $latdiff : $maxlatdiff; + } + $bboxloncoords[] = $prevzero = $inatPlaceCoords[$k][0]; + $bboxlatcoords[] = $prevone = $inatPlaceCoords[$k][1]; + } + $zoom = round( log(180/(max([$maxlondiff,$maxlatdiff]) / 4), 2) ) + 1; + $xtilemax = floor(((max($bboxloncoords) + 180) / 360) * pow(2, $zoom)); + $xtilemin = floor(((min($bboxloncoords) + 180) / 360) * pow(2, $zoom)); + $ytilemax = floor((1 - log(tan(deg2rad(max($bboxlatcoords))) + 1 / cos(deg2rad(max($bboxlatcoords)))) / pi()) /2 * pow(2, $zoom)); + $ytilemin = floor((1 - log(tan(deg2rad(min($bboxlatcoords))) + 1 / cos(deg2rad(min($bboxlatcoords)))) / pi()) /2 * pow(2, $zoom)); + */ + $dynpropArr['externalservicene'] = $maxlat.'|'.$maxlon; + $dynpropArr['externalservicesw'] = $minlat.'|'.$minlon; + } + } + } + return json_encode($dynpropArr); + } + //Polygon functions public function getFootprintWkt(){ $retStr = ''; @@ -119,16 +179,16 @@ public function getChildrenChecklist(){ $retArr = Array(); $targetStr = $this->clid; do{ - $sql = 'SELECT c.clid, c.name, child.clid as pclid '. - 'FROM fmchklstchildren child INNER JOIN fmchecklists c ON child.clidchild = c.clid '. - 'WHERE child.clid IN('.trim($targetStr,',').') '. - 'ORDER BY c.name '; + $sql = 'SELECT c.clid, c.name, child.clid as pclid + FROM fmchklstchildren child INNER JOIN fmchecklists c ON child.clidchild = c.clid + WHERE child.clid IN(' . trim($targetStr, ',') . ') AND child.clid != child.clidchild + ORDER BY c.name '; $rs = $this->conn->query($sql); $targetStr = ''; while($r = $rs->fetch_object()){ $retArr[$r->clid]['name'] = $r->name; $retArr[$r->clid]['pclid'] = $r->pclid; - $targetStr .= ','.$r->clid; + $targetStr .= ',' . $r->clid; } $rs->free(); }while($targetStr); @@ -140,16 +200,16 @@ public function getParentChecklists(){ $retArr = Array(); $targetStr = $this->clid; do{ - $sql = 'SELECT c.clid, c.name, child.clid as pclid '. - 'FROM fmchklstchildren child INNER JOIN fmchecklists c ON child.clid = c.clid '. - 'WHERE child.clidchild IN('.trim($targetStr,',').') '; + $sql = 'SELECT c.clid, c.name + FROM fmchklstchildren child INNER JOIN fmchecklists c ON child.clid = c.clid + WHERE child.clidchild IN(' . trim($targetStr, ',') . ') AND child.clid != child.clidchild'; $rs = $this->conn->query($sql); $targetStr = ''; while($r = $rs->fetch_object()){ $retArr[$r->clid] = $r->name; $targetStr .= ','.$r->clid; } - if($targetStr) $targetStr = substr($targetStr,1); + if($targetStr) $targetStr = substr($targetStr, 1); $rs->free(); }while($targetStr); asort($retArr); @@ -207,25 +267,34 @@ public function deleteChildChecklist($clidDel){ } //Point functions - public function addPoint($tid,$lat,$lng,$notes){ - $status = ''; + public function addPoint($tid, $lat, $lng, $notes){ + $status = false; if(is_numeric($tid) && is_numeric($lat) && is_numeric($lng)){ - $sql = 'INSERT INTO fmchklstcoordinates(clid,tid,decimallatitude,decimallongitude,notes) VALUES('.$this->clid.','.$tid.','.$lat.','.$lng.',"'.$this->cleanInStr($notes).'")'; - if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR: unable to add point. '.$this->conn->error; + $inputArr = array('tid' => $tid, 'decimalLatitude' => $lat, 'decimalLongitude' => $lng, 'notes' => $notes); + $inventoryManager = new ImInventories(); + $inventoryManager->setClid($this->clid); + if($inventoryManager->insertChecklistCoordinates($inputArr)){ + $status = true; + } + else{ + $this->errorMessage = $inventoryManager->getErrorMessage(); } } return $status; } public function removePoint($pointPK){ - $statusStr = ''; - if($pointPK && is_numeric($pointPK)){ - if(!$this->conn->query('DELETE FROM fmchklstcoordinates WHERE (chklstcoordid = '.$pointPK.')')){ - $statusStr = 'ERROR: unable to remove point. '.$this->conn->error; + $status = false; + if(is_numeric($pointPK)){ + $inventoryManager = new ImInventories(); + if($inventoryManager->deleteChecklistCoordinates($pointPK)){ + $status = true; + } + else{ + $this->errorMessage = $inventoryManager->getErrorMessage(); } } - return $statusStr; + return $status; } //Editor management @@ -454,8 +523,9 @@ public function parseChecklist($tidNode, $taxa, $targetClid, $parentClid, $targe if($copyAttributes) $clManagerArr = $inventoryManager->getManagers('ClAdmin', 'fmchecklists', $this->clid); if(!array_key_exists($GLOBALS['SYMB_UID'], $clManagerArr)) $clManagerArr[$GLOBALS['SYMB_UID']] = ''; if(!$targetClid){ - $fieldArr['name'] = $clMeta['name'].' new sub-checklist - '.$taxa; - $targetClid = $inventoryManager->insertChecklist($fieldArr); + $fieldArr['name'] = 'Copy ' . $clMeta['name'] . ' - ' . $taxa; + $inventoryManager->insertChecklist($fieldArr); + $targetClid = $inventoryManager->getPrimaryKey(); if($targetClid && $copyAttributes){ foreach($clManagerArr as $managerUid => $managerArr){ $inventoryManager->insertUserRole($managerUid, 'ClAdmin', 'fmchecklists', $targetClid, $GLOBALS['SYMB_UID']); @@ -467,7 +537,7 @@ public function parseChecklist($tidNode, $taxa, $targetClid, $parentClid, $targe $statusArr['targetClid'] = $targetClid; if($targetPid === '0'){ $projectFieldArr = array( - 'projname' => $clMeta['name'].' project', + 'projname' => $clMeta['name'] . ' project', 'managers' => $clMeta['authors'], 'ispublic' => ($clMeta['access'] == 'private'?0:1)); $targetPid = $inventoryManager->insertProject($projectFieldArr); @@ -483,8 +553,9 @@ public function parseChecklist($tidNode, $taxa, $targetClid, $parentClid, $targe $statusArr['targetPid'] = $targetPid; } if($parentClid === '0'){ - $fieldArr['name'] = $clMeta['name'].' parent checklist'; - $parentClid = $inventoryManager->insertChecklist($fieldArr); + $fieldArr['name'] = 'Parent: ' . $clMeta['name']; + $inventoryManager->insertChecklist($fieldArr); + $parentClid = $inventoryManager->getPrimaryKey(); if($parentClid && $copyAttributes){ foreach($clManagerArr as $managerUid => $managerArr){ $inventoryManager->insertUserRole($managerUid, 'ClAdmin', 'fmchecklists', $parentClid, $GLOBALS['SYMB_UID']); @@ -541,5 +612,23 @@ public function setClid($clid){ public function getClName(){ return $this->clName; } + + //Misc support functions + public function cleanOutArray($inputArray){ + $skip = array('defaultsettings', 'dynamicsql', 'footprintwkt'); + if(is_array($inputArray)){ + foreach($inputArray as $key => $value){ + if(is_array($value)){ + $inputArray[$key] = $this->cleanOutArray($value); + } + else{ + if(!in_array($key, $skip)){ + $inputArray[$key] = $this->cleanOutStr($value); + } + } + } + } + return $inputArray; + } } -?> \ No newline at end of file +?> diff --git a/classes/ChecklistLoaderManager.php b/classes/ChecklistLoaderManager.php index a20676f8fb..cb602c1b74 100644 --- a/classes/ChecklistLoaderManager.php +++ b/classes/ChecklistLoaderManager.php @@ -20,8 +20,7 @@ public function __destruct(){ public function uploadCsvList($thesId){ set_time_limit(300); - ini_set("max_input_time",300); - ini_set('auto_detect_line_endings', true); + ini_set('max_input_time',300); $successCnt = 0; $fh = fopen($_FILES['uploadfile']['tmp_name'],'r') or die("Can't open file. File may be too large. Try uploading file in sections."); @@ -148,7 +147,7 @@ public function uploadCsvList($thesId){ public function resolveProblemTaxa(){ if($this->problemTaxa){ //$taxHarvester = new TaxonomyHarvester(); - echo '
    +
    : - +
    : - +
    : - +
    :
    -
    +
    :
    @@ -155,43 +189,43 @@
    : - +
    +
    : - + - +
    : - +
    : - +
    : - +
    onclick="coordInputSelected(this)" /> - +
    onclick="coordInputSelected(this)" /> - - + +
    /> - +
    - + @@ -214,10 +250,12 @@ ?>
    -
    +
    - - + +
    @@ -232,39 +270,41 @@ ?>
    .
      -
    • +
    • vouchersExist(); if($vouchersExist){ ?> -
    • +
    • - - + +
    • -
    • -
    • +
    • +
    -
      -
    • -
    • +
        +
      • +
    @@ -274,10 +314,10 @@ } else{ if(!$clid){ - echo '
    Error:'.$LANG['CHECKIDNOTSET'].'
    '; + echo '
    ' . $LANG['ERROR'] . ':' . $LANG['CHECKIDNOTSET'] . '
    '; } else{ - echo '
    Error:'.$LANG['NOADMINPERM'].'
    '; + echo '
    ' . $LANG['ERROR'] . ':' . $LANG['NOADMINPERM'] . '
    '; } } ?> @@ -286,4 +326,4 @@ include($SERVER_ROOT.'/includes/footer.php'); ?> - \ No newline at end of file + diff --git a/checklists/voucherreporthandler.php b/checklists/voucherreporthandler.php index 1a0ad6f5b6..b4d6b07d7e 100644 --- a/checklists/voucherreporthandler.php +++ b/checklists/voucherreporthandler.php @@ -6,11 +6,6 @@ $rType = $_REQUEST['rtype']; if($rType == 'pensoftxlsx'){ - $vManager = null; - if(version_compare(phpversion(), '5.6', '<')) { - echo 'ERROR: Excel export feature requires PHP version 5.6 or greater'; - exit; - } $vManager = new ChecklistVoucherPensoft(); $vManager->setClid($clid); $vManager->setCollectionVariables(); diff --git a/classes/APITaxonomy.php b/classes/APITaxonomy.php index fa30bbd4ed..b89360ef3a 100644 --- a/classes/APITaxonomy.php +++ b/classes/APITaxonomy.php @@ -23,6 +23,7 @@ public function getTaxon($sciname){ while($r = $rs->fetch_object()){ $retArr[$r->tid]['sciname'] = $r->sciname; $retArr[$r->tid]['author'] = $r->author; + $retArr[$r->tid]['tid'] = $r->tid; } $rs->free(); if(count($retArr) > 1){ diff --git a/classes/ActionManager.php b/classes/ActionManager.php index 5552d63206..3a0ca96619 100644 --- a/classes/ActionManager.php +++ b/classes/ActionManager.php @@ -433,7 +433,7 @@ public function getLinkToRow() { if($this->tablename=="omoccurrences") { $occ = new OmOccurrences(); $occ->load($this->fk); - $result = "".$occ->getinstitutionCode().":".$occ->getcollectionCode()." ".$occ->getcatalogNumber().""; + $result = "" . htmlspecialchars($occ->getinstitutionCode(), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ":" . htmlspecialchars($occ->getcollectionCode(), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . " " . htmlspecialchars($occ->getcatalogNumber(), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ""; } if($this->tablename=="images") { $im = new ImageDetailManager($this->fk); @@ -449,7 +449,7 @@ public function getLinkToRow() { } $caption .= " " . $imArr['initialtimestamp']; $caption = trim($caption); - $result = "".$caption.""; + $result = "" . htmlspecialchars($caption, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ""; } return $result; } diff --git a/classes/AgentManager.php b/classes/AgentManager.php index 4d0c9f10af..e90ec1b7cf 100644 --- a/classes/AgentManager.php +++ b/classes/AgentManager.php @@ -1651,9 +1651,9 @@ function getBadDuplicatesForAgent($agentid) { $result .= "
  • Has bad duplicates

  • "; } $result .= ''; return $result; } @@ -2785,7 +2785,7 @@ public function getDetailsView($includeRelated=true, $editLinkURL='') { $returnvalue .= "
  • ".Agent::TAXONOMICGROUPS.": ".$model->gettaxonomicgroups()."
  • \n"; $returnvalue .= "
  • ".Agent::COLLECTIONSAT.": ".$model->getcollectionsat()."
  • \n"; $returnvalue .= "
  • ".Agent::MBOX_SHA1SUM.": ".$model->getmbox_sha1sum()."
  • \n"; - $returnvalue .= "
  • ".Agent::UUID.": ".$model->getuuid()."
  • \n"; + $returnvalue .= "
  • ".Agent::UUID.": " . htmlspecialchars($model->getuuid(), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . "
  • \n"; $returnvalue .= "
  • ".Agent::DATELASTMODIFIED.": ".$model->getdatelastmodified()."
  • \n"; $returnvalue .= "
  • ".Agent::LASTMODIFIEDBYUID.": ".$model->getlastmodifiedbyuid()."
  • \n"; $returnvalue .= "
    "; @@ -3029,7 +3029,7 @@ public function getShortTableRowView() { $returnvalue = '
    ".$model->getMinimalName()."" . htmlspecialchars($model->getMinimalName(), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . "$dates".$model->gettype()."
    '; + echo '
    '; echo ''; $cnt = 1; foreach($this->problemTaxa as $nameStr){ diff --git a/classes/ChecklistManager.php b/classes/ChecklistManager.php index 2b49cf4a66..5a95011ef4 100644 --- a/classes/ChecklistManager.php +++ b/classes/ChecklistManager.php @@ -1,5 +1,6 @@ clid){ $sql = 'SELECT c.clid, c.name, c.locality, c.publication, c.abstract, c.authors, c.parentclid, c.notes, '. 'c.latcentroid, c.longcentroid, c.pointradiusmeters, c.footprintwkt, c.access, c.defaultSettings, '. - 'c.dynamicsql, c.datelastmodified, c.uid, c.type, c.initialtimestamp '. + 'c.dynamicsql, c.datelastmodified, c.dynamicProperties, c.uid, c.type, c.initialtimestamp '. 'FROM fmchecklists c WHERE (c.clid = '.$this->clid.')'; $result = $this->conn->query($sql); if($result){ @@ -101,6 +102,7 @@ private function setMetaData(){ $this->clMetadata["defaultSettings"] = $row->defaultSettings; $this->clMetadata["dynamicsql"] = $row->dynamicsql; $this->clMetadata["datelastmodified"] = $row->datelastmodified; + $this->clMetadata['dynamicProperties'] = $row->dynamicProperties; } $result->free(); } @@ -146,10 +148,21 @@ public function getClMetaData(){ return $this->clMetadata; } + public function getAssociatedExternalService(){ + $resp = false; + if($this->clMetadata['dynamicProperties']){ + $dynpropArr = json_decode($this->clMetadata['dynamicProperties'], true); + if(array_key_exists('externalservice', $dynpropArr)) { + $resp = $dynpropArr['externalservice']; + } + } + return $resp; + } + public function getParentChecklist(){ $parentArr = array(); if($this->clid){ - $sql = 'SELECT cl.clid, cl.name FROM fmchecklists cl INNER JOIN fmchklstchildren ch ON cl.clid = ch.clid WHERE ch.clidchild = '.$this->clid; + $sql = 'SELECT cl.clid, cl.name FROM fmchecklists cl INNER JOIN fmchklstchildren ch ON cl.clid = ch.clid WHERE ch.clid != ch.clidchild AND ch.clidchild = ' . $this->clid; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $parentArr[$r->clid] = $r->name; @@ -200,7 +213,7 @@ public function getTaxaList($pageNumber = 1,$retLimit = 500){ if(!$this->basicSql) $this->setClSql(); $result = $this->conn->query($this->basicSql); while($row = $result->fetch_object()){ - $family = strtoupper($row->family); + $family = strtoupper($row->family ?? ''); if($row->rankid > 140 && !$family) $family = 'Incertae Sedis'; $this->filterArr[$family] = ''; $taxonGroup = $family; @@ -296,7 +309,10 @@ public function getTaxaList($pageNumber = 1,$retLimit = 500){ //echo $vSql; exit; $vResult = $this->conn->query($vSql); while ($row = $vResult->fetch_object()){ - $displayStr = ($row->recordedby?$row->recordedby:($row->catalognumber?$row->catalognumber:$row->othercatalognumbers)); + $displayStr = ''; + if($row->recordedby) $displayStr = $row->recordedby; + elseif($row->catalognumber) $displayStr = $row->catalognumber; + elseif($row->othercatalognumbers) $displayStr = $row->othercatalognumbers; if(strlen($displayStr) > 25){ //Collector string is too big, thus reduce $strPos = strpos($displayStr,';'); @@ -397,7 +413,6 @@ private function setVernaculars(){ 'WHERE ts1.taxauthid = 1 AND ts2.taxauthid = 1 AND (ts1.tid IN('.implode(',',array_keys($this->taxaList)).')) '; if($this->langId) $sql .= 'AND v.langid = '.$this->langId.' '; $sql .= 'ORDER BY v.sortsequence DESC '; - //echo $sql; exit; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ if($r->vernacularname) $this->taxaList[$r->tid]['vern'] = $this->cleanOutStr($r->vernacularname); @@ -415,7 +430,6 @@ private function setSynonyms(){ 'WHERE (ts.taxauthid = '.($this->thesFilter?$this->thesFilter:'1').') AND (ts2.taxauthid = '.($this->thesFilter?$this->thesFilter:'1').') '. 'AND (ts.tid IN('.implode(',',array_keys($this->taxaList)).')) AND (ts.tid != ts2.tid) '. 'ORDER BY t.sciname'; - //echo $sql; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $tempArr[$r->tid][] = ''.$r->sciname.''.($this->showAuthors && $r->author?' '.$r->author:''); @@ -441,6 +455,42 @@ private function setSubgenera(){ } } + public function getExternalVoucherArr(){ + $externalVoucherArr = array(); + if($this->taxaList){ + $clidStr = $this->clid; + if($this->childClidArr){ + $clidStr .= ','.implode(',',array_keys($this->childClidArr)); + } + $sql = 'SELECT clCoordID, clid, tid, sourceIdentifier, referenceUrl, dynamicProperties + FROM fmchklstcoordinates + WHERE (clid IN ('.$clidStr.')) AND (tid IN('.implode(',',array_keys($this->taxaList)).')) AND sourceName = "EXTERNAL_VOUCHER"'; + $rs = $this->conn->query($sql); + while ($r = $rs->fetch_object()){ + $dynPropArr = json_decode($r->dynamicProperties); + foreach($dynPropArr as $vouch) { + $displayStr = ''; + if(!empty($vouch->user)) $displayStr = $vouch->user; + if(strlen($displayStr) > 25){ + //Collector string is too big, thus reduce + $strPos = strpos($displayStr,';'); + if(!$strPos) $strPos = strpos($displayStr,','); + if(!$strPos) $strPos = strpos($displayStr,' ',10); + if($strPos) $displayStr = substr($displayStr,0,$strPos).'...'; + } + if($vouch->date) $displayStr .= ' '.$vouch->date; + if(!trim($displayStr)) $displayStr = 'undefined voucher'; + $displayStr .= ' ['.$vouch->repository.($vouch->id?'-'.$vouch->id:'').']'; + $externalVoucherArr[$r->tid][$r->clCoordID]['display'] = trim($displayStr); + $url = 'https://www.inaturalist.org/observations/'.$r->sourceIdentifier; + $externalVoucherArr[$r->tid][$r->clCoordID]['url'] = $url; + } + } + $rs->free(); + } + return $externalVoucherArr; + } + public function getVoucherCoordinates($limit=0){ $retArr = array(); if(!$this->basicSql) $this->setClSql(); @@ -455,7 +505,6 @@ public function getVoucherCoordinates($limit=0){ 'FROM fmchklstcoordinates cc INNER JOIN ('.$this->basicSql.') t ON cc.tid = t.tid '. 'WHERE cc.clid IN ('.$clidStr.') AND cc.decimallatitude BETWEEN -90 AND 90 AND cc.decimallongitude BETWEEN -180 AND 180 '; if($limit) $sql1 .= 'ORDER BY RAND() LIMIT '.$limit; - //echo $sql1; $rs1 = $this->conn->query($sql1); if($rs1){ while($r1 = $rs1->fetch_object()){ @@ -480,7 +529,6 @@ public function getVoucherCoordinates($limit=0){ WHERE cl.clid IN ('.$clidStr.') AND o.decimallatitude IS NOT NULL AND o.decimallongitude IS NOT NULL AND (o.localitysecurity = 0 OR o.localitysecurity IS NULL) '; if($limit) $sql2 .= 'ORDER BY RAND() LIMIT '.$limit; - //echo $sql2; $rs2 = $this->conn->query($sql2); if($rs2){ while($r2 = $rs2->fetch_object()){ @@ -508,13 +556,12 @@ public function getPolygonCoordinates(){ $sql .= 'INNER JOIN omoccurpoints p ON o.occid = p.occid WHERE (ST_Within(p.point,GeomFromText("'.$this->clMetadata['footprintwkt'].'"))) '; } else{ - $voucherManager = new ChecklistVoucherAdmin($this->conn); + $voucherManager = new ChecklistVoucherAdmin(); $voucherManager->setClid($this->clid); $voucherManager->setCollectionVariables(); $sql .= 'WHERE ('.$voucherManager->getSqlFrag().') '; } $sql .= 'LIMIT 50'; - //echo $sql; exit; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[] = $r->decimallatitude.','.$r->decimallongitude; @@ -617,57 +664,43 @@ private function setClSql(){ public function addNewSpecies($postArr){ if(!$this->clid) return 'ERROR adding species: checklist identifier not set'; $insertStatus = false; - $dataArr = array('tid','familyoverride','morphospecies','habitat','abundance','notes','source','internalnotes'); - $colSql = ''; - $valueSql = ''; - foreach($dataArr as $v){ - if(isset($postArr[$v]) && $postArr[$v]){ - $colSql .= ','.$v; - if(is_numeric($postArr[$v])) $valueSql .= ','.$postArr[$v]; - else $valueSql .= ',"'.$this->cleanInStr($postArr[$v]).'"'; - } - } - $conn = MySQLiConnectionFactory::getCon('write'); - $sql = 'INSERT INTO fmchklsttaxalink (clid'.$colSql.') VALUES ('.$this->clid.$valueSql.')'; - if($conn->query($sql)){ - if($this->clMetadata['type'] == 'rarespp' && $this->clMetadata['locality'] && is_numeric($postArr['tid'])){ - $sqlRare = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid '. - 'INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '. - 'SET o.localitysecurity = 1 '. - 'WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (o.localitySecurityReason IS NULL) '. - 'AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) AND (o.stateprovince = "'.$this->clMetadata['locality'].'") AND (ts2.tid = '.$postArr['tid'].')'; - //echo $sqlRare; exit; - $conn->query($sqlRare); - } - } - else{ - $mysqlErr = $conn->error; + $inventoryManager = new ImInventories(); + if(!$inventoryManager->insertChecklistTaxaLink($postArr)){ $insertStatus = 'ERROR adding species: '; - if(strpos($mysqlErr,'Duplicate') !== false){ - $insertStatus .= 'Species already exists within checklist'; + if($inventoryManager->getErrorMessage()){ + if(strpos($inventoryManager->getErrorMessage(), 'Duplicate') !== false){ + $insertStatus .= 'Species already exists within checklist'; + } + else{ + $insertStatus .= $inventoryManager->getErrorMessage(); + } } else{ - $insertStatus .= $conn->error; + $insertStatus .= 'unknown error'; } } - $conn->close(); return $insertStatus; } - //Checklist index page fucntions + //Checklist index page functions public function getChecklists($limitToKey=false){ $retArr = Array(); - $sql = 'SELECT p.pid, p.projname, p.ispublic, c.clid, c.name, c.access, c.defaultSettings, COUNT(l.tid) AS sppcnt + $sql = 'SELECT p.pid, p.projname, p.ispublic, c.clid, c.name, c.access, c.defaultSettings, c.latCentroid FROM fmchecklists c LEFT JOIN fmchklstprojlink cpl ON c.clid = cpl.clid - INNER JOIN fmchklsttaxalink l ON c.clid = l.clid LEFT JOIN fmprojects p ON cpl.pid = p.pid - WHERE ((c.access LIKE "public%") '; - if(isset($GLOBALS['USER_RIGHTS']['ClAdmin']) && $GLOBALS['USER_RIGHTS']['ClAdmin']) $sql .= 'OR (c.clid IN('.implode(',',$GLOBALS['USER_RIGHTS']['ClAdmin']).'))'; + WHERE c.type != "excludespp" AND ((c.access LIKE "public%") '; + if(isset($GLOBALS['USER_RIGHTS']['ClAdmin']) && $GLOBALS['USER_RIGHTS']['ClAdmin']){ + $sql .= 'OR (c.clid IN('.implode(',',$GLOBALS['USER_RIGHTS']['ClAdmin']).'))'; + } $sql .= ') AND ((p.pid IS NULL) OR (p.ispublic = 1) '; - if(isset($GLOBALS['USER_RIGHTS']['ProjAdmin']) && $GLOBALS['USER_RIGHTS']['ProjAdmin']) $sql .= 'OR (p.pid IN('.implode(',',$GLOBALS['USER_RIGHTS']['ProjAdmin']).'))'; + if(isset($GLOBALS['USER_RIGHTS']['ProjAdmin']) && $GLOBALS['USER_RIGHTS']['ProjAdmin']){ + $sql .= 'OR (p.pid IN('.implode(',',$GLOBALS['USER_RIGHTS']['ProjAdmin']).'))'; + } $sql .= ') '; if($this->pid) $sql .= 'AND (p.pid = '.$this->pid.') '; - $sql .= 'GROUP BY p.projname, c.Name HAVING sppcnt > 10'; + //Following line limits result to only checklists that have a linked taxon or is a parent checklist with possible inherited taxa + $sql .= 'AND c.clid IN(SELECT clid FROM fmchklsttaxalink UNION DISTINCT SELECT clid FROM fmchklstchildren) '; + $sql .= 'ORDER BY p.projname, c.name'; $rs = $this->conn->query($sql); while($row = $rs->fetch_object()){ if($limitToKey){ @@ -681,6 +714,7 @@ public function getChecklists($limitToKey=false){ $pid = 0; $projName = 'Miscellaneous Inventories'; } + if($row->latCentroid) $retArr[$pid]['displayMap'] = 1; $retArr[$pid]['name'] = $this->cleanOutStr($projName); $retArr[$pid]['clid'][$row->clid] = $this->cleanOutStr($row->name).($row->access=='private'?' (Private)':''); } @@ -746,11 +780,14 @@ public function getSpeciesSearch($term){ $term = preg_replace('/[^a-zA-Z\-\. ]+/', '', $term); $term = preg_replace('/\s{1}x{1}\s{0,1}$/i', ' _ ', $term); $term = preg_replace('/\s{1}[\D]{1}\s{1}/i', ' _ ', $term); + if($term){ - $sql = 'SELECT tid, sciname FROM taxa WHERE (rankid > 179) AND (sciname LIKE "'.$term.'%")'; + + $sql = 'SELECT tid, sciname, author FROM taxa WHERE (rankid > 179) AND (sciname LIKE "'.$term.'%")'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[] = $r->sciname; + $retArr[] = (object) [ 'value' => $r->sciname . ' ' . $r->author, + 'id' => $r->tid]; } $rs->free(); } @@ -903,7 +940,7 @@ public function getSpeciesCount(){ //Misc functions public function cleanOutText($str){ //Need to clean for MS Word ouput: strip html tags, convert all html entities and then reset as html tags - $str = strip_tags($str); + $str = strip_tags($str ?? ""); $str = html_entity_decode($str); $str = htmlspecialchars($str); return $str; diff --git a/classes/ChecklistVoucherAdmin.php b/classes/ChecklistVoucherAdmin.php index 2b498feaad..583fc20454 100644 --- a/classes/ChecklistVoucherAdmin.php +++ b/classes/ChecklistVoucherAdmin.php @@ -1,5 +1,6 @@ clid){ $sql = 'SELECT clid, name, locality, publication, abstract, authors, parentclid, notes, latcentroid, longcentroid, pointradiusmeters, '. - 'footprintwkt, access, defaultSettings, dynamicsql, datelastmodified, uid, type, initialtimestamp '. + 'footprintwkt, access, defaultSettings, dynamicsql, datelastmodified, dynamicProperties, uid, type, initialtimestamp '. 'FROM fmchecklists WHERE (clid = '.$this->clid.')'; $rs = $this->conn->query($sql); if($rs){ @@ -66,6 +67,7 @@ private function setMetaData(){ $this->clMetadata["defaultSettings"] = $row->defaultSettings; $this->clMetadata["dynamicsql"] = $row->dynamicsql; $this->clMetadata["datelastmodified"] = $row->datelastmodified; + $this->clMetadata['dynamicProperties'] = $row->dynamicProperties; } $rs->free(); } @@ -108,6 +110,17 @@ public function getPolygonCoordinates(){ return $retArr; } + public function getAssociatedExternalService(){ + $resp = false; + if($this->clMetadata['dynamicProperties']){ + $dynpropArr = json_decode($this->clMetadata['dynamicProperties'], true); + if(array_key_exists('externalservice', $dynpropArr)) { + $resp = $dynpropArr['externalservice']; + } + } + return $resp; + } + //Dynamic query variable functions public function setCollectionVariables(){ if($this->clid){ @@ -116,8 +129,8 @@ public function setCollectionVariables(){ if($row = $result->fetch_object()){ $this->clName = $this->cleanOutStr($row->name); $this->footprintWkt = $row->footprintwkt; - $sqlFrag = $row->dynamicsql; - $varArr = json_decode($sqlFrag,true); + $sqlFrag = $row->dynamicsql ?? ''; + $varArr = json_decode($sqlFrag, true); if(json_last_error() != JSON_ERROR_NONE){ $varArr = $this->parseSqlFrag($sqlFrag); $this->saveQueryVariables($varArr); @@ -129,7 +142,7 @@ public function setCollectionVariables(){ } $result->free(); //Get children checklists - $sqlChildBase = 'SELECT clidchild FROM fmchklstchildren WHERE clid IN('; + $sqlChildBase = 'SELECT clidchild FROM fmchklstchildren WHERE clid != clidchild AND clid IN('; $sqlChild = $sqlChildBase.$this->clid.')'; do{ $childStr = ""; @@ -417,9 +430,9 @@ public function batchTransferConflicts($occidArr, $removeTaxa){ $tidTarget = $this->getTidInterpreted($occid); if($oldClTaxaID && $tidTarget){ //Make sure target name is already linked to checklist - $sql2 = 'INSERT IGNORE INTO fmchklsttaxalink(tid, clid, morphospecies, familyoverride, habitat, abundance, notes, explicitExclude, source, internalnotes, dynamicProperties) '. - 'SELECT '.$tidTarget.' as tid, c.clid, c.morphospecies, c.familyoverride, c.habitat, c.abundance, c.notes, c.explicitExclude, c.source, c.internalnotes, c.dynamicProperties '. - 'FROM fmchklsttaxalink WHERE (cltaxaid = ?)'; + $sql2 = 'INSERT IGNORE INTO fmchklsttaxalink(tid, clid, morphospecies, familyoverride, habitat, abundance, notes, explicitExclude, source, internalnotes, dynamicProperties) + SELECT '.$tidTarget.' as tid, clid, morphospecies, familyoverride, habitat, abundance, notes, explicitExclude, source, internalnotes, dynamicProperties + FROM fmchklsttaxalink WHERE (cltaxaid = ?)'; if($stmt2 = $this->conn->prepare($sql2)) { $stmt2->bind_param('i', $oldClTaxaID); $stmt2->execute(); @@ -449,57 +462,64 @@ public function batchTransferConflicts($occidArr, $removeTaxa){ } } - //Data mod functions - protected function insertChecklistTaxaLink($tid, $clid = null, $morpho = ''){ + //Checklist Coordinate functions + public function addExternalVouchers($tid, $dataAsJson){ + // EG suggested storing external (e.g., iNaturalist) voucher records in the `fmchklstcoordinates` table as this table + // was un- or under-used as of schema 3.0. The `notes` column serves as a flag for these vouchers. --CDT 2023-08-21 $status = false; - if(!$clid) $clid = $this->clid; - if(is_numeric($tid) && is_numeric($clid)){ - $sql = 'INSERT INTO fmchklsttaxalink(tid,clid,morphospecies) VALUES(?,?,?)'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('iis', $tid, $clid, $morpho); - $stmt->execute(); - if($stmt->affected_rows && !$stmt->error){ - $status = $stmt->insert_id; - } - elseif($stmt->error) $this->errorMessage = 'ERROR inserting checklist-taxa-link: '.$stmt->error; - $stmt->close(); + $inputData = json_decode($dataAsJson, true); + // for single vouchers, add ll, for multiple use zero :(. + // we could try averaging ll for multiples, but then the software would be introducing non-real data, which is bad. + // not that zero/zero is real data either... CDT 8/2023 + $lat = (count($inputData) == 1 ? $inputData[0]['lat'] : 0); + $lng = (count($inputData) == 1 ? $inputData[0]['lng'] : 0); + $sourceIdentifier = $inputData[0]['id']; + $referenceUrl = null; + if($sourceIdentifier) $referenceUrl = 'https://www.inaturalist.org/observations/'.$sourceIdentifier; + if(is_numeric($tid) && $lat && $lng){ + unset($inputData[0]['lat']); + unset($inputData[0]['lng']); + unset($inputData[0]['taxon']); + $inputArr = array('tid' => $tid, 'decimalLatitude' => $lat, 'decimalLongitude' => $lng, 'sourceName' => 'EXTERNAL_VOUCHER', + 'sourceIdentifier' => $sourceIdentifier, 'referenceUrl' => $referenceUrl, 'dynamicProperties' => json_encode($inputData)); + $inventoryManager = new ImInventories(); + $inventoryManager->setClid($this->clid); + if($inventoryManager->insertChecklistCoordinates($inputArr)){ + $status = true; + } + else{ + $errStr = $inventoryManager->getErrorMessage(); + if(strpos($errStr, 'Duplicate') !== false) $errStr = 'Voucher already linked!'; + $this->errorMessage = $errStr; } - else $this->errorMessage = 'ERROR preparing statement for checklist-taxa-link insert: '.$this->conn->error; } return $status; } - protected function insertVoucher($clTaxaID, $occid, $editorNotes = null, $notes = null){ + //Data mod functions + protected function insertChecklistTaxaLink($tid, $clid = null, $morpho = ''){ $status = false; - if(is_numeric($clTaxaID) && is_numeric($occid)){ - if($editorNotes == '') $editorNotes = null; - if($notes == '') $notes = null; - $sql = 'INSERT INTO fmvouchers(clTaxaID, occid, editorNotes, notes) VALUES (?,?,?,?)'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('iiss', $clTaxaID, $occid, $editorNotes, $notes); - $stmt->execute(); - if($stmt->affected_rows){ - $status = $stmt->insert_id; - } - elseif($stmt->error) $this->errorMessage = 'ERROR inserting voucher: '.$stmt->error; - $stmt->close(); - } - else $this->errorMessage = 'ERROR preparing statement for voucher insert: '.$this->conn->error; + if(!$clid) $clid = $this->clid; + if(is_numeric($tid) && is_numeric($clid)){ + $inventoryManager = new ImInventories(); + $inputArr = array('tid' => $tid, 'clid' => $clid); + if($morpho) $inputArr['morphoSpecies'] = $morpho; + $status = $inventoryManager->insertChecklistTaxaLink($inputArr); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); } return $status; } - private function transferVouchers($target, $source){ + protected function insertVoucher($clTaxaID, $occid, $editorNotes = null, $notes = null){ $status = false; - if(is_numeric($target) && is_numeric($source)){ - $sql = 'UPDATE fmvouchers SET clTaxaID = ? WHERE clTaxaID = ?'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('ii', $target, $source); - $stmt->execute(); - if($stmt->error) $this->errorMessage = 'ERROR transferring vouchers: '.$stmt->error; - $stmt->close(); - } - else $this->errorMessage = 'ERROR preparing statement for voucher transfer: '.$this->conn->error; + if(is_numeric($clTaxaID) && is_numeric($occid)){ + $inventoryManager = new ImInventories(); + $inventoryManager->setClTaxaID($clTaxaID); + $inputArr = array('occid' => $occid); + if($editorNotes) $inputArr['editorNotes'] = $editorNotes; + if($notes) $inputArr['notes'] = $notes; + $status = $inventoryManager->insertChecklistVoucher($inputArr); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); } return $status; } @@ -507,15 +527,10 @@ private function transferVouchers($target, $source){ public function deleteVoucher($voucherID){ $status = false; if(is_numeric($voucherID)){ - $sql = 'DELETE FROM fmvouchers WHERE (voucherID = ?)'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('i', $voucherID); - $stmt->execute(); - if($stmt->affected_rows) $status = true; - elseif($stmt->error) $this->errorMessage = 'ERROR deleting vouchers: '.$stmt->error; - $stmt->close(); - } - else $this->errorMessage = 'ERROR preparing statement for voucher deletion: '.$this->conn->error; + $inventoryManager = new ImInventories(); + $inventoryManager->setVoucherID($voucherID); + $status = $inventoryManager->deleteChecklistVoucher(); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); } return $status; } @@ -774,17 +789,14 @@ protected function encodeStr($inStr){ $charSetOut = 'ISO-8859-1'; $retStr = $inStr; if($inStr && $charSetSource){ - if($charSetOut == 'UTF-8' && $charSetSource == 'ISO-8859-1'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == 'ISO-8859-1'){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } + if($charSetOut == 'UTF-8'){ + $retStr = mb_convert_encoding($inStr, 'UTF-8', mb_detect_encoding($inStr)); } - elseif($charSetOut == "ISO-8859-1" && $charSetSource == 'UTF-8'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == 'UTF-8'){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } + elseif($charSetOut == 'ISO-8859-1'){ + $retStr = mb_convert_encoding($inStr, 'ISO-8859-1', mb_detect_encoding($inStr)); + } + else{ + $retStr = mb_convert_encoding($inStr, $charSetOut, mb_detect_encoding($inStr)); } } return $retStr; diff --git a/classes/ChecklistVoucherManager.php b/classes/ChecklistVoucherManager.php index 695ea8bb2e..bae7b2513e 100644 --- a/classes/ChecklistVoucherManager.php +++ b/classes/ChecklistVoucherManager.php @@ -1,7 +1,7 @@ $v){ - $valStr = trim($v); - $innerSql .= ",".$k."=".($valStr?'"'.$this->cleanInStr($valStr).'" ':'NULL'); - } - $sqlClUpdate = 'UPDATE fmchklsttaxalink SET '.substr($innerSql,1).' WHERE (tid = '.$this->tid.') AND (clid = '.$this->clid.')'; - if(!$this->conn->query($sqlClUpdate)){ - $retStr = "ERROR editing details: ".$this->conn->error."
    SQL: ".$sqlClUpdate.";
    "; - } - return $retStr; + public function editClData($postArr){ + $status = false; + $inventoryManager = new ImInventories(); + $clTaxaID = $this->getClTaxaID($this->tid); + $inventoryManager->setClTaxaID($clTaxaID); + $status = $inventoryManager->updateChecklistTaxaLink($postArr); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); + return $status; } - public function renameTaxon($targetTid, $rareLocality = ''){ + public function remapTaxon($targetTid, $rareLocality = ''){ $statusStr = false; if(is_numeric($targetTid)){ + $inventoryManager = new ImInventories(); $clTaxaID = $this->getClTaxaID($this->tid); - $sql = 'UPDATE fmchklsttaxalink SET TID = '.$targetTid.' WHERE (clTaxaID = '.$clTaxaID.')'; - if($this->conn->query($sql)){ + $inventoryManager->setClTaxaID($clTaxaID); + //First transfer taxa that + $inputArr = array('tid' => $targetTid); + if($inventoryManager->updateChecklistTaxaLink($inputArr)){ $this->tid = $targetTid; $this->taxonName = ''; $statusStr = true; } else{ - $sqlTarget = 'SELECT clTaxaID, Habitat, Abundance, Notes, internalnotes, source, Nativity FROM fmchklsttaxalink WHERE (tid = '.$targetTid.') AND (clid = '.$this->clid.')'; - $rsTarget = $this->conn->query($sqlTarget); - if($row = $rsTarget->fetch_object()){ - $clTaxaIDTarget = $row->clTaxaID; - $habitatTarget = $this->cleanInStr($row->Habitat); - $abundTarget = $this->cleanInStr($row->Abundance); - $notesTarget = $this->cleanInStr($row->Notes); - $internalNotesTarget = $this->cleanInStr($row->internalnotes); - $sourceTarget = $this->cleanInStr($row->source); - $nativeTarget = $this->cleanInStr($row->Nativity); - - //Move all vouchers to new name - $sqlVouch = 'UPDATE IGNORE fmvouchers SET clTaxaID = '.$clTaxaIDTarget.' WHERE (clTaxaID = '.$clTaxaID.')'; - - if(!$this->conn->query($sqlVouch)){ - $this->errorMessage = 'ERROR transferring vouchers during taxon transfer: '.$this->conn->error; - } - //Delete all Vouchers that didn't transfer because they were already linked to target name - $sqlVouchDel = 'DELETE FROM fmvouchers WHERE (clTaxaID = '.$clTaxaID.')'; - if(!$this->conn->query($sqlVouchDel)){ - $this->errorMessage = "ERROR removing vouchers during taxon transfer: ".$this->conn->error; + if(!$inventoryManager->getErrorMessage()){ + //Transferred failed due to target name already exiting within checklist + $targetArr = array(); + $sqlTarget = 'SELECT clTaxaID, habitat, abundance, notes, internalNotes, source, nativity FROM fmchklsttaxalink WHERE (tid = '.$targetTid.') AND (clid = '.$this->clid.')'; + $rsTarget = $this->conn->query($sqlTarget); + if($row = $rsTarget->fetch_assoc()){ + $targetArr = $row; + $clTaxaIDTarget = $row['clTaxaID']; + unset($row['clTaxaID']); + if(!$inventoryManager->updateChecklistVouchersByClTaxaID(array('clTaxaID' => $clTaxaIDTarget))){ + $this->errorMessage = 'ERROR transferring vouchers during taxon transfer: ' . $this->conn->error; + } + //Delete all Vouchers that didn't transfer because they were already linked to target name + if(!$inventoryManager->deleteChecklistVouchersByClTaxaID()){ + $this->errorMessage = 'ERROR removing vouchers during taxon transfer: ' . $this->conn->error; + } } + $rsTarget->free(); //Merge chklsttaxalink data //Harvest source (unwanted) chklsttaxalink data - $sqlSourceCl = 'SELECT Habitat, Abundance, Notes, internalnotes, source, Nativity FROM fmchklsttaxalink WHERE (clTaxaID = '.$clTaxaID.')'; + $sourceArr = array(); + $sqlSourceCl = 'SELECT habitat, abundance, notes, internalNotes, source, nativity FROM fmchklsttaxalink WHERE (clTaxaID = '.$clTaxaID.')'; $rsSourceCl = $this->conn->query($sqlSourceCl); if($row = $rsSourceCl->fetch_object()){ - $habitatSource = $this->cleanInStr($row->Habitat); - $abundSource = $this->cleanInStr($row->Abundance); - $notesSource = $this->cleanInStr($row->Notes); - $internalNotesSource = $this->cleanInStr($row->internalnotes); - $sourceSource = $this->cleanInStr($row->source); - $nativeSource = $this->cleanInStr($row->Nativity); + $sourceArr = $row; } $rsSourceCl->free(); //Transfer source chklsttaxalink data to target record - $habitatStr = $habitatTarget.(($habitatTarget && $habitatSource)?'; ':'').$habitatSource; - $abundStr = $abundTarget.(($abundTarget && $abundSource)?'; ':'').$abundSource; - $notesStr = $notesTarget.(($notesTarget && $notesSource)?'; ':'').$notesSource; - $internalNotesStr = $internalNotesTarget.(($internalNotesTarget && $internalNotesSource)?'; ':'').$internalNotesSource; - $sourceStr = $sourceTarget.(($sourceTarget && $sourceSource)?'; ':'').$sourceSource; - $nativeStr = $nativeTarget.(($nativeTarget && $nativeSource)?'; ':'').$nativeSource; - $sqlCl = 'UPDATE fmchklsttaxalink SET - Habitat = '.($habitatStr ? '"'.$this->cleanInStr($habitatStr).'"':'NULL').', - Abundance = '.($abundStr ? '"'.$this->cleanInStr($abundStr).'"':'NULL').', - Notes = '.($notesStr ? '"'.$this->cleanInStr($notesStr).'"' : 'NULL').', - internalnotes = '.($internalNotesStr ? '"'.$this->cleanInStr($internalNotesStr).'"' : 'NULL').', - source = '. ($sourceStr ? '"'.$this->cleanInStr($sourceStr).'"' : 'NULL').', - Nativity = '. ($nativeStr? '"'.$this->cleanInStr($nativeStr).'"' : 'NULL').' - WHERE (clTaxaID = '.$clTaxaIDTarget.')'; - if($this->conn->query($sqlCl)){ - //Delete unwanted taxon - $sqlDel = 'DELETE FROM fmchklsttaxalink WHERE (clTaxaID = '.$clTaxaID.')'; - if($this->conn->query($sqlDel)){ - $this->tid = $targetTid; - $this->taxonName = ''; - $statusStr = true; - } - else $this->errorMessage = 'ERROR removing taxon during taxon transfer: '.$this->conn->error; + + foreach($sourceArr as $sourceField => $sourceValue){ + $newValue = $targetArr[$sourceField]; + if($newValue && $sourceValue) $newValue .= '; '; + $newValue .= $sourceValue; + $targetArr[$sourceField] = trim($newValue , '; '); } - else $this->errorMessage = 'ERROR updating new taxon during taxon transfer: '.$this->conn->error; + if($inventoryManager->deleteChecklistTaxaLink()){ + $inventoryManager->setClTaxaID($clTaxaIDTarget); + $inventoryManager->updateChecklistTaxaLink($targetArr); + $this->tid = $targetTid; + $this->taxonName = ''; + $statusStr = true; + } + else $this->errorMessage = 'ERROR removing taxon during taxon transfer: '.$this->conn->error; } - $rsTarget->free(); } if($rareLocality){ - $this->removeStateRareStatus($rareLocality); + $inventoryManager->removeStateLocalitySecurityByTid($rareLocality, $this->tid); } } return $statusStr; } public function deleteTaxon($rareLocality = ''){ - $statusStr = ''; + $status = false; + $inventoryManager = new ImInventories(); $clTaxaID = $this->getClTaxaID($this->tid); - - //Delete vouchers - $vSql = 'DELETE FROM fmvouchers WHERE (clTaxaID = '.$clTaxaID.')'; - $this->conn->query($vSql); - //Delete checklist record - $sql = 'DELETE FROM fmchklsttaxalink WHERE (clTaxaID = '.$clTaxaID.')'; - if($this->conn->query($sql)){ - if($rareLocality){ - $this->removeStateRareStatus($rareLocality); - } + $inventoryManager->setClTaxaID($clTaxaID); + //First delete all linked voucehrs + $status = $inventoryManager->deleteChecklistVouchersByClTaxaID(); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); + //Then delete checklist taxa linkage + if($status){ + $status = $inventoryManager->deleteChecklistTaxaLink(); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); } - else $statusStr = 'ERROR deleting taxon from checklist: '.$this->conn->error; - return $statusStr; - } - - private function removeStateRareStatus($rareLocality){ - //Remove state based security protection only if name is not on global list - $sql = 'SELECT IFNULL(securitystatus,0) as securitystatus FROM taxa WHERE tid = '.$this->tid; - //echo $sql; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - if($r->securitystatus == 0){ - //Set occurrence - $sqlRare = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid '. - 'INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '. - 'SET o.localitysecurity = NULL '. - 'WHERE (o.localitysecurity = 1) AND (o.localitySecurityReason IS NULL) AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) '. - 'AND o.stateprovince = "'.$rareLocality.'" AND ts2.tid = '.$this->tid; - //echo $sqlRare; exit; - if(!$this->conn->query($sqlRare)){ - $this->errorMessage = "ERROR resetting locality security during taxon delete: ".$this->conn->error; - } - } + if($rareLocality){ + $inventoryManager->removeStateLocalitySecurityByTid($rareLocality, $this->tid); } - $rs->free(); + return $status; } //Voucher functions @@ -196,6 +155,7 @@ public function getVoucherData(){ $voucherData[$r->voucherID]['editornotes'] = $r->editornotes; } $rs->free(); + $voucherData = $this->cleanOutArray($voucherData); } } return $voucherData; @@ -204,30 +164,11 @@ public function getVoucherData(){ public function editVoucher($voucherID, $notes, $editorNotes){ $status = false; if(is_numeric($voucherID)){ - if(!$notes) $notes = null; - if(!$editorNotes) $editorNotes = null; - $sql = 'UPDATE fmvouchers SET notes = ?, editornotes = ? WHERE (voucherID = ?)'; - if($stmt = $this->conn->prepare($sql)){ - $stmt->bind_param('ssi', $notes, $editorNotes, $voucherID); - $stmt->execute(); - if($stmt->affected_rows) $status = true; - elseif($stmt->error) $this->errorMessage = 'ERROR editing voucher: '.$stmt->error; - $stmt->close(); - } - } - return $status; - } - - public function addVoucher($vOccId, $vNotes, $vEditNotes){ - $status = false; - if(is_numeric($vOccId) && $this->clid){ - $status = $this->addVoucherRecord($vOccId, $vNotes, $vEditNotes); - if(!$status){ - $tid = $this->getTidInterpreted($vOccId); - if($clTaxaID = $this->insertChecklistTaxaLink($tid, $this->clid)){ - $status = $this->insertVoucher($clTaxaID, $vOccId, $vNotes, $vEditNotes); - } - } + $inventoryManager = new ImInventories(); + $inventoryManager->setVoucherID($voucherID); + $inputArr = array('notes' => $notes, 'editorNotes' => $editorNotes); + $status = $inventoryManager->updateChecklistVoucher($inputArr); + if(!$status) $this->errorMessage = $inventoryManager->getErrorMessage(); } return $status; } diff --git a/classes/ChecklistVoucherPensoft.php b/classes/ChecklistVoucherPensoft.php index 0764591628..077b335b2e 100644 --- a/classes/ChecklistVoucherPensoft.php +++ b/classes/ChecklistVoucherPensoft.php @@ -88,6 +88,9 @@ public function downloadPensoftXlsx(){ //$file = $TEMP_DIR_ROOT.'/downloads/'.$this->getExportFileName().'.xlsx'; $file = $this->getExportFileName().'.xlsx'; + ob_start(); + ob_clean(); + ob_end_flush(); header('Content-Description: Checklist Pensoft Export'); header('Content-Disposition: attachment; filename='.basename($file)); header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); diff --git a/classes/ChecklistVoucherReport.php b/classes/ChecklistVoucherReport.php index e5dfd61a18..8f398e330e 100644 --- a/classes/ChecklistVoucherReport.php +++ b/classes/ChecklistVoucherReport.php @@ -52,7 +52,7 @@ public function getNonVoucheredTaxa($startLimit, $limit = 100){ WHERE v.voucherID IS NULL AND (ctl.clid = '.$this->clid.') AND ts.taxauthid = 1 ORDER BY ts.family, t.sciname LIMIT '.($startLimit ? $startLimit.',' : '') . $limit; - //echo '
    '.$sql.'
    '; + //echo '
    nonVoucheredTaxa: '.$sql.'
    '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->family][$r->clTaxaID]['s'] = $this->cleanOutStr($r->sciname); @@ -78,12 +78,11 @@ public function getNewVouchers($startLimit = 500, $includeAll = 1){ $sql .= $this->getTableJoinFrag($sqlFrag); $sql .= 'WHERE ('.$sqlFrag.') AND (cl.clid = '.$this->clid.') AND (ts.taxauthid = 1) AND (ts2.taxauthid = 1) '; if($includeAll == 1){ - $idStr = $this->getVoucherTidStr('tid'); - if($idStr) $sql .= 'AND cl.tid NOT IN('.$idStr.') '; + $sql .= 'AND cl.tid IN(' . $this->getNonVoucherTidStr('tid') . ') '; } elseif($includeAll == 2){ - $idStr = $this->getVoucherOccidStr(); - if($idStr) $sql .= 'AND o.occid NOT IN('.$idStr.') '; + $inStr = $this->getVoucherOccidStr(); + if($inStr) $sql .= 'AND o.occid NOT IN(' . $inStr . ') '; } $sql .= 'ORDER BY ts.family, o.sciname LIMIT '.$startLimit.', 1000'; $rs = $this->conn->query($sql); @@ -105,19 +104,18 @@ public function getNewVouchers($startLimit = 500, $includeAll = 1){ } } elseif($includeAll == 3){ - $sql = 'SELECT DISTINCT cl.clTaxaID, TRIM(CONCAT_WS(" ",t.sciname,cl.morphoSpecies)) AS clsciname, o.occid, '. - 'c.institutioncode, c.collectioncode, o.catalognumber, '. - 'o.tidinterpreted, o.sciname, o.recordedby, o.recordnumber, o.eventdate, '. - 'CONCAT_WS("; ",o.country, o.stateprovince, o.county, o.locality) as locality '. - 'FROM omcollections c INNER JOIN omoccurrences o ON c.collid = o.collid '. - 'LEFT JOIN taxa t ON o.tidinterpreted = t.TID '. - 'LEFT JOIN taxstatus ts ON t.TID = ts.tid '; + $sql = 'SELECT DISTINCT cl.clTaxaID, TRIM(CONCAT_WS(" ",t.sciname,cl.morphoSpecies)) AS clsciname, o.occid, + c.institutioncode, c.collectioncode, o.catalognumber, + o.tidinterpreted, o.sciname, o.recordedby, o.recordnumber, o.eventdate, + CONCAT_WS("; ",o.country, o.stateprovince, o.county, o.locality) as locality + FROM omcollections c INNER JOIN omoccurrences o ON c.collid = o.collid + LEFT JOIN taxa t ON o.tidinterpreted = t.TID + LEFT JOIN taxstatus ts ON t.TID = ts.tid '; $sql .= $this->getTableJoinFrag($sqlFrag); $sql .= 'WHERE ('.$sqlFrag.') AND ((t.RankId < 220)) '; $idStr = $this->getVoucherOccidStr(); if($idStr) $sql .= 'AND (o.occid NOT IN('.$idStr.')) '; $sql .= 'ORDER BY o.family, o.sciname LIMIT '.$startLimit.', 500'; - //echo '
    '.$sql.'
    '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->clTaxaID][$r->occid]['tid'] = $r->tidinterpreted; @@ -142,6 +140,22 @@ public function getNewVouchers($startLimit = 500, $includeAll = 1){ return $retArr; } + private function getNonVoucherTidStr(){ + $idArr = array(); + $clidStr = $this->getClidFullStr(); + if($clidStr){ + $sql = 'SELECT c.tid FROM fmchklsttaxalink c LEFT JOIN fmvouchers v ON c.clTaxaID = v.clTaxaID WHERE v.voucherID IS NULL AND c.clid = '.$this->clid; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $idArr[] = $r->tid; + } + $rs->free(); + } + $retStr = '0'; + if($idArr) $retStr = implode(',', $idArr); + return $retStr; + } + public function getMissingTaxa(){ $retArr = Array(); if($sqlFrag = $this->getSqlFrag()){ @@ -331,20 +345,6 @@ private function getVoucherOccidStr(){ return implode(',',$idArr); } - private function getVoucherTidStr(){ - $idArr = array(); - $clidStr = $this->getClidFullStr(); - if($clidStr){ - $sql = 'SELECT DISTINCT tid FROM fmchklsttaxalink WHERE clid IN('.$clidStr.')'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $idArr[] = $r->tid; - } - $rs->free(); - } - return implode(',',$idArr); - } - private function getTableJoinFrag($sqlFrag){ $retSql = ''; if(strpos($sqlFrag,'MATCH(f.recordedby)') || strpos($sqlFrag,'MATCH(f.locality)')){ diff --git a/classes/CollectionMetadata.php b/classes/CollectionMetadata.php new file mode 100644 index 0000000000..16304fa537 --- /dev/null +++ b/classes/CollectionMetadata.php @@ -0,0 +1,111 @@ +verboseMode = 2; + set_time_limit(2000); + } + + public function __destruct() { + parent::__destruct(); + } + + // Main functions + + // Gets biorepo collids from available collections + public function getBiorepoCollsIds(){ + $dataArr = array(); + + $sql = 'SELECT DISTINCT col.collid FROM omcollections AS col LEFT JOIN omcollcatlink AS l ON col.CollID = l.collid LEFT JOIN omcollcategories AS cat ON l.ccpk = cat.ccpk WHERE l.ccpk NOT IN (6,8) AND available = "TRUE"'; + + $result = $this->conn->query($sql); + + while ($row = $result->fetch_assoc()){ + $dataArr[] = $row['collid']; + } + $result->free(); + + $dataStr = implode(",", $dataArr); + return $dataStr; + + } + + // Gets group of collections metadata based on category + public function getCollMeta(){ + + $dataArr = array(); + + $sql = 'SELECT col.collid, cat.category, col.collectioncode, col.collectionname FROM omcollections AS col LEFT JOIN omcollcatlink AS l ON col.CollID = l.collid LEFT JOIN omcollcategories AS cat ON l.ccpk = cat.ccpk'; + + $result = $this->conn->query($sql); + + while ($row = $result->fetch_assoc()){ + $dataArr[] = $row; + } + $result->free(); + + return $dataArr; + } + + // Gets collections metadata filtered by category + public function getCollMetaByCat($cat){ + // $sql = 'SELECT * FROM collmetadata WHERE collid = '.$collid.''; + $dataArr = array(); + + $sql = 'SELECT col.collid, col.collectioncode, col.institutioncode, col.collectionname FROM omcollections AS col LEFT JOIN omcollcatlink AS l ON col.CollID = l.collid LEFT JOIN omcollcategories AS cat ON l.ccpk = cat.ccpk WHERE cat.category = "'.$cat.'" ORDER BY col.collectionname;'; + + $result = $this->conn->query($sql); + while($row = $result->fetch_array()){ + $dataArr[] = $row; + } + $result->free(); + return $dataArr; + } + // Gets filtered biorepo collections metadata groups + public function getBiorepoGroups($filterName){ + // $sql = 'SELECT * FROM collmetadata WHERE collid = '.$collid.''; + $dataArr = array(); + + $sql = 'SELECT col.collid, col.collectioncode, col.institutioncode, col.collectionname FROM omcollections AS col LEFT JOIN omcollcatlink AS l ON col.CollID = l.collid WHERE l.ccpk NOT IN (6,8) GROUP BY '.$filterName.' ORDER BY '.$filterName.';'; + try{ + $result = $this->conn->query($sql); + while($row = $result->fetch_array()){ + $dataArr[] = $row; + } + $result->free(); + } catch (Exception $e){ + echo 'Caught exception: ', $e->getMessage(), "\n"; + + } + return $dataArr; + } + + // Gets filtered biorepo collections + public function getBiorepoColls($filterName, $filterVal){ + // $sql = 'SELECT * FROM collmetadata WHERE collid = '.$collid.''; + $dataArr = array(); + + $sql = 'SELECT DISTINCT col.collid, col.collectioncode, col.institutioncode, col.collectionname FROM omcollections AS col LEFT JOIN omcollcatlink AS l ON col.CollID = l.collid WHERE l.ccpk NOT IN (6,8) AND '.$filterName.'= "'.$filterVal.'" ORDER BY col.collectionname;'; + + $result = $this->conn->query($sql); + + while($row = $result->fetch_array()){ + $dataArr[] = $row; + } + $result->free(); + return $dataArr; + } +}; + +;?> \ No newline at end of file diff --git a/classes/DatasetsMetadata.php b/classes/DatasetsMetadata.php new file mode 100644 index 0000000000..94e86493e6 --- /dev/null +++ b/classes/DatasetsMetadata.php @@ -0,0 +1,55 @@ +verboseMode = 2; + set_time_limit(2000); + } + + public function __destruct() { + parent::__destruct(); + } + + // Main functions + + // Gets NEON Domains + public function getNeonDomains(){ + $dataArr = array(); + + $sql = 'SELECT d.name AS domainnumber, s.domainname, d.datasetid FROM omoccurdatasets AS d JOIN neon_field_sites AS s ON d.name = s.domainnumber GROUP BY domainnumber ORDER BY domainnumber;'; + + $result = $this->conn->query($sql); + + while ($row = $result->fetch_assoc()){ + $dataArr[] = $row; + } + $result->free(); + return $dataArr; + } + + // Gets NEON Sites filtered by Domain + public function getNeonSitesByDom($domainnumber){ + $dataArr = array(); + + $sql = 'SELECT siteid, sitename, domainnumber, datasetid FROM omoccurdatasets AS d JOIN neon_field_sites AS s ON d.name = s.siteid WHERE domainnumber = "'.$domainnumber.'" ORDER BY siteid;'; + + $result = $this->conn->query($sql); + + while ($row = $result->fetch_assoc()){ + $dataArr[] = $row; + } + $result->free(); + return $dataArr; + } +}; + +;?> \ No newline at end of file diff --git a/classes/DwcArchiverBaseManager.php b/classes/DwcArchiverBaseManager.php index 7fba7447bf..9adfba4a4d 100644 --- a/classes/DwcArchiverBaseManager.php +++ b/classes/DwcArchiverBaseManager.php @@ -10,7 +10,7 @@ class DwcArchiverBaseManager extends Manager{ protected $charSetSource = ''; protected $charSetOut = ''; protected $sqlBase; - protected $fileHandler; + private $fileHandler; public function __construct($conType, $connOverride){ parent::__construct(null, $conType, $connOverride); @@ -20,6 +20,7 @@ public function __construct($conType, $connOverride){ public function __destruct(){ parent::__destruct(); + if($this->fileHandler) fclose($this->fileHandler); } protected function setFileHandler($filePath){ @@ -77,17 +78,18 @@ protected function encodeStr($inStr){ $retStr = $inStr; if($inStr && $this->charSetSource){ if($this->charSetOut == 'UTF-8' && $this->charSetSource == 'ISO-8859-1'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); + if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == 'ISO-8859-1'){ + $retStr = mb_convert_encoding($inStr, 'UTF-8', 'ISO-8859-1'); } } - elseif($this->charSetOut == "ISO-8859-1" && $this->charSetSource == 'UTF-8'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); + elseif($this->charSetOut == 'ISO-8859-1' && $this->charSetSource == 'UTF-8'){ + if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == 'UTF-8'){ + $retStr = mb_convert_encoding($inStr, 'ISO-8859-1', 'UTF-8'); } } + else{ + $retStr = mb_convert_encoding($inStr, $this->charSetOut, mb_detect_encoding($inStr)); + } } return $retStr; } diff --git a/classes/DwcArchiverCore.php b/classes/DwcArchiverCore.php index a011a642c3..f351897d60 100644 --- a/classes/DwcArchiverCore.php +++ b/classes/DwcArchiverCore.php @@ -120,12 +120,12 @@ public function setTargetPath($tp = ''){ $this->targetPath = $tp; } else { //Set to temp download path - $tPath = $GLOBALS["tempDirRoot"]; + $tPath = $GLOBALS['TEMP_DIR_ROOT']; if (!$tPath) { $tPath = ini_get('upload_tmp_dir'); } if (!$tPath) { - $tPath = $GLOBALS["serverRoot"]; + $tPath = $GLOBALS['SERVER_ROOT']; if (substr($tPath, -1) != '/' && substr($tPath, -1) != '\\') { $tPath .= '/'; } @@ -162,30 +162,30 @@ public function setCollArr($collTarget, $collType = ''){ if ($rs = $this->conn->query($sql)) { while ($r = $rs->fetch_object()) { $this->collArr[$r->collid]['instcode'] = $r->institutioncode; - $this->collArr[$r->collid]['collcode'] = $r->collectioncode; + $this->collArr[$r->collid]['collcode'] = $r->collectioncode ?? ''; $this->collArr[$r->collid]['collname'] = $r->collectionname; - $this->collArr[$r->collid]['description'] = $r->fulldescription; - $this->collArr[$r->collid]['collectionguid'] = $r->collectionguid; - $this->collArr[$r->collid]['url'] = $r->url; - $this->collArr[$r->collid]['contact'][0]['individualName']['surName'] = $r->contact; - $this->collArr[$r->collid]['contact'][0]['electronicMailAddress'] = $r->email; - $this->collArr[$r->collid]['guidtarget'] = $r->guidtarget; - $this->collArr[$r->collid]['dwcaurl'] = $r->dwcaurl; - $this->collArr[$r->collid]['lat'] = $r->latitudedecimal; - $this->collArr[$r->collid]['lng'] = $r->longitudedecimal; - $this->collArr[$r->collid]['icon'] = $r->icon; + $this->collArr[$r->collid]['description'] = $r->fulldescription ?? ''; + $this->collArr[$r->collid]['collectionguid'] = $r->collectionguid ?? ''; + $this->collArr[$r->collid]['url'] = $r->url ?? ''; + $this->collArr[$r->collid]['contact'][0]['individualName']['surName'] = $r->contact ?? ''; + $this->collArr[$r->collid]['contact'][0]['electronicMailAddress'] = $r->email ?? ''; + $this->collArr[$r->collid]['guidtarget'] = $r->guidtarget ?? ''; + $this->collArr[$r->collid]['dwcaurl'] = $r->dwcaurl ?? ''; + $this->collArr[$r->collid]['lat'] = $r->latitudedecimal ?? ''; + $this->collArr[$r->collid]['lng'] = $r->longitudedecimal ?? ''; + $this->collArr[$r->collid]['icon'] = $r->icon ?? ''; $this->collArr[$r->collid]['colltype'] = $r->colltype; $this->collArr[$r->collid]['managementtype'] = $r->managementtype; - $this->collArr[$r->collid]['rights'] = $r->rights; - $this->collArr[$r->collid]['rightsholder'] = $r->rightsholder; - $this->collArr[$r->collid]['usageterm'] = $r->usageterm; - $this->collArr[$r->collid]['address1'] = $r->address1; - $this->collArr[$r->collid]['address2'] = $r->address2; - $this->collArr[$r->collid]['city'] = $r->city; - $this->collArr[$r->collid]['state'] = $r->stateprovince; - $this->collArr[$r->collid]['postalcode'] = $r->postalcode; - $this->collArr[$r->collid]['country'] = $r->country; - $this->collArr[$r->collid]['phone'] = $r->phone; + $this->collArr[$r->collid]['rights'] = $r->rights ?? ''; + $this->collArr[$r->collid]['rightsholder'] = $r->rightsholder ?? ''; + $this->collArr[$r->collid]['usageterm'] = $r->usageterm ?? ''; + $this->collArr[$r->collid]['address1'] = $r->address1 ?? ''; + $this->collArr[$r->collid]['address2'] = $r->address2 ?? ''; + $this->collArr[$r->collid]['city'] = $r->city ?? ''; + $this->collArr[$r->collid]['state'] = $r->stateprovince ?? ''; + $this->collArr[$r->collid]['postalcode'] = $r->postalcode ?? ''; + $this->collArr[$r->collid]['country'] = $r->country ?? ''; + $this->collArr[$r->collid]['phone'] = $r->phone ?? ''; if ($r->dynamicproperties) { if ($propArr = json_decode($r->dynamicproperties, true)) { if (isset($propArr['editorProps']['modules-panel'])) { @@ -254,11 +254,11 @@ public function setCustomWhereSql($sql){ public function addCondition($field, $cond, $value = ''){ $cond = strtoupper(trim($cond)); if (!preg_match('/^[A-Za-z]+$/', $field)) return false; - if (!preg_match('/^[A-Z]+$/', $cond)) return false; + if (!preg_match('/^[A-Z_]+$/', $cond)) return false; if ($field) { if ($this->overrideConditionLimit || in_array(strtolower($field), $this->condAllowArr)) { if (!$cond) $cond = 'EQUALS'; - if ($value != '' || ($cond == 'NULL' || $cond == 'NOTNULL')) { + if ($value != '' || ($cond == 'IS_NULL' || $cond == 'NOT_NULL')) { if (is_array($value)) $this->conditionArr[$field][$cond] = $this->cleanInArray($value); else $this->conditionArr[$field][$cond][] = $this->cleanInStr($value); } @@ -269,7 +269,7 @@ public function addCondition($field, $cond, $value = ''){ private function applyConditions(){ if($this->conditionSql) return true; if ($this->customWhereSql) { - $this->conditionSql = $this->customWhereSql . ' '; + $this->conditionSql = trim($this->customWhereSql) . ' '; } if (array_key_exists('collid', $this->conditionArr) && $this->conditionArr['collid']) { if (preg_match('/^[\d,]+$/', $this->conditionArr['collid'])) { @@ -297,7 +297,7 @@ private function applyConditions(){ $taxaArr = array(); $taxaArr['taxa'] = implode(';', $condArr['EQUALS']); if ($field == 'family') $taxaArr['taxontype'] = 3; - $taxaManager->setTaxonRequestVariable($taxaArr); + $taxaManager->setTaxonRequestVariable($taxaArr, true); $sqlFrag .= $taxaManager->getTaxonWhereFrag(); } elseif ($field == 'cultivationstatus') { if (current(current($condArr)) === '0') $sqlFrag .= 'AND (o.cultivationStatus = 0 OR o.cultivationStatus IS NULL) '; @@ -309,10 +309,10 @@ private function applyConditions(){ foreach ($condArr as $cond => $valueArr) { if ($field == 'o.otherCatalogNumbers') { $conj = 'OR'; - if ($cond == 'NOTEQUALS' || $cond == 'NOTLIKE' || $cond == 'NULL') $conj = 'AND'; + if ($cond == 'NOT_EQUALS' || $cond == 'NOT_LIKE' || $cond == 'IS_NULL') $conj = 'AND'; $sqlFrag2 .= 'AND (' . substr($this->getSqlFragment($field, $cond, $valueArr), 3) . ' '; $sqlFrag2 .= $conj . ' (' . substr($this->getSqlFragment('id.identifierValue', $cond, $valueArr), 3); - if ($cond == 'NOTEQUALS' || $cond == 'NOTLIKE') $sqlFrag2 .= ' OR id.identifierValue IS NULL'; + if ($cond == 'NOT_EQUALS' || $cond == 'NOT_LIKE') $sqlFrag2 .= ' OR id.identifierValue IS NULL'; $sqlFrag2 .= ')) '; } else { $sqlFrag2 .= $this->getSqlFragment($field, $cond, $valueArr); @@ -338,26 +338,26 @@ private function applyConditions(){ private function getSqlFragment($field, $cond, $valueArr){ $sql = ''; - if ($cond == 'NULL') { + if ($cond == 'IS_NULL') { $sql .= 'AND (' . $field . ' IS NULL) '; - } elseif ($cond == 'NOTNULL') { + } elseif ($cond == 'NOT_NULL') { $sql .= 'AND (' . $field . ' IS NOT NULL) '; } elseif ($cond == 'EQUALS') { $sql .= 'AND (' . $field . ' IN("' . implode('","', $valueArr) . '")) '; - } elseif ($cond == 'NOTEQUALS') { + } elseif ($cond == 'NOT_EQUALS') { $sql .= 'AND (' . $field . ' NOT IN("' . implode('","', $valueArr) . '") OR ' . $field . ' IS NULL) '; } else { $sqlFrag = ''; foreach ($valueArr as $value) { - if ($cond == 'STARTS') { + if ($cond == 'STARTS_WITH') { $sqlFrag .= 'OR (' . $field . ' LIKE "' . $value . '%") '; } elseif ($cond == 'LIKE') { $sqlFrag .= 'OR (' . $field . ' LIKE "%' . $value . '%") '; - } elseif ($cond == 'NOTLIKE') { + } elseif ($cond == 'NOT_LIKE') { $sqlFrag .= 'OR (' . $field . ' NOT LIKE "%' . $value . '%" OR ' . $field . ' IS NULL) '; - } elseif ($cond == 'LESSTHAN') { + } elseif ($cond == 'LESS_THAN') { $sqlFrag .= 'OR (' . $field . ' < "' . $value . '") '; - } elseif ($cond == 'GREATERTHAN') { + } elseif ($cond == 'GREATER_THAN') { $sqlFrag .= 'OR (' . $field . ' > "' . $value . '") '; } } @@ -897,10 +897,13 @@ public function createDwcArchive($fileNameSeed = ''){ else { $this->errorMessage = 'FAILED to create archive file due to failure to return occurrence records; check and adjust search variables'; $this->logOrEcho($this->errorMessage); - if($this->collArr){ - $collid = key($this->collArr); - if ($collid) $this->deleteArchive($collid); - unset($this->collArr[$collid]); + if($this->targetPath && strpos($this->targetPath, 'content/dwca')){ + //Archive is being published to Dwc-A publishing directory, thus remove from RSS feed since it's an empty archive + if($this->collArr){ + $collid = key($this->collArr); + if ($collid) $this->deleteArchive($collid); + unset($this->collArr[$collid]); + } } } $this->logOrEcho("\n-----------------------------------------------------\n"); @@ -1531,13 +1534,15 @@ public function getFullRss(){ $itemElem->appendChild($itemTitleElem); //Icon $imgLink = ''; - if (substr($cArr['icon'], 0, 17) == 'images/collicons/') { - //Link is a - $imgLink = $urlPathPrefix . $cArr['icon']; - } elseif (substr($cArr['icon'], 0, 1) == '/') { - $imgLink = $localDomain . $cArr['icon']; - } else { - $imgLink = $cArr['icon']; + if($cArr['icon']){ + if (substr($cArr['icon'], 0, 17) == 'images/collicons/') { + //Link is a + $imgLink = $urlPathPrefix . $cArr['icon']; + } elseif (substr($cArr['icon'], 0, 1) == '/') { + $imgLink = $localDomain . $cArr['icon']; + } else { + $imgLink = $cArr['icon']; + } } $iconElem = $newDoc->createElement('image'); $iconElem->appendChild($newDoc->createTextNode($imgLink)); @@ -1696,8 +1701,10 @@ private function writeOccurrenceFile(){ unset($r['localitySecurity']); unset($r['collID']); //Format dates - if($r['eventDate'] == '0000-00-00') $r['eventDate'] = ''; - $r['eventDate'] = str_replace('-00', '', $r['eventDate']); + if($r['eventDate']){ + if($r['eventDate'] == '0000-00-00') $r['eventDate'] = ''; + $r['eventDate'] = str_replace('-00', '', $r['eventDate']); + } if($r['eventDate2']){ if($r['eventDate2'] == '0000-00-00') $r['eventDate2'] = ''; $r['eventDate2'] = str_replace('-00', '', $r['eventDate2']); @@ -1751,7 +1758,7 @@ private function writeOccurrenceFile(){ } if ($assocOccurStr = $dwcOccurManager->getAssociationStr($r['occid'])) $r['t_associatedOccurrences'] = $assocOccurStr; if ($assocSeqStr = $dwcOccurManager->getAssociatedSequencesStr($r['occid'])) $r['t_associatedSequences'] = $assocSeqStr; - if ($assocTaxa = $dwcOccurManager->getAssocTaxa($r['occid'])) $r['associatedTaxa'] = $assocTaxa; + if ($assocTaxa = $dwcOccurManager->getAssociationStr($r['occid'], 'observational')) $r['associatedTaxa'] = $assocTaxa; } //$dwcOccurManager->appendUpperTaxonomy($r); $dwcOccurManager->appendUpperTaxonomy2($r); @@ -1783,11 +1790,11 @@ private function writeOccurrenceFile(){ } if ($this->includeAttributes){ $this->writeAttributeData($batchOccidArr); - if($this->attributeHandler !== null) $this->attributeHandler->__destruct(); + unset($this->attributeHandler); } if ($this->includeMaterialSample){ $this->writeMaterialSampleData($batchOccidArr); - if($this->materialSampleHandler !== null) $this->materialSampleHandler->__destruct(); + unset($this->materialSampleHandler); } } else { @@ -1892,18 +1899,18 @@ private function writeImageFile(){ if ($previousImgID == $r['imgID']) continue; $previousImgID = $r['imgID']; unset($r['imgID']); - if (substr($r['identifier'], 0, 1) == '/') $r['identifier'] = $localDomain . $r['identifier']; - if (substr($r['accessURI'], 0, 1) == '/') $r['accessURI'] = $localDomain . $r['accessURI']; - if (substr($r['thumbnailAccessURI'], 0, 1) == '/') $r['thumbnailAccessURI'] = $localDomain . $r['thumbnailAccessURI']; - if (substr($r['goodQualityAccessURI'], 0, 1) == '/') $r['goodQualityAccessURI'] = $localDomain . $r['goodQualityAccessURI']; + if ($r['identifier'] && substr($r['identifier'], 0, 1) == '/') $r['identifier'] = $localDomain . $r['identifier']; + if ($r['accessURI'] && substr($r['accessURI'], 0, 1) == '/') $r['accessURI'] = $localDomain . $r['accessURI']; + if ($r['thumbnailAccessURI'] && substr($r['thumbnailAccessURI'], 0, 1) == '/') $r['thumbnailAccessURI'] = $localDomain . $r['thumbnailAccessURI']; + if ($r['goodQualityAccessURI'] && substr($r['goodQualityAccessURI'], 0, 1) == '/') $r['goodQualityAccessURI'] = $localDomain . $r['goodQualityAccessURI']; - if ($r['goodQualityAccessURI'] == 'empty' || substr($r['goodQualityAccessURI'], 0, 10) == 'processing') $r['goodQualityAccessURI'] = ''; - if (substr($r['thumbnailAccessURI'], 0, 10) == 'processing') $r['thumbnailAccessURI'] = ''; + if ($r['goodQualityAccessURI'] && ($r['goodQualityAccessURI'] == 'empty' || substr($r['goodQualityAccessURI'], 0, 10) == 'processing')) $r['goodQualityAccessURI'] = ''; + if ($r['thumbnailAccessURI'] && substr($r['thumbnailAccessURI'], 0, 10) == 'processing') $r['thumbnailAccessURI'] = ''; if ($this->schemaType != 'backup') { - if (stripos($r['rights'], 'creativecommons.org') === 0) { + if ($r['rights'] && stripos($r['rights'], 'creativecommons.org') === 0) { $r['webstatement'] = $r['rights']; $r['rights'] = ''; - if (!$r['usageterms']) { + if (!$r['usageterms'] && $r['webstatement']) { if (strpos($r['webstatement'], '/zero/1.0/')) { $r['usageterms'] = 'CC0 1.0 (Public-domain)'; } @@ -1927,22 +1934,24 @@ private function writeImageFile(){ $r['associatedSpecimenReference'] = $urlPathPrefix . 'collections/individual/index.php?occid=' . $r['occid']; $r['type'] = 'StillImage'; $r['subtype'] = 'Photograph'; - $extStr = strtolower(substr($r['accessURI'], strrpos($r['accessURI'], '.') + 1)); - if ($r['format'] == '') { - if ($extStr == 'jpg' || $extStr == 'jpeg') { - $r['format'] = 'image/jpeg'; - } - elseif ($extStr == 'gif') { - $r['format'] = 'image/gif'; - } - elseif ($extStr == 'png') { - $r['format'] = 'image/png'; - } - elseif ($extStr == 'tiff' || $extStr == 'tif') { - $r['format'] = 'image/tiff'; - } - else { - $r['format'] = ''; + if($r['accessURI']){ + $extStr = strtolower(substr($r['accessURI'], strrpos($r['accessURI'], '.') + 1)); + if ($r['format'] == '') { + if ($extStr == 'jpg' || $extStr == 'jpeg') { + $r['format'] = 'image/jpeg'; + } + elseif ($extStr == 'gif') { + $r['format'] = 'image/gif'; + } + elseif ($extStr == 'png') { + $r['format'] = 'image/png'; + } + elseif ($extStr == 'tiff' || $extStr == 'tif') { + $r['format'] = 'image/tiff'; + } + else { + $r['format'] = ''; + } } } $r['metadataLanguage'] = 'en'; @@ -2063,7 +2072,7 @@ private function writeOutRecord($fh, $outputArr){ fputcsv($fh, $outputArr); } else { foreach ($outputArr as $k => $v) { - $outputArr[$k] = str_replace($this->delimiter, '', $v); + $outputArr[$k] = str_replace($this->delimiter, '', ($v ?? '')); } fwrite($fh, implode($this->delimiter, $outputArr) . "\n"); } @@ -2135,7 +2144,7 @@ public function setDelimiter($d){ } elseif ($d == 'csv' || $d == 'comma' || $d == ',') { $this->delimiter = ","; $this->fileExt = '.csv'; - } else { + } elseif ($d) { $this->delimiter = $d; $this->fileExt = '.txt'; } @@ -2143,18 +2152,22 @@ public function setDelimiter($d){ public function setIncludeDets($includeDets){ if($includeDets) $this->includeDets = true; + else $this->includeDets = false; } public function setIncludeImgs($includeImgs){ if($includeImgs) $this->includeImgs = true; + else $this->includeImgs = false; } public function setIncludeAttributes($include){ if($include) $this->includeAttributes = true; + else $this->includeAttributes = false; } public function setIncludeMaterialSample($include){ if($include) $this->includeMaterialSample = true; + else $this->includeMaterialSample = false; } public function hasAttributes($collid = false){ @@ -2261,16 +2274,17 @@ private function encodeStr($inStr){ $retStr = $inStr; if ($inStr && $this->charSetSource) { if ($this->charSetOut == 'UTF-8' && $this->charSetSource == 'ISO-8859-1') { - if (mb_detect_encoding($inStr, 'UTF-8,ISO-8859-1', true) == "ISO-8859-1") { - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); + if (mb_detect_encoding($inStr, 'UTF-8,ISO-8859-1', true) == 'ISO-8859-1') { + $retStr = mb_convert_encoding($inStr, 'UTF-8', 'ISO-8859-1'); } - } elseif ($this->charSetOut == "ISO-8859-1" && $this->charSetSource == 'UTF-8') { - if (mb_detect_encoding($inStr, 'UTF-8,ISO-8859-1') == "UTF-8") { - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); + } elseif ($this->charSetOut == 'ISO-8859-1' && $this->charSetSource == 'UTF-8') { + if (mb_detect_encoding($inStr, 'UTF-8,ISO-8859-1') == 'UTF-8') { + $retStr = mb_convert_encoding($inStr, 'ISO-8859-1', 'UTF-8'); } } + else{ + $retStr = mb_convert_encoding($inStr, $this->charSetOut, mb_detect_encoding($inStr)); + } } return $retStr; } diff --git a/classes/DwcArchiverOccurrence.php b/classes/DwcArchiverOccurrence.php index 7f7eaf75ab..abfd3edb39 100644 --- a/classes/DwcArchiverOccurrence.php +++ b/classes/DwcArchiverOccurrence.php @@ -1,4 +1,6 @@ occurDefArr['fields']['substrate'] = 'o.substrate'; $this->occurDefArr['terms']['verbatimAttributes'] = 'https://symbiota.org/terms/verbatimAttributes'; $this->occurDefArr['fields']['verbatimAttributes'] = 'o.verbatimAttributes'; + $this->occurDefArr['terms']['behavior'] = 'http://rs.tdwg.org/dwc/terms/behavior'; + $this->occurDefArr['fields']['behavior'] = 'o.behavior'; + $this->occurDefArr['terms']['vitality'] = 'http://rs.tdwg.org/dwc/terms/vitality'; + $this->occurDefArr['fields']['vitality'] = 'o.vitality'; $this->occurDefArr['terms']['fieldNumber'] = 'http://rs.tdwg.org/dwc/terms/fieldNumber'; $this->occurDefArr['fields']['fieldNumber'] = 'o.fieldNumber'; $this->occurDefArr['terms']['eventID'] = 'http://rs.tdwg.org/dwc/terms/eventID'; @@ -144,8 +150,8 @@ public function getOccurrenceArr(){ $this->occurDefArr['fields']['sex'] = 'o.sex'; $this->occurDefArr['terms']['individualCount'] = 'http://rs.tdwg.org/dwc/terms/individualCount'; $this->occurDefArr['fields']['individualCount'] = 'CASE WHEN o.individualCount REGEXP("(^[0-9]+$)") THEN o.individualCount ELSE NULL END AS individualCount'; - //$this->occurDefArr['terms']['samplingProtocol'] = 'http://rs.tdwg.org/dwc/terms/samplingProtocol'; - //$this->occurDefArr['fields']['samplingProtocol'] = 'o.samplingProtocol'; + $this->occurDefArr['terms']['samplingProtocol'] = 'http://rs.tdwg.org/dwc/terms/samplingProtocol'; + $this->occurDefArr['fields']['samplingProtocol'] = 'o.samplingProtocol'; //$this->occurDefArr['terms']['samplingEffort'] = 'http://rs.tdwg.org/dwc/terms/samplingEffort'; //$this->occurDefArr['fields']['samplingEffort'] = 'o.samplingEffort'; $this->occurDefArr['terms']['preparations'] = 'http://rs.tdwg.org/dwc/terms/preparations'; @@ -412,84 +418,239 @@ public function getExsiccateArr($occid){ return $retArr; } - public function getAssociationStr($occid){ - if(is_numeric($occid)){ - $relOccidArr = array(); + public function getAssociationStr($occid, $associationType = null){ + $occid = filter_var($occid, FILTER_SANITIZE_NUMBER_INT); + if($occid){ + + // Return symbiotaAssociations JSON for the associatedOccurrences field instead of the text string generated below + // TODO: There is room for fine-tuning what conditions will return the JSON + // Seems like it should be turned on for schemaType == 'backup', but re-importing that backup currently fails + if ($this->schemaType == 'symbiota') return $this->getAssociationJSON($occid); + + $internalAssocOccidArr = array(); $assocArr = array(); - //Get associations defined within omoccurassociations - $sql = 'SELECT assocID, occid, occidAssociate, relationship, subType, resourceUrl, identifier FROM omoccurassociations - WHERE (occid = '.$occid.' OR occidAssociate = '.$occid.') AND verbatimSciname IS NULL '; + //Get associations defined within omoccurassociations (both subject and object) + $assocTypeArr = array('internalOccurrence', 'externalOccurrence'); + if($associationType) $assocTypeArr = explode(' ', $associationType); + $sql = 'SELECT assocID, occid, associationType, occidAssociate, relationship, subType, resourceUrl, identifier, basisOfRecord, verbatimSciname, recordID FROM omoccurassociations + WHERE (occid = '.$occid.' OR occidAssociate = '.$occid.') AND associationType IN("'.implode('","', $assocTypeArr).'") '; $rs = $this->conn->query($sql); if($rs){ while($r = $rs->fetch_object()){ $relOccid = $r->occidAssociate; $relationship = $r->relationship; if($occid == $r->occidAssociate){ + //Association was defined within secondary occurrence record, thus switch subject/object $relOccid = $r->occid; $relationship = $this->getInverseRelationship($relationship); } if($relOccid){ + //Is an internally defined association $assocArr[$r->assocID]['occidassoc'] = $relOccid; - $relOccidArr[$relOccid][] = $r->assocID; - $assocArr[$r->assocID]['relationship'] = $relationship; - $assocArr[$r->assocID]['subtype'] = $r->subType; + $internalAssocOccidArr[$relOccid][] = $r->assocID; } elseif($r->resourceUrl){ - $assocArr[$r->assocID]['resourceurl'] = $r->resourceUrl; + $assocArr[$r->assocID]['resourceUrl'] = $r->resourceUrl; $assocArr[$r->assocID]['identifier'] = $r->identifier; - $assocArr[$r->assocID]['relationship'] = $relationship; - $assocArr[$r->assocID]['subtype'] = $r->subType; } + $assocArr[$r->assocID]['relationship'] = $relationship; + $assocArr[$r->assocID]['subtype'] = $r->subType; + if($r->basisOfRecord) $assocArr[$r->assocID]['basisOfRecord'] = $r->basisOfRecord; + if($r->verbatimSciname) $assocArr[$r->assocID]['scientificName'] = $r->verbatimSciname; + if($r->recordID) $assocArr[$r->assocID]['recordID'] = $r->recordID; } $rs->free(); } - //Append duplicate specimen duplicate associations - $sql = 'SELECT s.occid, l.occid as occidAssociate - FROM omoccurduplicatelink s INNER JOIN omoccurduplicates d ON s.duplicateid = d.duplicateid - INNER JOIN omoccurduplicatelink l ON d.duplicateid = l.duplicateid - WHERE s.occid IN('.$occid.') AND s.occid != l.occid '; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $assocKey = 'sd-'.$r->occidAssociate; - $assocArr[$assocKey]['occidassoc'] = $r->occidAssociate; - $assocArr[$assocKey]['relationship'] = 'herbariumSpecimenDuplicate'; - $relOccidArr[$r->occidAssociate][] = $assocKey; - } - $rs->free(); - } + //Append associations of duplicate specimen + $this->appendSpecimenDuplicateAssociations($occid, $assocArr, $internalAssocOccidArr); + //Append resource URLs to each output record - if($relOccidArr){ - $this->setServerDomain(); - //Replace GUID identifiers with occurrenceID values - $sql = 'SELECT occid, occurrenceID, recordID FROM omoccurrences WHERE occid IN('.implode(',',array_keys($relOccidArr)).')'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - foreach($relOccidArr[$r->occid] as $k => $targetAssocID){ - if($r->occurrenceID){ - $assocArr[$targetAssocID]['resourceurl'] = $r->occurrenceID; - if(substr($r->occurrenceID, 0, 4) != 'http'){ - $assocArr[$targetAssocID]['resourceurl'] = $this->serverDomain.$GLOBALS['CLIENT_ROOT'].'/collections/individual/index.php?guid='.$r->occurrenceID; - } - } - elseif($r->recordID){ - $assocArr[$targetAssocID]['resourceurl'] = $this->serverDomain.$GLOBALS['CLIENT_ROOT'].'/collections/individual/index.php?guid='.$r->recordID; - } + if($internalAssocOccidArr){ + $identifierArr = $this->getInternalResourceIdentifiers($internalAssocOccidArr); + foreach($identifierArr as $internalOccid => $idArr){ + foreach($internalAssocOccidArr[$internalOccid] as $targetAssocID){ + $assocArr[$targetAssocID] = array_merge($assocArr[$targetAssocID], $idArr); } } - $rs->free(); } //Create output strings $retStr = ''; foreach($assocArr as $assocateArr){ - $retStr .= ' | '.$assocateArr['relationship']; - if(!empty($assocateArr['subtype'])) $retStr .= ' ('.$assocateArr['subtype'].')'; - $retStr .= ': '.$assocateArr['resourceurl']; + if($associationType == 'observational'){ + $retStr .= ' | ' . $assocateArr['relationship']; + if(!empty($assocateArr['scientificName'])) $retStr .= ': '.$assocateArr['scientificName']; + } + else{ + $retStr .= ' | relationship: ' . $assocateArr['relationship']; + if(!empty($assocateArr['subtype'])) $retStr .= ' (' . $assocateArr['subtype'] . ')'; + if(!empty($assocateArr['identifier'])) $retStr .= ', identifier: ' . $assocateArr['identifier']; + elseif(!empty($assocateArr['recordID'])) $retStr .= ', recordID: ' . $assocateArr['recordID']; + if(!empty($assocateArr['basisOfRecord'])) $retStr .= ', basisOfRecord: ' . $assocateArr['basisOfRecord']; + if(!empty($assocateArr['scientificName'])) $retStr .= ', scientificName: ' . $assocateArr['scientificName']; + if(!empty($assocateArr['resourceUrl'])) $retStr .= ', resourceUrl: ' . $assocateArr['resourceUrl']; + } } } return trim($retStr,' |'); } + // Function to return any associations as JSON for the associatedOccurrences field + private function getAssociationJSON($occid) { + + // Build SQL to find any associations for the occurrence record passed with occid + $sql = 'SELECT occid, occidAssociate, relationship, subType, identifier,' . + 'basisOfRecord, resourceUrl, verbatimSciname, locationOnHost ' . + 'FROM omoccurassociations ' . + 'WHERE (occid = ' . $occid . ' OR occidAssociate = ' . $occid . ') '; + + if ($rs = $this->conn->query($sql)) { + + // No associations, so just return an empty string, and quit the function + if (!$rs->num_rows) return ''; + + // Build verbatimText array + // Get any pre-existing contents of the associatedOccurrences field in omoccurrences + $verbatimText = $this->getVerbatimTextObject($occid); + // Check if the contents of the field already is JSON + if ($verbatimText['verbatimText']){ + if ($assocOccArr = json_decode($verbatimText['verbatimText'], true)) { + $verbatimText['verbatimText'] = ''; + // There's already JSON here + // TODO: What should we do? Perform some checks? + } + } + + // No associatedOccurrences array exists, so build one + if (!isset($assocOccArr)) { + + // Build JSON array + $assocOccArr = array(); + + // Build symbiotaAssociations array + $symbiotaAssociations = array(); + $symbiotaAssociations['type'] = 'symbiotaAssociations'; + $symbiotaAssociations['version'] = OccurrenceUtilities::$assocOccurVersion; + $symbiotaAssociations['associations'] = array(); + + // Add the symbiotaAssociations array + array_push($assocOccArr, $symbiotaAssociations); + + // Add the verbatimText array + array_push($assocOccArr, $verbatimText); + } + + // Make an array to hold occurrence IDs that need an additional guid (internalOccurrences) + $relOccidArr = array(); + + // Get each associated occurrence + while ($assocArr = $rs->fetch_assoc()) { + + // Filter out any empty fields + $assocArr = array_filter($assocArr); + + // Set the association type field + if (array_key_exists('occidAssociate', $assocArr)) { + $assocArr['type'] = 'internalOccurrence'; + } else if (array_key_exists('identifier', $assocArr) || array_key_exists('resourceUrl', $assocArr)) { + $assocArr['type'] = 'externalOccurrence'; + } else if (array_key_exists('verbatimSciname', $assocArr)) { + $assocArr['type'] = 'genericObservation'; + } else { + // Should not happen, but if so, this seems to be the best fit + $assocArr['type'] = 'genericObservation'; + } + + // Check for cases where the occidAssociate is this occid. + // In those cases, we need to switch the occid and occidAssociate and get the inverse relationship + if (array_key_exists('occidAssociate', $assocArr) && $assocArr['occidAssociate'] == $occid) { + $assocArr['occidAssociate'] = $assocArr['occid']; + $assocArr['relationship'] = $this->getInverseRelationship($assocArr['relationship']); + } + + // remove occid key, no longer needed + unset($assocArr['occid']); + + // Check if the associated occurrence is an internal occurrence + // If so, we need to flag this to add the GUID identifier & resource url, in case it gets imported in another portal + if (array_key_exists('occidAssociate', $assocArr)) { + array_push($relOccidArr, $assocArr['occidAssociate']); + } + + // Add associated occurrence array to the full associatedOccurrences array + array_push($assocOccArr[0]['associations'], $assocArr); + + } + + // There are some associated occurrences with an internal occidAssociate + // For these, we need to get their guids and construct reference URLs, in case they become external references + if ($relOccidArr) { + $identifierArr = $this->getInternalResourceIdentifiers($relOccidArr); + foreach($identifierArr as $internalOccid => $idArr){ + foreach ($assocOccArr[0]['associations'] as $index => $associateArr) { + if (array_key_exists('occidAssociate', $associateArr) && $assocOccArr[0]['associations'][$index]['occidAssociate'] == $internalOccid) { + // Add the GUID as the identifier, and the resource URL in case this ends up being treated as an external resource + $assocOccArr[0]['associations'][$index] = array_merge($assocOccArr[0]['associations'][$index], $idArr); + } + } + } + } + + // Return the full symbiotaAssociations array as JSON + // TODO: this is returning "null" for fields that are empty, like verbatimText. + return json_encode( $assocOccArr, JSON_UNESCAPED_SLASHES); + } + } + + private function appendSpecimenDuplicateAssociations($occid, &$assocArr, &$internalAssocOccidArr){ + $sql = 'SELECT s.occid, l.occid as occidAssociate + FROM omoccurduplicatelink s INNER JOIN omoccurduplicates d ON s.duplicateid = d.duplicateid + INNER JOIN omoccurduplicatelink l ON d.duplicateid = l.duplicateid + WHERE s.occid IN('.$occid.') AND s.occid != l.occid '; + $rs = $this->conn->query($sql); + if($rs){ + while($r = $rs->fetch_object()){ + $assocKey = 'sd-'.$r->occidAssociate; + $assocArr[$assocKey]['occidassoc'] = $r->occidAssociate; + $assocArr[$assocKey]['relationship'] = 'herbariumSpecimenDuplicate'; + $internalAssocOccidArr[$r->occidAssociate][] = $assocKey; + } + $rs->free(); + } + } + + private function getVerbatimTextObject($occid){ + $verbatimText = array('type' => 'verbatimText', 'verbatimText' => ''); + + $sql = 'SELECT associatedOccurrences FROM omoccurrences WHERE occid = ' . $occid; + $rs = $this->conn->query($sql); + if($r = $rs->fetch_object()){ + if($r->associatedOccurrences) $verbatimText['verbatimText'] = $r->associatedOccurrences; + } + $rs->free(); + return $verbatimText; + } + + private function getInternalResourceIdentifiers($internalAssocOccidArr){ + $retArr = array(); + $this->setServerDomain(); + //Replace GUID identifiers with occurrenceID values + $sql = 'SELECT occid, sciname, occurrenceID, recordID FROM omoccurrences WHERE occid IN('.implode(',',array_keys($internalAssocOccidArr)).')'; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $retArr[$r->occid]['scientificName'] = $r->sciname; + $guid = $r->recordID; + if($r->occurrenceID) $guid = $r->occurrenceID; + $retArr[$r->occid]['identifier'] = $guid; + $resourceUrl = $guid; + if(substr($resourceUrl, 0, 4) != 'http'){ + $resourceUrl = $this->serverDomain.$GLOBALS['CLIENT_ROOT'].'/collections/individual/index.php?guid='.$guid; + } + $retArr[$r->occid]['resourceUrl'] = $resourceUrl; + } + $rs->free(); + return $retArr; + } + public function setIncludeAssociatedSequences(){ $sql = 'SELECT occid FROM omoccurgenetic LIMIT 1'; $rs = $this->conn->query($sql); @@ -516,21 +677,6 @@ public function getAssociatedSequencesStr($occid){ return trim($retStr,' |,'); } - public function getAssocTaxa($occid){ - $retStr = ''; - if(is_numeric($occid)){ - $sql = 'SELECT assocID, relationship, subType, verbatimSciname FROM omoccurassociations WHERE occid = '.$occid.' AND verbatimSciname IS NOT NULL '; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $retStr .= '|'.$r->relationship.($r->subType?' ('.$r->subType.')':'').': '.$r->verbatimSciname; - } - $rs->free(); - } - } - return trim($retStr,' |'); - } - private function getInverseRelationship($relationship){ if(!$this->relationshipArr) $this->setRelationshipArr(); if(array_key_exists($relationship, $this->relationshipArr)) return $this->relationshipArr[$relationship]; @@ -576,7 +722,7 @@ public function appendUpperTaxonomy(&$targetArr){ public function appendUpperTaxonomy2(&$r){ $target = (isset($r['taxonID'])?$r['taxonID']:false); - if(!$target) $target = ucfirst($r['family']); + if(!$target && !empty($r['family'])) $target = ucfirst($r['family']); if($target){ if(array_key_exists($target, $this->upperTaxonomy)){ if(isset($this->upperTaxonomy[$target]['k'])) $r['t_kingdom'] = $this->upperTaxonomy[$target]['k']; diff --git a/classes/DwcArchiverPublisher.php b/classes/DwcArchiverPublisher.php index 54de0a30a9..1c9c40569c 100644 --- a/classes/DwcArchiverPublisher.php +++ b/classes/DwcArchiverPublisher.php @@ -11,10 +11,12 @@ public function __destruct(){ parent::__destruct(); } - private function resetCollArr($collTarget){ + public function resetCollArr($id){ unset($this->collArr); $this->collArr = array(); - $this->setCollArr($collTarget); + $this->setCollArr($id); + $this->conditionArr['collid'] = $id; + $this->conditionSql = ''; } public function verifyCollRecords($collId){ @@ -80,7 +82,7 @@ public function batchCreateDwca($collIdArr){ public function writeRssFile(){ - $this->logOrEcho("Mapping data to RSS feed... \n"); + $this->logOrEcho('Mapping data to RSS feed... '); //Create new document and write out to target $newDoc = new DOMDocument('1.0',$this->charSetOut); @@ -212,7 +214,8 @@ public function writeRssFile(){ $redirectDoc->save($deprecatedPath); } - $this->logOrEcho("Done!\n"); + $this->logOrEcho('Done!', 1); + $this->logOrEcho('-----------------------------------------------------'); } //Misc data retrival functions @@ -313,22 +316,27 @@ private function aasort(&$array, $key){ } public function humanFileSize($filePath) { + $x = false; if(substr($filePath,0,4)=='http') { - $x = array_change_key_case(get_headers($filePath, 1),CASE_LOWER); - if( strcasecmp($x[0], 'HTTP/1.1 200 OK') != 0 ) { - $x = $x['content-length'][1]; - } - else { - $x = $x['content-length']; + if($headerArr = @get_headers($filePath, 1)){ + $x = array_change_key_case($headerArr, CASE_LOWER); + if( strcasecmp($x[0], 'HTTP/1.1 200 OK') != 0 ) { + $x = $x['content-length'][1]; + } + else { + $x = $x['content-length']; + } } } else { $x = @filesize($filePath); } - $x = round($x/1000000, 1); - if(!$x) $x = 0.1; - - return $x.'M '; + if($x !== false){ + $x = round($x/1000000, 1); + if(!$x) $x = 0.1; + return $x.'M'; + } + return '?M'; } } ?> \ No newline at end of file diff --git a/classes/EOLManager.php b/classes/EOLManager.php index 1b701580c9..648082bf46 100644 --- a/classes/EOLManager.php +++ b/classes/EOLManager.php @@ -83,7 +83,7 @@ private function queryEolIdentifier($tid, $sciName, $makePrimaryLink){ $url .= '&key='.$GLOBALS['TAXONOMIC_AUTHORITIES']['EOL']; } if($fh = fopen($url, 'r')){ - echo '
  • Reading identifier for '.$sciName.' (tid: '.$tid.')... '; + echo '
  • Reading identifier for '.$sciName.' (tid: ' . htmlspecialchars($tid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ')... '; $content = ''; while($line = fread($fh, 1024)){ $content .= trim($line); @@ -170,7 +170,7 @@ public function mapImagesForTaxa($tidStart,$restart){ $this->imgManager = new ImageShared(); while($r = $rs->fetch_object()){ $tid = $r->tid; - echo '
  • Mapping images for '.$this->cleanOutStr($r->sciname).' (tid: '.$tid.'; EOL:'.$r->sourceidentifier.")
  • \n"; + echo '
  • Mapping images for '.$this->cleanOutStr($r->sciname).' (tid: ' . htmlspecialchars($tid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '; EOL:' . htmlspecialchars($r->sourceidentifier, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ")
  • \n"; if($this->mapEolImages($tid, $this->cleanOutStr($r->sourceidentifier))){ $successCnt++; } @@ -334,20 +334,10 @@ private function loadImage($tid,$imageUrl,$resourceArr){ private function encodeString($inStr){ global $CHARSET; - $retStr = trim($inStr); + $retStr = $inStr; if($retStr){ - if(strtolower($CHARSET) == "utf-8" || strtolower($CHARSET) == "utf8"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($CHARSET) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } + $retStr = trim($inStr); + $retStr = mb_convert_encoding($retStr, $CHARSET, mb_detect_encoding($retStr)); } return $retStr; } diff --git a/classes/EOLUtilities.php b/classes/EOLUtilities.php index 4d68a39274..8bafddccbc 100644 --- a/classes/EOLUtilities.php +++ b/classes/EOLUtilities.php @@ -307,21 +307,10 @@ public function getErrorStr(){ //Misc functions private function encodeString($inStr){ - global $CHARSET; - $retStr = trim($inStr); - if($retStr){ - if(strtolower($CHARSET) == "utf-8" || strtolower($CHARSET) == "utf8"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($CHARSET) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } + $retStr = $inStr; + if($inStr){ + $retStr = trim($inStr); + $retStr = mb_convert_encoding($retStr, $GLOBALS['CHARSET'], mb_detect_encoding($retStr)); } return $retStr; } diff --git a/classes/GamesManager.php b/classes/GamesManager.php index 0e844c7648..2a6a270ace 100644 --- a/classes/GamesManager.php +++ b/classes/GamesManager.php @@ -133,7 +133,7 @@ public function setOOTD($oodID,$clid){ while(($row = $rs->fetch_object()) && ($cnt < 6)){ $file = ''; if (substr($row->url, 0, 1) == '/'){ - if(isset($GLOBALS['imageDomain']) && $GLOBALS['imageDomain']) $file = $GLOBALS['imageDomain'].$row->url; + if(!empty($GLOBALS['IMAGE_DOMAIN'])) $file = $GLOBALS['IMAGE_DOMAIN'] . $row->url; else $file = $domain.$row->url; } else{ @@ -343,7 +343,7 @@ public function getNameGameWordList(){ //Misc functions private function setClidStr(){ $clidArr = array($this->clid); - $sqlBase = 'SELECT clidchild FROM fmchklstchildren WHERE clid IN('; + $sqlBase = 'SELECT clidchild FROM fmchklstchildren WHERE clid != clidchild AND clid IN('; $sql = $sqlBase.$this->clid.')'; do{ $childStr = ""; diff --git a/classes/GeographicThesaurus.php b/classes/GeographicThesaurus.php index 7f69a3c5e5..d9134f474c 100644 --- a/classes/GeographicThesaurus.php +++ b/classes/GeographicThesaurus.php @@ -1,7 +1,8 @@ conn->query($sql); - while($r = $rs->fetch_object()){ - $retArr['geoThesID'] = $r->geoThesID; - $retArr['geoTerm'] = $r->geoTerm; - $retArr['abbreviation'] = $r->abbreviation; - $retArr['iso2'] = $r->iso2; - $retArr['iso3'] = $r->iso3; - $retArr['numCode'] = $r->numCode; - $retArr['category'] = $r->category; - $retArr['geoLevel'] = $r->geoLevel; - $retArr['acceptedID'] = $r->acceptedID; - $retArr['acceptedTerm'] = $r->acceptedTerm; - $retArr['parentID'] = $r->parentID; - $retArr['parentTerm'] = $r->parentTerm; - $retArr['notes'] = $r->notes; - $retArr['termStatus'] = $r->termStatus; + LEFT JOIN geographicpolygon gp ON t.geoThesID = gp.geoThesID + WHERE t.geoThesID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $geoThesID); + $stmt->execute(); + $rs = $stmt->get_result(); + while($r = $rs->fetch_object()){ + $retArr['geoThesID'] = $r->geoThesID; + $retArr['geoTerm'] = $r->geoTerm; + $retArr['abbreviation'] = $r->abbreviation; + $retArr['iso2'] = $r->iso2; + $retArr['iso3'] = $r->iso3; + $retArr['numCode'] = $r->numCode; + $retArr['category'] = $r->category; + $retArr['geoLevel'] = $r->geoLevel; + $retArr['acceptedID'] = $r->acceptedID; + $retArr['acceptedTerm'] = $r->acceptedTerm; + $retArr['parentID'] = $r->parentID; + $retArr['parentTerm'] = $r->parentTerm; + $retArr['notes'] = $r->notes; + $retArr['termStatus'] = $r->termStatus; + $retArr['wkt'] = $r->wkt; + $retArr['geoJSON'] = $r->geoJSON; + } + $rs->free(); + $stmt->close(); } - $rs->free(); if($retArr){ $childArr = $this->setChildCnt($retArr['geoThesID']); $cnt = 0; if($childArr) $cnt = current($childArr); $retArr['childCnt'] = $cnt; + if(!$retArr['acceptedID']) $retArr['synonyms'] = $this->getSynonyms($geoThesID); } } return $retArr; } - public function editGeoUnit($postArr){ - if(is_numeric($postArr['geoThesID'])){ - if(!$postArr['geoTerm']){ - $this->errorMessage = 'ERROR editing geoUnit: geographic term must have a value'; - return false; - } - $sql = 'UPDATE geographicthesaurus '. - 'SET geoterm = "'.$this->cleanInStr($postArr['geoTerm']).'", '. - 'abbreviation = '.($postArr['abbreviation']?'"'.$this->cleanInStr($postArr['abbreviation']).'"':'NULL').', '. - 'iso2 = '.($postArr['iso2']?'"'.$this->cleanInStr($postArr['iso2']).'"':'NULL').', '. - 'iso3 = '.($postArr['iso3']?'"'.$this->cleanInStr($postArr['iso3']).'"':'NULL').', '. - 'numcode = '.(is_numeric($postArr['numCode'])?'"'.$this->cleanInStr($postArr['numCode']).'"':'NULL').', '. - 'geoLevel = '.(is_numeric($postArr['geoLevel'])?$this->cleanInStr($postArr['geoLevel']):'NULL').', '. - 'acceptedID = '.(is_numeric($postArr['acceptedID'])?'"'.$this->cleanInStr($postArr['acceptedID']).'"':'NULL').', '. - 'parentID = '.(is_numeric($postArr['parentID'])?'"'.$this->cleanInStr($postArr['parentID']).'"':'NULL').', '. - 'notes = '.($postArr['notes']?'"'.$this->cleanInStr($postArr['notes']).'"':'NULL').' '. - 'WHERE (geoThesID = '.$postArr['geoThesID'].')'; - if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR saving edits: '.$this->conn->error; - return false; + private function getSynonyms($geoThesID){ + $retArr = array(); + $sql = 'SELECT geoThesID, geoTerm FROM geographicthesaurus WHERE acceptedID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $geoThesID); + $stmt->execute(); + $rs = $stmt->get_result(); + while($r = $rs->fetch_object()){ + $retArr[$r->geoThesID] = $r->geoTerm; } } - return true; + return $retArr; } - public function addGeoUnit($postArr){ + public function editGeoUnit($postArr){ + if(!is_numeric($postArr['geoThesID'])) { + $this->errorMessage = 'ERROR editing geoUnit: geographic thesaurus id must be numeric'; + return false; + } else { + $postArr['geoThesID'] = intval($postArr['geoThesID']); + } + if(!$postArr['geoTerm']){ - $this->errorMessage = 'ERROR adding geoUnit: geographic term must have a value'; + $this->errorMessage = 'ERROR editing geoUnit: geographic term must have a value'; return false; } - else{ - $sql = 'INSERT INTO geographicthesaurus(geoterm, abbreviation, iso2, iso3, numcode, geoLevel, acceptedID, parentID, notes) '. - 'VALUES("'.$this->cleanInStr($postArr['geoTerm']).'", '. - ($postArr['abbreviation']?'"'.$this->cleanInStr($postArr['abbreviation']).'"':'NULL').', '. - ($postArr['iso2']?'"'.$this->cleanInStr($postArr['iso2']).'"':'NULL').', '. - ($postArr['iso3']?'"'.$this->cleanInStr($postArr['iso3']).'"':'NULL').', '. - (is_numeric($postArr['numCode'])?'"'.$this->cleanInStr($postArr['numCode']).'"':'NULL').', '. - (is_numeric($postArr['geoLevel'])?$this->cleanInStr($postArr['geoLevel']):'NULL').', '. - (is_numeric($postArr['acceptedID'])?'"'.$this->cleanInStr($postArr['acceptedID']).'"':'NULL').', '. - (is_numeric($postArr['parentID'])?'"'.$this->cleanInStr($postArr['parentID']).'"':'NULL').', '. - ($postArr['notes']?'"'.$this->cleanInStr($postArr['notes']).'"':'NULL').')'; - echo $sql; - if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR adding unit: '.$this->conn->error; + + $sql = <<<'SQL' + UPDATE geographicthesaurus SET geoterm = ?, abbreviation = ?, iso2 = ?, iso3 = ?, + numcode = ?, geoLevel = ?, acceptedID = ?, parentID = ?, notes = ? + WHERE geoThesID = ? + SQL; + + try { + SymbUtil::execute_query($this->conn,$sql, [ + $postArr['geoTerm'], + empty($postArr['abbreviation'])? null: $postArr['abbreviation'], + empty($postArr['iso2'])? null: $postArr['iso2'], + empty($postArr['iso3'])? null: $postArr['iso3'], + empty($postArr['numcode'])? null: $postArr['numcode'], + empty($postArr['geoLevel'])? null: intval($postArr['geoLevel']), + empty($postArr['acceptedID'])? null: $postArr['acceptedID'], + empty($postArr['parentID'])? null: $postArr['parentID'], + empty($postArr['notes'])? null: $postArr['notes'], + $postArr['geoThesID'] + ]); + } catch (\Throwable $th) { + $this->errorMessage = 'ERROR saving edits: '.$this->conn->error; + return false; + } + + if(!empty($postArr['polygon'])) { + $sql = <<<'SQL' + SELECT * from geographicpolygon WHERE geoThesID = ? + SQL; + + $polygon_exists = SymbUtil::execute_query($this->conn,$sql, [htmlspecialchars($postArr['geoThesID'])]); + + if(!$polygon_exists) { + $this->errorMessage = 'ERROR saving polygon edits: '.$this->conn->error; return false; } + + if($polygon_exists->num_rows <= 0) { + $this->addPolygon($postArr['geoThesID'], $postArr['polygon']); + } else { + $this->updatePolygon($postArr['geoThesID'], $postArr['polygon']); + } + } else { + $this->deletePolygon($postArr['geoThesID']); } + return true; } - public function addChildGeoUnit($postArr){ - //Add new child - //Uses an INSERT INTO sql statement + private function addPolygon($geoThesID, $polygon): bool { + //Needs a stored procedured because of packet's being too large issue + $sql = <<<'SQL' + CALL insertGeographicPolygon(?, ?); + SQL; + try { + SymbUtil::execute_query($this->conn,$sql, [$geoThesID, $polygon]); + return true; + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR saving new polygon: ' . $e->getMessage(); + return false; + } + } - /* $statusStr = ''; - if(is_numeric($postArr['geoThesID'])){ - $sql = 'UPDATE geographicthesaurus '. - 'SET geoterm = '.($postArr['geoterm']?'"'.$postArr['geoterm'].'"':'NULL'). - ', iso2 = '.($postArr['iso2']?'"'.$postArr['iso2'].'"':'NULL'). - ', iso3 = '.($postArr['iso3']?'"'.$postArr['iso3'].'"':'NULL'). - ' WHERE (geoThesID = '.$postArr['geoThesID'].')'; - if($this->conn->query($sql)){ - $statusStr = 'SUCCESS: changes saved'; + private function updatePolygon($geoThesID, $polygon) { + $sql = <<<'SQL' + CALL updateGeographicPolygon(?, ?); + SQL; + try { + SymbUtil::execute_query($this->conn,$sql, [$geoThesID, $polygon]); + return true; + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR updatePolygon on '. $geoThesID .':' . $e->getMessage(); + return false; + } + } + + private function deletePolygon($geoThesID) { + $sql = <<<'SQL' + DELETE FROM geographicpolygon WHERE geoThesID = ? + SQL; + + try { + SymbUtil::execute_query($this->conn,$sql, [$geoThesID]); + return true; + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR deletePolygon on '. $geoThesID . ':' . $e->getMessage(); + return false; + } + } + + public function addGeoUnit($postArr){ + try { + if(!$postArr['geoTerm']){ + $this->errorMessage = 'ERROR adding geoUnit: geographic term must have a value'; + return false; } else{ - $statusStr = 'ERROR: changes not saved'.$this->conn->error; + $sql = 'INSERT INTO geographicthesaurus(geoterm, abbreviation, iso2, iso3, numcode, geoLevel, acceptedID, parentID, notes) '. + 'VALUES("'.$this->cleanInStr($postArr['geoTerm']).'", '. + ($postArr['abbreviation']?'"'.$this->cleanInStr($postArr['abbreviation']).'"':'NULL').', '. + ($postArr['iso2']?'"'.$this->cleanInStr($postArr['iso2']).'"':'NULL').', '. + ($postArr['iso3']?'"'.$this->cleanInStr($postArr['iso3']).'"':'NULL').', '. + (is_numeric($postArr['numCode'])?'"'.$this->cleanInStr($postArr['numCode']).'"':'NULL').', '. + (is_numeric($postArr['geoLevel'])?$this->cleanInStr($postArr['geoLevel']):'NULL').', '. + (is_numeric($postArr['acceptedID'])?'"'.$this->cleanInStr($postArr['acceptedID']).'"':'NULL').', '. + (is_numeric($postArr['parentID'])?'"'.$this->cleanInStr($postArr['parentID']).'"':'NULL').', '. + ($postArr['notes']?'"'.$this->cleanInStr($postArr['notes']).'"':'NULL').')'; + + $this->conn->query($sql); + $geoThesID = $this->conn->insert_id; + + if(!empty($postArr['polygon']) && $geoThesID) { + $this->addPolygon($geoThesID, $postArr['polygon']); + } } + return $geoThesID; + } catch(Exception $e) { + $this->errorMessage = 'ERROR adding geounit: '. $e->getMessage(); + return false; } - return $statusStr; */ } public function deleteGeoUnit($geoThesID){ @@ -160,9 +247,31 @@ public function deleteGeoUnit($geoThesID){ return true; } + public function getChildren(array $parentIDs): array { + if(count($parentIDs) <= 0) return []; + + $parameters = str_repeat('?,', count($parentIDs) - 1) . '?'; + $sql = <<conn,$sql, $parentIDs); + $children = $result->fetch_all(MYSQLI_ASSOC); + $result->free(); + $children_ids = array_map(fn($v) => $v["geoThesID"], $children); + + return array_merge($children, $this->getChildren($children_ids)); + } catch(Exception $e) { + $this->errorMessage = 'ERROR getting children for geoUnits (' . implode(',', $parentIDs) .'): '. $e->getMessage(); + return []; + } + } + private function setChildCnt($geoIdStr){ $retArr = array(); - $sql = 'SELECT parentID, count(*) as cnt FROM geographicthesaurus WHERE parentID IN('.$geoIdStr.') GROUP BY parentID '; + $sql = 'SELECT parentID, count(*) AS cnt FROM geographicthesaurus WHERE parentID IN('.$geoIdStr.') GROUP BY parentID '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->parentID] = $r->cnt; @@ -208,10 +317,15 @@ public function getParentGeoTermArr($geoLevelMax = 0){ return $retArr; } - public function getAcceptedGeoTermArr($geoLevelMax = 0){ + public function getAcceptedGeoTermArr($geoLevelMax = 0, $parentID = 0){ $retArr = array(); $sql = 'SELECT geoThesID, geoTerm FROM geographicthesaurus '; - if($geoLevelMax) $sql .= 'WHERE (geoLevel = '.$geoLevelMax.') '; + $conditionArr = array(); + if($geoLevelMax) $conditionArr[] = '(geoLevel = '.$geoLevelMax.')'; + if($parentID) $conditionArr[] = '(parentID = '.$parentID.')'; + if($conditionArr){ + $sql .= 'WHERE ' . implode(' AND ', $conditionArr); + } $sql .= 'ORDER BY geoTerm'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ @@ -227,15 +341,15 @@ public function getGeoRankArr(){ $rankArr = $GLOBALS['GEO_THESAURUS_RANKING']; } else{ - $rankArr = array(10 => 'Oceans', 20 => 'Island Group', 30 => 'Island', 40 => 'Continent/Region', 50 => 'Country', 60 => 'ADM1', 70 => 'ADM2', 80 => 'ADM3', + $rankArr = array(10 => 'Oceans', 20 => 'Island Group', 30 => 'Island', 40 => 'Continent/Region', 50 => 'Country', 60 => 'State/Province', 70 => 'County', 80 => 'Municipality', 100 => 'City/Town', 110 => 'Place Name', 150 => 'Lake/Pond', 160 => 'River/Creek'); } return $rankArr; } //Reporting and data transfer functions - public function getThesaurusStatus(){ - $retArr = false; + public function getThesaurusStatus() { + $retArr = []; $fullCnt = 0; $sql = 'SELECT geoLevel, COUNT(*) as cnt FROM geographicthesaurus GROUP BY geoLevel'; $rs = $this->conn->query($sql); @@ -244,58 +358,63 @@ public function getThesaurusStatus(){ $fullCnt += $r->cnt; } $rs->free(); + try { - if($fullCnt < 100){ - $sql = 'SELECT COUNT(*) as cnt FROM lkupcountry '; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - $retArr['lkup']['country'] = $r->cnt; - } - $rs->free(); + if($this->lkupTablesExist() && $fullCnt < 100){ + $sql = 'SELECT COUNT(*) as cnt FROM lkupcountry '; + $rs = $this->conn->query($sql); + if($r = $rs->fetch_object()){ + $retArr['lkup']['country'] = $r->cnt; + } + $rs->free(); - $sql = 'SELECT COUNT(*) as cnt FROM lkupstateprovince '; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - $retArr['lkup']['state'] = $r->cnt; - } - $rs->free(); + $sql = 'SELECT COUNT(*) as cnt FROM lkupstateprovince '; + $rs = $this->conn->query($sql); + if($r = $rs->fetch_object()){ + $retArr['lkup']['state'] = $r->cnt; + } + $rs->free(); - $sql = 'SELECT COUNT(*) as cnt FROM lkupcounty '; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - $retArr['lkup']['county'] = $r->cnt; - } - $rs->free(); + $sql = 'SELECT COUNT(*) as cnt FROM lkupcounty '; + $rs = $this->conn->query($sql); + if($r = $rs->fetch_object()){ + $retArr['lkup']['county'] = $r->cnt; + } + $rs->free(); - $sql = 'SELECT COUNT(*) as cnt FROM lkupmunicipality '; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - $retArr['lkup']['municipality'] = $r->cnt; + $sql = 'SELECT COUNT(*) as cnt FROM lkupmunicipality '; + $rs = $this->conn->query($sql); + if($r = $rs->fetch_object()){ + $retArr['lkup']['municipality'] = $r->cnt; + } + $rs->free(); } - $rs->free(); + return !empty($retArr)? $retArr: false; + } catch(Exception $e) { + return false; } - return $retArr; } public function transferDeprecatedThesaurus(){ $status = true; + if(!$this->lkupTablesExist()) return false; $sqlArr = array(); $sqlArr[] = 'INSERT INTO geographicthesaurus(geoterm,iso2,iso3,numcode,category,geoLevel,termstatus) - SELECT countryName, iso, iso3, numcode, "Country", 50 as geoLevel, 1 as termStatus FROM lkupcountry WHERE iso IS NOT NULL'; + SELECT countryName, iso, iso3, numcode, "Country", 50 as geoLevel, 1 as termStatus FROM lkupcountry WHERE iso IS NOT NULL'; $sqlArr[] = 'UPDATE geographicthesaurus SET acceptedID = (SELECT geoThesID FROM geographicthesaurus WHERE geoTerm = "United States") WHERE geoterm IN("USA","U.S.A.","United States of America")'; $sqlArr[] = 'INSERT INTO geographicthesaurus(geoterm,abbreviation,parentID,category,geoLevel,termStatus) - SELECT DISTINCT s.stateName, s.abbrev, t.geoThesID, "State", 60 as geoLevel, 1 as termStatus - FROM lkupcountry c INNER JOIN lkupstateprovince s ON c.countryid = s.countryid - INNER JOIN geographicthesaurus t ON c.iso = t.iso2 - WHERE t.category = "country" AND t.termstatus = 1 AND t.acceptedID IS NULL'; + SELECT DISTINCT s.stateName, s.abbrev, t.geoThesID, "State", 60 as geoLevel, 1 as termStatus + FROM lkupcountry c INNER JOIN lkupstateprovince s ON c.countryid = s.countryid + INNER JOIN geographicthesaurus t ON c.iso = t.iso2 + WHERE t.category = "country" AND t.termstatus = 1 AND t.acceptedID IS NULL'; $sqlArr[] = 'INSERT INTO geographicthesaurus(geoterm,parentID,category,geoLevel,termStatus) - SELECT DISTINCT REPLACE(REPLACE(REPLACE(c.countyName," Co.","")," County","")," Parish",""), t.geoThesID, "County", 70 as geoLevel, 1 as termStatus - FROM lkupstateprovince s INNER JOIN lkupcounty c ON s.stateid = c.stateid - INNER JOIN geographicthesaurus t ON s.stateName = t.geoterm - WHERE t.category = "State" AND t.termstatus = 1'; + SELECT DISTINCT REPLACE(REPLACE(REPLACE(c.countyName," Co.","")," County","")," Parish",""), t.geoThesID, "County", 70 as geoLevel, 1 as termStatus + FROM lkupstateprovince s INNER JOIN lkupcounty c ON s.stateid = c.stateid + INNER JOIN geographicthesaurus t ON s.stateName = t.geoterm + WHERE t.category = "State" AND t.termstatus = 1'; foreach($sqlArr as $sql){ if(!$this->conn->query($sql)){ @@ -319,24 +438,23 @@ public function getGBCountryList(){ $retArr[$key]['id'] = $countryObj->boundaryID; $retArr[$key]['name'] = $countryObj->boundaryName; $retArr[$key]['canonical'] = $countryObj->boundaryCanonical; + $retArr[$key]['iso'] = $countryObj->boundaryISO; $retArr[$key]['license'] = $this->licenseTranslate($countryObj->boundaryLicense); $region = ''; if(in_array($countryObj->Continent,$contArr)) $region = $countryObj->Continent; elseif(in_array($countryObj->{'UNSDG-subregion'},$contArr)) $region = $countryObj->{'UNSDG-subregion'}; else $region = $countryObj->Continent.'/'.$countryObj->{'UNSDG-subregion'}; if($region == 'Northern America') $region == 'North America'; - if($key == 'ATA') $region = 'Antartica'; + if($countryObj->boundaryISO == 'ATA') $region = 'Antartica'; $retArr[$key]['region'] = $region; - //$retArr[$key]['geoJSON'] = $countryObj->gjDownloadURL; - //$retArr[$key]['simplifiedGeoJSON'] = $countryObj->simplifiedGeometryGeoJSON; - $retArr[$key]['link'] = $countryObj->apiURL; + $retArr[$key]['geoJson'] = $countryObj->simplifiedGeometryGeoJSON; $retArr[$key]['img'] = $countryObj->imagePreview; } ksort($retArr); //Check to see if country is already in thesaurus $sql = 'SELECT g.geoThesID, g.iso3, p.geoThesID AS polygonID FROM geographicthesaurus g LEFT JOIN geographicpolygon p ON g.geoThesID = p.geoThesID - WHERE g.geoLevel = 50 AND g.acceptedID IS NULL AND g.iso3 IN("'.implode('","',array_keys($retArr)).'")'; + WHERE g.geoLevel = 50 AND g.acceptedID IS NULL AND g.iso3 IN("'.implode('","', array_keys($retArr)) .'")'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->iso3]['geoThesID'] = $r->geoThesID; @@ -344,13 +462,15 @@ public function getGBCountryList(){ } $rs->free(); } + return $retArr; } public function getGBGeoList($countryCode){ $retArr = array(); $contArr = $this->getContinentArr(); - $url = 'https://www.geoboundaries.org/api/current/gbOpen/'.$countryCode.'/ALL/'; + $urlBase = 'https://www.geoboundaries.org/api/current/gbOpen/'; + $url = $urlBase . $countryCode.'/ALL/'; $json = $this->getGeoboundariesJSON($url); $obj = json_decode($json); if($obj){ @@ -370,24 +490,43 @@ public function getGBGeoList($countryCode){ if($region == 'Northern America') $region == 'North America'; if($countryCode == 'ATA') $region = 'Antartica'; $retArr[$type]['region'] = $region; - $retArr[$type]['geoJson'] = $boundaryObj->gjDownloadURL; - $retArr[$type]['simpleGeoJson'] = $boundaryObj->simplifiedGeometryGeoJSON; - $retArr[$type]['link'] = $boundaryObj->apiURL; + $retArr[$type]['gbCount'] = $boundaryObj->admUnitCount; + //$retArr[$type]['geoJson'] = $boundaryObj->gjDownloadURL; + $retArr[$type]['geoJson'] = $boundaryObj->simplifiedGeometryGeoJSON; + $retArr[$type]['link'] = $urlBase.$countryCode.'/'.$type.'/'; $retArr[$type]['img'] = $boundaryObj->imagePreview; } } ksort($retArr); //Check to see if country is already in thesaurus - $sql = 'SELECT g.geoThesID, g.iso3, p.geoThesID AS polygonID - FROM geographicthesaurus g LEFT JOIN geographicpolygon p ON g.geoThesID = p.geoThesID - WHERE g.geoLevel = 50 AND g.acceptedID IS NULL AND g.iso3 IN("'.$countryCode.'")'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $retArr['ADM0']['geoThesID'] = $r->geoThesID; - if($r->polygonID) $retArr['ADM0']['polygon'] = 1; + $sql = <<conn,$sql, [$countryCode]); + if(($row = $result->fetch_object()) && isset($retArr['ADM0'])) { + $retArr['ADM0']['geoThesID'] = $row->geoThesID; + if($row->polygonID) $retArr['ADM0']['polygon'] = 1; + $children = $this->getChildren([$row->geoThesID]); + + $result->free(); + + foreach ($retArr as $key => $value) { + if($key === 'ADM0') continue; + $geoLevel = $this->getGeoLevel($key); + $geoThesIDs = array_filter($children, fn($val) => $val['hasPolygon'] === 1 && $val['geoLevel'] === $geoLevel); + if(count($geoThesIDs) > 0) { + $retArr[$key]['geoThesID'] = $geoThesIDs; + $retArr[$key]['polygon'] = 1; + } + } + } + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR getGBGeoList on iso3 ' . $countryCode . ': ' . $e->getMessage(); } - $rs->free(); - $this->checkLowerDivision($retArr); } return $retArr; } @@ -410,104 +549,372 @@ private function licenseTranslate($licenseStr){ return $retStr; } - private function checkLowerDivision(&$retArr, $type = 'ADM1'){ - $admLevel = substr($type,-1); - $geoLevel = 0; - if($admLevel == 1) $geoLevel = 60; - elseif($admLevel == 2) $geoLevel = 70; - elseif($admLevel == 3) $geoLevel = 80; - elseif($admLevel > 3) return false; - if($geoLevel){ - $admNext = 'ADM'.($admLevel+1); - if(isset($retArr[$type]['geoThesID']) && isset($retArr[$admNext])){ - $sql = 'SELECT g.geoThesID, g.iso3, p.geoThesID AS polygonID - FROM geographicthesaurus g LEFT JOIN geographicpolygon p ON g.geoThesID = p.geoThesID - WHERE g.geoLevel = '.$geoLevel.' AND g.acceptedID IS NULL AND g.parentID = '.$retArr[$type]['geoThesID']; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $retArr[$admNext]['geoThesID'] = $r->geoThesID; - if($r->polygonID) $retArr[$admNext]['polygon'] = 1; - $this->checkLowerDivision($retArr, $admNext); + //Assumes Most Points probably the biggest or main polygon which is fine for + //this function + private function getBiggestPolygon($arr) { + if(!is_array($arr) || count($arr) === 0) { + return null; + } elseif(is_array($arr[0]) && count($arr[0]) === 2 && !is_array($arr[0][0])) { + return $arr; + } else { + $maxPoly = []; + for ($i=0; $i < count($arr); $i++) { + $poly = $this->getBiggestPolygon($arr[$i]); + if(count($maxPoly) < count($poly)) { + $maxPoly = $poly; } - $rs->free(); } + return $maxPoly; } } - public function addGeoBoundary($gbID){ - $status = false; - $url = 'https://www.geoboundaries.org/api/gbID/'.$gbID.'/'; - $json = $this->getGeoboundariesJSON($url); - $obj = json_decode($json); + private function normalize($v) { + $mag = sqrt(pow($v[0], 2) + pow($v[1], 2)); + if($mag === 0) return; + $v[0] = $v[0] / $mag; + $v[1] = $v[1] / $mag; + return $v; + } - return $status; + private function getIntersection($v1,$o1, $v2, $o2) { + + //Currently igonores vertical slope case + $m1 = $v1[0] != 0? $v1[1] / $v1[0]: 0; + $m2 = $v2[0] != 0? $v2[1] / $v2[0]: 0; + + //If slopes are paralell then no intersection + if($m1 === $m2) return false; + + $dm = ($m1 - $m2); + + $x = $dm != 0? ($o2 - $o1) / $dm: 0; + return [ + $x, + ($m2 * $x) + $o2 + ]; } - private function getGeoThesID($iso3, $type){ - $countryGeoThesID = 0; - $sql = 'SELECT geoThesID FROM geographicthesaurus WHERE iso3 = "'.$iso3.'"'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $countryGeoThesID = $r->geoThesID; - } - $rs->free(); - if(!$countryGeoThesID) $countryGeoThesID = $this->insertGeoBoundary(); - if($type = 'ADM0') return $countryGeoThesID; - if($countryGeoThesID){ - if($type = 'ADM1'){ - $this->getGeoThesID($iso3, 'ADM1'); + private function getPointWithinPoly($coordinates) { + $polygon = $this->getBiggestPolygon($coordinates); + + if(!is_array($polygon) || count($polygon) < 2) return false; + + $maxDistance = 0; + $maxIndex = 1; + + for ($i=1; $i < count($polygon); $i++) { + $v1 = $polygon[$i]; + $v2 = $polygon[$i - 1]; + + $distance = sqrt(pow($v1[0] - $v2[0], 2) + pow($v1[1] - $v2[1], 2)); + if ($maxDistance < $distance) { + $maxIndex = $i; + $maxDistance = $distance; } } - } - private function insertGeoBoundary(){ - $retID = 0; - $sql = ''; - if($this->conn->query($sql)){ - $retID = $this->conn->insert_id; + $start = [ + ($polygon[$maxIndex - 1][0] + $polygon[$maxIndex][0]) / 2, + ($polygon[$maxIndex - 1][1] + $polygon[$maxIndex][1]) / 2 + ]; + $ray = $this->normalize([ + -($polygon[$maxIndex - 1][1] - $polygon[$maxIndex][1]), + $polygon[$maxIndex - 1][0] - $polygon[$maxIndex][0] + ]); + $crosses = []; + + for ($i=1; $i < count($polygon); $i++) { + $edge1 = $polygon[$i - 1]; + $edge2 = $polygon[$i]; + + $pt = [($edge1[0] + $edge2[0]) / 2, ($edge1[1] + $edge2[1]) / 2]; + + if ($i === $maxIndex) { + continue; + } + + $edgeVector = $this->normalize([$edge1[0] - $edge2[0], $edge1[1] - $edge2[1]]); + + $ray_offest = $start[1] - (($ray[0] != 0? $ray[1] / $ray[0]: 0) * $start[0]); + $edge_offset = $edge1[1] - (($edgeVector[0] != 0? $edgeVector[1] / $edgeVector[0]: 0) * $edge1[0]); + + $intersection = $this->getIntersection($ray, $ray_offest, $edgeVector, $edge_offset); + + if(!$intersection) continue; + + $longBounds = ($edge1[0] <= $intersection[0] && $edge2[0] >= $intersection[0]) || + ($edge1[0] >= $intersection[0] && $edge2[0] <= $intersection[0]); + + $latBounds = ($edge1[1] <= $intersection[1] && $edge2[1] >= $intersection[1]) || + ($edge1[1] >= $intersection[1] && $edge2[1] <= $intersection[1]); + + if($latBounds && $longBounds) { + array_push($crosses, $intersection); + } } - else{ - echo '
    ERROR inserting geoBoundary: '.$this->conn->query().'
    '; + + if (count($crosses) === 0) { + return false; } - return $retID; + + array_push($crosses, $start); + + usort($crosses, function ($a, $b) { + if ($a[0] === $b[0]) { + return $a[1] === $b[1]? 0: ($a[1] > $b[1]? 1: -1); + } else { + return $a[1] > $b[1]? 1: -1; + } + }); + + $pt = ["long" => ($crosses[0][0] + $crosses[1][0]) / 2, "lat" => ($crosses[0][1] + $crosses[1][1]) / 2]; + return $pt; } - private function addGBPolygon($gbID){ - $status = false; - $url = 'https://www.geoboundaries.org/api/gbID/'.$gbID.'/'; + public function addGeoBoundary(string $url, bool $addMissing = false, int $baseParentId = null, array $potentialParents = []): array { $json = $this->getGeoboundariesJSON($url); $obj = json_decode($json); - if(isset($obj->simplifiedGeometryGeoJSON)){ - $geoJson = $this->getGeoboundariesJSON($obj->simplifiedGeometryGeoJSON); - $geoJson = $this->cleanGeoJson($geoJson); - $sql = 'REPLACE INTO geographicpolygon(geoThesID, footprintPolygon, geoJson) - SELECT geoThesID, ST_GeomFromGeoJSON(\''.$geoJson.'\'),\''.$geoJson.'\' FROM geographicthesaurus WHERE acceptedID IS NULL AND iso3 = "'.$obj->boundaryISO.'"'; - if($this->conn->query($sql)){ - $status = true; + unset($json); + + $results = []; + + foreach ($obj->features as $feature) { + $properties = $feature->properties; + $geoLevel = $this->getGeoLevel($properties->shapeType); + $parentID = null; + + if($properties->shapeName === null) continue; + + $geoThesIDs = $this->getGeoThesIDByName($properties->shapeName, $geoLevel, $potentialParents); + $iso = !empty($properties->shapeGroup)? $properties->shapeGroup: $properties->shapeISO; + + //only does iso check for adm0 or countries because only case where + //there is just one + if (empty($geoThesIDs) && $geoLevel === 50) { + $geoThesIDs = $this->getGeoThesIDByIso3($iso, $geoLevel); } - else{ - $this->errorMessage = $this->conn->error; - echo 'ERROR adding geoJSON to database: '.$this->conn->error; + $geoThesIDs = array_filter( + $geoThesIDs, + fn($val) => $val['hasPolygon'] === 0 + ); + + if(is_array($geoThesIDs) && count($geoThesIDs) != 1) { + $testPoint = $this->getPointWithinPoly($feature->geometry->coordinates); + $parents = !empty($geoThesIDs)? + array_filter(array_map(fn($val) => $val['parentID'], $geoThesIDs), fn($val) => $val !== null): + $potentialParents; + + if($testPoint) { + $parentID = $this->findParentGeometry( + $testPoint["lat"], + $testPoint["long"], + $geoLevel - 10, + //map and grab parentIds + $parents + ); + $geoThesIDs = array_filter( + $this->getGeoThesIDByName($properties->shapeName, $geoLevel, [$parentID]), + fn($val) => $val['hasPolygon'] === 0, + ); + } + } + + if(is_array($geoThesIDs) && count($geoThesIDs) === 1) { + $key = array_keys($geoThesIDs)[0]; + $this->addPolygon($geoThesIDs[$key]['geoThesID'], json_encode($feature)); + //update iso3 because data could be missing or wrong + if($iso !== $geoThesIDs[$key]['iso3']) { + try { + $sql = <<<'SQL' + UPDATE geographicthesaurus set iso3 = ? where geoThesID = ? + SQL; + SymbUtil::execute_query($this->conn,$sql, [$iso, $geoThesIDs[$key]['geoThesID']]); + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR updating iso3 to match boundaryISO:' . $e->getMessage(); + } + } + array_push($results, $geoThesIDs[$key]['geoThesID']); + } else if ($addMissing) { + array_push($results, $this->addGeoUnit([ + "geoTerm" => $properties->shapeName, + "iso2" =>"", + "iso3" => $iso, + "geoLevel" => $geoLevel, + "abbreviation" =>"", + "numCode" =>"", + "acceptedID" => "", + "parentID" => $parentID ?$parentID: $baseParentId, + "notes" =>"", + "polygon" => json_encode($feature), + ])); } } - return $status; + + return $results; } - private function cleanGeoJson($geoJson){ - $jsonObj = json_decode($geoJson); - $retObj = []; - foreach($jsonObj->features as $fKey => $featureObj){ - foreach($featureObj->geometry->coordinates as $coordKey1 => $coordObj1){ - foreach($coordObj1 as $coordKey2 => $coordObj2){ - $jsonObj->features[$fKey]->geometry->coordinates[$coordKey1][$coordKey2][0] = round($coordObj2[0],6); - $jsonObj->features[$fKey]->geometry->coordinates[$coordKey1][$coordKey2][1] = round($coordObj2[1],6); - } + + public function getGeoLevelString(int $geolevel) { + switch($geolevel) { + case 10: return 'Oceans'; + case 20: return 'Island Group'; + case 30: return 'Island'; + case 40: return 'Continent/Region'; + case 50: return 'Country'; + case 60: return 'State/Province'; + case 70: return 'County'; + case 80: return 'Municipality'; + case 100: return 'City/Town'; + case 110: return 'Place Name'; + case 150: return 'Lake/Pond'; + case 160: return 'River/Creek'; + //This seems like a sensible default + default: return "Place Name"; + } + } + + public function searchGeothesaurus(string $geoterm, int|null $geolevel = null, string|null $parent = null, bool $distict_geoterms = false): array { + $sql = <<conn,$sql, $params); + + $geoterms = $result->fetch_all(MYSQLI_ASSOC); + for($i=0; $i < count($geoterms); $i++) { + $label = $geoterms[$i]["geoterm"]; + + if($geolevel === null) { + $label .= " (" . $this->getGeoLevelString($geoterms[$i]["geoLevel"]) . ")"; } + if($geoterms[$i]["parentID"] !== null) { + $label .= " child of " . $geoterms[$i]["parentterm"] . " (" . $this->getGeoLevelString($geoterms[$i]["parentlevel"]) . ")"; + } + + $geoterms[$i]['label'] = $label; + } + + return $geoterms; + } + + public function getGeoLevel(string $type): int { + switch ($type) { + case 'ADM1': + return 60; + case 'ADM2': + return 70; + case 'ADM3': + return 80; + default: + return 50; + } + } + + public function findParentGeometry($lat, $long, $parentGeoLevel = 50, $potentialParents = []) { + $sql = <<<'SQL' + SELECT g.geoThesID from geographicthesaurus g + join geographicpolygon gp on g.geoThesID = gp.geoThesID + WHERE ST_CONTAINS(gp.footprintPolygon, ST_GEOMFROMTEXT(?)) = 1 and + g.geoLevel <= ? + SQL; + + $geom = 'POINT (' . $long . ' '. $lat . ')'; + + if(!empty($potentialParents)) { + $sql.=' and g.geoThesID in ('. implode(',', $potentialParents) .')'; + } + + $sql .= ' ORDER BY g.geoLevel DESC'; + + try { + $stmt = $this->conn->prepare($sql); + $stmt->bind_param("si", $geom, $parentGeoLevel); + $stmt->execute(); + $stmt->bind_result($result); + $stmt->fetch(); + + return $result; + } catch(Exception $e) { + $this->errorMessage = 'ERROR while finding parent polygon: ' . $e->getMessage(); + return false; + } + + } + + public function getGeoThesIDByName(string $geoTerm, int $geoLevel = null, array $parentIDs = []): array { + $params = [$geoTerm]; + $sql = <<conn,$sql, $params); + $geoThesID = $result->fetch_all(MYSQLI_ASSOC); + $result->free(); + return $geoThesID; + + } catch (\Throwable $e) { + $this->errorMessage = 'ERROR getGeoThesIDByName for ' . $geoTerm. ':' . $e->getMessage(); + return []; + } + } + + private function getGeoThesIDByIso3($iso3, $geoLevel = null){ + $params = [$iso3]; + $sql = <<conn,$sql, $params); + $geoThesID = $result->fetch_all(MYSQLI_ASSOC); + $result->free(); + return $geoThesID; + } catch(\Throwable $e) { + $this->errorMessage = 'ERROR getGeoThesIDByIso3 for ' . $iso3 . ':' . $e->getMessage(); + return []; } - return json_encode($jsonObj->features[0]->geometry); } private function getGeoboundariesJSON($url){ @@ -532,10 +939,17 @@ private function getContinentArr(){ return array('Asia','Caribbean','Oceania','Africa','Europe','Central America','Northern America','South America'); } - // Setters and getters - - - - + //Mics support functions + private function lkupTablesExist(){ + $bool = false; + // Check to see is old deprecated lookup tables exist + $sql = 'SHOW tables LIKE "lkupcountry"'; + $rs = $this->conn->query($sql); + if($rs->num_rows){ + $bool = true; + } + $rs->free(); + return $bool; + } } -?> \ No newline at end of file +?> diff --git a/classes/GlossaryManager.php b/classes/GlossaryManager.php index 7969a908a1..d2f1e496f6 100644 --- a/classes/GlossaryManager.php +++ b/classes/GlossaryManager.php @@ -32,18 +32,18 @@ class GlossaryManager extends Manager { public function __construct(){ parent::__construct(null, 'write'); - $this->imageRootPath = $GLOBALS["imageRootPath"]; + $this->imageRootPath = $GLOBALS['$IMAGE_ROOT_PATH']; if(substr($this->imageRootPath,-1) != "/") $this->imageRootPath .= "/"; - $this->imageRootUrl = $GLOBALS["imageRootUrl"]; + $this->imageRootUrl = $GLOBALS['$IMAGE_ROOT_URL']; if(substr($this->imageRootUrl,-1) != "/") $this->imageRootUrl .= "/"; - if(array_key_exists('imgTnWidth',$GLOBALS)){ - $this->tnPixWidth = $GLOBALS['imgTnWidth']; + if(!empty($GLOBALS['IMG_TN_WIDTH'])){ + $this->tnPixWidth = $GLOBALS['IMG_TN_WIDTH']; } - if(array_key_exists('imgWebWidth',$GLOBALS)){ - $this->webPixWidth = $GLOBALS['imgWebWidth']; + if(!empty($GLOBALS['IMG_WEB_WIDTH'])){ + $this->webPixWidth = $GLOBALS['IMG_WEB_WIDTH']; } - if(array_key_exists('imgFileSizeLimit',$GLOBALS)){ - $this->webFileSizeLimit = $GLOBALS['imgFileSizeLimit']; + if(!empty($GLOBALS['IMG_FILE_SIZE_LIMIT'])){ + $this->webFileSizeLimit = $GLOBALS['IMG_FILE_SIZE_LIMIT']; } } @@ -62,11 +62,15 @@ public function getTermSearch($keyword, $language, $tid, $deepSearch = 1){ $sqlWhere .= ') '; } if($language) $sqlWhere .= 'AND (g.language = "'.$this->cleanInStr($language).'") '; - if($tid) $sqlWhere .= 'AND (t.tid = '.$tid.' OR t2.tid = '.$tid.') '; + if($tid) $sqlWhere .= 'AND (taxalink.tid = '.$tid.' OR taxalink2.tid = '.$tid.') '; $sql = 'SELECT DISTINCT g.glossid, g.term, g.definition, tl.relationshipType, g2.glossid as glossid2, g2.term AS term2, g2.definition AS def2 FROM glossary g LEFT JOIN glossarytermlink tl ON g.glossid = tl.glossgrpid LEFT JOIN glossary g2 ON tl.glossid = g2.glossid '; - if($tid) $sql .= 'INNER JOIN glossarytermlink tl ON g.glossid = tl.glossid INNER JOIN glossarytaxalink t ON tl.glossgrpid = t.glossid INNER JOIN glossarytaxalink t2 ON g.glossid = t2.glossid '; + if($tid){ + $sql .= 'LEFT JOIN glossarytermlink termlink ON g.glossid = termlink.glossid + LEFT JOIN glossarytaxalink taxalink ON termlink.glossgrpid = taxalink.glossid + LEFT JOIN glossarytaxalink taxalink2 ON g.glossid = taxalink2.glossid '; + } if($sqlWhere) $sql .= 'WHERE '.substr($sqlWhere, 3); if($rs = $this->conn->query($sql)){ while($r = $rs->fetch_object()){ @@ -103,6 +107,15 @@ public function getTermArr(){ return $retArr; } + public function remapDescriptionCrossLinks(&$termArr){ + if(!empty($termArr['definition'])){ + $subjectStr = $termArr['definition']; + $pattern = '/href=["\']*([A-Za-z -]+)["\']*/i'; + $replacement = 'href="' . $GLOBALS['CLIENT_ROOT'] . '/glossary/individual.php?term=${1}"'; + $termArr['definition'] = preg_replace($pattern, $replacement, $subjectStr); + } + } + public function getTermTaxaArr(){ if(!$this->tidArr) $this->tidArr = $this->getTaxaArr(); return $this->tidArr; @@ -574,7 +587,7 @@ public function getTaxonSources($tidStr = ''){ public function addSource($pArr){ $status = true; - if(is_numeric($pArr['tid'])){ + if($pArr['tid'] && is_numeric($pArr['tid'])){ $terms = $this->cleanInStr($pArr['contributorTerm']); $images = $this->cleanInStr($pArr['contributorImage']); $translator = $this->cleanInStr($pArr['translator']); @@ -898,9 +911,10 @@ private function databaseImage($imgWebUrl,$imgTnUrl,$imgLgUrl){ global $SYMB_UID; if(!$imgWebUrl) return 'ERROR: web url is null '; $urlBase = $this->urlBase; - //If central images are on remote server and new ones stored locally, then we need to use full domain - //e.g. this portal is sister portal to central portal - if($GLOBALS['imageDomain']) $urlBase = $this->getDomain().$urlBase; + if(!empty($GLOBALS['IMAGE_DOMAIN'])){ + //Central images are on remote server and new ones stored locally, thus need to use full local domain (this portal is sister portal to central portal) + $urlBase = $this->getDomain().$urlBase; + } if(strtolower(substr($imgWebUrl,0,7)) != 'http://' && strtolower(substr($imgWebUrl,0,8)) != 'https://'){ $imgWebUrl = $urlBase.$imgWebUrl; } diff --git a/classes/GlossaryUpload.php b/classes/GlossaryUpload.php index e77eaf7f08..76ff8a7616 100644 --- a/classes/GlossaryUpload.php +++ b/classes/GlossaryUpload.php @@ -16,8 +16,7 @@ function __construct() { $this->conn = MySQLiConnectionFactory::getCon("write"); $this->setUploadTargetPath(); set_time_limit(3000); - ini_set("max_input_time",120); - ini_set('auto_detect_line_endings', true); + ini_set('max_input_time', 120); } function __destruct(){ @@ -443,11 +442,8 @@ private function getUploadGlossaryFieldArr(){ //Setters and getters private function setUploadTargetPath(){ - $tPath = $GLOBALS["tempDirRoot"]; - if(!$tPath){ - $tPath = ini_get('upload_tmp_dir'); - } - if(!$tPath && isset($GLOBALS["TEMP_DIR_ROOT"])){ + $tPath = ini_get('upload_tmp_dir'); + if(!$tPath && !empty($GLOBALS['TEMP_DIR_ROOT'])){ $tPath = $GLOBALS['TEMP_DIR_ROOT']; } if(!$tPath){ @@ -522,34 +518,20 @@ private function encodeArr(&$inArr){ } private function encodeString($inStr){ - global $CHARSET; $retStr = $inStr; - //Get rid of UTF-8 curly smart quotes and dashes - $badwordchars=array("\xe2\x80\x98", // left single quote - "\xe2\x80\x99", // right single quote - "\xe2\x80\x9c", // left double quote - "\xe2\x80\x9d", // right double quote - "\xe2\x80\x94", // em dash - "\xe2\x80\xa6" // elipses - ); - $fixedwordchars=array("'", "'", '"', '"', '-', '...'); - $inStr = str_replace($badwordchars, $fixedwordchars, $inStr); - if($inStr){ - if(strtolower($CHARSET) == "utf-8" || strtolower($CHARSET) == "utf8"){ - //$this->outputMsg($inStr.': '.mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true); - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($CHARSET) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } - } + //Get rid of UTF-8 curly smart quotes and dashes + $badwordchars=array("\xe2\x80\x98", // left single quote + "\xe2\x80\x99", // right single quote + "\xe2\x80\x9c", // left double quote + "\xe2\x80\x9d", // right double quote + "\xe2\x80\x94", // em dash + "\xe2\x80\xa6" // elipses + ); + $fixedwordchars=array("'", "'", '"', '"', '-', '...'); + $retStr = str_replace($badwordchars, $fixedwordchars, $inStr); + $retStr = mb_convert_encoding($retStr, $GLOBALS['CHARSET'], mb_detect_encoding($retStr)); + } return $retStr; } } diff --git a/classes/ImInventories.php b/classes/ImInventories.php index 0772594667..0515150948 100644 --- a/classes/ImInventories.php +++ b/classes/ImInventories.php @@ -4,7 +4,13 @@ class ImInventories extends Manager{ private $clid; + private $clTaxaID; + private $voucherID; private $pid; + private $fieldMap = array(); + private $parameterArr = array(); + private $typeStr = ''; + private $primaryKey; public function __construct($conType = 'write') { parent::__construct(null, $conType); @@ -15,21 +21,21 @@ public function __destruct(){ } //Checklist functions - public function getChecklistMetadata($pid){ + public function getChecklistMetadata($pid = null){ $retArr = array(); if($this->clid){ $sql = 'SELECT clid, name, locality, publication, abstract, authors, parentclid, notes, latcentroid, longcentroid, pointradiusmeters, - access, defaultsettings, dynamicsql, datelastmodified, uid, type, footprintwkt, sortsequence, initialtimestamp + access, defaultsettings, dynamicsql, datelastmodified, dynamicProperties, uid, type, footprintwkt, sortsequence, initialtimestamp FROM fmchecklists WHERE (clid = '.$this->clid.')'; $result = $this->conn->query($sql); if($row = $result->fetch_object()){ - $retArr['name'] = $this->cleanOutStr($row->name); - $retArr['locality'] = $this->cleanOutStr($row->locality); - $retArr['notes'] = $this->cleanOutStr($row->notes); + $retArr['name'] = $row->name; + $retArr['locality'] = $row->locality; + $retArr['notes'] = $row->notes; $retArr['type'] = $row->type; - $retArr['publication'] = $this->cleanOutStr($row->publication); - $retArr['abstract'] = $this->cleanOutStr($row->abstract); - $retArr['authors'] = $this->cleanOutStr($row->authors); + $retArr['publication'] = $row->publication; + $retArr['abstract'] = $row->abstract; + $retArr['authors'] = $row->authors; $retArr['parentclid'] = $row->parentclid; $retArr['uid'] = $row->uid; $retArr['latcentroid'] = $row->latcentroid; @@ -39,13 +45,15 @@ public function getChecklistMetadata($pid){ $retArr['defaultsettings'] = $row->defaultsettings; $retArr['dynamicsql'] = $row->dynamicsql; $retArr['hasfootprintwkt'] = ($row->footprintwkt?'1':'0'); + $retArr['footprintwkt'] = $row->footprintwkt; $retArr['sortsequence'] = $row->sortsequence; $retArr['datelastmodified'] = $row->datelastmodified; + $retArr['dynamicProperties'] = $row->dynamicProperties; } $result->free(); if($retArr){ if($retArr['type'] == 'excludespp'){ - $sql = 'SELECT clid FROM fmchklstchildren WHERE clidchild = '.$this->clid; + $sql = 'SELECT clid FROM fmchklstchildren WHERE clid != clidchild AND clidchild = ' . $this->clid; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr['excludeparent'] = $r->clid; @@ -57,7 +65,7 @@ public function getChecklistMetadata($pid){ $rs = $this->conn->query($sql); if($rs){ if($r = $rs->fetch_object()){ - $retArr['clNameOverride'] = $this->cleanOutStr($r->clNameOverride); + $retArr['clNameOverride'] = $r->clNameOverride; $retArr['mapchecklist'] = $r->mapChecklist; $retArr['sortOverride'] = $r->sortSequence; } @@ -69,99 +77,89 @@ public function getChecklistMetadata($pid){ return $retArr; } - public function insertChecklist($fieldArr){ - $clid = false; - if($fieldArr['name']){ - $clName = $fieldArr['name']; - $authors = (!empty($fieldArr['authors']) ? $fieldArr['authors'] : NULL); - $type = (!empty($fieldArr['type']) ? $fieldArr['type'] : 'static'); - $locality = (!empty($fieldArr['locality']) ? $fieldArr['locality'] : NULL); - $publication = (!empty($fieldArr['publication']) ? $fieldArr['publication'] : NULL); - $abstract = (!empty($fieldArr['abstract']) ? strip_tags($fieldArr['abstract'], '') : NULL); - $notes = (!empty($fieldArr['notes']) ? $fieldArr['notes'] : NULL); - $latCentroid = (!empty($fieldArr['latcentroid']) && is_numeric($fieldArr['latcentroid']) ? $fieldArr['latcentroid'] : NULL); - $longCentroid = (!empty($fieldArr['longcentroid']) && is_numeric($fieldArr['longcentroid']) ? $fieldArr['longcentroid'] : NULL); - $pointRadiusMeters = (!empty($fieldArr['pointradiusmeters']) && is_numeric($fieldArr['pointradiusmeters']) ? $fieldArr['pointradiusmeters'] : NULL); - $access = (!empty($fieldArr['access']) ? $fieldArr['access'] : 'private'); - $defaultSettings = (!empty($fieldArr['defaultsettings']) ? $fieldArr['defaultsettings'] : NULL); - $dynamicSql = (!empty($fieldArr['dynamicsql']) ? $fieldArr['dynamicsql'] : NULL); - $uid = (!empty($fieldArr['uid']) && is_numeric($fieldArr['uid']) && $fieldArr['uid'] ? $fieldArr['uid'] : NULL); - $footprintWkt = (!empty($fieldArr['footprintwkt']) ? $fieldArr['footprintwkt'] : NULL); - $sortSequence = (!empty($fieldArr['sortsequence']) && is_numeric($fieldArr['sortsequence']) ? $fieldArr['sortsequence'] : 50); - $sql = 'INSERT INTO fmchecklists(name, authors, type, locality, publication, abstract, notes, latcentroid, longcentroid, pointradiusmeters, access, defaultsettings, dynamicsql, uid, footprintWkt, sortsequence) '. - 'VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) '; + public function insertChecklist($inputArr){ + $status = false; + if($inputArr['name']){ + if(empty($inputArr['uid'])) $inputArr['uid'] = $GLOBALS['SYMB_UID']; + $this->setChecklistFieldMap(); + $this->setParameterArr($inputArr); + $sql = 'INSERT INTO fmchecklists('; + $sqlValues = ''; + $paramArr = array(); + $delimiter = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sql .= $delimiter.$fieldName; + $sqlValues .= $delimiter.'?'; + $paramArr[] = $value; + $delimiter = ', '; + } + $sql .= ') VALUES('.$sqlValues.') '; if($stmt = $this->conn->prepare($sql)){ - $stmt->bind_param('sssssssdddsssisi', $clName, $authors, $type, $locality, $publication, $abstract, $notes, $latCentroid, $longCentroid, $pointRadiusMeters, $access, $defaultSettings, $dynamicSql, $uid, $footprintWkt, $sortSequence); + $stmt->bind_param($this->typeStr, ...$paramArr); if($stmt->execute()){ - if($stmt->affected_rows && !$stmt->error){ - $clid = $stmt->insert_id; + if($stmt->affected_rows || !$stmt->error){ + $this->primaryKey = $stmt->insert_id; + $status = true; } else $this->errorMessage = 'ERROR inserting fmchecklists record (2): '.$stmt->error; } else $this->errorMessage = 'ERROR inserting fmchecklists record (1): '.$stmt->error; $stmt->close(); } + else $this->errorMessage = 'ERROR preparing statement for fmchecklists insert: '.$this->conn->error; } - return $clid; + return $status; } public function updateChecklist($inputArr){ $status = false; + $oldMetadata = $this->getChecklistMetadata(); + $this->setChecklistFieldMap(); + $this->setParameterArr($inputArr); $sqlFrag = ''; - $fieldArr = array('name' => 's', 'authors' => 's', 'type' => 's', 'locality' => 's', 'publication' => 's', 'abstract' => 's', 'notes' => 's', 'latcentroid' => 'd', 'longcentroid' => 'd', - 'pointradiusmeters' => 'i', 'access' => 's', 'defaultsettings' => 's', 'dynamicsql' => 's', 'footprintWkt' => 's', 'uid' => 'i', 'sortsequence' => 'i'); - $typeStr = ''; $paramArr = array(); - foreach($inputArr as $fieldName => $fieldValue){ - $fieldName = strtolower($fieldName); - if(array_key_exists($fieldName, $fieldArr)){ - if($fieldArr[$fieldName] == 'i' || $fieldArr[$fieldName] == 'd'){ - if(!is_numeric($fieldValue)) $fieldValue = NULL; - if($fieldName == 'sortsequence' && !$fieldValue) $fieldValue = 50; - } - else{ - if(!$fieldValue) $fieldValue = NULL; - } - $sqlFrag .= $fieldName.' = ?, '; - $paramArr[] = $fieldValue; - $typeStr .= $fieldArr[$fieldName]; - } + foreach($this->parameterArr as $fieldName => $value){ + $sqlFrag .= $fieldName . ' = ?, '; + $paramArr[] = $value; } - $sql = 'UPDATE fmchecklists SET '.trim($sqlFrag,', ').' WHERE (clid = ?)'; + $sql = 'UPDATE fmchecklists SET '.trim($sqlFrag, ', ').' WHERE (clid = ?)'; if($paramArr){ $paramArr[] = $this->clid; - $typeStr .= 'i'; - if($stmt = $this->conn->prepare($sql)){ - $stmt->bind_param($typeStr, ...$paramArr); + $this->typeStr .= 'i'; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param($this->typeStr, ...$paramArr); if($stmt->execute()){ - if(!$stmt->error){ - $status = true; - } - else $this->errorMessage = 'ERROR updating fmchecklists record (2): '.$stmt->error; + if($stmt->affected_rows || !$stmt->error) $status = true; + else $this->errorMessage = 'ERROR updating fmchecklists record: '.$stmt->error; } else $this->errorMessage = 'ERROR updating fmchecklists record (1): '.$stmt->error; $stmt->close(); } + else $this->errorMessage = 'ERROR preparing statement for updating fmchecklists: '.$this->conn->error; if($status){ - if($inputArr['type'] == 'rarespp' && $inputArr['locality']){ - $sql = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid '. - 'INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '. - 'INNER JOIN fmchklsttaxalink cl ON ts2.tid = cl.tid '. - 'SET o.localitysecurity = 1 '. - 'WHERE (cl.clid = '.$this->clid.') AND (o.stateprovince = "'.$this->cleanInStr($inputArr['locality']).'") AND (o.localitySecurityReason IS NULL) '. - 'AND (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) '; - if(!$this->conn->query($sql)){ - $this->errorMessage = 'Error updating rare state species: '.$this->conn->error; + if($inputArr['type'] == 'rarespp'){ + if($inputArr['locality']){ + if(!$this->setStateBasedLocalitySecurity($this->cleanInStr($inputArr['locality']))){ + $this->errorMessage = 'Error updating rare state species: '.$this->errorMessage; + } + } + } + elseif($oldMetadata['type'] == 'rarespp'){ + //Checklist type changed from rarespp, thus remove state-based protections + if($inputArr['locality']){ + $this->removeStateLocalitySecurity($inputArr['locality']); } } elseif($inputArr['type'] == 'excludespp' && is_numeric($inputArr['excludeparent'])){ - $sql = 'INSERT IGNORE INTO fmchklstchildren(clid, clidchild) VALUES(?, ?)'; - if($stmt = $this->conn->prepare($sql)){ - $stmt->bind_param('ii', $inputArr['excludeparent'], $this->clid); - if(!$stmt->execute()){ - $this->errorMessage = 'Error updating parent checklist for exclusion species list: '.$this->conn->error; + if($inputArr['excludeparent'] != $this->clid){ + $sql = 'INSERT IGNORE INTO fmchklstchildren(clid, clidchild) VALUES(?, ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ii', $inputArr['excludeparent'], $this->clid); + if(!$stmt->execute()){ + $this->errorMessage = 'Error updating parent checklist for exclusion species list: ' . $this->conn->error; + } + $stmt->close(); } - $stmt->close(); } } } @@ -169,12 +167,18 @@ public function updateChecklist($inputArr){ return $status; } + private function setChecklistFieldMap(){ + $this->fieldMap = array('name' => 's', 'authors' => 's', 'type' => 's', 'locality' => 's', 'publication' => 's', 'abstract' => 's', 'notes' => 's', + 'latCentroid' => 'd', 'longCentroid' => 'd', 'pointRadiusMeters' => 'i', 'access' => 's', 'defaultSettings' => 's', 'dynamicSql' => 's', + 'dynamicProperties' => 's', 'uid' => 'i', 'footprintWkt' => 's', 'sortSequence' => 'i'); + } + public function deleteChecklist(){ $status = false; $roleArr = $this->getManagers('ClAdmin', 'fmchecklists', $this->clid); unset($roleArr[$GLOBALS['SYMB_UID']]); if(!$roleArr){ - $this->deleteChecklistTaxaLinks(); + $this->deleteChecklistTaxaLinksByClid(); $sql = 'DELETE FROM fmchecklists WHERE clid = ?'; if($stmt = $this->conn->prepare($sql)){ $stmt->bind_param('i', $this->clid); @@ -211,7 +215,96 @@ public function getChecklistArr($pid = 0){ } //Checklist taxa linkages - private function deleteChecklistTaxaLinks(){ + public function insertChecklistTaxaLink($inputArr){ + $status = false; + $this->setChecklistTaxaLinkFieldMap(); + $this->setParameterArr($inputArr); + if(!empty($this->parameterArr['clid']) && !empty($this->parameterArr['tid'])){ + if(!isset($this->parameterArr['morphoSpecies'])){ + $this->parameterArr['morphoSpecies'] = ''; + $this->typeStr .= 's'; + } + $sql = 'INSERT INTO fmchklsttaxalink('; + $sqlValues = ''; + $paramArr = array(); + $delimiter = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sql .= $delimiter . $fieldName; + $sqlValues .= $delimiter . '?'; + if($fieldName == 'morphoSpecies' && !$value) $paramArr[] = ''; + else $paramArr[] = $value; + $delimiter = ', '; + } + $sql .= ') VALUES(' . $sqlValues . ') '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param($this->typeStr, ...$paramArr); + if($stmt->execute()){ + if($stmt->affected_rows || !$stmt->error){ + $this->primaryKey = $stmt->insert_id; + $status = true; + } + else $this->errorMessage = 'ERROR inserting fmchklsttaxalink record (2): ' . $stmt->error; + } + else $this->errorMessage = 'ERROR inserting fmchklsttaxalink record (1): ' . $stmt->error; + $stmt->close(); + } + else $this->errorMessage = 'ERROR preparing statement for fmchklsttaxalink insert: ' . $this->conn->error; + $this->clid = $this->parameterArr['clid']; + //If checklist is a state based locality security checklist, adjust matching checklists + $clMetadata = $this->getChecklistMetadata(); + if($clMetadata['type'] == 'rarespp' && $clMetadata['locality']){ + $this->setStateBasedLocalitySecurity($clMetadata['locality'], $this->parameterArr['tid']); + } + } + return $status; + } + + public function updateChecklistTaxaLink($inputArr){ + $status = false; + $this->setChecklistTaxaLinkFieldMap(); + $this->setParameterArr($inputArr); + $sqlFrag = ''; + $paramArr = array(); + foreach($this->parameterArr as $fieldName => $value){ + $sqlFrag .= $fieldName . ' = ?, '; + $paramArr[] = $value; + } + $sql = 'UPDATE IGNORE fmchklsttaxalink SET ' . trim($sqlFrag, ', ') . ' WHERE (clTaxaID = ?)'; + if($paramArr){ + $paramArr[] = $this->clTaxaID; + $this->typeStr .= 'i'; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param($this->typeStr, ...$paramArr); + if($stmt->execute()){ + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = $stmt->error; + } + else $this->errorMessage = $stmt->error; + $stmt->close(); + } + else $this->errorMessage = $this->conn->error; + } + return $status; + } + + public function deleteChecklistTaxaLink(){ + $status = false; + if($this->clTaxaID){ + $sql = 'DELETE FROM fmchklsttaxalink WHERE clTaxaID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->clTaxaID); + $stmt->execute(); + if($stmt->affected_rows && !$stmt->error){ + $status = true; + } + else $this->errorMessage = $stmt->error; + $stmt->close(); + } + } + return $status; + } + + private function deleteChecklistTaxaLinksByClid(){ $status = false; if($this->clid){ $sql = 'DELETE FROM fmchklsttaxalink WHERE clid = ?'; @@ -226,20 +319,228 @@ private function deleteChecklistTaxaLinks(){ return $status; } + private function setChecklistTaxaLinkFieldMap(){ + $this->fieldMap = array('clid' => 'i', 'tid' => 'i', 'morphoSpecies' => 's', 'familyOverride' => 's', 'habitat' => 's', 'abundance' => 's', 'notes' => 's', + 'explicitExclude' => 'i', 'source' => 's', 'nativity' => 's', 'endemic' => 's', 'invasive' => 's', 'internalNotes' => 's'); + } + + //Checklist vouchers management + public function insertChecklistVoucher($inputArr){ + $status = false; + if($this->clTaxaID && is_numeric($inputArr['occid'])){ + $this->setChecklistVoucherFieldMap(); + $this->setParameterArr($inputArr); + $sql = 'INSERT IGNORE INTO fmvouchers('; + $paramArr = array(); + $sqlValues = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sql .= $fieldName . ', '; + $sqlValues .= '?, '; + $paramArr[] = $value; + } + $paramArr[] = $this->clTaxaID; + $this->typeStr .= 'i'; + $sql .= 'clTaxaID) VALUES(' . $sqlValues . '?) '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param($this->typeStr, ...$paramArr); + if($stmt->execute()){ + if($stmt->affected_rows || !$stmt->error){ + $this->primaryKey = $stmt->insert_id; + $status = true; + } + else $this->errorMessage = $stmt->error; + } + else $this->errorMessage = $stmt->error; + $stmt->close(); + } + else $this->errorMessage = $this->conn->error; + } + return $status; + } + + public function updateChecklistVoucher($inputArr){ + $status = false; + if($this->voucherID){ + $this->setChecklistVoucherFieldMap(); + $this->setParameterArr($inputArr); + $paramArr = array(); + $sqlFrag = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sqlFrag .= $fieldName . ' = ?, '; + $paramArr[] = $value; + } + $paramArr[] = $this->voucherID; + $this->typeStr .= 'i'; + $sql = 'UPDATE IGNORE fmvouchers SET '.trim($sqlFrag, ', ').' WHERE (voucherID = ?)'; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param($this->typeStr, ...$paramArr); + $stmt->execute(); + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = $stmt->error; + $stmt->close(); + } + else $this->errorMessage = $this->conn->error; + } + return $status; + } + + public function updateChecklistVouchersByClTaxaID($inputArr){ + $status = false; + if($this->clTaxaID){ + $this->setChecklistVoucherFieldMap(); + $this->setParameterArr($inputArr); + $paramArr = array(); + $sqlFrag = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sqlFrag .= $fieldName . ' = ?, '; + $paramArr[] = $value; + } + $paramArr[] = $this->clTaxaID; + $this->typeStr .= 'i'; + $sql = 'UPDATE IGNORE fmvouchers SET '.trim($sqlFrag, ', ').' WHERE (clTaxaID = ?)'; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param($this->typeStr, ...$paramArr); + $stmt->execute(); + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = $stmt->error; + $stmt->close(); + } + else $this->errorMessage = $this->conn->error; + } + return $status; + } + + public function deleteChecklistVoucher(){ + $status = false; + if($this->voucherID){ + $sql = 'DELETE FROM fmvouchers WHERE voucherID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->voucherID); + $stmt->execute(); + if($stmt->error) $this->errorMessage = $stmt->error; + else $status = true; + $stmt->close(); + } + } + return $status; + } + + public function deleteChecklistVouchersByClTaxaID(){ + $status = false; + if($this->clTaxaID){ + $sql = 'DELETE FROM fmvouchers WHERE clTaxaID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->clTaxaID); + $stmt->execute(); + if($stmt->error) $this->errorMessage = $stmt->error; + else $status = true; + $stmt->close(); + } + } + return $status; + } + + private function setChecklistVoucherFieldMap(){ + $this->fieldMap = array('clTaxaID' => 'i', 'occid' => 'i', 'editorNotes' => 's', 'preferredImage' => 'i', 'notes' => 's'); + } + + //Set state-based locality security + private function setStateBasedLocalitySecurity($state, $tid = null){ + $status = false; + $id = $tid; + $sql = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '; + if(!$tid){ + $sql .= 'INNER JOIN fmchklsttaxalink cl ON ts2.tid = cl.tid '; + } + $sql .= 'SET o.localitysecurity = 1 + WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (cultivationStatus = 0 OR cultivationStatus IS NULL) AND (o.localitySecurityReason IS NULL) + AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) AND (o.stateprovince = ?) '; + if($tid){ + $sql .= ' AND (ts2.tid = ?) '; + } elseif($this->clid){ + $sql .= ' AND (cl.clid = ?) '; + $id = $this->clid; + } else{ + return false; + } + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('si', $state, $id); + $stmt->execute(); + if($stmt->error) $this->errorMessage = $stmt->error; + else $status = true; + $stmt->close(); + } + return $status; + } + + private function removeStateLocalitySecurity($state){ + $status = false; + if($this->clid){ + // Removes security for all taxa associated with the checklist, excluding globally protected taxa + $sql = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid + INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted + INNER JOIN fmchklsttaxalink cl ON ts2.tid = cl.tid + SET o.localitysecurity = 0 + WHERE (o.localitysecurity = 1) AND (o.localitySecurityReason IS NULL) AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) + AND (o.stateprovince = ?) AND (cl.clid = ?) + AND o.tidinterpreted NOT IN(SELECT s2.tid FROM taxstatus s2 INNER JOIN taxstatus s1 ON s2.tidaccepted = s1.tidaccepted INNER JOIN taxa t ON s1.tid = t.tid WHERE t.securityStatus > 0)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('si', $state, $this->clid); + $stmt->execute(); + if($stmt->error) $this->errorMessage = $stmt->error; + else $status = true; + $stmt->close(); + } + } + return $status; + } + + public function removeStateLocalitySecurityByTid($rareLocality, $tid){ + if(is_numeric($tid)){ + //Remove state based security protection only if name is not on global list + $globalStatus = 0; + $sql = 'SELECT securityStatus FROM taxa WHERE tid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $tid); + $stmt->execute(); + $stmt->bind_result($globalStatus); + $stmt->fetch(); + $stmt->close(); + } + if(!$globalStatus){ + $sqlRare = 'UPDATE omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid + INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted + SET o.localitysecurity = 0 + WHERE (o.localitysecurity = 1) AND (o.localitySecurityReason IS NULL) AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) + AND o.stateprovince = ? AND ts2.tid = ?'; + if($stmt = $this->conn->prepare($sqlRare)){ + $stmt->bind_param('si', $rareLocality, $tid); + $stmt->execute(); + if($stmt->error){ + $this->errorMessage = 'ERROR resetting locality security during taxon delete: '.$stmt->error; + } + $stmt->close(); + } + } + } + } + //Child-Parent checklist functions public function insertChildChecklist($clidChild, $modifiedUid){ $status = false; - $sql = 'INSERT INTO fmchklstchildren(clid, clidchild, modifiedUid) VALUES(?,?,?) '; - if($stmt = $this->conn->prepare($sql)){ - $stmt->bind_param('iii', $this->clid, $clidChild, $modifiedUid); - if($stmt->execute()){ - if($stmt->affected_rows && !$stmt->error){ - $status = true; + if($this->clid != $clidChild){ + $sql = 'INSERT INTO fmchklstchildren(clid, clidchild, modifiedUid) VALUES(?,?,?) '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('iii', $this->clid, $clidChild, $modifiedUid); + if($stmt->execute()){ + if($stmt->affected_rows && !$stmt->error){ + $status = true; + } + else $this->errorMessage = 'ERROR inserting child checklist record (2): ' . $stmt->error; } - else $this->errorMessage = 'ERROR inserting child checklist record (2): '.$stmt->error; + else $this->errorMessage = 'ERROR inserting child checklist record (1): ' . $stmt->error; + $stmt->close(); } - else $this->errorMessage = 'ERROR inserting child checklist record (1): '.$stmt->error; - $stmt->close(); } return $status; } @@ -255,6 +556,84 @@ public function deleteChildChecklist($clidDel){ return $status; } + //Checklist coordinates functions + public function insertChecklistCoordinates($inputArr){ + $status = false; + if($this->clid && isset($inputArr['tid']) && $inputArr['tid']){ + $this->setChecklistCoordinatesFieldMap(); + $this->setParameterArr($inputArr); + $sql = 'INSERT IGNORE INTO fmchklstcoordinates('; + $sqlValues = ''; + $paramArr = array(); + foreach($this->parameterArr as $fieldName => $value){ + $sql .= $fieldName . ', '; + $sqlValues .= '?, '; + $paramArr[] = $value; + } + $paramArr[] = $this->clid; + $this->typeStr .= 'i'; + $sql .= 'clid) VALUES(' . $sqlValues . '?) '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param($this->typeStr, ...$paramArr); + if($stmt->execute()){ + if($stmt->affected_rows || !$stmt->error){ + $this->primaryKey = $stmt->insert_id; + $status = true; + } + else $this->errorMessage = 'ERROR inserting fmchklstcoordinates record (2): '.$stmt->error; + } + else $this->errorMessage = 'ERROR inserting fmchklstcoordinates record (1): '.$stmt->error; + $stmt->close(); + } + else $this->errorMessage = 'ERROR preparing statement for fmchklstcoordinates insert: '.$this->conn->error; + } + return $status; + } + + public function updateChecklistCoordinates($inputArr){ + $status = false; + if($this->clid && isset($inputArr['clCoordID']) && $inputArr['clCoordID']){ + $this->setChecklistCoordinatesFieldMap(); + $this->setParameterArr($inputArr); + $paramArr = array(); + $sqlFrag = ''; + foreach($this->parameterArr as $fieldName => $value){ + $sqlFrag .= $fieldName . ' = ?, '; + $paramArr[] = $value; + } + $paramArr[] = $inputArr['clCoordID']; + $this->typeStr .= 'i'; + $sql = 'UPDATE fmchklstcoordinates SET '.trim($sqlFrag, ', ').' WHERE (clCoordID = ?)'; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param($this->typeStr, ...$paramArr); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error) $status = true; + else $this->errorMessage = 'ERROR updating fmchklstcoordinates record: '.$stmt->error; + $stmt->close(); + } + else $this->errorMessage = 'ERROR preparing statement for updating fmchklstcoordinates: '.$this->conn->error; + } + return $status; + } + + public function deleteChecklistCoordinates($pk){ + if($this->assocID){ + $sql = 'DELETE FROM fmchklstcoordinates WHERE chklstCoordID = '.$pk; + if($this->conn->query($sql)){ + return true; + } + else{ + $this->errorMessage = 'ERROR deleting fmchklstcoordinates record: '.$this->conn->error; + return false; + } + } + } + + private function setChecklistCoordinatesFieldMap(){ + $this->fieldMap = array('tid' => 'i', 'decimalLatitude' => 'd', 'decimalLongitude' => 'd', 'sourceName' => 's', + 'sourceIdentifier' => 's', 'referenceUrl' => 's', 'notes' => 's', 'dynamicProperties' => 's'); + } + //Inventory Project functions public function getProjectMetadata(){ $returnArr = Array(); @@ -295,7 +674,7 @@ public function insertProject($inputArr){ $fullDescription = (isset($inputArr['fulldescription'])?$inputArr['fulldescription']:NULL); $notes = (isset($inputArr['notes'])?$inputArr['notes']:NULL); $isPublic = (isset($inputArr['ispublic'])?$inputArr['ispublic']:0); - $sql = 'INSERT INTO fmprojects(projname, managers, fulldescription, notes, ispublic) VALUES(?, ?, ?, ?, ?)'; + $sql = 'INSERT IGNORE INTO fmprojects(projname, managers, fulldescription, notes, ispublic) VALUES(?, ?, ?, ?, ?)'; if($stmt = $this->conn->prepare($sql)){ $stmt->bind_param('ssssi', $projName, $managers, $fullDescription, $notes, $isPublic); if($stmt->execute()){ @@ -319,7 +698,7 @@ public function updateProject($inputArr){ $notes = $inputArr['notes']; $isPublic = $inputArr['ispublic']; - $sql = 'UPDATE fmprojects SET projname = ?, managers = ?, fulldescription = ?, notes = ?, ispublic = ? WHERE (pid = ?)'; + $sql = 'UPDATE IGNORE fmprojects SET projname = ?, managers = ?, fulldescription = ?, notes = ?, ispublic = ? WHERE (pid = ?)'; if($stmt = $this->conn->prepare($sql)){ $stmt->bind_param('ssssii', $projName, $managers, $fullDescription, $notes, $isPublic, $this->pid); if($stmt->execute()){ @@ -340,7 +719,7 @@ public function deleteProject($projID){ $sql = 'DELETE FROM fmprojects WHERE pid = '.$projID; if(!$this->conn->query($sql)){ $status = false; - $this->errorStr = 'ERROR deleting inventory project: '.$this->conn->error; + $this->errorMessage = 'ERROR deleting inventory project: '.$this->conn->error; } } return $status; @@ -434,11 +813,41 @@ public function getUserArr(){ return $retArr; } + //Mics support functions + private function setParameterArr($inputArr){ + //Reset class variables, which is very important if more than one write function is called per class instance + unset($this->parameterArr); + $this->parameterArr = array(); + $this->typeStr = ''; + //Prepare type and value variables used within prepared statement + foreach($this->fieldMap as $field => $type){ + $postField = ''; + if(isset($inputArr[$field])) $postField = $field; + elseif(isset($inputArr[strtolower($field)])) $postField = strtolower($field); + if($postField){ + $value = trim($inputArr[$postField]); + if($field == 'clid' || $field == 'tid') $value = filter_var($value, FILTER_SANITIZE_NUMBER_INT); + if(!$value) $value = null; + $this->parameterArr[$field] = $value; + $this->typeStr .= $type; + } + } + if(!$this->clid && !empty($inputArr['clid'])) $this->clid = filter_var($inputArr['clid'], FILTER_SANITIZE_NUMBER_INT); + } + //Setter and getter functions public function setClid($clid){ if(is_numeric($clid)) $this->clid = $clid; } + public function setClTaxaID($clTaxaID){ + if(is_numeric($clTaxaID)) $this->clTaxaID = $clTaxaID; + } + + public function setVoucherID($voucherID){ + if(is_numeric($voucherID)) $this->voucherID = $voucherID; + } + public function getPid(){ return $this->pid; } @@ -446,5 +855,9 @@ public function getPid(){ public function setPid($pid){ if(is_numeric($pid)) $this->pid = $pid; } + + public function getPrimaryKey(){ + return $this->primaryKey; + } } -?> \ No newline at end of file +?> diff --git a/classes/ImageBatchProcessor.php b/classes/ImageBatchProcessor.php index 7b2197e8b2..5e0e86558c 100644 --- a/classes/ImageBatchProcessor.php +++ b/classes/ImageBatchProcessor.php @@ -3,17 +3,17 @@ // If running as standalone scripts outside of the Symbiota file structure, you must include ImageLocalProcessor class (ImageLocalProcessor.php) if(isset($SERVER_ROOT) && $SERVER_ROOT){ include_once($SERVER_ROOT.'/classes/ImageLocalProcessor.php'); - @include_once($serverRoot.'/classes/SpecProcessorGPI.php'); - @include_once($serverRoot.'/classes/SpecProcessorNEVP.php'); + @include_once($SERVER_ROOT.'/classes/SpecProcessorGPI.php'); + @include_once($SERVER_ROOT.'/classes/SpecProcessorNEVP.php'); } elseif(isset($serverRoot) && $serverRoot){ - if(file_exists($serverRoot.'/config/dbconnection.php')){ + if(file_exists($serverRoot.'/config/dbconnection.php')){ include_once($serverRoot.'/config/dbconnection.php'); } else{ include_once('ImageBatchConnectionFactory.php'); } - if (file_exists($serverRoot.'/classes/ImageLocalProcessor.php')) { + if (file_exists($serverRoot.'/classes/ImageLocalProcessor.php')) { @require_once($serverRoot.'/classes/ImageLocalProcessor.php'); } // Check for the symbiota class files used herein for parsing @@ -30,13 +30,13 @@ } else{ //Files reside in same folder and script is run from within the folder - if(file_exists('ImageLocalProcessor.php')) { + if(file_exists('ImageLocalProcessor.php')) { @require_once('ImageLocalProcessor.php'); } - if(file_exists('SpecProcessorGPI.php')) { + if(file_exists('SpecProcessorGPI.php')) { @require_once('SpecProcessorGPI.php'); } - if (file_exists('SpecProcessorNEVP.php')) { + if (file_exists('SpecProcessorNEVP.php')) { @require_once('SpecProcessorNEVP.php'); } } diff --git a/classes/ImageCleaner.php b/classes/ImageCleaner.php index 66c59239d6..1ec88e535a 100644 --- a/classes/ImageCleaner.php +++ b/classes/ImageCleaner.php @@ -68,7 +68,7 @@ public function buildThumbnailImages($limit){ $status = true; $cnt++; $imgId = $row->imgid; - $this->logOrEcho($cnt.': Building thumbnail: '.$imgId.'...'); + $this->logOrEcho($cnt.': Building thumbnail: ' . htmlspecialchars($imgId, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'); $this->conn->autocommit(false); //Tag for updating; needed to ensure two parallel processes are not processing the same image $testSql = 'SELECT thumbnailurl, url FROM images WHERE (imgid = '.$imgId.') FOR UPDATE '; @@ -128,8 +128,8 @@ private function getSqlWhere(){ private function buildImageDerivatives($imgId, $catNum, $recUrlWeb, $recUrlTn, $recUrlOrig, $setFormat = false){ $status = true; - if(substr($recUrlWeb,0,10) == 'processing') $recUrlWeb = ''; - if(substr($recUrlTn,0,10) == 'processing') $recUrlTn = ''; + if(isset($recUrlWeb) && substr($recUrlWeb,0,10) == 'processing') $recUrlWeb = ''; + if(isset($recUrlTn) && substr($recUrlTn,0,10) == 'processing') $recUrlTn = ''; //Build target path $targetPath = ''; if($this->collid){ @@ -151,7 +151,7 @@ private function buildImageDerivatives($imgId, $catNum, $recUrlWeb, $recUrlTn, $ $imgUrl = ''; $webIsEmpty = false; - if(strpos($recUrlOrig, 'tropicos.org/ImageDownload.aspx')){ + if(isset($recUrlOrig) && strpos($recUrlOrig, 'tropicos.org/ImageDownload.aspx')){ //Is a TROPICOS image, thus try to harvest web image from their website if(preg_match('/imageid=(\d+)$/', $recUrlOrig, $m)){ $newImgPath = $this->imgManager->getTargetPath().'mo_'.$m[1].'.jpg'; @@ -374,7 +374,7 @@ public function refreshThumbnails($postArr){ $url = $r->url; $urlTn = $r->thumbnailurl; $urlOrig = $r->originalurl; - $this->logOrEcho($cnt.'. Rebuilding thumbnail: '.$r->imgid.' [cat#: '.$r->catalognumber.']...',0,'div'); + $this->logOrEcho($cnt.'. Rebuilding thumbnail: ' . htmlspecialchars($r->imgid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ' [cat#: ' . htmlspecialchars($r->catalognumber, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ']...',0,'div'); //echo 'evaluate_ts: '.$postArr['evaluate_ts'].'
    '; $tsSource = 0; if($postArr['evaluate_ts']){ diff --git a/classes/ImageDetailManager.php b/classes/ImageDetailManager.php index 5247a07418..6a57710331 100644 --- a/classes/ImageDetailManager.php +++ b/classes/ImageDetailManager.php @@ -58,9 +58,9 @@ public function getImageMetadata(){ public function editImage($postArr){ $status = ""; - $searchStr = $GLOBALS["imageRootUrl"]; + $searchStr = $GLOBALS['IMAGE_ROOT_URL']; if(substr($searchStr,-1) != "/") $searchStr .= "/"; - $replaceStr = $GLOBALS["imageRootPath"]; + $replaceStr = $GLOBALS['IMAGE_ROOT_PATH']; if(substr($replaceStr,-1) != "/") $replaceStr .= "/"; $url = $postArr["url"]; $tnUrl = $postArr["thumbnailurl"]; diff --git a/classes/ImageExplorer.php b/classes/ImageExplorer.php index f5a6232c8d..10c1472853 100644 --- a/classes/ImageExplorer.php +++ b/classes/ImageExplorer.php @@ -9,22 +9,22 @@ class ImageExplorer{ public function __construct(){ $this->conn = MySQLiConnectionFactory::getCon("readonly"); } - + public function __destruct(){ if(!($this->conn === null)) $this->conn->close(); } - /* - * Input: JSON array - * Input criteria: taxon (INT: tid), country (string), state (string), tag (string), - * idNeeded (INT: 0,1), collid (INT), photographer (INT: photographerUid), - * cntPerCategory (INT: 0-2), start (INT), limit (INT) + /* + * Input: JSON array + * Input criteria: taxon (INT: tid), country (string), state (string), tag (string), + * idNeeded (INT: 0,1), collid (INT), photographer (INT: photographerUid), + * cntPerCategory (INT: 0-2), start (INT), limit (INT) * e.g. {"state": {"Arizona", "New Mexico"},"taxa":{"Pinus"}} - * Output: Array of images + * Output: Array of images */ public function getImages($searchCriteria){ $retArr = array(); - if (array_key_exists('taxon',$searchCriteria)) { + if (array_key_exists('taxon',$searchCriteria)) { // rewrite key "taxon" shown to users to that recognised for the fieldname "taxa". $searchCriteria['taxa'] = $searchCriteria['taxon']; unset($searchCriteria['taxon']); @@ -37,7 +37,7 @@ public function getImages($searchCriteria){ $retArr[$r['imgid']] = $r; } $rs->free(); - + if($retArr){ //Grab sciname and tid assigned to img, whether accepted or not $sql2 = 'SELECT i.imgid, t.tid, t.sciname FROM images i INNER JOIN taxa t ON i.tid = t.tid '. @@ -54,7 +54,7 @@ public function getImages($searchCriteria){ echo 'ERROR populating assigned tid and sciname for image: '.$this->conn->error.'
    '; echo 'SQL: '.$sql2; } - + //Set image count $cntSql = 'SELECT count(DISTINCT i.imgid) AS cnt '.substr($sql,strpos($sql,' FROM ')); $cntSql = substr($cntSql,0,strpos($cntSql,' LIMIT ')); @@ -76,14 +76,14 @@ public function getImages($searchCriteria){ } return $retArr; } - - /* + + /* * Input: array of criteria (e.g. array("state" => array("Arizona", "New Mexico")) - * Input criteria: taxa (INT: tid), country (string), state (string), tag (string), - * idNeeded (INT: 0,1), collid (INT), photographer (INT: photographerUid), - * cntPerCategory (INT: 0-2), start (INT), limit (INT) + * Input criteria: taxa (INT: tid), country (string), state (string), tag (string), + * idNeeded (INT: 0,1), collid (INT), photographer (INT: photographerUid), + * cntPerCategory (INT: 0-2), start (INT), limit (INT) * e.g. {"state": ["Arizona", "New Mexico"],"taxa":["Pinus"}} - * Output: String, SQL to be used to query database + * Output: String, SQL to be used to query database */ private function getSql($searchCriteria){ $sqlWhere = ''; @@ -103,26 +103,16 @@ private function getSql($searchCriteria){ $sqlWhere .= 'AND (i.tid IN('.implode(',',$this->cleanInArray($tidArr)).')) '; } } - + // do something with "TEXT" - if (isset($searchCriteria['text']) && $searchCriteria['text']) { + if (isset($searchCriteria['text']) && $searchCriteria['text']) { $sqlWhere .= 'AND o.scientificName like "%'.$this->cleanInStr($searchCriteria['text'][0]).'%" '; } //Set country if(isset($searchCriteria['country']) && $searchCriteria['country']){ $countryArr = $this->cleanInArray($searchCriteria['country']); - /* - $countryArr = array(); - $sqlCountry = 'SELECT countryname FROM lkupcountry '. - 'WHERE countryid IN('.implode(',',$this->cleanInArray($searchCriteria['country'])).')'; - $rsCountry = $this->conn->query($sqlCountry); - while($rCountry = $rsCountry->fetch_object()){ - $countryArr[] = $rCountry->countryname; - } - $rsCountry->free(); - */ - + //Deal with multiple USA synonyms $usaArr = array('usa','united states','united states of america','u.s.a','us'); foreach($countryArr as $countryStr){ @@ -137,16 +127,6 @@ private function getSql($searchCriteria){ //Set state if(isset($searchCriteria['state']) && $searchCriteria['state']){ $stateArr = $this->cleanInArray($searchCriteria['state']); - /* - $stateArr = array(); - $sqlState = 'SELECT statename FROM lkupstateprovince '. - 'WHERE stateid IN('.implode(',',$this->cleanInArray($searchCriteria['state'])).')'; - $rsState = $this->conn->query($sqlState); - while($rState = $rsState->fetch_object()){ - $stateArr[] = $rState->statename; - } - $rsState->free(); - */ $sqlWhere .= 'AND o.stateProvince IN("'.implode('","',$stateArr).'") '; } @@ -155,40 +135,40 @@ private function getSql($searchCriteria){ $sqlWhere .= 'AND it.keyvalue IN("'.implode('","',$this->cleanInArray($searchCriteria['tags'])).'") '; } else{ - /* If no tags, then limit to sort value less than 500, + /* If no tags, then limit to sort value less than 500, * this is old system for limiting certain images to specimen details page only, - * will replace with tag system in near future + * will replace with tag system in near future */ $sqlWhere .= 'AND i.sortsequence < 500 '; } - - //Set collection + + //Set collection if(isset($searchCriteria['collection']) && $searchCriteria['collection']){ $sqlWhere .= 'AND o.collid IN('.implode(',',$this->cleanInArray($searchCriteria['collection'])).') '; } - //Set photographers + //Set photographers if(isset($searchCriteria['photographer']) && $searchCriteria['photographer']){ $sqlWhere .= 'AND i.photographerUid IN('.implode(',',$this->cleanInArray($searchCriteria['photographer'])).') '; } - - if (isset($searchCriteria['idToSpecies']) && $searchCriteria['idToSpecies'] - && isset($searchCriteria['idNeeded']) && $searchCriteria['idNeeded'] ) { - // if both are checked, don't include filter on either + + if (isset($searchCriteria['idToSpecies']) && $searchCriteria['idToSpecies'] + && isset($searchCriteria['idNeeded']) && $searchCriteria['idNeeded'] ) { + // if both are checked, don't include filter on either $includeVerification = FALSE; // used later to include/exclude the join to omoccurrverification - } else { + } else { $includeVerification = FALSE; //Needing to be identified to species or lower if(isset($searchCriteria['idNeeded']) && $searchCriteria['idNeeded']){ $includeVerification = TRUE; // include occurrences with no verification of identification and an id of genus or higher or those with an identification verification of poor - // differs from the query below only in rankid<220 - // complexity is added by futureproofing for use of omoccurrverification for categories other than identification, + // differs from the query below only in rankid<220 + // complexity is added by futureproofing for use of omoccurrverification for categories other than identification, // testing for null omoccurrverification isn't adequate. - $sqlWhere .= "AND ( " . + $sqlWhere .= "AND ( " . " (o.occid NOT IN (SELECT occid FROM omoccurverification WHERE (category = \"identification\")) AND (t.rankid < 220 OR o.tidinterpreted IS NULL) ) " . - // " OR " . - // " (v.category = 'identification' AND v.ranking < 5) " . + // " OR " . + // " (v.category = 'identification' AND v.ranking < 5) " . " ) "; } //Identified to species or lower @@ -196,10 +176,10 @@ private function getSql($searchCriteria){ $includeVerification = TRUE; // include occurrences with no verification of identification and an id of species or lower or those with an identification verification of good // differs from the query above only in rankid>=220 and ranking>=5 - $sqlWhere .= "AND ( (o.occid IS NULL AND t.rankid IN(220,230,240,260)) OR " . + $sqlWhere .= "AND ( (o.occid IS NULL AND t.rankid IN(220,230,240,260)) OR " . " (o.occid NOT IN (SELECT occid FROM omoccurverification WHERE (category = \"identification\")) AND t.rankid IN(220,230,240,260)) " . - " OR " . - " (v.category = 'identification' AND v.ranking >= 5) " . + " OR " . + " (v.category = 'identification' AND v.ranking >= 5) " . " ) "; } // identified with a low quality identification @@ -234,7 +214,7 @@ private function getSql($searchCriteria){ } // Strip off the leading AND from the assembled where clause. if($sqlWhere) $sqlStr .= 'WHERE '.substr($sqlWhere,3); - + // add the group by clause if(isset($searchCriteria['countPerCategory'])){ $countPerCategory = (int)$searchCriteria['countPerCategory']; @@ -243,7 +223,7 @@ private function getSql($searchCriteria){ $sqlStr .= 'GROUP BY ts.tidaccepted '; } elseif($searchCriteria['countPerCategory'] === 'specimen'){ - //one per occurrence (countPerCategory == 1) + //one per occurrence (countPerCategory == 1) $sqlStr .= 'GROUP BY o.occid '; } else{ @@ -251,7 +231,7 @@ private function getSql($searchCriteria){ //Do nothing } } - + //$sqlStr .= 'ORDER BY i.sortsequence '; //Set start and limit $start = (isset($searchCriteria['start'])?$searchCriteria['start']:0); @@ -269,7 +249,7 @@ public function testSql($searchCriteria){ //$imgArr = $this->getImages($searchCriteria); //print_r($imgArr); } - + private function getAcceptedTid($inTidArr){ $retArr = array(); $sql = 'SELECT tidaccepted, tid FROM taxstatus WHERE taxauthid = 1 AND tid IN('.preg_replace('/^,+/','',implode(',',$inTidArr)).') '; @@ -283,44 +263,44 @@ private function getAcceptedTid($inTidArr){ //Inner query gets all accepted children for input tid, which is an accepted tid //Outer query gets all synonyms for input tid and their children - //Return should be all children and their synonyms + //Return should be all children and their synonyms private function getChildSql($inTid){ $sqlInner = 'SELECT DISTINCT ts.tid '. 'FROM taxstatus ts INNER JOIN taxaenumtree e ON ts.tid = e.tid '. 'WHERE ts.taxauthid = 1 AND e.taxauthid = 1 AND ts.tid = ts.tidaccepted '. 'AND (e.parenttid = '.$inTid.' OR ts.parenttid = '.$inTid.') '; - $sql = 'SELECT DISTINCT tid FROM taxstatus '. + $sql = 'SELECT DISTINCT tid FROM taxstatus '. 'WHERE (taxauthid = 1) AND (tidaccepted = '.$inTid.' OR tidaccepted IN('.$sqlInner.'))'; return $sql; } - - /** - * Construct the same query as getChildSql, only execute the query and get the list of tids, - * instead of returning the sql. Nested query in this context ends up preventing use of + + /** + * Construct the same query as getChildSql, only execute the query and get the list of tids, + * instead of returning the sql. Nested query in this context ends up preventing use of * and index, and substatively slows down query on taxon names. - * + * * @param inTid the taxon id for which to find the list of all synonyms and their children. * @return a list tids consisting of the provided tid, all synonyms and children. */ - private function getChildTids($inTid) { + private function getChildTids($inTid) { $result = "$inTid"; $comma = ','; $sqlInner = 'SELECT DISTINCT ts.tid '. 'FROM taxstatus ts INNER JOIN taxaenumtree e ON ts.tid = e.tid '. 'WHERE ts.taxauthid = 1 AND e.taxauthid = 1 AND ts.tid = ts.tidaccepted '. 'AND (e.parenttid = ? OR ts.parenttid = ? ) '; - $sql = 'SELECT DISTINCT tid FROM taxstatus '. + $sql = 'SELECT DISTINCT tid FROM taxstatus '. 'WHERE (taxauthid = 1) AND (tidaccepted = ? OR tidaccepted IN('.$sqlInner.'))'; $stmt = $this->conn->prepare($sql); - if ($stmt) { + if ($stmt) { $stmt->bind_param('iii',$inTid,$inTid,$inTid); $stmt->bind_result($tid); $stmt->execute(); - while ($stmt->fetch()) { + while ($stmt->fetch()) { $result .= $comma.$tid; - } + } $stmt->close(); - } + } return $result; } @@ -339,7 +319,7 @@ private function getTaxaChildren($inTidArr){ $rs->free(); } return array_unique($childArr); - } + } private function getTaxaSynonyms($inTidArr){ $synArr = array(); @@ -354,22 +334,22 @@ private function getTaxaSynonyms($inTidArr){ } $rs->free(); return array_unique($synArr); - } + } public function getCountries(){ $retArr = array(); - $sql = 'SELECT DISTINCT countryname FROM lkupcountry '; + $sql = 'SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[] = $r->countryname; + $retArr[] = $r->geoterm; } $rs->free(); return $retArr; } - + public function getStates(){ $retArr = array(); - $sql = 'SELECT DISTINCT statename FROM lkupstateprovince '; + $sql = 'SELECT DISTINCT geoterm FROM geographicthesaurus WHERE geolevel = 60 '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[] = $r->statename; @@ -384,7 +364,7 @@ public function getCollections(){ $sql = 'SELECT count(i.imgid) as ct, c.collid, c.institutioncode, c.collectioncode ' . ' FROM omcollections c '. ' LEFT JOIN omoccurrences o ON c.collid = o.collid '. - ' LEFT JOIN images i ON o.occid = i.occid '. + ' LEFT JOIN images i ON o.occid = i.occid '. ' WHERE i.imgid is not null '. ' and i.sortsequence < 500 '. ' GROUP BY c.collid, c.institutioncode, c.collectioncode '. @@ -393,11 +373,11 @@ public function getCollections(){ $sql = 'SELECT count(i.imgid) as ct, c.collid, c.institutioncode, c.collectioncode ' . ' FROM omcollections c '. ' INNER JOIN omoccurrences o ON c.collid = o.collid '. - ' INNER JOIN images i ON o.occid = i.occid '. + ' INNER JOIN images i ON o.occid = i.occid '. ' WHERE i.sortsequence < 500 '. ' GROUP BY c.collid, c.institutioncode, c.collectioncode '; $stmt = $this->conn->prepare($sql); - if ($stmt) { + if ($stmt) { $stmt->bind_result($count,$collid,$instcode,$collcode); $stmt->execute(); while($stmt->fetch()){ @@ -410,8 +390,8 @@ public function getCollections(){ } return json_encode($retArr); } - - public function getTags() { + + public function getTags() { $retArr = array(); $sql = "select tagkey from imagetagkey order by sortorder asc "; $stmt = $this->conn->stmt_init(); @@ -419,10 +399,10 @@ public function getTags() { if ($stmt) { $stmt->execute(); $stmt->bind_result($shortlabel); - while ($stmt->fetch()) { + while ($stmt->fetch()) { $retArr[] = $shortlabel; } - } + } return json_encode($retArr); } diff --git a/classes/ImageImport.php b/classes/ImageImport.php index da16b177fe..4aed7b52bc 100644 --- a/classes/ImageImport.php +++ b/classes/ImageImport.php @@ -17,7 +17,6 @@ class ImageImport{ function __construct() { set_time_limit(2000); - ini_set('auto_detect_line_endings', true); $this->conn = MySQLiConnectionFactory::getCon("write"); $this->setUploadTargetPath(); @@ -143,12 +142,12 @@ public function getTranslation($inStr){ } private function setUploadTargetPath(){ - $tPath = $GLOBALS["tempDirRoot"]; + $tPath = $GLOBALS['TEMP_DIR_ROOT']; if(!$tPath){ $tPath = ini_get('upload_tmp_dir'); } if(!$tPath){ - $tPath = $GLOBALS["serverRoot"]."/temp/downloads"; + $tPath = $GLOBALS['SERVER_ROOT']."/temp/downloads"; } if(substr($tPath,-1) != '/') $tPath .= "/"; $this->uploadTargetPath = $tPath; diff --git a/classes/ImageLibraryBrowser.php b/classes/ImageLibraryBrowser.php index 7be150a5cd..52367c34e9 100644 --- a/classes/ImageLibraryBrowser.php +++ b/classes/ImageLibraryBrowser.php @@ -30,10 +30,10 @@ public function getFamilyList(){ return $returnArray; } - public function getGenusList($taxon = ''){ + public function getGenusList(){ $retArr = array(); $sql = 'SELECT DISTINCT t.UnitName1 '.$this->getListSql().' '; - if($taxon) $sql .= 'AND (ts.Family = "'.$this->cleanInputStr($taxon).'") '; + if($this->searchTerm) $sql .= 'AND (ts.Family = "'.$this->cleanInputStr($this->searchTerm).'") '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[] = $r->UnitName1; @@ -43,10 +43,10 @@ public function getGenusList($taxon = ''){ return $retArr; } - public function getSpeciesList($taxon = ''){ + public function getSpeciesList(){ $retArr = Array(); $tidArr = Array(); - $taxon = $this->cleanInputStr($taxon); + $taxon = $this->cleanInputStr($this->searchTerm); $taxon = trim($taxon,' %'); if($taxon){ $this->setTaxonRequestVariable(array('taxa'=>$taxon,'usethes'=>1,'taxontype'=>2)); @@ -148,7 +148,7 @@ public function getPhotographerList(){ //Setters and getters public function setSearchTerm($t){ - $this->searchTerm = filter_var($t, FILTER_SANITIZE_STRING); + $this->searchTerm = htmlspecialchars($t, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE); } } ?> \ No newline at end of file diff --git a/classes/ImageLibrarySearch.php b/classes/ImageLibrarySearch.php index a09d931d83..f86eb0a538 100644 --- a/classes/ImageLibrarySearch.php +++ b/classes/ImageLibrarySearch.php @@ -4,23 +4,25 @@ class ImageLibrarySearch extends OccurrenceTaxaManager{ - private $dbStr; + private $dbStr = ''; private $taxonType = 2; private $taxaStr; private $useThes = 1; private $photographerUid; - private $tags; + private $tagExistance = 0; + private $tag; private $keywords; - private $imageCount = 'all'; + private $imageCount = 0; private $imageType = 0; private $recordCount = 0; private $tidFocus; private $searchSupportManager = null; private $sqlWhere = ''; + private $errorStr = ''; - function __construct() { - parent::__construct(); + function __construct($type = 'readonly') { + parent::__construct($type); if(array_key_exists('TID_FOCUS', $GLOBALS) && preg_match('/^[\d,]+$/', $GLOBALS['TID_FOCUS'])){ $this->tidFocus = $GLOBALS['TID_FOCUS']; } @@ -30,28 +32,22 @@ function __destruct(){ parent::__destruct(); } - public function getImageArr($pageRequest,$cntPerPage){ + public function getImageArr($pageRequest, $cntPerPage){ $retArr = Array(); - $includeOccurrenceTable = false; - if($this->imageType == 1 || $this->imageType == 2 || ($this->dbStr && $this->dbStr != 'all')) $includeOccurrenceTable = true; $this->setSqlWhere(); $this->setRecordCnt(); - $sql = 'SELECT i.imgid, i.tid, i.url, i.thumbnailurl, i.originalurl, i.photographeruid, i.caption, i.occid, '; - if($includeOccurrenceTable) $sql .= 'IFNULL(t.sciname,o.sciname) as sciname '; - else $sql .= 't.sciname '; + $sql = 'SELECT i.imgid, i.tid, IFNULL(t.sciname,o.sciname) as sciname, i.url, i.thumbnailurl, i.originalurl, i.photographeruid, i.caption, i.occid '; /* $sql = 'SELECT DISTINCT i.imgid, o.tidinterpreted, t.tid, t.sciname, i.url, i.thumbnailurl, i.originalurl, i.photographeruid, i.caption, '. 'o.occid, o.stateprovince, o.catalognumber, CONCAT_WS("-",c.institutioncode, c.collectioncode) as instcode '; */ $sqlWhere = $this->sqlWhere; - if($this->imageCount == 'taxon') $sqlWhere .= 'GROUP BY sciname '; - elseif($this->imageCount == 'specimen') $sqlWhere .= 'GROUP BY i.occid '; - if($this->sqlWhere){ - if($includeOccurrenceTable) $sqlWhere .= 'ORDER BY o.sciname '; - else $sqlWhere .= 'ORDER BY t.sciname '; - } + if($this->imageCount == 1) $sqlWhere .= 'GROUP BY sciname '; + elseif($this->imageCount == 2) $sqlWhere .= 'GROUP BY i.occid '; + if($this->sqlWhere) $sqlWhere .= 'ORDER BY o.sciname '; $bottomLimit = ($pageRequest - 1)*$cntPerPage; $sql .= $this->getSqlBase().$sqlWhere.'LIMIT '.$bottomLimit.','.$cntPerPage; + //echo '
    Spec sql: '.$sql.'
    '; $occArr = array(); $result = $this->conn->query($sql); $imgId = 0; @@ -105,13 +101,72 @@ private function setSqlWhere(){ $sqlWhere .= OccurrenceSearchSupport::getDbWhereFrag($this->cleanInStr($this->dbStr)); } if(isset($this->taxaArr['taxa'])){ - $sqlWhereTaxa = $this->getTaxonWhereFrag(); - if(!$this->imageType || $this->imageType == 3){ - if(strpos($sqlWhereTaxa, 'o.tidinterpreted')) $sqlWhereTaxa = str_replace('o.tidinterpreted', 't.tid', $sqlWhereTaxa); - if(strpos($sqlWhereTaxa, 'o.sciname')) $sqlWhereTaxa = str_replace('o.sciname', 't.sciname', $sqlWhereTaxa); - if(strpos($sqlWhereTaxa, 'o.family')) $sqlWhereTaxa = str_replace('o.family', 'ts.family', $sqlWhereTaxa); + $sqlWhereTaxa = ''; + foreach($this->taxaArr['taxa'] as $searchTaxon => $searchArr){ + $taxonType = $this->taxaArr['taxontype']; + if(isset($searchArr['taxontype'])) $taxonType = $searchArr['taxontype']; + if($taxonType == TaxaSearchType::TAXONOMIC_GROUP){ + //Class, order, or other higher rank + if(isset($searchArr['tid'])){ + $tidArr = array_keys($searchArr['tid']); + //$sqlWhereTaxa .= 'OR (o.tidinterpreted IN(SELECT DISTINCT tid FROM taxaenumtree WHERE (taxauthid = '.$this->taxAuthId.') AND (parenttid IN('.trim($tidStr,',').') OR (tid = '.trim($tidStr,',').')))) '; + $sqlWhereTaxa .= 'OR ((e.taxauthid = '.$this->taxAuthId.') AND ((i.tid IN('.implode(',', $tidArr).')) OR e.parenttid IN('.implode(',', $tidArr).'))) '; + } + } + elseif($taxonType == TaxaSearchType::FAMILY_ONLY){ + $sqlWhereTaxa .= 'OR ((ts.family = "'.$searchTaxon.'") AND (ts.taxauthid = '.$this->taxAuthId.')) '; + } + else{ + if($taxonType == TaxaSearchType::COMMON_NAME){ + //Common name search + $famArr = array(); + if(array_key_exists("families",$searchArr)){ + $famArr = $searchArr["families"]; + } + if(array_key_exists("tid",$searchArr)){ + $tidArr = array_keys($searchArr['tid']); + $sql = 'SELECT DISTINCT t.sciname '. + 'FROM taxa t INNER JOIN taxaenumtree e ON t.tid = e.tid '. + 'WHERE (t.rankid = 140) AND (e.taxauthid = '.$this->taxAuthId.') AND (e.parenttid IN('.implode(',',$tidArr).'))'; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $famArr[] = $r->sciname; + } + } + if($famArr){ + $famArr = array_unique($famArr); + $sqlWhereTaxa .= 'OR (ts.family IN("'.implode('","',$famArr).'")) '; + } + /* + if(array_key_exists("scinames",$searchArr)){ + foreach($searchArr["scinames"] as $sciName){ + $sqlWhereTaxa .= "OR (o.sciname Like '".$sciName."%') "; + } + } + */ + } + else{ + if(array_key_exists("tid",$searchArr)){ + $rankid = current($searchArr['tid']); + $tidArr = array_keys($searchArr['tid']); + $sqlWhereTaxa .= "OR (i.tid IN(".implode(',',$tidArr).")) "; + if($rankid < 220) $sqlWhereTaxa .= 'OR ((e.taxauthid = '.$this->taxAuthId.') AND (e.parenttid IN('.implode(',', $tidArr).')) AND (ts.taxauthid = '.$this->taxAuthId.' AND ts.tid = ts.tidaccepted)) '; + elseif($rankid == 220) $sqlWhereTaxa .= 'OR (ts.parenttid IN('.implode(',', $tidArr).') AND ts.taxauthid = '.$this->taxAuthId.' AND ts.tid = ts.tidaccepted) '; + } + else{ + //Return matches for "Pinus a" + $sqlWhereTaxa .= "OR (t.sciname LIKE '".$this->cleanInStr($searchTaxon)."%') "; + } + } + if(array_key_exists("synonyms",$searchArr)){ + $synArr = $searchArr["synonyms"]; + if($synArr){ + $sqlWhereTaxa .= 'OR (i.tid IN('.implode(',',array_keys($synArr)).')) '; + } + } + } } - if($sqlWhereTaxa) $sqlWhere .= 'AND ('.substr($sqlWhereTaxa,3).') '; + if($sqlWhereTaxa) $sqlWhere .= "AND (".substr($sqlWhereTaxa,3).") "; } elseif($this->tidFocus){ $sqlWhere .= 'AND (e.parenttid IN('.$this->tidFocus.')) AND (e.taxauthid = 1) '; @@ -119,8 +174,14 @@ private function setSqlWhere(){ if($this->photographerUid){ $sqlWhere .= 'AND (i.photographeruid IN('.$this->photographerUid.')) '; } - if($this->tags){ - $sqlWhere .= 'AND (it.keyvalue = "'.$this->cleanInStr($this->tags).'") '; + if($this->tag){ + $sqlWhere .= 'AND i.imgid '; + $tagFrag = ''; + if($this->tag != 'ANYTAG') $tagFrag = 'WHERE keyvalue = "'.$this->cleanInStr($this->tag).'"'; + if(!$this->tagExistance){ + $sqlWhere .= 'NOT '; + } + $sqlWhere .= 'IN(SELECT imgid FROM imagetag '.$tagFrag.')'; } if($this->keywords){ $keywordArr = explode(";",$this->keywords); @@ -132,35 +193,30 @@ private function setSqlWhere(){ } if($this->imageType){ if($this->imageType == 1){ - //Specimen Images - $sqlWhere .= 'AND (i.occid IS NOT NULL) AND (c.colltype = "Preserved Specimens") '; - } - elseif($this->imageType == 2){ - //Image Vouchered Observations - $sqlWhere .= 'AND (i.occid IS NOT NULL) AND (c.colltype != "Preserved Specimens") '; + //Specimen or Vouchered Observations Images + $sqlWhere .= 'AND (i.occid IS NOT NULL) '; } elseif($this->imageType == 3){ //Field Images (lacking specific locality details) $sqlWhere .= 'AND (i.occid IS NULL) '; } } - if(strpos($sqlWhere,'ts.taxauthid')) $sqlWhere = str_replace('t.tid', 'ts.tid', $sqlWhere); + if(strpos($sqlWhere,'ts.taxauthid')) $sqlWhere = str_replace('i.tid', 'ts.tid', $sqlWhere); if($sqlWhere) $this->sqlWhere = 'WHERE '.substr($sqlWhere,4); } private function setRecordCnt(){ $sql = 'SELECT COUNT(DISTINCT i.imgid) AS cnt '; if($this->imageCount){ - if($this->imageCount == 'taxon') $sql = "SELECT COUNT(DISTINCT i.tid) AS cnt "; - elseif($this->imageCount == 'specimen') $sql = "SELECT COUNT(DISTINCT i.occid) AS cnt "; - else $sql = "SELECT COUNT(DISTINCT i.imgid) AS cnt "; + if($this->imageCount == 1) $sql = 'SELECT COUNT(DISTINCT i.tid) AS cnt '; + elseif($this->imageCount == 2) $sql = 'SELECT COUNT(DISTINCT i.occid) AS cnt '; } $sql .= $this->getSqlBase().$this->sqlWhere; - $rs = $this->conn->query($sql); - if($r = $rs->fetch_object()){ - $this->recordCount = $r->cnt; + $result = $this->conn->query($sql); + if($row = $result->fetch_object()){ + $this->recordCount = $row->cnt; } - $rs->free(); + $result->free(); } private function getSqlBase(){ @@ -177,18 +233,15 @@ private function getSqlBase(){ if(strpos($this->sqlWhere,'e.taxauthid') || $this->tidFocus){ $sql .= 'INNER JOIN taxaenumtree e ON i.tid = e.tid '; } - if($this->tags){ - $sql .= 'INNER JOIN imagetag it ON i.imgid = it.imgid '; - } if($this->keywords){ $sql .= 'INNER JOIN imagekeywords ik ON i.imgid = ik.imgid '; } - if($this->imageType == 1 || $this->imageType == 2){ - $sql .= 'INNER JOIN omoccurrences o ON i.occid = o.occid INNER JOIN omcollections c ON o.collid = c.collid '; - } - elseif($this->dbStr && $this->dbStr != 'all'){ + if($this->dbStr && $this->dbStr != 'all'){ $sql .= 'INNER JOIN omoccurrences o ON i.occid = o.occid '; } + else{ + $sql .= 'LEFT JOIN omoccurrences o ON i.occid = o.occid '; + } return $sql; } @@ -211,38 +264,41 @@ public function getQueryTermStr(){ if($this->taxaStr) $retStr .= '&taxa='.$this->taxaStr; if($this->useThes) $retStr .= '&usethes=1'; if($this->photographerUid) $retStr .= '&phuid='.$this->photographerUid; - if($this->tags) $retStr .= '&tags='.urlencode($this->tags); + $retStr .= '&tagExistance='.$this->tagExistance; + if($this->tag) $retStr .= '&tag='.urlencode($this->tag); if($this->keywords) $retStr .= '&keywords='.$this->keywords; if($this->imageCount) $retStr .= '&imagecount='.$this->imageCount; if($this->imageType) $retStr .= '&imagetype='.$this->imageType; return trim($retStr,' &'); } - private function getPhotographerStr($uidStr){ - $retArr = array(); - if($uidStr){ - $sql = 'SELECT CONCAT_WS(" ",firstname,lastname) as name FROM users WHERE uid IN('.$uidStr.')'; - $rs = $this->conn->query($sql); - while ($r = $rs->fetch_object()) { - $retArr[] = $r->name; - } - $rs->free(); - } - return implode(', ',$retArr); - } - - private function getCollectionStr($collidStr){ - $retArr = array(); - $collidStr = trim($collidStr,';, '); - if($collidStr && preg_match('/^[,\s\d]+$/', $collidStr)){ - $sql = 'SELECT CONCAT(collectionname," (",CONCAT_WS(" ",institutioncode,collectioncode),")") as collname FROM omcollections WHERE collid IN('.$collidStr.')'; - $rs = $this->conn->query($sql); - while ($r = $rs->fetch_object()) { - $retArr[] = $r->collname; + //Action editing functions + public function batchAssignImageTag($postArr){ + $status = false; + $imageArr = $postArr['imgid']; + $tagName = $postArr['imgTagAction']; + if($imageArr && $tagName){ + $cnt = 0; + $fail = 0; + foreach($imageArr as $imgid){ + if(is_numeric($imgid)){ + $sql = 'INSERT IGNORE INTO imagetag(imgid, keyValue) VALUE(?, ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('is', $imgid, $tagName); + $stmt->execute(); + if($stmt->affected_rows) $cnt++; + elseif($stmt->error){ + $this->errorStr = 'ERROR adding image tag: '.$this->error; + $status = false; + } + else $fail++; + $stmt->close(); + } + } } - $rs->free(); + $status = $cnt . '-' . $fail; } - return implode(', ',$retArr); + return $status; } //Listing functions @@ -255,23 +311,23 @@ public function getPhotographerUidArr(){ } $rs1->free(); if($retArr){ - $sql2 = 'SELECT uid, CONCAT_WS(", ", lastname, firstname) AS fullname FROM users WHERE uid IN('.implode(',',array_keys($retArr)).')'; + $sql2 = 'SELECT uid, CONCAT_WS(", ", lastname, firstname) AS fullname FROM users WHERE uid IN(' . implode(',', array_keys($retArr)) . ')'; $rs2 = $this->conn->query($sql2); while ($r2 = $rs2->fetch_object()) { $retArr[$r2->uid] = $r2->fullname; } $rs2->free(); } - asort($retArr,SORT_NATURAL | SORT_FLAG_CASE); + asort($retArr, SORT_NATURAL | SORT_FLAG_CASE); return $retArr; } public function getTagArr(){ $retArr = array(); - $sql = 'SELECT DISTINCT keyvalue FROM imagetag ORDER BY keyvalue '; + $sql = 'SELECT tagkey, CONCAT_WS(" - ",shortlabel,tagDescription) as displayText FROM imagetagkey ORDER BY tagkey'; if($rs = $this->conn->query($sql)){ while($r = $rs->fetch_object()){ - $retArr[] = $r->keyvalue; + $retArr[$r->tagkey] = $r->displayText; } } $rs->free(); @@ -302,8 +358,12 @@ private function resetTaxaStr(){ } //Setters and getters + public function getDbStr(){ + return $this->dbStr; + } + public function setCollectionVariables($reqArr){ - $this->dbStr = OccurrenceSearchSupport::getDbRequestVariable($reqArr); + $this->dbStr = trim(OccurrenceSearchSupport::getDbRequestVariable(), ',; '); } public function setTaxonType($t){ @@ -316,7 +376,7 @@ public function getTaxonType(){ public function setTaxaStr($str){ if(strpos($str,'<') === false){ - $this->taxaStr = filter_var(trim($str), FILTER_SANITIZE_STRING); + $this->taxaStr = trim($str); if($this->taxaStr){ if(is_numeric($this->taxaStr)) $this->resetTaxaStr(); $this->setTaxonRequestVariable(array('taxa'=>$this->taxaStr,'taxontype'=>$this->taxonType,'usethes'=>$this->useThes)); @@ -344,16 +404,20 @@ public function getPhotographerUid(){ return $this->photographerUid; } - public function setTags($t){ - if(strpos($t,'<') === false) $this->tags = filter_var($t, FILTER_SANITIZE_STRING); + public function setTagExistance($t){ + $this->tagExistance = $t; } - public function getTags(){ - return $this->tags; + public function setTag($t){ + $this->tag = $t; + } + + public function getTag(){ + return $this->tag; } public function setKeywords($k){ - if(strpos($k,'<') === false) $this->keywords = filter_var($k, FILTER_SANITIZE_STRING); + $this->keywords = $k; } public function getKeywords(){ @@ -361,7 +425,7 @@ public function getKeywords(){ } public function setImageCount($c){ - if(in_array($c, array('all','taxon','specimen'))) $this->imageCount = $c; + if(is_numeric($c)) $this->imageCount = $c; } public function getImageCount(){ @@ -380,17 +444,8 @@ public function getRecordCnt(){ return $this->recordCount; } - public function getSearchTermDisplayStr(){ - $retStr = ''; - if($this->dbStr) $retStr .= $this->getCollectionStr($this->dbStr); - if($this->taxaStr) $retStr .= '; '.$this->taxaStr; - if($this->photographerUid) $retStr .= '; '.$this->getPhotographerStr($this->photographerUid); - if($this->tags) $retStr .= '; '.$this->tags; - if($this->keywords) $retStr .= '; '.$this->keywords; - if($this->imageType == 1) $retStr .= '; Limit to specimens'; - elseif($this->imageType == 2) $retStr .= '; Limit to observations'; - elseif($this->imageType == 3) $retStr .= '; Limit to field images'; - return htmlspecialchars(trim($retStr,';, ')); + public function getErrorStr(){ + return $this->errorStr; } } -?> \ No newline at end of file +?> diff --git a/classes/ImageLocalProcessor.php b/classes/ImageLocalProcessor.php index 427e85b4b7..c4d5aa7d9d 100644 --- a/classes/ImageLocalProcessor.php +++ b/classes/ImageLocalProcessor.php @@ -68,13 +68,12 @@ class ImageLocalProcessor { function __construct(){ ini_set('memory_limit','1024M'); - ini_set('auto_detect_line_endings', true); //Use deaults located within symbini, if they are available //Will be replaced by values within configuration file, if they are set - if(isset($GLOBALS['imgWebWidth']) && $GLOBALS['imgWebWidth']) $this->webPixWidth = $GLOBALS['imgWebWidth']; - if(isset($GLOBALS['imgTnWidth']) && $GLOBALS['imgTnWidth']) $this->tnPixWidth = $GLOBALS['imgTnWidth']; - if(isset($GLOBALS['imgLgWidth']) && $GLOBALS['imgLgWidth']) $this->lgPixWidth = $GLOBALS['imgLgWidth']; - if(isset($GLOBALS['imgFileSizeLimit']) && $GLOBALS['imgFileSizeLimit']) $this->webFileSizeLimit = $GLOBALS['imgFileSizeLimit']; + if(!empty($GLOBALS['IMG_WEB_WIDTH'])) $this->webPixWidth = $GLOBALS['IMG_WEB_WIDTH']; + if(!empty($GLOBALS['IMG_TN_WIDTH'])) $this->tnPixWidth = $GLOBALS['IMG_TN_WIDTH']; + if(!empty($GLOBALS['IMG_LG_WIDTH'])) $this->lgPixWidth = $GLOBALS['IMG_LG_WIDTH']; + if(!empty($GLOBALS['IMG_FILE_SIZE_LIMIT'])) $this->webFileSizeLimit = $GLOBALS['IMG_FILE_SIZE_LIMIT']; } function __destruct(){ @@ -247,7 +246,7 @@ private function setImagePaths(){ //Set target base path if(!$this->targetPathBase){ //Assume that we should use the portal's default image root path - $this->targetPathBase = $GLOBALS['imageRootPath']; + $this->targetPathBase = $GLOBALS['IMAGE_ROOT_PATH']; } if($this->targetPathBase && substr($this->targetPathBase,-1) != '/' && substr($this->targetPathBase,-1) != "\\"){ $this->targetPathBase .= '/'; @@ -256,9 +255,9 @@ private function setImagePaths(){ //Set image base URL if(!$this->imgUrlBase){ //Assume that we should use the portal's default image url prefix - $this->imgUrlBase = $GLOBALS['imageRootUrl']; + $this->imgUrlBase = $GLOBALS['IMAGE_ROOT_URL']; } - if(isset($GLOBALS['imageDomain']) && $GLOBALS['imageDomain']){ + if(!empty($GLOBALS['IMAGE_DOMAIN'])){ //Since imageDomain is set, portal is not central portal thus add portals domain to url base if(substr($this->imgUrlBase,0,7) != 'http://' && substr($this->imgUrlBase,0,8) != 'https://'){ $urlPrefix = "http://"; @@ -576,6 +575,7 @@ private function prepTarget($targetPath, $fileName, $occid){ private function processImageFile($imageArr, $targetFileName, $targetPath, $pathFrag = ''){ $sourceFileName = $imageArr['originalurl']; $sourcePath = $this->sourcePathBase.$pathFrag; + $smallOriginal = 0; //$this->logOrEcho("Processing image (".date('Y-m-d h:i:s A')."): ".$fileName); //ob_flush(); flush(); @@ -587,6 +587,7 @@ private function processImageFile($imageArr, $targetFileName, $targetPath, $path $fileNameBase = substr($sourceFileName,0,$p); } list($width, $height) = ImageShared::getImgDim($sourcePath.$sourceFileName); + if($width && $height){ $fileSize = 0; if(substr($sourcePath,0,7)=='http://' || substr($sourcePath,0,8)=='https://') { @@ -600,98 +601,55 @@ private function processImageFile($imageArr, $targetFileName, $targetPath, $path //ob_flush(); //flush(); - //Set medium web image - $medUrl = ''; - if($this->medProcessingCode){ - $medFileName = $fileNameBase.$this->medSourceSuffix.$fileNameExt; - $medTargetFileName = substr($targetFileName,0,-4).$this->medSourceSuffix.'.jpg'; - if(isset($imageArr['url']) && $imageArr['url']){ - $medFileName = $imageArr['url']; - $medTargetFileName = $imageArr['url']; - } - if($this->medProcessingCode == 1){ - // evaluate source and import - if($fileSize < $this->webFileSizeLimit && $width < ($this->webPixWidth*2)){ - if(copy($sourcePath.$sourceFileName, $targetPath.$medTargetFileName)){ - $medUrl = $medTargetFileName; - $this->logOrEcho('Source image imported as web image ', 1); - } - } - else{ - if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$medTargetFileName, $this->webPixWidth, round($this->webPixWidth*$height/$width), $width, $height)){ - $medUrl = $medTargetFileName; - $this->logOrEcho('Web image created from source image ', 1); - } - } - } - elseif($this->medProcessingCode == 2){ - // import source and use as is - if($this->uriExists($sourcePath.$medFileName)){ - if(copy($sourcePath.$medFileName, $targetPath.$medTargetFileName)){ - $medUrl = $medTargetFileName; - $this->logOrEcho('Web image imported as is ', 1); - } - } - else $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ', 1); - } - elseif($this->medProcessingCode == 3){ - // map to source as the web image - if($this->uriExists($sourcePath.$medFileName)){ - $medUrl = $sourcePath.$medFileName; - $this->logOrEcho('Source used as web image ', 1); - } - else{ - $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ',1); - } - } - } - if($medUrl) $imageArr['url'] = $medUrl; - else $this->logOrEcho('Failed to create web image ', 1); + //Set large image $lgUrl = ''; if($this->lgProcessingCode){ $lgTargetFileName = $targetFileName; if($this->lgProcessingCode == 1){ // 1 = evaluate source for import as large image - if($width > ($this->webPixWidth*1.3)){ - //Source image is big enough to serve as large version - if($width > $this->lgPixWidth){ - //Image is too wide, thus let's resize and import - if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName, $this->lgPixWidth, round($this->lgPixWidth*$height/$width), $width, $height)){ - $lgUrl = $lgTargetFileName; - $this->logOrEcho('Resized source as large derivative ',1); - } + if($width > $this->lgPixWidth){ + //Image is too wide, thus let's resize and import + if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName, $this->lgPixWidth, round($this->lgPixWidth*$height/$width), $width, $height)){ + $lgUrl = $lgTargetFileName; + $this->logOrEcho('Resized source as large derivative ',1); } - elseif($fileSize && $fileSize > $this->lgFileSizeLimit) { - // Image file size is too big, thus let's resize and import + } + elseif($fileSize && $fileSize > $this->lgFileSizeLimit) { + // Image file size is too big, thus let's resize and import - // Figure out what factor to reduce filesize by - $scaleFactor = sqrt($this->lgFileSizeLimit / $fileSize); + // Figure out what factor to reduce filesize by + $scaleFactor = sqrt($this->lgFileSizeLimit / $fileSize); - // Scale by a factor of the square root of the filesize ratio - // Note, this is a good approximation to reduce the filesize, but will not be exact - // True reduction will also depend on the JPEG quality of the source & the large file - $newWidth = round($width * $scaleFactor); + // Scale by a factor of the square root of the filesize ratio + // Note, this is a good approximation to reduce the filesize, but will not be exact + // True reduction will also depend on the JPEG quality of the source & the large file + $newWidth = round($width * $scaleFactor); - // Resize the image - if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName, $newWidth, round($newWidth*$height/$width), $width, $height)){ - $lgUrl = $lgTargetFileName; - $this->logOrEcho('Resized source as large derivative ',1); - } + // Resize the image + if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName, $newWidth, round($newWidth*$height/$width), $width, $height)){ + $lgUrl = $lgTargetFileName; + $this->logOrEcho('Resized source as large derivative ',1); + } + } + else{ + if($width < ($this->webPixWidth*1.3)){ + //Source image is relatively small - no need to create a medium asset + $smallOriginal = 1; + } + + //Source can serve as large version, thus just import as is + if(copy($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName)){ + $lgUrl = $lgTargetFileName; + $this->logOrEcho('Imported source as large derivative ',1); } else{ - //Source can serve as large version, thus just import as is - if(copy($sourcePath.$sourceFileName, $targetPath.$lgTargetFileName)){ - $lgUrl = $lgTargetFileName; - $this->logOrEcho('Imported source as large derivative ',1); - } - else{ - $this->logOrEcho("WARNING: unable to import large derivative (".$sourcePath.$sourceFileName.") ",1); - } + $this->logOrEcho("WARNING: unable to import large derivative (".$sourcePath.$sourceFileName.") ",1); } - $imgHash = md5_file($targetPath.$lgTargetFileName); - if($imgHash) $imageArr['mediamd5'] = $imgHash; } + $imgHash = md5_file($targetPath.$lgTargetFileName); + if($imgHash) $imageArr['mediamd5'] = $imgHash; + } elseif($this->lgProcessingCode == 2){ // 2 = map to source @@ -740,6 +698,109 @@ private function processImageFile($imageArr, $targetFileName, $targetPath, $path } } if($lgUrl) $imageArr['originalurl'] = $lgUrl; + + //Set medium web image + $medUrl = ''; + if($this->medProcessingCode){ + $medFileName = $fileNameBase.$this->medSourceSuffix.$fileNameExt; + $medTargetFileName = substr($targetFileName,0,-4).$this->medSourceSuffix.'.jpg'; + if(isset($imageArr['url']) && $imageArr['url']){ + $medFileName = $imageArr['url']; + $medTargetFileName = $imageArr['url']; + } + if($this->medProcessingCode == 1){ + // evaluate source and import + if (!$smallOriginal){ + if($fileSize < $this->webFileSizeLimit && $width < ($this->webPixWidth*2)){ + if(copy($sourcePath.$sourceFileName, $targetPath.$medTargetFileName)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Source image imported as web image ', 1); + } + } + else{ + if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$medTargetFileName, $this->webPixWidth, round($this->webPixWidth*$height/$width), $width, $height)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Web image created from source image ', 1); + } + } + } + else { + $medUrl = $lgUrl; + $this->logOrEcho('Web image linked to original as source image is relativly small', 1); + } + } + elseif($this->medProcessingCode == 2){ + // import source and use as is + if($this->uriExists($sourcePath.$medFileName)){ + if(copy($sourcePath.$medFileName, $targetPath.$medTargetFileName)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Web image imported as is ', 1); + } + } + else $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ', 1); + } + elseif($this->medProcessingCode == 3){ + // map to source as the web image + if($this->uriExists($sourcePath.$medFileName)){ + $medUrl = $sourcePath.$medFileName; + $this->logOrEcho('Source used as web image ', 1); + } + else{ + $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ',1); + } + } + } + if($medUrl) $imageArr['url'] = $medUrl; + else $this->logOrEcho('Failed to create web image ', 1); + + //Set medium web image + $medUrl = ''; + if($this->medProcessingCode){ + $medFileName = $fileNameBase.$this->medSourceSuffix.$fileNameExt; + $medTargetFileName = substr($targetFileName,0,-4).$this->medSourceSuffix.'.jpg'; + if(isset($imageArr['url']) && $imageArr['url']){ + $medFileName = $imageArr['url']; + $medTargetFileName = $imageArr['url']; + } + if($this->medProcessingCode == 1){ + // evaluate source and import + if($fileSize < $this->webFileSizeLimit && $width < ($this->webPixWidth*2)){ + if(copy($sourcePath.$sourceFileName, $targetPath.$medTargetFileName)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Source image imported as web image ', 1); + } + } + else{ + if($this->createNewImage($sourcePath.$sourceFileName, $targetPath.$medTargetFileName, $this->webPixWidth, round($this->webPixWidth*$height/$width), $width, $height)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Web image created from source image ', 1); + } + } + } + elseif($this->medProcessingCode == 2){ + // import source and use as is + if($this->uriExists($sourcePath.$medFileName)){ + if(copy($sourcePath.$medFileName, $targetPath.$medTargetFileName)){ + $medUrl = $medTargetFileName; + $this->logOrEcho('Web image imported as is ', 1); + } + } + else $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ', 1); + } + elseif($this->medProcessingCode == 3){ + // map to source as the web image + if($this->uriExists($sourcePath.$medFileName)){ + $medUrl = $sourcePath.$medFileName; + $this->logOrEcho('Source used as web image ', 1); + } + else{ + $this->logOrEcho('WARNING: predesignated medium does not appear to exist ('.$sourcePath.$medFileName.') ',1); + } + } + } + if($medUrl) $imageArr['url'] = $medUrl; + else $this->logOrEcho('Failed to create web image ', 1); + //Set thumbnail image $tnUrl = ''; if($this->tnProcessingCode){ @@ -945,7 +1006,7 @@ private function getOccid($catalogNumber){ 'VALUES('.$this->activeCollid.',"'.$catalogNumber.'","unprocessed","'.date('Y-m-d H:i:s').'")'; if($this->conn->query($sql2)){ $occid = $this->conn->insert_id; - $this->logOrEcho('Specimen record does not exist; new empty specimen record created and assigned an "unprocessed" status (occid = '.$occid.') ',1); + $this->logOrEcho('Specimen record does not exist; new empty specimen record created and assigned an "unprocessed" status (occid = ' . htmlspecialchars($occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ') ',1); } else $this->logOrEcho("ERROR creating new occurrence record: ".$this->conn->error,1); } @@ -999,17 +1060,17 @@ private function databaseImage($imgArr){ } if($this->conn->query('DELETE FROM images WHERE imgid = '.$r->imgid)){ //Remove images - $urlPath = current(parse_url($r->url, PHP_URL_PATH)); + $urlPath = parse_url($r->url, PHP_URL_PATH); if($urlPath && strpos($urlPath, $this->imgUrlBase) === 0){ $wFile = str_replace($this->imgUrlBase,$this->targetPathBase,$urlPath); if(file_exists($wFile) && is_writable($wFile)) unlink($wFile); } - $urlTnPath = current(parse_url($r->thumbnailUrl, PHP_URL_PATH)); + $urlTnPath = parse_url($r->thumbnailUrl, PHP_URL_PATH); if($urlTnPath && strpos($urlTnPath, $this->imgUrlBase) === 0){ $wFile = str_replace($this->imgUrlBase,$this->targetPathBase,$urlTnPath); if(file_exists($wFile) && is_writable($wFile)) unlink($wFile); } - $urlLgPath = current(parse_url($r->originalUrl, PHP_URL_PATH)); + $urlLgPath = parse_url($r->originalUrl, PHP_URL_PATH); if($urlLgPath && strpos($urlLgPath, $this->imgUrlBase) === 0){ $wFile = str_replace($this->imgUrlBase,$this->targetPathBase,$urlLgPath); if(file_exists($wFile) && is_writable($wFile)) unlink($wFile); @@ -1034,7 +1095,7 @@ private function databaseImage($imgArr){ $sql2 = ''; foreach($imgArr as $fieldName => $fieldValue){ if(array_key_exists($fieldName, $this->imageTableMap)){ - $sql1 .= $fieldName.','; + $sql1 .= $fieldName . ','; $sql2 .= '?, '; $paramType .= $this->imageTableMap[$fieldName]['type']; $paramArr[] = $fieldValue; @@ -1651,7 +1712,7 @@ private function sendMetadata($email,$mdFileName){ 'Content-Transfer-Encoding: 8bit'.$eol. 'Images in the attached file have been processed and are ready to be uploaded into your collection. '. 'This can be done using the image loading tools located in the Processing Tools (see link below).'. - ''.$url.''. + '' . htmlspecialchars($url, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''. '
    If you have problems with the new password, contact the System Administrator '; //Add attachment @@ -2054,11 +2115,11 @@ private function uriExists($url) { $localUrl = ''; if(substr($url,0,1) == '/'){ - if(isset($GLOBALS['imageDomain']) && $GLOBALS['imageDomain']){ - $url = $GLOBALS['imageDomain'].$url; + if(!empty($GLOBALS['IMAGE_DOMAIN'])){ + $url = $GLOBALS['IMAGE_DOMAIN'].$url; } - elseif($GLOBALS['imageRootUrl'] && strpos($url,$GLOBALS['imageRootUrl']) === 0){ - $localUrl = str_replace($GLOBALS['imageRootUrl'],$GLOBALS['imageRootPath'],$url); + elseif($GLOBALS['IMAGE_ROOT_URL'] && strpos($url,$GLOBALS['IMAGE_ROOT_URL']) === 0){ + $localUrl = str_replace($GLOBALS['IMAGE_ROOT_URL'],$GLOBALS['IMAGE_ROOT_PATH'],$url); } else{ $urlPrefix = "http://"; @@ -2104,22 +2165,9 @@ private function uriExists($url) { } private function encodeString($inStr){ - global $CHARSET; $retStr = trim($inStr); - if($inStr){ - if(strtolower($CHARSET) == "utf-8" || strtolower($CHARSET) == "utf8"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($CHARSET) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } + $retStr = mb_convert_encoding($inStr, $GLOBALS['CHARSET'], mb_detect_encoding($inStr)); } return $retStr; } diff --git a/classes/ImageProcessor.php b/classes/ImageProcessor.php index 63d2facd95..7c9d8e923d 100644 --- a/classes/ImageProcessor.php +++ b/classes/ImageProcessor.php @@ -18,7 +18,6 @@ class ImageProcessor { private $destructConn = true; function __construct($con = null){ - ini_set('auto_detect_line_endings', true); if($con){ //Inherits connection from another class $this->conn = $con; @@ -173,7 +172,7 @@ public function processCyVerseImages($pmTerm, $postArr){ if(strpos($result[0],'503')){ $this->logOrEcho('ERROR: CyVerse Bisque system appears to be offline',1); $this->logOrEcho('Response code: '.$result[0],2); - $this->logOrEcho('FAILED URL: '.$url.'',2); + $this->logOrEcho('FAILED URL: ' . htmlspecialchars($url, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '',2); return false; } else{ @@ -297,7 +296,7 @@ public function loadFileData($postArr){ 'sourceurl = '.($sourceUrl?'"'.$sourceUrl.'"':'NULL').' '. 'WHERE imgid = '.$r1->imgid; if($this->conn->query($sql2)){ - $this->logOrEcho('Existing image replaced with new image mapping: '.($catalogNumber?$catalogNumber:$otherCatalogNumbers).'',1); + $this->logOrEcho('Existing image replaced with new image mapping: '.($catalogNumber?$catalogNumber:$otherCatalogNumbers).'',1); //Delete physical images it previous version was mapped locally $this->deleteImage($r1->url); $this->deleteImage($r1->originalurl); @@ -470,7 +469,7 @@ private function getPrimaryKey($targetIdentifier,$sourceIdentifier,$fileName = ' 'VALUES('.$this->collid.',"'.$targetIdentifier.'","unprocessed","'.date('Y-m-d H:i:s').'")'; if($this->conn->query($sql2)){ $occid = $this->conn->insert_id; - $this->logOrEcho('Linked image to new "unprocessed" specimen record (#'.$occid.') ',2); + $this->logOrEcho('Linked image to new "unprocessed" specimen record (#' . htmlspecialchars($occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ') ',2); } else $this->logOrEcho("ERROR creating new occurrence record: ".$this->conn->error,2); } @@ -494,7 +493,7 @@ private function insertImage($occid,$targetFieldArr){ } $sql = 'INSERT INTO images(occid'.$sqlInsert.') VALUES ('.$occid.$sqlValues.')'; if($this->conn->query($sql)){ - $occLink = '#'.$occid.''; + $occLink = '#' . htmlspecialchars($occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''; $this->logOrEcho('Image linked to existing record'.(isset($targetFieldArr['sourceIdentifier'])?' ('.$targetFieldArr['sourceIdentifier'].')':'').': '.$occLink,2); } else{ diff --git a/classes/ImageShared.php b/classes/ImageShared.php index 299b8587bc..8866d2610e 100644 --- a/classes/ImageShared.php +++ b/classes/ImageShared.php @@ -1,9 +1,11 @@ conn = MySQLiConnectionFactory::getCon("write"); - $this->imageRootPath = $GLOBALS["imageRootPath"]; + public function __construct($conn = null){ + if($conn){ + $this->conn = $conn; + $this->connShared = true; + } + else $this->conn = MySQLiConnectionFactory::getCon('write'); + $this->imageRootPath = $GLOBALS['IMAGE_ROOT_PATH']; if(substr($this->imageRootPath,-1) != "/") $this->imageRootPath .= "/"; - $this->imageRootUrl = $GLOBALS["imageRootUrl"]; + $this->imageRootUrl = $GLOBALS['IMAGE_ROOT_URL']; if(substr($this->imageRootUrl,-1) != "/") $this->imageRootUrl .= "/"; - if(array_key_exists('imgTnWidth',$GLOBALS)){ - $this->tnPixWidth = $GLOBALS['imgTnWidth']; + if(array_key_exists('IMG_TN_WIDTH',$GLOBALS)){ + $this->tnPixWidth = $GLOBALS['IMG_TN_WIDTH']; } - if(array_key_exists('imgWebWidth',$GLOBALS)){ - $this->webPixWidth = $GLOBALS['imgWebWidth']; + if(array_key_exists('IMG_WEB_WIDTH',$GLOBALS)){ + $this->webPixWidth = $GLOBALS['IMG_WEB_WIDTH']; } - if(array_key_exists('imgLgWidth',$GLOBALS)){ - $this->lgPixWidth = $GLOBALS['imgLgWidth']; + if(array_key_exists('IMG_LG_WIDTH',$GLOBALS)){ + $this->lgPixWidth = $GLOBALS['IMG_LG_WIDTH']; } - if(array_key_exists('imgFileSizeLimit',$GLOBALS)){ - $this->webFileSizeLimit = $GLOBALS['imgFileSizeLimit']; + if(array_key_exists('IMG_FILE_SIZE_LIMIT',$GLOBALS)){ + $this->webFileSizeLimit = $GLOBALS['IMG_FILE_SIZE_LIMIT']; } //Needed to avoid 403 errors ini_set('user_agent','Mozilla/4.0 (compatible; MSIE 6.0)'); @@ -85,46 +97,54 @@ public function __construct(){ ); $this->context = stream_context_create($opts); ini_set('memory_limit','512M'); - } + } public function __destruct(){ if($this->sourceGdImg) imagedestroy($this->sourceGdImg); - if(!($this->conn === null)){ - $this->conn->close(); - $this->conn = null; + if(!$this->connShared){ + if(!($this->conn === null)){ + $this->conn->close(); + $this->conn = null; + } } } - public function reset(){ - if($this->sourceGdImg) imagedestroy($this->sourceGdImg); - $this->sourceGdImg = null; + public function reset(){ + if($this->sourceGdImg) imagedestroy($this->sourceGdImg); + $this->sourceGdImg = null; - $this->sourcePath = ''; + $this->sourcePath = ''; $this->imgName = ''; $this->imgExt = ''; $this->sourceWidth = 0; $this->sourceHeight = 0; - $this->imgTnUrl = ''; - $this->imgWebUrl = ''; - $this->imgLgUrl = ''; + $this->imgTnUrl = null; + $this->imgWebUrl = null; + $this->imgLgUrl = null; + $this->archiveUrl = null; + $this->sourceUrl = null; + $this->referenceUrl = null; //Image metadata - $this->caption = ''; - $this->photographer = ''; - $this->photographerUid = ''; - $this->sourceUrl = ''; - $this->format = ''; - $this->owner = ''; - $this->locality = ''; - $this->occid = ''; - $this->tid = ''; - $this->sourceIdentifier = ''; - $this->rights = ''; - $this->accessRights = ''; - $this->copyright = ''; - $this->notes = ''; + $this->caption = null; + $this->photographer = null; + $this->photographerUid = null; + $this->format = null; + $this->hashFunction = null; + $this->hashValue = null; + $this->mediaMD5 = null; + $this->owner = null; + $this->locality = null; + $this->occid = null; + $this->tid = null; + $this->sourceIdentifier = null; + $this->rights = null; + $this->accessRights = null; + $this->copyright = null; + $this->anatomy = null; + $this->notes = null; $this->sortSeq = 50; $this->sortOccurrence = 5; @@ -133,7 +153,7 @@ public function reset(){ unset($this->errArr); $this->errArr = array(); - } + } public function uploadImage($imgFile = 'imgfile'){ if($this->targetPath){ @@ -215,8 +235,8 @@ public function parseUrl($url){ $url = str_replace(' ','%20',$url); //If image is relative, add proper domain if(substr($url,0,1) == '/'){ - if(isset($GLOBALS['imageDomain']) && $GLOBALS['imageDomain']){ - $url = $GLOBALS['imageDomain'].$url; + if(!empty($GLOBALS['IMAGE_DOMAIN'])){ + $url = $GLOBALS['IMAGE_DOMAIN'] . $url; } else{ $url = $this->getDomainUrl().$url; @@ -384,17 +404,17 @@ public function processImage(){ } } - $status = $this->databaseImage(); + $status = $this->insertImage(); return $status; } public function createNewImage($subExt, $targetWidth, $qualityRating = 0, $targetPathOverride = ''){ - global $useImageMagick; + global $USE_IMAGE_MAGICK; $status = false; if($this->sourcePath){ if(!$qualityRating) $qualityRating = $this->jpgCompression; - if($useImageMagick) { + if($USE_IMAGE_MAGICK) { // Use ImageMagick to resize images $status = $this->createNewImageImagick($subExt,$targetWidth,$qualityRating,$targetPathOverride); } @@ -446,11 +466,11 @@ private function createNewImageGD($subExt, $newWidth, $qualityRating, $targetPat } if(!$this->sourceGdImg){ if($this->imgExt == '.gif'){ - $this->sourceGdImg = imagecreatefromgif($this->sourcePath); + $this->sourceGdImg = imagecreatefromgif($this->sourcePath); if(!$this->format) $this->format = 'image/gif'; } elseif($this->imgExt == '.png'){ - $this->sourceGdImg = imagecreatefrompng($this->sourcePath); + $this->sourceGdImg = imagecreatefrompng($this->sourcePath); if(!$this->format) $this->format = 'image/png'; } else{ @@ -500,7 +520,7 @@ private function createNewImageGD($subExt, $newWidth, $qualityRating, $targetPat return $status; } - private function databaseImage(){ + public function insertImage(){ $status = false; if($this->imgLgUrl || $this->imgWebUrl){ $urlBase = $this->getUrlBase(); @@ -526,14 +546,15 @@ private function databaseImage(){ //Save currently loaded record $guid = UuidFactory::getUuidV4(); - $sql = 'INSERT INTO images (tid, url, thumbnailurl, originalurl, photographer, photographeruid, format, caption, owner, sourceurl, - copyright, locality, occid, notes, username, sortsequence, sortoccurrence, sourceIdentifier, rights, accessrights, recordID) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; + $sql = 'INSERT INTO images (tid, url, thumbnailurl, originalurl, archiveUrl, sourceurl, referenceUrl, photographer, photographeruid, format, caption, owner, + locality, occid, anatomy, notes, username, sortsequence, sortoccurrence, sourceIdentifier, rights, accessrights, copyright, hashFunction, hashValue, mediaMD5, recordID) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; if($stmt = $this->conn->prepare($sql)) { $userName = $this->cleanInStr($GLOBALS['USERNAME']); - if($stmt->bind_param('issssissssssissiissss', $this->tid, $this->imgWebUrl, $this->imgTnUrl, $this->imgLgUrl, $this->photographer, $this->photographerUid, $this->format, - $this->caption, $this->owner, $this->sourceUrl, $this->copyright, $this->locality, $this->occid, $this->notes, $userName, $this->sortSeq, $this->sortOccurrence, - $this->sourceIdentifier, $this->rights, $this->accessRights, $guid)){ + if($stmt->bind_param('isssssssissssisssiissssssss', $this->tid, $this->imgWebUrl, $this->imgTnUrl, $this->imgLgUrl, $this->archiveUrl, $this->sourceUrl, $this->referenceUrl, + $this->photographer, $this->photographerUid, $this->format, $this->caption, $this->owner, $this->locality, $this->occid, $this->anatomy, + $this->notes, $userName, $this->sortSeq, $this->sortOccurrence, $this->sourceIdentifier, $this->rights, $this->accessRights, $this->copyright, + $this->hashFunction, $this->hashValue, $this->mediaMD5, $guid)){ $stmt->execute(); if($stmt->affected_rows == 1){ $status = true; @@ -553,7 +574,7 @@ public function getUrlBase(){ $urlBase = $this->urlBase; //If central images are on remote server and new ones stored locally, then we need to use full domain //e.g. this portal is sister portal to central portal - if($GLOBALS['imageDomain']) $urlBase = $this->getDomainUrl().$urlBase; + if($GLOBALS['IMAGE_DOMAIN']) $urlBase = $this->getDomainUrl().$urlBase; return $urlBase; } @@ -744,13 +765,8 @@ public function setPhotographer($v){ } public function setPhotographerUid($v){ - if(is_numeric($v)){ - $this->photographerUid = $v; - } - } - - public function setSourceUrl($v){ - $this->sourceUrl = $this->cleanInStr($v); + $v = OccurrenceUtilities::verifyUser($v, $this->conn); + $this->photographerUid = $v; } public function setImgLgUrl($v){ @@ -765,6 +781,18 @@ public function setImgTnUrl($v){ $this->imgTnUrl = $this->cleanInStr($v); } + public function setArchiveUrl($v){ + $this->archiveUrl = $this->cleanInStr($v); + } + + public function setSourceUrl($v){ + $this->sourceUrl = $this->cleanInStr($v); + } + + public function setReferenceUrl($v){ + $this->referenceUrl = $this->cleanInStr($v); + } + public function getTargetPath(){ return $this->targetPath; } @@ -777,6 +805,18 @@ public function getFormat(){ return $this->format; } + public function setHashFunction($v){ + $this->hashFunction = $this->cleanInStr($v); + } + + public function setHashValue($v){ + $this->hashValue = $this->cleanInStr($v); + } + + public function setMediaMD5($v){ + $this->mediaMD5 = $this->cleanInStr($v); + } + public function setOwner($v){ $this->owner = $this->cleanInStr($v); } @@ -801,6 +841,10 @@ public function getTid(){ return $this->tid; } + public function setAnatomy($v){ + $this->anatomy = $this->cleanInStr($v); + } + public function setNotes($v){ $this->notes = $this->cleanInStr($v); } @@ -923,10 +967,10 @@ private function setSourceFileSize(){ if(isset($x['content-length'][1])) $this->sourceFileSize = $x['content-length'][1]; elseif(isset($x['content-length'])) $this->sourceFileSize = $x['content-length']; } - else { - if(isset($x['content-length'])) $this->sourceFileSize = $x['content-length']; - } - /* + else { + if(isset($x['content-length'])) $this->sourceFileSize = $x['content-length']; + } + /* $ch = curl_init($this->sourcePath); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -962,8 +1006,8 @@ public function uriExists($uri) { $fileName = str_replace($GLOBALS['IMAGE_ROOT_URL'],$GLOBALS['IMAGE_ROOT_PATH'],$uri); if(file_exists($fileName)) return true; } - if(isset($GLOBALS['imageDomain']) && $GLOBALS['imageDomain']){ - $uri = $GLOBALS['imageDomain'].$uri; + if(!empty($GLOBALS['IMAGE_DOMAIN'])){ + $uri = $GLOBALS['IMAGE_DOMAIN'].$uri; } else{ $uri = $urlPrefix.$uri; @@ -1081,6 +1125,7 @@ private static function getImgDim1($imgUrl) { $block_size = hexdec($block_size[1]); while(!feof($handle)) { $i += $block_size; + if(!$block_size) return false; $new_block .= fread($handle, $block_size); if(isset($new_block[$i]) && $new_block[$i]=="\xFF") { // New block detected, check for SOF marker @@ -1142,7 +1187,7 @@ private static function getImgDim2($imgUrl) { private function cleanInStr($str){ $newStr = trim($str); $newStr = preg_replace('/\s\s+/', ' ',$newStr); - $newStr = $this->conn->real_escape_string($newStr); + //$newStr = $this->conn->real_escape_string($newStr); return $newStr; } } diff --git a/classes/InstitutionManager.php b/classes/InstitutionManager.php index f0c6ad2fa1..93ea8eaad5 100644 --- a/classes/InstitutionManager.php +++ b/classes/InstitutionManager.php @@ -1,193 +1,206 @@ conn = MySQLiConnectionFactory::getCon("write"); + parent::__construct(null, 'write'); } public function __destruct(){ - if(!($this->conn === null)) $this->conn->close(); + parent::__destruct(); } public function getInstitutionData(){ $retArr = Array(); if($this->iid){ - $sql = 'SELECT iid, institutioncode, institutionname, institutionname2, address1, address2, city, '. - 'stateprovince, postalcode, country, phone, contact, email, url, notes '. - 'FROM institutions '. - 'WHERE iid = '.$this->iid; - //echo $sql; - $rs = $this->conn->query($sql); - while($row = $rs->fetch_assoc()){ - $retArr = $this->cleanOutArr($row); + $sql = 'SELECT iid, institutioncode, institutionname, institutionname2, address1, address2, city, stateprovince, postalcode, country, phone, contact, email, url, notes + FROM institutions + WHERE iid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->iid); + $stmt->execute(); + $rs = $stmt->get_result(); + while($r = $rs->fetch_assoc()){ + $retArr = $r; + } + $rs->free(); + $stmt->close(); } - $rs->free(); } return $retArr; } - public function submitInstitutionEdits($postData){ - $status = true; - if($postData['institutioncode'] && $postData['institutionname']){ - $sql = 'UPDATE institutions SET '. - 'institutioncode = "'.$this->cleanInStr($postData['institutioncode']).'",'. - 'institutionname = "'.$this->cleanInStr($postData['institutionname']).'",'. - 'institutionname2 = '.($postData['institutionname2']?'"'.$this->cleanInStr($postData['institutionname2']).'"':'NULL').','. - 'address1 = '.($postData['address1']?'"'.$this->cleanInStr($postData['address1']).'"':'NULL').','. - 'address2 = '.($postData['address2']?'"'.$this->cleanInStr($postData['address2']).'"':'NULL').','. - 'city = '.($postData['city']?'"'.$this->cleanInStr($postData['city']).'"':'NULL').','. - 'stateprovince = '.($postData['stateprovince']?'"'.$this->cleanInStr($postData['stateprovince']).'"':'NULL').','. - 'postalcode = '.($postData['postalcode']?'"'.$this->cleanInStr($postData['postalcode']).'"':'NULL').','. - 'country = '.($postData['country']?'"'.$this->cleanInStr($postData['country']).'"':'NULL').','. - 'phone = '.($postData['phone']?'"'.$this->cleanInStr($postData['phone']).'"':'NULL').','. - 'contact = '.($postData['contact']?'"'.$this->cleanInStr($postData['contact']).'"':'NULL').','. - 'email = '.($postData['email']?'"'.$this->cleanInStr($postData['email']).'"':'NULL').','. - 'url = '.($postData['url']?'"'.$this->cleanInStr($postData['url']).'"':'NULL').','. - 'notes = '.($postData['notes']?'"'.$this->cleanInStr($postData['notes']).'"':'NULL').' '. - 'WHERE iid = '.$postData['iid']; - //echo "
    $sql
    "; exit; - if(!$this->conn->query($sql)){ - $status = false; - $this->errorStr = 'ERROR editing institution: '.$this->conn->error; + public function updateInstitution($postData){ + $status = false; + if(!empty($postData['institutioncode']) && !empty($postData['institutionname'])){ + $institutionCode = $postData['institutioncode']; + $institutionName = $postData['institutionname']; + $institutionName2 = !empty($postData['institutionname2']) ? $postData['institutionname2'] : null; + $address1 = !empty($postData['address1']) ? $postData['address1'] : null; + $address2 = !empty($postData['address2']) ? $postData['address2'] : null; + $city = !empty($postData['city']) ? $postData['city'] : null; + $stateProvince = !empty($postData['stateprovince']) ? $postData['stateprovince'] : null; + $postalCode = !empty($postData['postalcode']) ? $postData['postalcode'] : null; + $country = !empty($postData['country']) ? $postData['country'] : null; + $phone = !empty($postData['phone']) ? $postData['phone'] : null; + $contact = !empty($postData['contact']) ? $postData['contact'] : null; + $email = !empty($postData['email']) ? $postData['email'] : null; + $url = !empty($postData['url']) ? $postData['url'] : null; + $notes = !empty($postData['notes']) ? $postData['notes'] : null; + $modifiedUid = $GLOBALS['SYMB_UID']; + $sql = 'UPDATE institutions SET institutioncode = ?, institutionname = ?, institutionname2 = ?, address1 = ?, address2 = ?, city = ?, stateprovince = ?, + postalcode = ?, country = ?, phone = ?, contact = ?, email = ?, url = ?, notes = ?, modifiedUid = ?, modifiedTimeStamp = now() + WHERE iid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ssssssssssssssii', $institutionCode, $institutionName, $institutionName2, $address1, $address2, + $city, $stateProvince, $postalCode, $country, $phone, $contact, $email, $url, $notes, $modifiedUid, $postData['iid']); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error) $status = true; + else $this->errorMessage = $stmt->error; + $stmt->close(); } } return $status; } - public function submitInstitutionAdd($postData){ - $newIID = 0; - $sql = 'INSERT INTO institutions (institutioncode, institutionname, institutionname2, address1, address2, city, '. - 'stateprovince, postalcode, country, phone, contact, email, url, notes) '. - 'VALUES ("'.$postData['institutioncode'].'","'. - $this->cleanInStr($postData['institutionname']).'",'. - ($postData['institutionname2']?'"'.$this->cleanInStr($postData['institutionname2']).'"':'NULL').','. - ($postData['address1']?'"'.$this->cleanInStr($postData['address1']).'"':'NULL').','. - ($postData['address2']?'"'.$this->cleanInStr($postData['address2']).'"':'NULL').','. - ($postData['city']?'"'.$this->cleanInStr($postData['city']).'"':'NULL').','. - ($postData['stateprovince']?'"'.$this->cleanInStr($postData['stateprovince']).'"':'NULL').','. - ($postData['postalcode']?'"'.$this->cleanInStr($postData['postalcode']).'"':'NULL').','. - ($postData['country']?'"'.$this->cleanInStr($postData['country']).'"':'NULL').','. - ($postData['phone']?'"'.$this->cleanInStr($postData['phone']).'"':'NULL').','. - ($postData['contact']?'"'.$this->cleanInStr($postData['contact']).'"':'NULL').','. - ($postData['email']?'"'.$this->cleanInStr($postData['email']).'"':'NULL').','. - ($postData['url']?'"'.$postData['url'].'"':'NULL').','. - ($postData['notes']?'"'.$this->cleanInStr($postData['notes']).'"':'NULL').') '; - //echo "
    $sql
    "; exit; - if($this->conn->query($sql)){ - $newIID = $this->conn->insert_id; - if($newIID && $postData['targetcollid']){ - $sql2 = 'UPDATE omcollections SET iid = '.$newIID.' WHERE (iid IS NULL) AND (collid = '.$postData['targetcollid'].')'; - $this->conn->query($sql2); - } + public function insertInstitution($postData){ + $status = false; + if(empty($postData['institutioncode']) || empty($postData['institutionname'])){ + $this->errorMessage = 'required field are null'; + return false; } - else{ - $this->errorStr = 'ERROR creating institution: '.$this->conn->error; - } - return $newIID; - } - - public function deleteInstitution($delIid){ - $status = true; - //Check to see if record is linked to collections - $sql = 'SELECT collid, CONCAT_WS(" ",CollectionName,CONCAT(InstitutionCode,IFNULL(CONCAT(":",CollectionCode),""))) AS name '. - 'FROM omcollections WHERE iid = '.$delIid.' ORDER BY CollectionName,InstitutionCode,CollectionCode'; - //echo $sql; - $rs = $this->conn->query($sql); - if($rs->num_rows){ - $status = false; - $this->errorStr = 'ERROR deleting institution: Following collections need to be unlinked to institution before deletion is allowed'; - $this->errorStr .= '
      '; - while($r = $rs->fetch_object()){ - $this->errorStr .= '
    • '.$r->name.'
    • '; + $institutionCode = $postData['institutioncode']; + $institutionName = $postData['institutionname']; + $institutionName2 = !empty($postData['institutionname2']) ? $postData['institutionname2'] : null; + $address1 = !empty($postData['address1']) ? $postData['address1'] : null; + $address2 = !empty($postData['address2']) ? $postData['address2'] : null; + $city = !empty($postData['city']) ? $postData['city'] : null; + $stateProvince = !empty($postData['stateprovince']) ? $postData['stateprovince'] : null; + $postalCode = !empty($postData['postalcode']) ? $postData['postalcode'] : null; + $country = !empty($postData['country']) ? $postData['country'] : null; + $phone = !empty($postData['phone']) ? $postData['phone'] : null; + $contact = !empty($postData['contact']) ? $postData['contact'] : null; + $email = !empty($postData['email']) ? $postData['email'] : null; + $url = !empty($postData['url']) ? $postData['url'] : null; + $notes = !empty($postData['notes']) ? $postData['notes'] : null; + $modifiedUid = $GLOBALS['SYMB_UID']; + $sql = 'INSERT INTO institutions (institutioncode, institutionname, institutionname2, address1, address2, city, stateprovince, postalcode, country, phone, contact, email, url, notes, modifiedUid, modifiedTimeStamp) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now())'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ssssssssssssssi', $institutionCode, $institutionName, $institutionName2, $address1, $address2, + $city, $stateProvince, $postalCode, $country, $phone, $contact, $email, $url, $notes, $modifiedUid); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error){ + $this->iid = $stmt->insert_id; + $status = true; } - $this->errorStr .= '

    '; + else $this->errorMessage = $stmt->error; + $stmt->close(); } - $rs->free(); - if(!$status) return false; - //Check outgoing and incoming loans - $sql = 'SELECT loanid '. - 'FROM omoccurloans '. - 'WHERE iidOwner = '.$delIid.' OR iidBorrower = '.$delIid; - $rs = $this->conn->query($sql); - if($rs->num_rows){ - $status = false; - $this->errorStr = 'ERROR deleting institution: Institution is linked to '.$rs->num_rows.' loan records'; - } - $rs->free(); - - if($status){ - //If record is not linked to other data, OK to delete - $sql = 'DELETE FROM institutions WHERE iid = '.$delIid; - //echo $sql; exit; - if(!$this->conn->query($sql)){ - $status = false; - $this->errorStr = 'ERROR deleting institution: '.$this->conn->error; + if($status && $this->iid){ + if(is_numeric($postData['targetcollid'])){ + $this->updateCollectionLink($postData['targetcollid'], $this->iid); } } return $status; } - public function removeCollection($collid){ + public function deleteInstitution($delIid){ $status = true; - $sql = 'UPDATE omcollections SET iid = NULL WHERE collid = '.$collid; - //echo $sql; exit; - if(!$this->conn->query($sql)){ - $status = false; - $this->errorStr = 'ERROR removing collection from institution: '.$this->conn->error; + if($this->verifyInstitutionDeletion($delIid)){ + $sql = 'DELETE FROM institutions WHERE iid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $delIid); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error) $status = true; + else{ + $status = false; + $this->errorMessage = $stmt->error; + } + $stmt->close(); + } } return $status; } - public function addCollection($collid,$iid){ + private function verifyInstitutionDeletion($iid){ $status = true; - if(is_numeric($collid) && is_numeric($iid)){ - $sql = 'UPDATE omcollections SET iid = '.$iid.' WHERE collid = '.$collid; - //echo $sql; exit; - if(!$this->conn->query($sql)){ + //Check to see if record is linked to collections + $sql = 'SELECT CONCAT_WS(" ", collectionName, CONCAT_WS(":", institutionCode, collectionCode)) AS name + FROM omcollections + WHERE iid = ? + ORDER BY collectionName, institutionCode, collectionCode'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $iid); + $stmt->execute(); + $collectionName = ''; + $stmt->bind_result($collectionName); + while($stmt->fetch()){ + $this->warningArr[] = $collectionName; $status = false; - $this->errorStr = 'ERROR adding collection to institution: '.$this->conn->error; + } + $stmt->close(); + if(!$status){ + $this->errorMessage = 'LINKED_COLLECTIONS'; + return false; } } - return $status; - } - public function setInstitutionId($id){ - if(is_numeric($id)){ - $this->iid = $id; + //Check outgoing and incoming loans + $sql = 'SELECT loanid FROM omoccurloans WHERE iidOwner = ? OR iidBorrower = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ii', $iid, $iid); + $stmt->execute(); + $loanID = ''; + $stmt->bind_result($loanID); + while($stmt->fetch()){ + $this->warningArr[] = $loanID; + $status = false; + } + $stmt->close(); + if(!$status){ + $this->errorMessage = 'LINKED_LOANS'; + return false; + } } + return $status; } - public function getInstitutionId(){ - return $this->iid; - } - - public function getErrorStr(){ - return $this->errorStr; + //Collection functions + public function updateCollectionLink($collid, $iid){ + $status = false; + $sql = 'UPDATE omcollections SET iid = ? WHERE collid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ii', $iid, $collid); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error) $status = true; + else $this->errorMessage = $stmt->error; + $stmt->close(); + } + return $status; } + //Misc data retrival functions public function getInstitutionList(){ $retArr = Array(); - $sql = 'SELECT i.iid, c.collid, i.institutioncode, i.institutionname, i.institutionname2, i.address1, i.address2, i.city, '. - 'i.stateprovince, i.postalcode, i.country, i.phone, i.contact, i.email, i.url, i.notes '. - 'FROM institutions i LEFT JOIN omcollections c ON i.iid = c.iid '. - 'ORDER BY i.institutionname, i.institutioncode'; - //echo $sql; + $sql = 'SELECT i.iid, c.collid, i.institutioncode, i.institutionname + FROM institutions i LEFT JOIN omcollections c ON i.iid = c.iid + ORDER BY i.institutionname, i.institutioncode'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ if(isset($retArr[$r->iid])){ - $collStr = $retArr[$r->iid]['collid'].','.$r->collid; + $collStr = $retArr[$r->iid]['collid'] . ',' . $r->collid; $retArr[$r->iid]['collid'] = $collStr; } else{ - $retArr[$r->iid] = $this->cleanOutArr($r); + $retArr[$r->iid]['collid'] = $r->collid; + $retArr[$r->iid]['institutioncode'] = $this->cleanOutStr($r->institutioncode); + $retArr[$r->iid]['institutionname'] = $this->cleanOutStr($r->institutionname); } } $rs->free(); @@ -196,39 +209,25 @@ public function getInstitutionList(){ public function getCollectionList(){ $retArr = Array(); - $sql = 'SELECT collid, iid, CONCAT(collectionname, " (", CONCAT_WS("-",institutioncode, collectioncode),")") AS collname '. - 'FROM omcollections '. - 'ORDER BY collectionname,institutioncode'; - //echo $sql; exit; + $sql = 'SELECT collid, iid, CONCAT(collectionname, " (", CONCAT_WS("-",institutioncode, collectioncode),")") AS collname + FROM omcollections + ORDER BY collectionname, institutioncode'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[$r->collid]['name'] = $r->collname; + $retArr[$r->collid]['name'] = $this->cleanOutStr($r->collname); $retArr[$r->collid]['iid'] = $r->iid; } $rs->free(); return $retArr; } - private function cleanOutArr($inArr){ - $outArr = array(); - foreach($inArr as $k => $v){ - $outArr[$k] = $this->cleanOutStr($v); - } - return $outArr; - } - - private function cleanOutStr($str){ - $newStr = str_replace('"',""",$str); - $newStr = str_replace("'","'",$newStr); - //$newStr = $this->conn->real_escape_string($newStr); - return $newStr; + //Setters and getters + public function setInstitutionId($id){ + $this->iid = filter_var($id, FILTER_SANITIZE_NUMBER_INT); } - private function cleanInStr($str){ - $newStr = trim($str); - $newStr = preg_replace('/\s\s+/', ' ',$newStr); - $newStr = $this->conn->real_escape_string($newStr); - return $newStr; + public function getInstitutionId(){ + return $this->iid; } } ?> \ No newline at end of file diff --git a/classes/KeyCharAdmin.php b/classes/KeyCharAdmin.php index 6a460c5cbd..c0628d7953 100644 --- a/classes/KeyCharAdmin.php +++ b/classes/KeyCharAdmin.php @@ -281,7 +281,7 @@ public function uploadCsImage($formArr){ global $PARAMS_ARR; $statusStr = ''; if(is_numeric($formArr['cid']) && is_numeric($formArr['cs'])){ - $imageRootPath = $GLOBALS["imageRootPath"]; + $imageRootPath = $GLOBALS['IMAGE_ROOT_PATH']; if(substr($imageRootPath,-1) != "/") $imageRootPath .= "/"; if(file_exists($imageRootPath)){ $imageRootPath .= 'ident/'; @@ -297,7 +297,7 @@ public function uploadCsImage($formArr){ } } //Create url prefix - $imageRootUrl = $GLOBALS["imageRootUrl"]; + $imageRootUrl = $GLOBALS['IMAGE_ROOT_URL']; if(substr($imageRootUrl,-1) != "/") $imageRootUrl .= "/"; $imageRootUrl .= 'ident/csimgs/'; @@ -381,7 +381,7 @@ private function createNewCsImage($path){ public function deleteCsImage($csImgId){ $statusStr = 'SUCCESS: image uploaded successful'; //Remove image from file system - $imageRootPath = $GLOBALS["imageRootPath"]; + $imageRootPath = $GLOBALS['IMAGE_ROOT_PATH']; if(substr($imageRootPath,-1) != "/") $imageRootPath .= "/"; $imageRootPath .= 'ident/csimgs/'; $sql = 'SELECT url FROM kmcsimages WHERE csimgid = '.$csImgId; @@ -498,10 +498,13 @@ public function getGlossaryList(){ $sql = 'SELECT glossid, term, language FROM glossary'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[$r->glossid]['term'] = $r->term; - $retArr[$r->glossid]['lang'] = $r->language; + //$k variable is needed to so that list can be alphabetical even when html tags (e.g. italics) are embedded into the terms + $k = strip_tags(strtolower($r->term)); + $retArr[$k][$r->glossid]['term'] = $r->term; + $retArr[$k][$r->glossid]['lang'] = $r->language; } $rs->free(); + ksort($retArr); return $retArr; } @@ -531,12 +534,12 @@ public function setLanguage($l){ public function setLangId($lang=''){ if(!$lang){ - if($GLOBALS['defaultLang']) $lang = $GLOBALS['defaultLang']; + if($GLOBALS['DEFAULT_LANG']) $lang = $GLOBALS['DEFAULT_LANG']; else $lang = 'English'; } if(is_numeric($lang)) $this->langId = $lang; else{ - $sql = 'SELECT langid FROM adminlanguages WHERE langname = "'.$lang.'" OR iso639_1 = "'.$lang.'" OR iso639_2 = "'.$lang.'" '; + $sql = 'SELECT langid FROM adminlanguages WHERE langname = "'.$this->cleanInStr($lang).'" OR iso639_1 = "'.$this->cleanInStr($lang).'" OR iso639_2 = "'.$this->cleanInStr($lang).'" '; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $this->langId = $r->langid; @@ -547,8 +550,11 @@ public function setLangId($lang=''){ //General functions private function cleanOutStr($str){ - $newStr = str_replace('"',""",$str); - $newStr = str_replace("'","'",$newStr); + $newStr = $str; + if(isset($str)){ + $newStr = str_replace('"',""",$str); + $newStr = str_replace("'","'",$newStr); + } //$newStr = $this->conn->real_escape_string($newStr); return $newStr; } diff --git a/classes/KeyDataManager.php b/classes/KeyDataManager.php index 43df896775..736746c718 100644 --- a/classes/KeyDataManager.php +++ b/classes/KeyDataManager.php @@ -101,7 +101,7 @@ private function getCharList(){ $charName = $row->CharName; if($row->chardescr) $charName = ''.$charName.''; if($row->helpurl) $charName .= ' '; - if($row->glossid) $charName .= ' '; + if($row->glossid) $charName .= ' '; $diffRank = false; //if($row->DifficultyRank && $row->DifficultyRank > 1 && !array_key_exists($charCID,$this->charArr)) $diffRank = true; @@ -219,8 +219,8 @@ public function getCharArr(){ $headingID = $r->hid; $charName = $r->CharName; if($r->chardescr) $charName = ''.$charName.''; - if($r->helpurl) $charName .= ' '; - if($r->charglossid) $charName .= ' '; + if($r->helpurl) $charName .= ' '; + if($r->charglossid) $charName .= ' '; $diffRank = false; //if($r->DifficultyRank && $r->DifficultyRank > 1 && !array_key_exists($charCID,$this->charArr)) $diffRank = true; @@ -245,8 +245,8 @@ public function getCharArr(){ } $charStateName = $r->CharStateName; if($r->csdescr) $charStateName = ''.$r->CharStateName.''; - if($r->csglossid) $charStateName .= ' '; - if($r->csimgurl) $charStateName .= ' '; + if($r->csglossid) $charStateName .= ' '; + if($r->csimgurl) $charStateName .= ' '; $headingArray[$headingID][$charCID][$cs][$language] = $charStateName; } } @@ -447,7 +447,7 @@ public function getIntroHtml(){ $returnStr .= "This key is still in the developmental phase. The application, data model, and actual data will need tuning. ". "The key has been developed to minimize the exclusion of species due to the ". "lack of data. The consequences of this is that a 'shrubs' selection may show non-shrubs until that information is corrected. ". - "User input is necessary for the key to improve! Please email me with suggestions, comments, or problems: ".$GLOBALS['ADMIN_EMAIL']."

    "; + "User input is necessary for the key to improve! Please email me with suggestions, comments, or problems: " . htmlspecialchars($GLOBALS['ADMIN_EMAIL'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . "

    "; $returnStr .= "Note: If few morphological characters are displayed for a particular checklist, it is likely due to not yet having enough ". "morphological data compiled for that subset of species. If you would like to help, please email me at the above address. "; return $returnStr; @@ -611,7 +611,7 @@ public function getClType(){ public function setRelevanceValue($rel){ if(is_numeric($rel)){ - $this->relevanceValue = filter_var($rel, FILTER_SANITIZE_NUMBER_INT); + $this->relevanceValue = $rel; } } diff --git a/classes/KeyMatrixEditor.php b/classes/KeyMatrixEditor.php index 59d73d58e8..3b9f315a5e 100644 --- a/classes/KeyMatrixEditor.php +++ b/classes/KeyMatrixEditor.php @@ -178,10 +178,10 @@ private function processTaxa($tid,$indent=0){ private function echoTaxaRow($tid,$sciname,$indent = 0){ if(!is_numeric($indent)) $indent = 0; if(is_numeric($tid)){ - echo '
    '; + echo ''; + echo ''; foreach($this->stateArr as $cs => $csName){ $isSelected = false; $isInherited = false; @@ -299,4 +299,4 @@ public function setClid($clid){ } } } -?> \ No newline at end of file +?> diff --git a/classes/Manager.php b/classes/Manager.php index caffcabd66..c75f340b4f 100644 --- a/classes/Manager.php +++ b/classes/Manager.php @@ -23,6 +23,7 @@ public function __construct($id=null, $conType='readonly', $connOverride = null) $this->isConnInherited = true; } else $this->conn = MySQLiConnectionFactory::getCon($conType); + if($this->conn === null) exit; if($id != null || is_numeric($id)){ $this->id = $id; } @@ -108,10 +109,24 @@ public function sanitizeInt($int){ return filter_var($int, FILTER_SANITIZE_NUMBER_INT); } + public function cleanOutArray($inputArray){ + if(is_array($inputArray)){ + foreach($inputArray as $key => $value){ + if(is_array($value)){ + $inputArray[$key] = $this->cleanOutArray($value); + } + else{ + $inputArray[$key] = $this->cleanOutStr($value); + } + } + } + return $inputArray; + } + public function cleanOutStr($str){ //Sanitize output if(!is_string($str) && !is_numeric($str) && !is_bool($str)) $str = ''; - $str = htmlspecialchars($str, HTML_SPECIAL_CHARS_FLAGS); + $str = htmlspecialchars($str, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE); return $str; } @@ -132,10 +147,11 @@ protected function cleanInArray($arr){ return $newArray; } - protected function encodeString($inStr){ + protected function encodeString($inStr, $charsetOut = ''){ $retStr = ''; + if(!$charsetOut && !empty($GLOBALS['CHARSET'])) $charsetOut = $GLOBALS['CHARSET']; if($inStr){ - $retStr = $inStr; + $retStr = trim($inStr); //Get rid of UTF-8 curly smart quotes and dashes $badwordchars=array("\xe2\x80\x98", // left single quote "\xe2\x80\x99", // right single quote @@ -145,23 +161,9 @@ protected function encodeString($inStr){ "\xe2\x80\xa6" // elipses ); $fixedwordchars=array("'", "'", '"', '"', '-', '...'); - $inStr = str_replace($badwordchars, $fixedwordchars, $inStr); - - if($inStr){ - if(strtolower($GLOBALS['CHARSET']) == "utf-8" || strtolower($GLOBALS['CHARSET']) == "utf8"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($GLOBALS['CHARSET']) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } - //$line = iconv('macintosh', 'UTF-8', $line); - //mb_detect_encoding($buffer, 'windows-1251, macroman, UTF-8'); + $retStr = str_replace($badwordchars, $fixedwordchars, $retStr); + if($retStr){ + $retStr = mb_convert_encoding($retStr, $charsetOut, mb_detect_encoding($retStr)); } } return $retStr; diff --git a/classes/MapSupport.php b/classes/MapSupport.php index 674f574200..3b5c06a6c2 100644 --- a/classes/MapSupport.php +++ b/classes/MapSupport.php @@ -3,8 +3,11 @@ class MapSupport extends Manager{ + private $targetPath = ''; + private $targetUrl = ''; + public function __construct(){ - parent::__construct(); + parent::__construct(null, 'write'); } public function __destruct(){ @@ -12,6 +15,217 @@ public function __destruct(){ } //Static Map functions + public function getTaxaList($tidFilter = false, $onlyTaxaWithoutMaps = false){ + $retArr = array(); + //Following SQL grabs all accepted taxa at species / infraspecific rank that don't yet have a distribution map + //Eventually well probably add ability to refresh all maps older than a certain date, and/or by other criteria + //Return limit is currently set at 1000 records, which maybe should be variable that is set by user? + $sql = 'SELECT DISTINCT t.tid, t.sciname + FROM taxa t INNER JOIN taxstatus ts ON t.tid = ts.tidaccepted + INNER JOIN omoccurrences o ON ts.tid = o.tidinterpreted '; + if($tidFilter) $sql .= 'INNER JOIN taxaenumtree e ON t.tid = e.tid '; + if($onlyTaxaWithoutMaps) $sql .= 'LEFT JOIN taxamaps m ON t.tid = m.tid '; + $sql .= 'WHERE t.rankid > 219 AND ts.taxauthid = 1 '; + if($tidFilter) $sql .= 'AND e.taxauthid = 1 AND (e.parentTid = ? OR ts.tid = ?) '; + if($onlyTaxaWithoutMaps) $sql .= 'AND m.tid IS NULL '; + $sql .= ' ORDER BY t.sciname'; + if($stmt = $this->conn->prepare($sql)){ + if($tidFilter) $stmt->bind_param('ii', $tidFilter, $tidFilter); + $stmt->execute(); + $stmt->bind_result($tid, $sciname); + while($stmt->fetch()){ + array_push($retArr, ['tid' => $tid, 'sciname' => $sciname]); + } + $stmt->close(); + } + return $retArr; + } + + public function getCoordinates($tid, $bounds){ + $retArr = array(); + $tidArr = $this->getRelatedTids($tid); + if($tidArr){ + $latMin = -90; + $latMax = 90; + $lngMin = -180; + $lngMax = 180; + if($bounds){ + $boundArr = explode(';', $bounds); + $latMin = floatval($boundArr[2]); + $latMax = floatval($boundArr[0]); + $lngMin = floatval($boundArr[3]); + $lngMax = floatval($boundArr[1]); + } + + //return [$latMin, $latMax, $lngMin, $lngMax]; + //return ['(' . implode(',', $tidArr) . ')' ]; + $sql = 'SELECT DISTINCT decimalLatitude, decimalLongitude + FROM omoccurrences + WHERE (decimalLatitude BETWEEN '.$latMin.' AND '.$latMax.') AND (decimalLongitude BETWEEN '.$lngMin.' AND '.$lngMax.') + AND (cultivationStatus IS NULL OR cultivationStatus = 0) AND (localitySecurity IS NULL OR localitySecurity = 0) + AND (coordinateUncertaintyInMeters IS NULL OR coordinateUncertaintyInMeters < 5000) + AND tidinterpreted IN('.implode(',', $tidArr).')'; + if($rs = $this->conn->query($sql)){ + while($r = $rs->fetch_object()){ + //$retArr[] = $r->decimalLongitude.','.$r->decimalLatitude; + array_push($retArr, ['lat' => floatval($r->decimalLatitude), 'lng' => floatval($r->decimalLongitude)]); + } + $rs->free(); + //if(count($retArr) > 100) $this->removeOutliers($retArr); + } + } + return $retArr; + } + + private function getRelatedTids($tidAccepted){ + //First get all accepted children; currently expecting input to be at the species ranke, and thus only grabbing one layer down (subsp, var, f) + $tidArr = array($tidAccepted => $tidAccepted); + $sql = 'SELECT tid FROM taxstatus WHERE taxauthid = 1 AND tid = tidaccepted AND parenttid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $tidAccepted); + $stmt->execute(); + $stmt->bind_result($tid); + while($stmt->fetch()){ + $tidArr[$tid] = $tid; + } + $stmt->close(); + } + //Append all non-accepted synonyms + $sql = 'SELECT tid FROM taxstatus WHERE taxauthid = 1 AND tidaccepted IN('.implode(',', $tidArr).')'; + if($rs = $this->conn->query($sql)){ + while($r = $rs->fetch_object()){ + $tidArr[$r->tid] = $r->tid; + } + $rs->free(); + } + return $tidArr; + } + + private function removeOutliers(&$coodArr){ + //Function to theoretically be used when generating a point map + //Remove outlier points, which are likely georeferencing errors and untagged cultivated occurrences + //Not needed for Heat Map, which is expected to automatically filter out outliers + //Remove excess points that obsure map, if needed + //https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-statistics/how-spatial-outlier-detection-works.htm + } + + public function postImage($portArr){ + $status = false; + $tid = $portArr['tid']; + $title = $portArr['title']; + $mapType = 'heatmap'; + + if(isset($portArr['maptype'])) $mapType = $portArr['maptype']; + if(!empty($_FILES['mapupload']['name'])){ + $ext = substr($_FILES['mapupload']['name'], strrpos($_FILES['mapupload']['name'], '.')); + $fileName = $tid.'_'.$mapType.'_'.time() . $ext; + + //Clear Out Old Thumbnails + $this->deleteAllTaxonMaps($tid); + + $this->setTargetPaths(); + if(move_uploaded_file($_FILES['mapupload']['tmp_name'], $this->targetPath.$fileName)){ + $this->targetPath; + $status = $this->insertImage($tid, $title, $this->targetUrl.$fileName); + } + else{ + $this->errorMessage = 'ERROR uploading file (code '.$_FILES['uploadfile']['error'].'): '; + return false; + } + } + } + + private function insertImage($tid, $title, $url){ + $status = false; + $sql = 'INSERT INTO taxamaps(tid, title, url) VALUES(?, ?, ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('iss', $tid, $title, $url); + $stmt->execute(); + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = 'ERROR inserting taxon map: '.$stmt->error; + $stmt->close(); + } + return $status; + } + + public function deleteAllTaxonMaps($tid){ + $status = false; + + $sql = <<conn->prepare($sql)){ + + $stmt->bind_param('i', $tid); + + $stmt->execute(); + $result = $stmt->get_result(); + $cnt = 0; + + while ($myrow = $result->fetch_assoc()) { + $cnt++; + $image_loc = str_replace($GLOBALS['IMAGE_ROOT_URL'], $GLOBALS['IMAGE_ROOT_PATH'], $myrow['url']); + if(file_exists($image_loc)) { + unlink($image_loc); + } + } + $stmt->close(); + + //Bails if there isn't anything to delete + if($cnt === 0) return true; + } + + //Remove Thumbnail data + $sql = 'DELETE FROM taxamaps WHERE tid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $tid); + $stmt->execute(); + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = 'ERROR deleting all taxon maps: '.$stmt->error; + $stmt->close(); + } + + return $status; + } + + private function deleteMap($mid){ + $status = false; + $sql = 'DELETE FROM taxamaps WHERE mid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $mid); + $stmt->execute(); + if($stmt->affected_rows) $status = true; + elseif($stmt->error) $this->errorMessage = 'ERROR deleting taxon map: '.$stmt->error; + $stmt->close(); + } + return $status; + } + + private function setTargetPaths(){ + $targetPath = $GLOBALS['IMAGE_ROOT_PATH']; + $targetUrl = $GLOBALS['IMAGE_ROOT_URL']; + if(!is_writable($targetPath)){ + $this->errorMessage = 'ABORT: target path does not exist or is not writable'; + return false; + } + if(substr($targetPath, -1) != '/') $targetPath .= '/'; + if(substr($targetUrl, -1) != '/') $targetUrl .= '/'; + + $targetPath .= 'maps/'; + $targetUrl .= 'maps/'; + if(!is_dir($targetPath)) mkdir($targetPath); + $ymd = date('Y-m-d'); + $targetPath .= $ymd.'/'; + $targetUrl .= $ymd.'/'; + if(!is_dir($targetPath)) mkdir($targetPath); + $this->targetPath = $targetPath; + $this->targetUrl = $targetUrl; + } + + //Deprecated Static Google Map functions public static function getStaticMap($coordArr){ $mapThumbnails = false; if(isset($GLOBALS['MAP_THUMBNAILS']) && $GLOBALS['MAP_THUMBNAILS']) $mapThumbnails = $GLOBALS['MAP_THUMBNAILS']; @@ -39,4 +253,4 @@ public static function getStaticMap($coordArr){ return $url; } } -?> \ No newline at end of file +?> diff --git a/classes/MediaResolutionTools.php b/classes/MediaResolutionTools.php index b064013d58..98a9d88367 100644 --- a/classes/MediaResolutionTools.php +++ b/classes/MediaResolutionTools.php @@ -157,7 +157,7 @@ private function archiveImage($imgFilePath, $imgid){ } } else{ - $this->logOrEcho('ERROR: image unwritable (imgid: '.$imgid.', path: '.$path.')'); + $this->logOrEcho('ERROR: image unwritable (imgid: ' . htmlspecialchars($imgid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ', path: ' . htmlspecialchars($path, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ')'); } } return $status; @@ -288,7 +288,7 @@ public function migrateCollectionDerivatives($imgIdStart, $limit){ } if(!$pathFrag) $pathFrag = date('Ymd').'/'; if(!file_exists($this->imgRootPath.$pathFrag)) mkdir($this->imgRootPath.$pathFrag); - $this->logOrEcho($processingCnt.': Processing: '.$r->occid.''); + $this->logOrEcho($processingCnt.': Processing: ' . htmlspecialchars($r->occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''); if($this->transferThumbnail && $r->thumbnailurl){ $fileName = basename($r->thumbnailurl); $targetPath = $this->imgRootPath.$pathFrag.$fileName; diff --git a/classes/ObservationSubmitManager.php b/classes/ObservationSubmitManager.php index ab373ebf12..c60617ddec 100644 --- a/classes/ObservationSubmitManager.php +++ b/classes/ObservationSubmitManager.php @@ -29,22 +29,25 @@ public function addObservation($postArr){ $eventDay = date('d',$dateObj); $startDay = date('z',$dateObj)+1; } - //Get tid for scinetific name + //Get tid for scientific name $tid = 0; - $localitySecurity = (array_key_exists('localitysecurity',$postArr)?1:0); + $localitySecurity = (array_key_exists('localitysecurity', $postArr) ? 1 : 0); if($postArr['sciname']){ $result = $this->conn->query('SELECT tid, securitystatus FROM taxa WHERE (sciname = "'.$postArr['sciname'].'")'); if($row = $result->fetch_object()){ $tid = $row->tid; - if($row->securitystatus > 0) $localitySecurity = $row->securitystatus; - if(!$localitySecurity){ - //Check to see if species is rare or sensitive within a state - $sql = 'SELECT cl.tid '. - 'FROM fmchecklists c INNER JOIN fmchklsttaxalink cl ON c.clid = cl.clid '. - 'WHERE c.type = "rarespp" AND c.locality = "'.$postArr['stateprovince'].'" AND cl.tid = '.$tid; - $rs = $this->conn->query($sql); - if($rs->num_rows){ - $localitySecurity = 1; + if(empty($postArr['cultivationstatus'])){ + //Set localitySecurity based on global or state protection setting, but only if not cultivated + if($row->securitystatus > 0) $localitySecurity = $row->securitystatus; + if(!$localitySecurity){ + //Check to see if species is rare or sensitive within a state + $sql = 'SELECT cl.tid + FROM fmchecklists c INNER JOIN fmchklsttaxalink cl ON c.clid = cl.clid + WHERE c.type = "rarespp" AND c.locality = "'.$postArr['stateprovince'].'" AND cl.tid = '.$tid; + $rs = $this->conn->query($sql); + if($rs->num_rows){ + $localitySecurity = 1; + } } } } diff --git a/classes/OccurrenceAccessStats.php b/classes/OccurrenceAccessStats.php index 7c7ab772ab..43a8eb8a98 100644 --- a/classes/OccurrenceAccessStats.php +++ b/classes/OccurrenceAccessStats.php @@ -25,7 +25,7 @@ public function __destruct(){ public function recordAccessEventByArr($occidArr, $accessType){ $status = true; - if(isset($GLOBALS['STORE_STATISTICS'])){ + if(!empty($GLOBALS['STORE_STATISTICS'])){ if($occurAccessID = $this->insertAccessEvent($accessType)){ foreach($occidArr as $occid){ if(is_numeric($occid)){ @@ -41,7 +41,7 @@ public function recordAccessEventByArr($occidArr, $accessType){ public function recordAccessEvent($occid, $accessType){ $status = false; - if(isset($GLOBALS['STORE_STATISTICS'])){ + if(!empty($GLOBALS['STORE_STATISTICS'])){ if(is_numeric($occid)){ $this->verboseMode = 1; $this->setLogFH($this->logPath); @@ -55,7 +55,7 @@ public function recordAccessEvent($occid, $accessType){ public function batchRecordEventsBySql($sqlFrag, $accessType){ $status = true; - if(isset($GLOBALS['STORE_STATISTICS'])){ + if(!empty($GLOBALS['STORE_STATISTICS'])){ $this->verboseMode = 1; $this->setLogFH($this->logPath); if($occurAccessID = $this->insertAccessEvent($accessType, $sqlFrag)){ @@ -67,7 +67,7 @@ public function batchRecordEventsBySql($sqlFrag, $accessType){ public function insertAccessEvent($accessType, $queryStr = null){ $occurAccessID = false; - if(isset($GLOBALS['STORE_STATISTICS'])){ + if(!empty($GLOBALS['STORE_STATISTICS'])){ $remoteAddr = $_SERVER['REMOTE_ADDR']; $userData = @get_browser(); if($userData) $userData = json_encode($userData); @@ -91,16 +91,18 @@ public function insertAccessEvent($accessType, $queryStr = null){ public function insertAccessOccurrence($occurAccessID, $occid){ $status = false; - $sql = 'INSERT INTO omoccuraccesslink(occurAccessID, occid) VALUES(?, ?)'; - $stmt = $this->conn->stmt_init(); - $stmt->prepare($sql); - $stmt->bind_param('ii', $occurAccessID, $occid); - if($stmt->execute()) $status = true; - else{ - $this->errorMessage = date('Y-m-d H:i:s').' - ERROR creating access occurrence instance: '.$this->conn->error; - $this->logOrEcho($this->errorMessage); + if(!empty($GLOBALS['STORE_STATISTICS'])){ + $sql = 'INSERT IGNORE INTO omoccuraccesslink(occurAccessID, occid) VALUES(?, ?)'; + $stmt = $this->conn->stmt_init(); + $stmt->prepare($sql); + $stmt->bind_param('ii', $occurAccessID, $occid); + if($stmt->execute()) $status = true; + else{ + $this->errorMessage = date('Y-m-d H:i:s').' - ERROR creating access occurrence instance: '.$this->conn->error; + $this->logOrEcho($this->errorMessage); + } + $stmt->close(); } - $stmt->close(); return $status; } diff --git a/classes/OccurrenceAssociations.php b/classes/OccurrenceAssociations.php index b931dce657..dc6dae3640 100644 --- a/classes/OccurrenceAssociations.php +++ b/classes/OccurrenceAssociations.php @@ -171,7 +171,7 @@ private function databaseAssocSpecies($assocArr, $occid){ $sql = trim($sql,', '); //echo $sql; exit; if(!$this->conn->query($sql)){ - echo '
  • ERROR adding assocaited values ('.$occid.'): '.$this->conn->error.'
  • '; + echo '
  • ERROR adding assocaited values (' . htmlspecialchars($occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '): ' . htmlspecialchars($this->conn->error, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '
  • '; //echo '
  • SQL: '.$sql.'
  • '; } } diff --git a/classes/OccurrenceAttributeSearch.php b/classes/OccurrenceAttributeSearch.php index a845064c26..8e8e4c7fa4 100644 --- a/classes/OccurrenceAttributeSearch.php +++ b/classes/OccurrenceAttributeSearch.php @@ -59,10 +59,10 @@ private function getTraitSearchHTML($traitID,$classStr=''){ $classArr = explode(' ',$classStr); $divClass = array_pop($classArr); } - $retStr = '
    '; + $retStr = '
    '; if(isset($this->traitArr[$traitID]['states'])){ if($this->traitArr[$traitID]['type']=='TF'){ - $retStr .= '
    '.$this->traitArr[$traitID]['name'].':
    '; + $retStr .= '
    ' . $this->traitArr[$traitID]['name'] . ':
    '; $retStr .= '
    '; } else $retStr .= '
    '; @@ -75,18 +75,20 @@ private function getTraitSearchHTML($traitID,$classStr=''){ continue; } else{ - $retStr .= '
    '; - $retStr .= ' '; - $retStr .= $sArr['name']; + $retStr .= '
    '; + $retStr .= '
    '; + $retStr .= '
    '; + $retStr .= '
    '; + $retStr .= '
    '; if($depTraitIdArr){ foreach($depTraitIdArr as $depTraitId){ - $retStr .= $this->getTraitSearchHTML($depTraitId,trim($classStr.' child-'.$sid)); + $retStr .= $this->getTraitSearchHTML($depTraitId, trim($classStr . ' child-' . $sid)); } } $retStr .= '
    '; } } - $retStr .= '
    '; + $retStr .= '
    '; } $retStr .= '
    '; return $retStr; diff --git a/classes/OccurrenceAttributes.php b/classes/OccurrenceAttributes.php index 692fbd02b6..1d4dc77a78 100644 --- a/classes/OccurrenceAttributes.php +++ b/classes/OccurrenceAttributes.php @@ -691,10 +691,10 @@ public function getFilterAttribute($attributeName){ public function getLocalFilterOptions(){ $retArr = array(); - $sql = 'SELECT DISTINCT countryName AS localstr FROM lkupcountry UNION SELECT DISTINCT stateName AS localstr FROM lkupstateprovince'; + $sql = 'SELECT geoterm FROM geographicthesaurus WHERE geolevel IN(50,60)'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[] = $r->localstr; + $retArr[] = $r->geoterm; } $rs->free(); sort($retArr); diff --git a/classes/OccurrenceChecklistManager.php b/classes/OccurrenceChecklistManager.php index 073eb4da7d..006d824e5a 100644 --- a/classes/OccurrenceChecklistManager.php +++ b/classes/OccurrenceChecklistManager.php @@ -41,8 +41,9 @@ public function getChecklist($taxonAuthorityId){ } $result = $this->conn->query($sql); while($r = $result->fetch_object()){ - $family = strtoupper($r->family); - if(!$family) $family = 'undefined'; + $family = $r->family; + if($family) $family = strtoupper($family); + else $family = 'undefined'; $sciName = $r->sciname; if($sciName && substr($sciName,-5)!='aceae' && substr($sciName,-4)!='idae'){ $returnVec[$family][$sciName] = $r->tid; diff --git a/classes/OccurrenceCleaner.php b/classes/OccurrenceCleaner.php index 4ccfb576e4..dea39077e7 100644 --- a/classes/OccurrenceCleaner.php +++ b/classes/OccurrenceCleaner.php @@ -43,7 +43,7 @@ public function getDuplicateCatalogNumber($type, $start, $limit = 500){ } $rs->free(); - $retArr = array(); + $stagingArr = array(); if($dupArr){ $sqlFrag = ''; if($type=='cat'){ @@ -52,13 +52,15 @@ public function getDuplicateCatalogNumber($type, $start, $limit = 500){ else{ $sqlFrag = 'occid, otherCatalogNumbers, otherCatalogNumbers AS dupid FROM omoccurrences WHERE collid = '.$this->collid.' AND otherCatalogNumbers IN("'.implode('","', $dupArr).'") ORDER BY otherCatalogNumbers'; } - $retArr = $this->getDuplicates($sqlFrag); + $stagingArr = $this->getDuplicates($sqlFrag); } if($type=='other' && count($dupArr) < $limit){ - $retArr = array_merge($retArr, $this->setAdditionalIdentifiers($cnt, ($limit - count($dupArr)))); + $stagingArr = array_merge($stagingArr, $this->setAdditionalIdentifiers($cnt, ($limit - count($dupArr)))); } + //Replace catalog number keys with renumbered numeric keys, thus avoid unusual characters interferring with naming form target element + $retArr = array_values($stagingArr); return $retArr; } @@ -104,13 +106,10 @@ public function getDuplicateCollectorNumber($start){ $sql = 'SELECT o.occid, o.eventdate, recordedby, o.recordnumber '. 'FROM omoccurrences o INNER JOIN '. '(SELECT eventdate, recordnumber FROM omoccurrences GROUP BY eventdate, recordnumber, collid '. - 'HAVING Count(*)>1 AND collid = '.$this->collid. - ' AND eventdate IS NOT NULL AND recordnumber IS NOT NULL '. - 'AND recordnumber NOT IN("sn","s.n.","Not Provided","unknown")) intab '. - 'ON o.eventdate = intab.eventdate AND o.recordnumber = intab.recordnumber '. + 'HAVING Count(*)>1 AND collid = '.$this->collid.' AND eventdate IS NOT NULL AND recordnumber IS NOT NULL '. + 'AND recordnumber NOT IN("sn","s.n.","Not Provided","unknown")) intab ON o.eventdate = intab.eventdate AND o.recordnumber = intab.recordnumber '. 'WHERE collid = '.$this->collid.' '; } - //echo $sql; $rs = $this->conn->query($sql); $collArr = array(); while($r = $rs->fetch_object()){ @@ -150,7 +149,7 @@ private function getDuplicates($sqlFragment){ $sqlFragment; $rs = $this->conn->query($sql); while($row = $rs->fetch_assoc()){ - $retArr[$row['dupid']][$row['occid']] = array_change_key_case($row); + $retArr[strtolower($row['dupid'])][$row['occid']] = array_change_key_case($row); } $rs->free(); return $retArr; @@ -160,6 +159,7 @@ public function mergeDupeArr($occidArr){ $status = true; $this->verboseMode = 2; $editorManager = new OccurrenceEditorManager($this->conn); + $editorManager->setCollId($this->collid); foreach($occidArr as $target => $occArr){ $mergeArr = array($target); foreach($occArr as $source){ @@ -200,9 +200,7 @@ public function indexCollectors(){ //Query unlinked specimens and try to parse each collector $collArr = array(); - $sql = 'SELECT occid, recordedby '. - 'FROM omoccurrences '. - 'WHERE recordedbyid IS NULL'; + $sql = 'SELECT occid, recordedby FROM omoccurrences WHERE recordedbyid IS NULL'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $collArr[$r->recordedby][] = $r->occid; @@ -211,7 +209,7 @@ public function indexCollectors(){ foreach($collArr as $collStr => $occidArr){ // check to see if collector is listed in agents table. - $sql = "select distinct agentid from agentname where name = ? "; + $sql = 'select distinct agentid from agentname where name = ? '; if ($stmt = $this->conn->prepare($sql)) { $stmt->bind_param('s',$collStr); $stmt->execute(); @@ -240,9 +238,7 @@ public function indexCollectors(){ //Add recordedbyid to omoccurrence table if($recById){ - $sql = 'UPDATE omoccurrences '. - 'SET recordedbyid = '.$recById. - ' WHERE occid IN('.implode(',',$occidArr).') AND recordedbyid IS NULL '; + $sql = 'UPDATE omoccurrences SET recordedbyid = '.$recById.' WHERE occid IN('.implode(',',$occidArr).') AND recordedbyid IS NULL '; $this->conn->query($sql); } } @@ -336,9 +332,9 @@ public function countryCleanFirstStep(){ //Bad countries public function getBadCountryCount(){ $retCnt = 0; - $sql = 'SELECT COUNT(DISTINCT o.country) AS cnt '. - 'FROM omoccurrences o LEFT JOIN lkupcountry l ON o.country = l.countryname '. - 'WHERE o.country IS NOT NULL AND o.collid = '.$this->collid.' AND l.countryid IS NULL '; + $sql = 'SELECT COUNT(DISTINCT country) AS cnt + FROM omoccurrences + WHERE country IS NOT NULL AND collid = '.$this->collid.' AND country NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50)'; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -349,10 +345,10 @@ public function getBadCountryCount(){ public function getBadCountryArr(){ $retArr = array(); - $sql = 'SELECT country, count(o.occid) as cnt '. - 'FROM omoccurrences o LEFT JOIN lkupcountry l ON o.country = l.countryname '. - 'WHERE o.country IS NOT NULL AND o.collid = '.$this->collid.' AND l.countryid IS NULL '. - 'GROUP BY o.country '; + $sql = 'SELECT country, count(occid) as cnt + FROM omoccurrences + WHERE country IS NOT NULL AND collid = '.$this->collid.' AND country NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50) + GROUP BY country'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->country] = $r->cnt; @@ -366,19 +362,21 @@ public function getBadCountryArr(){ public function getGoodCountryArr($includeStates = false){ $retArr = array(); if($includeStates){ - $sql = 'SELECT c.countryname, s.statename FROM lkupcountry c LEFT JOIN lkupstateprovince s ON c.countryid = s.countryid '; + $sql = 'SELECT g1.geoterm as countryName, g2.geoterm AS stateName + FROM geographicthesaurus g1 INNER JOIN geographicthesaurus g2 ON g1.geoThesID = g2.parentID + WHERE g1.geoLevel = 50 AND g2.geoLevel = 60'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[$r->countryname][] = $r->statename; + $retArr[$r->countryName][] = $r->stateName; } $rs->free(); ksort($retArr); } else{ - $sql = 'SELECT countryname FROM lkupcountry'; + $sql = 'SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[] = $r->countryname; + $retArr[] = $r->geoterm; } $rs->free(); sort($retArr); @@ -389,9 +387,7 @@ public function getGoodCountryArr($includeStates = false){ public function getNullCountryNotStateCount(){ $retCnt = 0; - $sql = 'SELECT COUNT(DISTINCT stateprovince) AS cnt '. - 'FROM omoccurrences '. - 'WHERE (collid = '.$this->collid.') AND (country IS NULL) AND (stateprovince IS NOT NULL)'; + $sql = 'SELECT COUNT(DISTINCT stateprovince) AS cnt FROM omoccurrences WHERE (collid = '.$this->collid.') AND (country IS NULL) AND (stateprovince IS NOT NULL)'; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -419,8 +415,10 @@ public function getNullCountryNotStateArr(){ //States cleaning functions public function getBadStateCount($country = ''){ $retCnt = array(); - $sql = 'SELECT COUNT(DISTINCT o.stateprovince) as cnt '.$this->getBadStateSqlBase(); - if($country) $sql .= 'AND o.country = "'.$this->cleanInStr($country).'" '; + $sql = 'SELECT COUNT(DISTINCT stateprovince) as cnt + FROM omoccurrences WHERE (country IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50)) AND (stateprovince IS NOT NULL) + AND (collid = '.$this->collid.') AND (stateprovince NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 60)) '; + if($country) $sql .= 'AND country = "'.$this->cleanInStr($country).'" '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -431,63 +429,43 @@ public function getBadStateCount($country = ''){ public function getBadStateArr(){ $retArr = array(); - $sqlFrag = $this->getBadStateSqlBase(); - if($sqlFrag){ - $sql = 'SELECT o.country, o.stateprovince, count(DISTINCT o.occid) as cnt '. - $this->getBadStateSqlBase(). - 'GROUP BY o.stateprovince '; - $rs = $this->conn->query($sql); - $cnt = 0; - while($r = $rs->fetch_object()){ - $retArr[$r->country][ucwords(strtolower($r->stateprovince))] = $r->cnt; - $cnt++; - } - $rs->free(); - $this->featureCount = $cnt; - ksort($retArr); - } - else{ - $this->errorMessage = ''; - } - return $retArr; - } - - private function getBadStateSqlBase(){ - $retStr = ''; - $countryArr = array(); - $sql = 'SELECT DISTINCT c.countryname FROM lkupcountry c INNER JOIN lkupstateprovince s ON c.countryid = s.countryid '; + $sql = 'SELECT country, stateprovince, count(DISTINCT occid) as cnt + FROM omoccurrences + WHERE (country IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 50)) AND (stateprovince IS NOT NULL) + AND (collid = '.$this->collid.') AND (stateprovince NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 60)) + GROUP BY stateprovince '; $rs = $this->conn->query($sql); + $cnt = 0; while($r = $rs->fetch_object()){ - $countryArr[] = $r->countryname; + $retArr[$r->country][ucwords(strtolower($r->stateprovince))] = $r->cnt; + $cnt++; } $rs->free(); - - if($countryArr){ - $retStr = 'FROM omoccurrences o LEFT JOIN lkupstateprovince l ON o.stateprovince = l.statename '. - 'WHERE (o.country IN("'.implode('","', $countryArr).'")) AND (o.stateprovince IS NOT NULL) AND (o.collid = '.$this->collid.') AND (l.stateid IS NULL) '; - } - - return $retStr; + $this->featureCount = $cnt; + ksort($retArr); + return $retArr; } public function getGoodStateArr($includeCounties = false){ $retArr = array(); if($includeCounties){ - $sql = 'SELECT c.countryname, s.statename, co.countyname '. - 'FROM lkupstateprovince s INNER JOIN lkupcountry c ON s.countryid = c.countryid '. - 'LEFT JOIN lkupcounty co ON s.stateid = co.stateid '; + $sql = 'SELECT g1.geoterm as countryName, g2.geoterm AS stateName, g3.geoterm AS countyName + FROM geographicthesaurus g1 INNER JOIN geographicthesaurus g2 ON g1.geoThesID = g2.parentID + LEFT JOIN geographicthesaurus g3 ON g2.geoThesID = g3.parentID + WHERE g1.geoLevel = 50 AND g2.geoLevel = 60 AND g3.geoLevel = 70 '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[strtoupper($r->countryname)][ucwords(strtolower($r->statename))][] = str_replace(array(' county',' co.',' co'),'',strtolower($r->countyname)); + $retArr[strtoupper($r->countryName)][ucwords(strtolower($r->stateName))][] = str_ireplace(array(' county',' co.',' co'),'',$r->countyName); } $rs->free(); } else{ - $sql = 'SELECT c.countryname, s.statename '. - 'FROM lkupstateprovince s INNER JOIN lkupcountry c ON s.countryid = c.countryid '; + $sql = 'SELECT g1.geoterm as countryName, g2.geoterm AS stateName + FROM geographicthesaurus g1 INNER JOIN geographicthesaurus g2 ON g1.geoThesID = g2.parentID + WHERE g1.geoLevel = 50 AND g2.geoLevel = 60'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[$r->countryname][] = $r->statename; + $retArr[$r->countryName][] = $r->stateName; } $rs->free(); } @@ -498,7 +476,7 @@ public function getGoodStateArr($includeCounties = false){ public function getNullStateNotCountyCount(){ $retCnt = 0; - $sql = 'SELECT COUNT(DISTINCT county) AS cnt '.$this->getNullStateNotCountySqlFrag(); + $sql = 'SELECT COUNT(DISTINCT county) AS cnt FROM omoccurrences WHERE (collid = '.$this->collid.') AND (country IS NOT NULL) AND (stateprovince IS NULL) AND (county IS NOT NULL)'; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -509,7 +487,9 @@ public function getNullStateNotCountyCount(){ public function getNullStateNotCountyArr(){ $retArr = array(); - $sql = 'SELECT country, county, COUNT(occid) AS cnt '.$this->getNullStateNotCountySqlFrag().'GROUP BY county'; + $sql = 'SELECT country, county, COUNT(occid) AS cnt FROM omoccurrences + WHERE (collid = '.$this->collid.') AND (stateprovince IS NULL) AND (county IS NOT NULL) AND (country IS NOT NULL) + GROUP BY county'; $rs = $this->conn->query($sql); $cnt = 0; while($r = $rs->fetch_object()){ @@ -522,17 +502,13 @@ public function getNullStateNotCountyArr(){ return $retArr; } - private function getNullStateNotCountySqlFrag(){ - $retStr = 'FROM omoccurrences '. - 'WHERE (collid = '.$this->collid.') AND (stateprovince IS NULL) AND (county IS NOT NULL) AND (country IS NOT NULL) '; - return $retStr; - } - //Bad Counties public function getBadCountyCount($state = ''){ $retCnt = array(); - $sql = 'SELECT COUNT(DISTINCT o.county) as cnt '.$this->getBadCountySqlFrag(); - if($state) $sql .= 'AND o.stateprovince = "'.$this->cleanInStr($state).'" '; + $sql = 'SELECT COUNT(DISTINCT county) as cnt + FROM omoccurrences WHERE (county IS NOT NULL) AND (country = "USA") AND (stateprovince IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 60)) '. + 'AND (collid = '.$this->collid.') AND (county NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 70)) '; + if($state) $sql .= 'AND stateprovince = "'.$this->cleanInStr($state).'" '; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -543,8 +519,10 @@ public function getBadCountyCount($state = ''){ public function getBadCountyArr(){ $retArr = array(); - $sql = 'SELECT o.country, o.stateprovince, o.county, count(o.occid) as cnt '.$this->getBadCountySqlFrag().'GROUP BY o.country, o.stateprovince, o.county '; - //echo $sql; exit; + $sql = 'SELECT country, stateprovince, county, count(occid) as cnt + FROM omoccurrences WHERE (county IS NOT NULL) AND (country = "USA") AND (stateprovince IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 60)) + AND (collid = '.$this->collid.') AND (county NOT IN(SELECT geoterm FROM geographicthesaurus WHERE geolevel = 70)) + GROUP BY country, stateprovince, county '; $rs = $this->conn->query($sql); $cnt = 0; while($r = $rs->fetch_object()){ @@ -557,32 +535,15 @@ public function getBadCountyArr(){ return $retArr; } - private function getBadCountySqlFrag(){ - $retStr = ''; - $stateyArr = array(); - $sql = 'SELECT DISTINCT s.statename '. - 'FROM lkupstateprovince s INNER JOIN lkupcounty co ON s.stateid = co.stateid '; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $stateyArr[] = $r->statename; - } - $rs->free(); - if($stateyArr){ - $retStr = 'FROM omoccurrences o LEFT JOIN lkupcounty l ON o.county = l.countyname '. - 'WHERE (o.county IS NOT NULL) AND (o.country = "USA") AND (o.stateprovince IN("'.implode('","', $stateyArr).'")) '. - 'AND (o.collid = '.$this->collid.') AND (l.countyid IS NULL) '; - } - return $retStr; - } - public function getGoodCountyArr(){ $retArr = array(); - $sql = 'SELECT DISTINCT statename, REPLACE(countyname," County","") AS countyname '. - 'FROM lkupcounty c INNER JOIN lkupstateprovince s ON c.stateid = s.stateid '. - 'ORDER BY c.countyname'; + $sql = 'SELECT DISTINCT g1.geoterm as stateName, REPLACE(g2.geoterm," County","") as countyName + FROM geographicthesaurus g1 INNER JOIN geographicthesaurus g2 ON g1.geoThesID = g2.parentID + WHERE g1.geoLevel = 60 AND g2.geoLevel = 70 + ORDER BY g2.geoterm'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $retArr[strtolower($r->statename)][] = $r->countyname; + $retArr[strtolower($r->stateName)][] = $r->countyName; } $rs->free(); $retArr[] = 'unknown'; @@ -591,7 +552,9 @@ public function getGoodCountyArr(){ public function getNullCountyNotLocalityCount(){ $retCnt = 0; - $sql = 'SELECT COUNT(DISTINCT locality) AS cnt '.$this->getNullCountyNotLocalitySqlFrag(); + $sql = 'SELECT COUNT(DISTINCT locality) AS cnt FROM omoccurrences + WHERE (collid = '.$this->collid.') AND (county IS NULL) AND (locality IS NOT NULL) + AND country IN("USA","United States") AND (stateprovince IS NOT NULL) AND (stateprovince NOT IN("District Of Columbia","DC"))'; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $retCnt = $r->cnt; @@ -602,9 +565,11 @@ public function getNullCountyNotLocalityCount(){ public function getNullCountyNotLocalityArr(){ $retArr = array(); - $sql = 'SELECT country, stateprovince, locality, COUNT(occid) AS cnt '. - $this->getNullCountyNotLocalitySqlFrag(). - 'GROUP BY country, stateprovince, locality'; + $sql = 'SELECT country, stateprovince, locality, COUNT(occid) AS cnt + FROM omoccurrences + WHERE (collid = '.$this->collid.') AND (county IS NULL) AND (locality IS NOT NULL) + AND country IN("USA","United States") AND (stateprovince IS NOT NULL) AND (stateprovince NOT IN("District Of Columbia","DC")) + GROUP BY country, stateprovince, locality'; $rs = $this->conn->query($sql); $cnt = 0; while($r = $rs->fetch_object()){ @@ -619,13 +584,6 @@ public function getNullCountyNotLocalityArr(){ return $retArr; } - private function getNullCountyNotLocalitySqlFrag(){ - $retStr = 'FROM omoccurrences '. - 'WHERE (collid = '.$this->collid.') AND (county IS NULL) AND (locality IS NOT NULL) '. - 'AND country IN("USA","United States") AND (stateprovince IS NOT NULL) AND (stateprovince NOT IN("District Of Columbia","DC")) '; - return $retStr; - } - //Coordinate field verifier public function getCoordStats(){ $retArr = array(); @@ -698,11 +656,10 @@ public function verifyCoordAgainstPolitical($queryCountry){ 'AND (occid NOT IN(SELECT occid FROM omoccurverification WHERE category = "coordinate")) '. 'ORDER BY decimallatitude, decimallongitude '. 'LIMIT 50000'; - //echo $sql; exit; $rs = $this->conn->query($sql); $previousCoordStr = ''; while($r = $rs->fetch_object()){ - echo '
  • Checking occurrence '.$r->occid.'...
  • '; + echo '
  • Checking occurrence ' . htmlspecialchars($r->occid, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...
  • '; $recCnt++; if($previousCoordStr != $r->decimallatitude.','.$r->decimallongitude){ $googleUnits = $this->callGoogleApi($r->decimallatitude, $r->decimallongitude); @@ -986,7 +943,6 @@ public function getCollMap(){ $sql = 'SELECT collid, CONCAT_WS("-",institutioncode, collectioncode) AS code, collectionname, icon, colltype, managementtype FROM omcollections '; if($this->collid) $sql .= 'WHERE (collid IN('.$this->collid.')) '; $sql .= 'ORDER BY collectionname,institutioncode,collectioncode'; - //echo $sql; $rs = $this->conn->query($sql); while($row = $rs->fetch_object()){ $retArr[$row->collid]['code'] = $row->code; diff --git a/classes/OccurrenceCollectionProfile.php b/classes/OccurrenceCollectionProfile.php index ad8dacb0d1..5e8a4251d4 100644 --- a/classes/OccurrenceCollectionProfile.php +++ b/classes/OccurrenceCollectionProfile.php @@ -70,119 +70,7 @@ public function getCollectionCategories(){ return $retArr; } - public function getMetadataHtml($LANG, $LANG_TAG){ - $outStr = '
    '.$this->collMeta[$this->collid]["fulldescription"].'
    '; - if(isset($this->collMeta[$this->collid]['contactjson'])){ - if($contactArr = json_decode($this->collMeta[$this->collid]['contactjson'],true)){ - foreach($contactArr as $cArr){ - $title = (isset($LANG['CONTACT'])?$LANG['CONTACT']:'Contact'); - if(isset($cArr['role']) && $cArr['role']) $title = $cArr['role']; - $outStr .= '
    '.$title.': '; - $outStr .= $cArr['firstName'].' '.$cArr['lastName']; - if(isset($cArr['email']) && $cArr['email']) $outStr .= ', '.$cArr['email']; - if(isset($cArr['phone']) && $cArr['phone']) $outStr .= ', '.$cArr['phone']; - if(isset($cArr['orcid']) && $cArr['orcid']) $outStr .= ' (ORCID #: '.$cArr['orcid'].')'; - $outStr .= '
    '; - } - } - } - if(isset($this->collMeta[$this->collid]['resourcejson'])){ - if($resourceArr = json_decode($this->collMeta[$this->collid]['resourcejson'],true)){ - $title = (isset($LANG['HOMEPAGE'])?$LANG['HOMEPAGE']:'Homepage'); - foreach($resourceArr as $rArr){ - if(isset($rArr['title'][$LANG_TAG]) && $rArr['title'][$LANG_TAG]) $title = $rArr['title'][$LANG_TAG]; - $outStr .= '
    '.$title.': '; - $outStr .= ''.$rArr['url'].''; - $outStr .= '
    '; - } - } - } - $outStr .= '
    '; - $outStr .= ''.$LANG['COLLECTION_TYPE'].': '.$this->collMeta[$this->collid]['colltype']; - $outStr .= '
    '; - $outStr .= '
    '; - $outStr .= ''.$LANG['MANAGEMENT'].': '; - if($this->collMeta[$this->collid]['managementtype'] == 'Live Data'){ - $outStr .= (isset($LANG['LIVE_DATA'])?$LANG['LIVE_DATA']:'Live Data managed directly within data portal'); - } - else{ - if($this->collMeta[$this->collid]['managementtype'] == 'Aggregate'){ - $outStr .= (isset($LANG['DATA_AGGREGATE'])?$LANG['DATA_AGGREGATE']:'Data harvested from a data aggregator'); - } - else{ - $outStr .= (isset($LANG['DATA_SNAPSHOT'])?$LANG['DATA_SNAPSHOT']:'Data snapshot of local collection database '); - } - } - $outStr .= '
    '; - if($this->collMeta[$this->collid]['managementtype'] != 'Live Data') $outStr .= '
    '.$LANG['LAST_UPDATE'].': '.$this->collMeta[$this->collid]['uploaddate'].'
    '; - if($this->collMeta[$this->collid]['managementtype'] == 'Live Data'){ - $outStr .= '
    '; - $outStr .= ''.$LANG['GLOBAL_UNIQUE_ID'].': '.$this->collMeta[$this->collid]['recordid']; - $outStr .= '
    '; - } - if($this->collMeta[$this->collid]['dwcaurl']){ - $dwcaUrl = $this->collMeta[$this->collid]['dwcaurl']; - $outStr .= '
    '; - $outStr .= ''.(isset($LANG['DWCA_PUB'])?$LANG['DWCA_PUB']:'DwC-Archive Access Point').': '; - $outStr .= ''.$dwcaUrl.''; - $outStr .= '
    '; - } - $outStr .= '
    '; - if($this->collMeta[$this->collid]['managementtype'] == 'Live Data'){ - if($GLOBALS['SYMB_UID']){ - $outStr .= ''.(isset($LANG['LIVE_DOWNLOAD'])?$LANG['LIVE_DOWNLOAD']:'Live Data Download').': '; - $outStr .= ''.(isset($LANG['FULL_DATA'])?$LANG['FULL_DATA']:'DwC-Archive File').''; - } - } - elseif($this->collMeta[$this->collid]['managementtype'] == 'Snapshot'){ - $pathArr = $this->getDwcaPath($this->collMeta[$this->collid]['collid']); - if($pathArr){ - $outStr .= '
    '.(isset($LANG['IPT_SOURCE'])?$LANG['IPT_SOURCE']:'IPT / DwC-A Source').':
    '; - $outStr .= '
    '; - foreach($pathArr as $titleStr => $pathStr){ - $outStr .= ''.$titleStr.'
    '; - } - $outStr .= '
    '; - } - } - $outStr .= '
    '; - $outStr .= '
    '.(isset($LANG['DIGITAL_METADATA'])?$LANG['DIGITAL_METADATA']:'Digital Metadata').': EML File
    '; - $outStr .= '
    '.$LANG['USAGE_RIGHTS'].': '; - if($this->collMeta[$this->collid]['rights']){ - $rights = $this->collMeta[$this->collid]['rights']; - $rightsUrl = ''; - if(substr($rights,0,4) == 'http'){ - $rightsUrl = $rights; - if($GLOBALS['RIGHTS_TERMS']){ - if($rightsArr = array_keys($GLOBALS['RIGHTS_TERMS'],$rights)){ - $rights = current($rightsArr); - } - } - } - if($rightsUrl) $outStr .= ''; - $outStr .= $rights; - if($rightsUrl) $outStr .= ''; - } - elseif(file_exists('../../includes/usagepolicy.php')){ - $outStr .= ''.(isset($LANG['USAGE_POLICY'])?$LANG['USAGE_POLICY']:'Usage policy').''; - } - $outStr .= '
    '; - if($this->collMeta[$this->collid]['rightsholder']){ - $outStr .= '
    '; - $outStr .= ''.$LANG['RIGHTS_HOLDER'].': '; - $outStr .= $this->collMeta[$this->collid]['rightsholder']; - $outStr .= '
    '; - } - if($this->collMeta[$this->collid]['accessrights']){ - $outStr .= '
    '. - ''.$LANG['ACCESS_RIGHTS'].': '. - $this->collMeta[$this->collid]['accessrights']. - '
    '; - } - return $outStr; - } - - private function getDwcaPath($collid){ + public function getDwcaPath($collid){ $retArr = array(); if(is_numeric($collid)){ $sql = 'SELECT uspid, title, path FROM uploadspecparameters WHERE (collid = '.$collid.') AND (uploadtype = 8)'; @@ -684,6 +572,10 @@ public function runStatisticsQuery($collId,$taxon,$country){ $pTID = $r->TID; } $rs->free(); + if (!$pTID){ + echo ""; + return $returnArr; + } $sqlWhere .= 'AND ((o.sciname = "'.$this->cleanInStr($taxon).'") OR (o.tidinterpreted IN(SELECT DISTINCT tid FROM taxaenumtree WHERE taxauthid = 1 AND parenttid IN('.$pTID.')))) '; } if($country) $sqlWhere .= 'AND o.country = "'.$this->cleanInStr($country).'" '; @@ -921,9 +813,10 @@ public function getCategoryArr(){ public function traitCodingActivated(){ $bool = false; $sql = 'SELECT traitid FROM tmtraits LIMIT 1'; - $rs = $this->conn->query($sql); - if($rs->num_rows) $bool = true; - $rs->free(); + if($rs = $this->conn->query($sql)){ + if($rs->num_rows) $bool = true; + $rs->free(); + } return $bool; } @@ -935,4 +828,4 @@ public function cleanOutArr(&$arr){ } } } -?> \ No newline at end of file +?> diff --git a/classes/OccurrenceDownload.php b/classes/OccurrenceDownload.php index 67e49a6f26..10758e38ee 100644 --- a/classes/OccurrenceDownload.php +++ b/classes/OccurrenceDownload.php @@ -109,6 +109,9 @@ public function downloadData(){ fclose($fh); } //Send data file out + ob_start(); + ob_clean(); + ob_end_flush(); header('Content-Description: '.$contentDesc); header('Content-Type: '.$this->getContentType()); header('Content-Disposition: attachment; filename='.$fileName); @@ -318,7 +321,7 @@ private function getDataEntryXML($days, $limit){ $itemElem->appendChild($itemTitleElem); $collLinkElem = $newDoc->createElement('collectionName',$r->collectionname.' ('.$r->instcode.')'); $itemElem->appendChild($collLinkElem); - $catalogLinkElem = $newDoc->createElement('catalogNumber',$r->catalognumber); + $catalogLinkElem = $newDoc->createElement('catalogNumber', $r->catalognumber ?? ''); $itemElem->appendChild($catalogLinkElem); if($r->guidtarget){ @@ -329,7 +332,7 @@ private function getDataEntryXML($days, $limit){ if($r->guidtarget == 'catalogNumber'){ $occID = $r->catalognumber; } - $guidLinkElem = $newDoc->createElement('occurrenceID',$occID); + $guidLinkElem = $newDoc->createElement('occurrenceID', $occID ?? ''); $itemElem->appendChild($guidLinkElem); } @@ -350,21 +353,21 @@ private function getDataEntryXML($days, $limit){ $tnLinkElem->appendChild($newDoc->createTextNode($tnUrl)); $itemElem->appendChild($tnLinkElem); - $latLinkElem = $newDoc->createElement('decimalLatitude',$r->decimallatitude); + $latLinkElem = $newDoc->createElement('decimalLatitude',$r->decimallatitude ?? ''); $itemElem->appendChild($latLinkElem); - $lngLinkElem = $newDoc->createElement('decimalLongitude',$r->decimallongitude); + $lngLinkElem = $newDoc->createElement('decimalLongitude',$r->decimallongitude ?? ''); $itemElem->appendChild($lngLinkElem); $eventDateLinkElem = $newDoc->createElement('verbatimEventDate'); - $eventDateLinkElem->appendChild($newDoc->createTextNode($r->eventdate)); + $eventDateLinkElem->appendChild($newDoc->createTextNode($r->eventdate ?? '')); $itemElem->appendChild($eventDateLinkElem); //$pubDateLinkElem = $newDoc->createElement('pubDate',$r->datelastmodified); $pubDateLinkElem = $newDoc->createElement('pubDate',gmdate(DATE_RSS, strtotime($r->datelastmodified))); $itemElem->appendChild($pubDateLinkElem); - $creatorLinkElem = $newDoc->createElement('creator',$r->recordenteredby); + $creatorLinkElem = $newDoc->createElement('creator', $r->recordenteredby ?? ''); $itemElem->appendChild($creatorLinkElem); if($r->genericcolumn2){ - $ipAddr = $newDoc->createElement('ipAddress',$r->genericcolumn2); + $ipAddr = $newDoc->createElement('ipAddress', $r->genericcolumn2 ?? ''); $itemElem->appendChild($ipAddr); //Transcription Lat //Transcription Long @@ -380,7 +383,7 @@ public function setSqlWhere($sqlStr){ public function addCondition($field, $cond, $value = ''){ if($field){ if(!trim($cond)) $cond = 'EQUALS'; - if($value || ($cond == 'NULL' || $cond == 'NOTNULL')){ + if($value || ($cond == 'IS_NULL' || $cond == 'NOT_NULL')){ $this->conditionArr[$field][$cond][] = $this->cleanInStr($value); } } @@ -412,33 +415,33 @@ private function applyConditions(){ private function getSqlFragment($field, $cond, $valueArr){ $sqlFrag = ''; - if($cond == 'NULL'){ + if($cond == 'IS_NULL'){ $sqlFrag .= 'OR o.'.$field.' IS NULL '; } - elseif($cond == 'NOTNULL'){ + elseif($cond == 'NOT_NULL'){ $sqlFrag .= 'OR o.'.$field.' IS NOT NULL '; } elseif($cond == 'EQUALS'){ $sqlFrag .= 'OR o.'.$field.' IN("'.implode('","',$valueArr).'") '; } - elseif($cond == 'NOTEQUALS'){ + elseif($cond == 'NOT_EQUALS'){ $sqlFrag .= 'OR o.'.$field.' NOT IN("'.implode('","',$valueArr).'") OR o.'.$field.' IS NULL '; } else{ foreach($valueArr as $value){ - if($cond == 'STARTS'){ + if($cond == 'STARTS_WITH'){ $sqlFrag .= 'OR o.'.$field.' LIKE "'.$value.'%" '; } elseif($cond == 'LIKE'){ $sqlFrag .= 'OR o.'.$field.' LIKE "%'.$value.'%" '; } - elseif($cond == 'NOTLIKE'){ + elseif($cond == 'NOT_LIKE'){ $sqlFrag .= 'OR o.'.$field.' NOT LIKE "%'.$value.'%" OR o.'.$field.' IS NULL '; } - elseif($cond == 'LESSTHAN'){ + elseif($cond == 'LESS_THAN'){ $sqlFrag .= 'OR o.'.$field.' < "'.$value.'" '; } - elseif($cond == 'GREATERTHAN'){ + elseif($cond == 'GREATER_THAN'){ $sqlFrag .= 'OR o.'.$field.' > "'.$value.'" '; } } @@ -498,7 +501,7 @@ private function getSql(){ 'o.georeferenceRemarks, o.minimumElevationInMeters, o.maximumElevationInMeters, o.verbatimElevation, '. 'IFNULL(o.modified,o.datelastmodified) AS modified, o.occid, CONCAT("urn:uuid:", o.recordID) AS recordID '; } - $sql .= 'FROM omcollections c INNER JOIN omoccurrences o ON c.collid = o.collid LEFT JOIN taxa t ON o.tidinterpreted = t.tid '; + $sql .= 'FROM omcollections c INNER JOIN omoccurrences o ON c.collid = o.collid LEFT JOIN taxa t ON o.tidinterpreted = t.tid LEFT JOIN taxstatus ts ON t.tid = ts.tid '; $sql .= $this->setTableJoins($this->sqlWhere); $this->applyConditions(); $sql .= $this->sqlWhere; @@ -528,9 +531,9 @@ private function setTableJoins($sqlWhere){ } private function getOutputFilePath(){ - $retStr = $GLOBALS['tempDirRoot']; + $retStr = $GLOBALS['TEMP_DIR_ROOT']; if(!$retStr){ - $retStr = $GLOBALS['serverRoot']; + $retStr = $GLOBALS['SERVER_ROOT']; if(substr($retStr,-1) != '/' && substr($retStr,-1) != "\\") $retStr .= '/'; $retStr .= 'temp/'; } @@ -731,18 +734,9 @@ private function encodeArr(&$inArr){ private function encodeStr($inStr){ $retStr = $inStr; - if($this->charSetSource){ - if($this->charSetOut == 'UTF-8' && $this->charSetSource == 'ISO-8859-1'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif($this->charSetOut == "ISO-8859-1" && $this->charSetSource == 'UTF-8'){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } + if($retStr){ + if($this->charSetOut && $this->charSetOut != $this->charSetSource){ + $retStr = mb_convert_encoding($retStr, $this->charSetOut, mb_detect_encoding($retStr)); } } return $retStr; diff --git a/classes/OccurrenceDuplicate.php b/classes/OccurrenceDuplicate.php index 552814a51f..135de5a2de 100644 --- a/classes/OccurrenceDuplicate.php +++ b/classes/OccurrenceDuplicate.php @@ -356,10 +356,10 @@ public function getDupesOccid($occidQuery){ $result = $this->conn->query($sql); while($row = $result->fetch_assoc()) { foreach($row as $k => $v){ - $vStr = trim($v); - $retArr[$row['occid']][$k] = $vStr; + if($v) $v = trim($v); + $retArr[$row['occid']][$k] = $v; //Identify relevant fields - if($vStr) $relArr[$k] = ''; + if($v) $relArr[$k] = ''; } } $result->free(); @@ -492,16 +492,12 @@ private function rankOcr($occArr){ //Action functions - public function mergeRecords($targetOccid,$sourceOccid){ + public function mergeRecords($targetOccid, $sourceOccid, $collId){ $status = true; $editorManager = new OccurrenceEditorManager($this->conn); - if($editorManager->mergeRecords($targetOccid,$sourceOccid)){ - if(!$editorManager->deleteOccurrence($sourceOccid)){ - $this->errorStr = trim($editorManager->getErrorStr(),' ;'); - } - } - else{ - $this->errorStr = $editorManager->getErrorStr; + $editorManager->setCollId($collId); + if(!$editorManager->mergeRecords($targetOccid,$sourceOccid)){ + $this->errorStr = $editorManager->getErrorStr(); $status = false; } return $status; @@ -626,6 +622,7 @@ public function batchLinkDuplicates($collid = 0, $verbose = true){ if(preg_match('#\d#',$recNum)){ $lastName = $this->parseLastName($r2->recordedby); if(strpos($lastName,'.')) $lastName = $r2->recordedby; + $lastName = substr($lastName, 0, 25); if(isset($lastName) && $lastName && !preg_match('#\d#',$lastName)){ $rArr[$recNum][$lastName][$r2->dupid][] = $r2->occid; if($r2->collid == $collid && (!$this->obsUid || $r2->observeruid == $this->obsUid)) $keepArr[$recNum][$lastName] = 1; @@ -657,8 +654,7 @@ public function batchLinkDuplicates($collid = 0, $verbose = true){ $dupId = 0; if($mArr) $dupId = key($mArr); if(!$dupId){ - //Create a new dupliate project - $sqlI1 = 'INSERT INTO omoccurduplicates(title,dupetype) VALUES("'.$this->cleanInStr($dupIdStr).'",1)'; + $sqlI1 = 'INSERT IGNORE INTO omoccurduplicates(title,dupetype) VALUES("'.$this->cleanInStr($dupIdStr).'",1)'; if($this->conn->query($sqlI1)){ $dupId = $this->conn->insert_id; if($verbose) echo '
  • New duplicate project created: #'.$dupId.'
  • '; @@ -678,7 +674,7 @@ public function batchLinkDuplicates($collid = 0, $verbose = true){ $sqlI2 = 'INSERT INTO omoccurduplicatelink(duplicateid,occid) VALUES '; foreach($unlinkedArr as $v){ $sqlI2 .= '('.$dupId.','.$v.'),'; - $outLink .= ' '.$v.','; + $outLink .= ' ' . htmlspecialchars($v, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ','; } if($this->conn->query(trim($sqlI2,','))){ if($verbose) echo '
  • '.count($unlinkedArr).' duplicates linked ('.trim($outLink,' ,').')
  • '; @@ -774,4 +770,4 @@ private function cleanInStr($str){ return $newStr; } } -?> \ No newline at end of file +?> diff --git a/classes/OccurrenceEditorDeterminations.php b/classes/OccurrenceEditorDeterminations.php index 3702fedec1..b7296c82ee 100644 --- a/classes/OccurrenceEditorDeterminations.php +++ b/classes/OccurrenceEditorDeterminations.php @@ -1,5 +1,7 @@ occid.',"'.$this->cleanInStr($detArr['identifiedby']).'","'.$this->cleanInStr($detArr['dateidentified']).'","'. $sciname.'",'.($detArr['scientificnameauthorship']?'"'.$this->cleanInStr($detArr['scientificnameauthorship']).'"':'NULL').','. @@ -90,18 +92,29 @@ public function addDetermination($detArr, $isEditor){ $detArr['makecurrent'].','.$detArr['printqueue'].','.($isEditor==3?0:1).','. ($detArr['identificationreferences']?'"'.$this->cleanInStr($detArr['identificationreferences']).'"':'NULL').','. ($notes?'"'.$notes.'"':'NULL').',"'.$guid.'",'.$sortSeq.')'; - if($this->conn->query($sql)){ + $detId = 0; + try { + $this->conn->query($sql); $detId = $this->conn->insert_id; + } catch (mysqli_sql_exception $e) { + $status = $LANG['ERROR_FAILED_ADD'].': '.$this->conn->error; + } + if($detId){ //If is current, move old determination from omoccurrences to omoccurdeterminations and then load new record into omoccurrences if($isCurrent){ - //If determination is already in omoccurdeterminations, INSERT will fail move omoccurrences determination to table + //If determination is already in omoccurdeterminations, INSERT will fail $guid = UuidFactory::getUuidV4(); - $sqlInsert = 'INSERT INTO omoccurdeterminations(occid, identifiedBy, dateIdentified, sciname, scientificNameAuthorship, '. + $sqlInsert = 'INSERT IGNORE INTO omoccurdeterminations(occid, identifiedBy, dateIdentified, sciname, scientificNameAuthorship, '. 'identificationQualifier, identificationReferences, identificationRemarks, recordID, sortsequence) '. 'SELECT occid, IFNULL(identifiedby,"unknown") AS idby, IFNULL(dateidentified,"s.d.") AS di, '. 'sciname, scientificnameauthorship, identificationqualifier, identificationreferences, identificationremarks, "'.$guid.'", 10 AS sortseq '. 'FROM omoccurrences WHERE (occid = '.$this->occid.') AND (identifiedBy IS NOT NULL OR dateIdentified IS NOT NULL OR sciname IS NOT NULL)'; $this->conn->query($sqlInsert); + try { + //$this->conn->query($sqlInsert); + } catch (mysqli_sql_exception $e) { + echo 'Duplicate: '.$this->conn->error; + } $tidToAdd = $detArr['tidtoadd']; if($tidToAdd && !is_numeric($tidToAdd)) $tidToAdd = 0; $this->updateBaseOccurrence($detId); @@ -113,9 +126,6 @@ public function addDetermination($detArr, $isEditor){ } } } - else{ - $status = $LANG['ERROR_FAILED_ADD'].': '.$this->conn->error; - } return $status; } @@ -214,7 +224,7 @@ public function makeDeterminationCurrent($detId){ $status = $LANG['DET_NOW_CURRENT']; //Make sure determination data within omoccurrences is in omoccurdeterminations. If already there, INSERT will fail and nothing lost $guid = UuidFactory::getUuidV4(); - $sqlInsert = 'INSERT INTO omoccurdeterminations(occid, identifiedBy, dateIdentified, sciname, scientificNameAuthorship, '. + $sqlInsert = 'INSERT IGNORE INTO omoccurdeterminations(occid, identifiedBy, dateIdentified, sciname, scientificNameAuthorship, '. 'identificationQualifier, identificationReferences, identificationRemarks, recordID, sortsequence) '. 'SELECT occid, IFNULL(identifiedby,"unknown") AS idby, IFNULL(dateidentified,"s.d.") AS iddate, sciname, scientificnameauthorship, '. 'identificationqualifier, identificationreferences, identificationremarks, "'.$guid.'", 10 AS sortseq '. @@ -250,7 +260,17 @@ private function updateBaseOccurrence($detId){ if(isset($taxonArr['tid']) && $taxonArr['tid']) $sql .= ', o.tidinterpreted = '.$taxonArr['tid']; if(isset($taxonArr['security']) && $taxonArr['security']) $sql .= ', o.localitysecurity = '.$taxonArr['security'].', o.localitysecurityreason = ""'; $sql .= ' WHERE (d.iscurrent = 1) AND (d.detid = '.$detId.')'; - $this->conn->query($sql); + $updated_base = $this->conn->query($sql); + + //Whenever occurrence is updated also update associated images + if($updated_base && isset($taxonArr['tid']) && $taxonArr['tid']) { + $sql = <<<'SQL' + UPDATE images i + INNER JOIN omoccurdeterminations od on od.occid = i.occid + SET tid = ? WHERE detid = ?; + SQL; + SymbUtil::execute_query($this->conn,$sql, [$taxonArr['tid'], $detId]); + } } } @@ -349,7 +369,9 @@ public function getNewDetItem($catNum, $sciName, $allCatNum = 0){ } public function getCollName(){ - return $this->collMap['collectionname'].' ('.$this->collMap['institutioncode'].($this->collMap['collectioncode']?':'.$this->collMap['collectioncode']:'').')'; + $retStr = ''; + if($this->collMap) $retStr = $this->collMap['collectionname'].' ('.$this->collMap['institutioncode'].($this->collMap['collectioncode']?':'.$this->collMap['collectioncode']:'').')'; + return $retStr; } } ?> diff --git a/classes/OccurrenceEditorImages.php b/classes/OccurrenceEditorImages.php index 3de944bee4..e24a6ae047 100644 --- a/classes/OccurrenceEditorImages.php +++ b/classes/OccurrenceEditorImages.php @@ -14,9 +14,9 @@ class OccurrenceEditorImages extends OccurrenceEditorManager { public function __construct(){ parent::__construct(); - $this->imageRootPath = $GLOBALS["imageRootPath"]; + $this->imageRootPath = $GLOBALS['IMAGE_ROOT_PATH']; if(substr($this->imageRootPath,-1) != "/") $this->imageRootPath .= "/"; - $this->imageRootUrl = $GLOBALS["imageRootUrl"]; + $this->imageRootUrl = $GLOBALS['IMAGE_ROOT_URL']; if(substr($this->imageRootUrl,-1) != "/") $this->imageRootUrl .= "/"; } @@ -62,7 +62,7 @@ public function addImageOccurrence($postArr){ if($ocrSource) $ocrSource .= ': '.date('Y-m-d'); $sql = 'INSERT INTO specprocessorrawlabels(imgid, rawstr, source) VALUES('.$this->activeImgId.',"'.$this->cleanRawFragment($rawStr).'","'.$this->cleanInStr($ocrSource).'")'; if(!$this->conn->query($sql)){ - $this->errorStr = $LANG['ERROR_LOAD_OCR'].': '.$this->conn->error; + $this->errorArr[] = $LANG['ERROR_LOAD_OCR'].': '.$this->conn->error; } } } @@ -228,7 +228,7 @@ public function deleteImage($imgIdDel, $removeImg){ $status = true; $imgManager = new ImageShared(); if(!$imgManager->deleteImage($imgIdDel, $removeImg)){ - $this->errorStr = implode('',$imgManager->getErrArr()); + $this->errorArr[] = implode('',$imgManager->getErrArr()); $status = false; } return $status; @@ -434,7 +434,7 @@ public function addImage($postArr){ $status = $imgManager->insertImageTags($postArr); //Get errors and warnings - $this->errorStr = $imgManager->getErrStr(); + $this->errorArr[] = $imgManager->getErrStr(); return $status; } diff --git a/classes/OccurrenceEditorManager.php b/classes/OccurrenceEditorManager.php index 31df47eeab..de328c594e 100644 --- a/classes/OccurrenceEditorManager.php +++ b/classes/OccurrenceEditorManager.php @@ -2,6 +2,8 @@ include_once($SERVER_ROOT.'/config/dbconnection.php'); include_once($SERVER_ROOT.'/classes/OccurrenceDuplicate.php'); include_once($SERVER_ROOT.'/classes/UuidFactory.php'); +include_once($SERVER_ROOT.'/utilities/SymbUtil.php'); + if($LANG_TAG != 'en' && file_exists($SERVER_ROOT.'/content/lang/collections/editor/occurrenceeditor.'.$LANG_TAG.'.php')) include_once($SERVER_ROOT.'/content/lang/collections/editor/occurrenceeditor.'.$LANG_TAG.'.php'); else include_once($SERVER_ROOT.'/content/lang/collections/editor/occurrenceeditor.en.php'); @@ -26,8 +28,6 @@ class OccurrenceEditorManager { protected $errorArr = array(); protected $isShareConn = false; - private $paleoActivated = false; - public function __construct($conn = null){ if($conn){ $this->conn = $conn; @@ -37,13 +37,14 @@ public function __construct($conn = null){ $this->fieldArr['omoccurrences'] = array('basisofrecord' => 's', 'catalognumber' => 's', 'othercatalognumbers' => 's', 'occurrenceid' => 's', 'ownerinstitutioncode' => 's', 'institutioncode' => 's', 'collectioncode' => 's', 'eventid' => 's', 'family' => 's', 'sciname' => 's', 'tidinterpreted' => 'n', 'scientificnameauthorship' => 's', 'identifiedby' => 's', 'dateidentified' => 's', - 'identificationreferences' => 's', 'identificationremarks' => 's', 'taxonremarks' => 's', 'identificationqualifier' => 's', 'typestatus' => 's', - 'recordedby' => 's', 'recordnumber' => 's', 'associatedcollectors' => 's', 'eventdate' => 'd', 'eventdate2' => 'd', 'year' => 'n', 'month' => 'n', 'day' => 'n', 'startdayofyear' => 'n', - 'enddayofyear' => 'n', 'verbatimeventdate' => 's', 'habitat' => 's', 'substrate' => 's', 'fieldnumber' => 's', 'occurrenceremarks' => 's', 'datageneralizations' => 's', - 'associatedtaxa' => 's', 'verbatimattributes' => 's', 'dynamicproperties' => 's', 'reproductivecondition' => 's', 'cultivationstatus' => 's', 'establishmentmeans' => 's', + 'identificationreferences' => 's', 'identificationremarks' => 's', 'taxonremarks' => 's', 'identificationqualifier' => 's', 'typestatus' => 's', 'recordedby' => 's', 'recordnumber' => 's', + 'associatedcollectors' => 's', 'eventdate' => 'd', 'eventdate2' => 'd', 'year' => 'n', 'month' => 'n', 'day' => 'n', 'startdayofyear' => 'n', 'enddayofyear' => 'n', + 'verbatimeventdate' => 's', 'habitat' => 's', 'substrate' => 's', 'fieldnumber' => 's', 'occurrenceremarks' => 's', 'datageneralizations' => 's', + 'associatedtaxa' => 's', 'verbatimattributes' => 's', 'behavior' => 's', 'vitality' => 's', 'dynamicproperties' => 's', 'reproductivecondition' => 's', 'cultivationstatus' => 's', 'establishmentmeans' => 's', 'lifestage' => 's', 'sex' => 's', 'individualcount' => 's', 'samplingprotocol' => 's', 'preparations' => 's', + 'continent' => 's', 'waterbody' => 's', 'islandgroup' => 's', 'island' => 's', 'countrycode' => 's', 'country' => 's', 'stateprovince' => 's', 'county' => 's', 'municipality' => 's', 'locationid' => 's', 'locality' => 's', 'localitysecurity' => 'n', 'localitysecurityreason' => 's', - 'locationremarks' => 'n', 'decimallatitude' => 'n', 'decimallongitude' => 'n', 'geodeticdatum' => 's', 'coordinateuncertaintyinmeters' => 'n', 'verbatimcoordinates' => 's', + 'locationremarks' => 's', 'decimallatitude' => 'n', 'decimallongitude' => 'n', 'geodeticdatum' => 's', 'coordinateuncertaintyinmeters' => 'n', 'verbatimcoordinates' => 's', 'footprintwkt' => 's', 'georeferencedby' => 's', 'georeferenceprotocol' => 's', 'georeferencesources' => 's', 'georeferenceverificationstatus' => 's', 'georeferenceremarks' => 's', 'minimumelevationinmeters' => 'n', 'maximumelevationinmeters' => 'n','verbatimelevation' => 's', 'minimumdepthinmeters' => 'n', 'maximumdepthinmeters' => 'n', 'verbatimdepth' => 's','disposition' => 's', 'language' => 's', 'duplicatequantity' => 'n', @@ -83,28 +84,31 @@ private function setCollMap(){ $this->collMap['collectionname'] = $this->cleanOutStr($this->collMap['collectionname']); } $rs->free(); + $this->getDynamicPropertiesArr(); } else return false; } } - public function getDynamicPropertiesArr(){ + private function getDynamicPropertiesArr(){ $retArr = array(); $propArr = array(); - if(array_key_exists('dynamicproperties', $this->collMap)){ - $propArr = json_decode($this->collMap['dynamicproperties'],true); + if(!empty($this->collMap['dynamicproperties'])){ + $propArr = json_decode($this->collMap['dynamicproperties'], true); if(isset($propArr['editorProps'])){ $retArr = $propArr['editorProps']; if(isset($retArr['modules-panel'])){ foreach($retArr['modules-panel'] as $module){ - if(isset($module['paleo']['status']) && $module['paleo']['status']){ - $this->paleoActivated = true; + if(!empty($module['paleo']['status'])){ + $this->collMap['paleoActivated'] = true; + } + if(!empty($module['matSample']['status'])){ + $this->collMap['matSampleActivated'] = true; } } } } } - return $retArr; } //Query functions @@ -498,12 +502,12 @@ private function setSqlWhere(){ $customWhere .= $cao.' ('.substr($this->setCustomSqlFragment($customField, $customTerm, $customValue, $cao, $cop, $ccp),3).' '; if($customTerm != 'NOT_EQUALS' && $customTerm != 'NOT_LIKE'){ $caoOverride = 'OR'; - if($customTerm == 'NULL') $caoOverride = 'AND'; + if($customTerm == 'IS_NULL') $caoOverride = 'AND'; $customWhere .= $this->setCustomSqlFragment('id.identifierValue', $customTerm, $customValue, $caoOverride, $cop, $ccp); } else{ $customWhere .= 'AND o.occid NOT IN(SELECT occid FROM omoccuridentifiers WHERE identifierValue '; - if($customTerm == 'NOT_LIKE') $customWhere .= 'NOT_LIKE'; + if($customTerm == 'NOT_LIKE') $customWhere .= 'NOT LIKE'; else $customWhere .= '!='; $customWhere .= ' "'.$this->cleanInStr($customValue).'")'; } @@ -530,34 +534,34 @@ private function setSqlWhere(){ private function setCustomSqlFragment($customField, $customTerm, $customValue, $cao, $cop, $ccp){ $sqlFrag = ''; - if($customTerm == 'NULL'){ + if($customTerm == 'IS_NULL'){ $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' IS NULL) '.($ccp?$ccp.' ':''); } - elseif($customTerm == 'NOTNULL'){ + elseif($customTerm == 'NOT_NULL'){ $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' IS NOT NULL) '.($ccp?$ccp.' ':''); } elseif($customTerm == 'NOT_EQUALS'){ if(!is_numeric($customValue)) $customValue = '"'.$customValue.'"'; $sqlFrag .= $cao.($cop?' '.$cop:'').' (('.$customField.' != '.$customValue.') OR ('.$customField.' IS NULL)) '.($ccp?$ccp.' ':''); } - elseif($customTerm == 'GREATER'){ + elseif($customTerm == 'GREATER_THAN'){ if(!is_numeric($customValue)) $customValue = '"'.$customValue.'"'; $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' > '.$customValue.') '.($ccp?$ccp.' ':''); } - elseif($customTerm == 'LESS'){ + elseif($customTerm == 'LESS_THAN'){ if(!is_numeric($customValue)) $customValue = '"'.$customValue.'"'; $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' < '.$customValue.') '.($ccp?$ccp.' ':''); } elseif($customTerm == 'LIKE' && $customValue){ - $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' LIKE "%'.trim($customValue,'%').'%") '.($ccp?$ccp.' ':''); + $sqlFrag .= $cao . ($cop ? ' ' . $cop : '') . ' (' . $customField . ' LIKE "%' . $customValue . '%") ' . ($ccp ? $ccp . ' ' : ''); } elseif($customTerm == 'NOT_LIKE' && $customValue){ $sqlFrag .= $cao.($cop?' '.$cop:'').' (('.$customField.' NOT LIKE "%'.trim($customValue,'%').'%") OR ('.$customField.' IS NULL)) '.($ccp?$ccp.' ':''); } - elseif($customTerm == 'STARTS' && $customValue){ + elseif($customTerm == 'STARTS_WITH' && $customValue){ $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' LIKE "'.trim($customValue,'%').'%") '.($ccp?$ccp.' ':''); } - elseif($customValue){ + elseif($customValue !== ''){ $sqlFrag .= $cao.($cop?' '.$cop:'').' ('.$customField.' = "'.$customValue.'") '.($ccp?$ccp.' ':''); } return $sqlFrag; @@ -775,15 +779,21 @@ private function setAdditionalIdentifiers(&$occurrenceArr){ //Iterate through occurrences and merge addtional identifiers and otherCatalogNumbers field values foreach($occurrenceArr as $occid => $occurArr){ $otherCatNumArr = array(); - if($ocnStr = trim($occurArr['othercatalognumbers'],',;| ')){ + if($occurArr['othercatalognumbers']){ + $ocnStr = trim($occurArr['othercatalognumbers'],',;| '); $ocnStr = str_replace(array(',',';'),'|',$ocnStr); $ocnArr = explode('|',$ocnStr); foreach($ocnArr as $identUnit){ - $unitArr = explode(':',trim($identUnit,': ')); - $tag = ''; - if(count($unitArr) > 1) $tag = trim(array_shift($unitArr)); - $value = trim(implode(', ',$unitArr)); - $otherCatNumArr[$value] = $tag; + $identUnit = trim($identUnit, ': '); + if($identUnit){ + $tag = ''; + $value = $identUnit; + if(preg_match('/^([A-Za-z\s]+[\s#:]+)(\d+)$/', $identUnit, $m)){ + $tag = $m[1]; + $value = $m[2]; + } + $otherCatNumArr[$value] = $tag; + } } } if(isset($occurArr['identifiers'])){ @@ -791,9 +801,11 @@ private function setAdditionalIdentifiers(&$occurrenceArr){ foreach($occurArr['identifiers'] as $idKey => $idArr){ $idName = $idArr['name']; $idValue = $idArr['value']; - if(array_key_exists($idValue, $otherCatNumArr)){ - if(!$idName && $otherCatNumArr[$idValue]) $occurrenceArr[$occid]['identifiers'][$idKey]['name'] = $otherCatNumArr[$idValue]; - unset($otherCatNumArr[$idValue]); + foreach($otherCatNumArr as $ocnValue => $ocnTag){ + if($ocnValue == $idValue || $ocnValue == $idName . ' ' . $idValue){ + if(!$idName && $otherCatNumArr[$idValue]) $occurrenceArr[$occid]['identifiers'][$idKey]['name'] = $otherCatNumArr[$idValue]; + unset($otherCatNumArr[$ocnValue]); + } } } } @@ -873,7 +885,7 @@ public function editOccurrence($postArr, $editorStatus){ } //Get current paleo values to be saved within versioning tables $editFieldArr['omoccurpaleo'] = array_intersect($editArr, $this->fieldArr['omoccurpaleo']); - if($this->paleoActivated && $editFieldArr['omoccurpaleo']){ + if(isset($this->collMap['paleoActivated']) && $editFieldArr['omoccurpaleo']){ $sql = 'SELECT '.implode(',',$editFieldArr['omoccurpaleo']).' FROM omoccurpaleo WHERE occid = '.$this->occid; $rs = $this->conn->query($sql); if($rs->num_rows) $oldValueArr['omoccurpaleo'] = $rs->fetch_assoc(); @@ -970,6 +982,7 @@ public function editOccurrence($postArr, $editorStatus){ //Edit record only if user is authorized to autoCommit if($autoCommit){ $status = $LANG['SUCCESS_EDITS_SUBMITTED'].' '; + $postArr = array_merge($postArr, $this->getDatefields($postArr)); $sql = ''; //Apply autoprocessing status if set if(array_key_exists('autoprocessingstatus',$postArr) && $postArr['autoprocessingstatus']){ @@ -1007,7 +1020,7 @@ public function editOccurrence($postArr, $editorStatus){ $this->conn->query($sqlHost); } //Update occurrence record - $sql = 'UPDATE omoccurrences SET '.substr($sql,1).' WHERE (occid = '.$this->occid.')'; + $sql = 'UPDATE IGNORE omoccurrences SET '.substr($sql,1).' WHERE (occid = '.$this->occid.')'; if($this->conn->query($sql)){ if(strtolower($postArr['processingstatus']) != 'unprocessed'){ //UPDATE uid within omcrowdsourcequeue, only if not yet processed @@ -1024,7 +1037,7 @@ public function editOccurrence($postArr, $editorStatus){ //Deal with additional identifiers if(isset($postArr['idvalue'])) $this->updateIdentifiers($postArr, $identArr); //Deal with paleo fields - if($this->paleoActivated && array_key_exists('eon',$postArr)){ + if(isset($this->collMap['paleoActivated']) && array_key_exists('eon',$postArr)){ //Check to see if paleo record already exists $paleoRecordExist = false; $paleoSql = 'SELECT paleoid FROM omoccurpaleo WHERE occid = '.$this->occid; @@ -1151,12 +1164,104 @@ private function getIdentifiers($occidStr){ return $retArr; } + private function addLatestIdentToDetermination($occid) : void { + //If determination is already in omoccurdeterminations, INSERT will fail + $guid = UuidFactory::getUuidV4(); + $sqlInsert = 'INSERT IGNORE INTO omoccurdeterminations(occid, identifiedBy, dateIdentified, sciname, scientificNameAuthorship, '. + 'identificationQualifier, identificationReferences, identificationRemarks, recordID, sortsequence, isCurrent) '. + 'SELECT occid, IFNULL(identifiedby,"unknown") AS idby, IFNULL(dateidentified,"s.d.") AS di, '. + 'sciname, scientificnameauthorship, identificationqualifier, identificationreferences, identificationremarks, "'.$guid.'", 10 AS sortseq, (SELECT IF(COUNT(*) > 0, 0, 1) AS isCur from omoccurdeterminations where isCurrent = 1 and occid = '. $occid . ') '. + 'FROM omoccurrences WHERE (occid = ' . $occid . ') AND (identifiedBy IS NOT NULL OR dateIdentified IS NOT NULL OR sciname IS NOT NULL)'; + try { + $this->conn->query($sqlInsert); + } catch (mysqli_sql_exception $e) { + echo 'Duplicate: '.$this->conn->error; + error_log('Error Duplicate determination from latest identification:' . $e->getMessage()); + } + } + + // Function is only exists to move otherCatalogNumber when merging records + // This should be removed when otherCatalogNumber is no longer in omoccurrences + private function addLegacyIdentifers($occid) : void { + $sql_cnt = 'SELECT COUNT(*) AS cnt FROM omoccuridentifiers oi join omoccurrences o on o.occid = oi.occid WHERE o.occid = ?'; + $sql_insert = 'INSERT INTO omoccuridentifiers(occid, identifierName, identifierValue, notes, modifiedUid) select occid,"legacyOtherCatalogNumber" as identifierName, otherCatalogNumbers as identifierValue, "Auto generated during record merge" as notes, ? as modifiedUid from omoccurrences where occid = ?'; + try { + $result_cnt = SymbUtil::execute_query($this->conn,$sql_cnt, [$occid]); + $cnt = ($result_cnt->fetch_assoc())["cnt"]; + if($cnt === 0) { + SymbUtil::execute_query($this->conn,$sql_insert,[$GLOBALS['SYMB_UID'], $occid]); + } + } catch (mysqli_sql_exception $e) { + error_log('Error: Failed to add otherCatalogNumbers to omoccuridentifiers for occid '. $occid . ' :' . $e->getMessage()); + } + } + + // Copy of updateBaseOccurrence in OccurrenceEditorDeterminations for temporary utility till 3.2 + // TODO (Logan) remove once latest Identification section in editor is removed + private function updateBaseOccurrence($detId){ + if(is_numeric($detId)){ + $taxonArr = $this->getTaxonVariables($detId); + $sql = 'UPDATE omoccurrences o INNER JOIN omoccurdeterminations d ON o.occid = d.occid + SET o.identifiedBy = d.identifiedBy, o.dateIdentified = d.dateIdentified, o.sciname = d.sciname, o.scientificNameAuthorship = d.scientificnameauthorship, + o.identificationQualifier = d.identificationqualifier, o.identificationReferences = d.identificationReferences, o.identificationRemarks = d.identificationRemarks, + o.taxonRemarks = d.taxonRemarks, o.genus = NULL, o.specificEpithet = NULL, o.taxonRank = NULL, o.infraspecificepithet = NULL, o.scientificname = NULL '; + if(isset($taxonArr['family']) && $taxonArr['family']) $sql .= ', o.family = "'.$this->cleanInStr($taxonArr['family']).'"'; + if(isset($taxonArr['tid']) && $taxonArr['tid']) $sql .= ', o.tidinterpreted = '.$taxonArr['tid']; + if(isset($taxonArr['security']) && $taxonArr['security']) $sql .= ', o.localitysecurity = '.$taxonArr['security'].', o.localitysecurityreason = ""'; + $sql .= ' WHERE (d.iscurrent = 1) AND (d.detid = '.$detId.')'; + $updated_base = $this->conn->query($sql); + + //Whenever occurrence is updated also update associated images + if($updated_base && isset($taxonArr['tid']) && $taxonArr['tid']) { + $sql = <<<'SQL' + UPDATE images i + INNER JOIN omoccurdeterminations od on od.occid = i.occid + SET tid = ? WHERE detid = ?; + SQL; + SymbUtil::execute_query($this->conn,$sql, [$taxonArr['tid'], $detId]); + } + } + } + + // Copy of getTaxonVariables in OccurrenceEditorDeterminations for temporary utility till 3.2 + // TODO (Logan) remove once latest Identification section in editor is removed + private function getTaxonVariables($detId){ + $retArr = array(); + $sqlTid = 'SELECT t.tid, t.securitystatus, ts.family + FROM omoccurdeterminations d INNER JOIN taxa t ON d.sciname = t.sciname + INNER JOIN taxstatus ts ON t.tid = ts.tid + WHERE (d.detid = '.$detId.') AND (taxauthid = 1)'; + $rs = $this->conn->query($sqlTid); + if($r = $rs->fetch_object()){ + $retArr['tid'] = $r->tid; + $retArr['family'] = $r->family; + $retArr['security'] = ($r->securitystatus == 1 ? 1 : 0); + } + $rs->free(); + if($retArr && !$retArr['security'] && $retArr['tid']){ + $sql2 = 'SELECT c.clid + FROM fmchecklists c INNER JOIN fmchklsttaxalink cl ON c.clid = cl.clid + INNER JOIN taxstatus ts1 ON cl.tid = ts1.tid + INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted + INNER JOIN omoccurrences o ON c.locality = o.stateprovince + WHERE c.type = "rarespp" AND ts1.taxauthid = 1 AND ts2.taxauthid = 1 + AND (ts2.tid = '.$retArr['tid'].') AND (o.occid = '.$this->occid.')'; + $rs2 = $this->conn->query($sql2); + if($rs2->num_rows){ + $retArr['security'] = 1; + } + $rs2->free(); + } + return $retArr; + } + public function addOccurrence($postArr){ global $LANG; $status = $LANG['SUCCESS_NEW_OCC_SUBMITTED']; if($postArr){ + $postArr = array_merge($postArr, $this->getDatefields($postArr)); $guid = UuidFactory::getUuidV4(); - $sql = 'INSERT INTO omoccurrences(collid, recordID, '.implode(',',array_keys($this->fieldArr['omoccurrences'])).') VALUES ('.$postArr['collid'].', "'.$guid.'"'; + $sql = 'INSERT IGNORE INTO omoccurrences(collid, recordID, '.implode(',',array_keys($this->fieldArr['omoccurrences'])).') VALUES ('.$postArr['collid'].', "'.$guid.'"'; //if(array_key_exists('cultivationstatus',$postArr) && $postArr['cultivationstatus']) $postArr['cultivationstatus'] = $postArr['cultivationstatus']; //if(array_key_exists('localitysecurity',$postArr) && $postArr['localitysecurity']) $postArr['localitysecurity'] = $postArr['localitysecurity']; if(!isset($postArr['dateentered']) || !$postArr['dateentered']) $postArr['dateentered'] = date('Y-m-d H:i:s'); @@ -1176,6 +1281,7 @@ public function addOccurrence($postArr){ } else $sql .= ', NULL'; } + $sql .= ')'; if($this->conn->query($sql)){ $this->occid = $this->conn->insert_id; @@ -1184,7 +1290,7 @@ public function addOccurrence($postArr){ //Deal with identifiers if(isset($postArr['idvalue'])) $this->updateIdentifiers($postArr); //Deal with paleo - if($this->paleoActivated && array_key_exists('eon',$postArr)){ + if(isset($this->collMap['paleoActivated']) && array_key_exists('eon',$postArr)){ //Add new record $paleoFrag1 = ''; $paleoFrag2 = ''; @@ -1258,6 +1364,9 @@ public function addOccurrence($postArr){ $status = $LANG['FAILED_ADD_OCC'].": ".$this->conn->error.'
    SQL: '.$sql; } } + + $this->addLatestIdentToDetermination($this->occid); + return $status; } @@ -1288,14 +1397,48 @@ private function updateIdentifiers($identArr, $existingIdentArr = null){ } } + private function getDatefields($occurrenceArr){ + $dateArr = array(); + if(isset($occurrenceArr['eventdate'])){ + $dateArr['year'] = ''; + $dateArr['month'] = ''; + $dateArr['day'] = ''; + $dateArr['startdayofyear'] = ''; + $dateArr['enddayofyear'] = ''; + if(preg_match('/(\d{4})-(\d{2})-(\d{2})/', $occurrenceArr['eventdate'], $m)){ + if(!empty((int) $m[1])) $dateArr['year'] = (int) $m[1]; + if(!empty((int) $m[2])) $dateArr['month'] = (int) $m[2]; + if(!empty((int) $m[3])) $dateArr['day'] = (int) $m[3]; + } + if(!empty((int)$dateArr['day'])){ + if($dayOfYear = date('z', strtotime($occurrenceArr['eventdate']))){ + $dayOfYear++; + $dateArr['startdayofyear'] = $dayOfYear; + $endDayOfYear = $dayOfYear; + if(!empty($occurrenceArr['eventdate2'])){ + if($dayOfYear = date('z', strtotime($occurrenceArr['eventdate2']))){ + $dayOfYear++; + $endDayOfYear = $dayOfYear; + } + } + $dateArr['enddayofyear'] = $endDayOfYear; + } + } + } + return $dateArr; + } + public function deleteOccurrence($delOccid){ global $CHARSET, $USER_DISPLAY_NAME, $LANG; - $status = true; - if(is_numeric($delOccid)){ + + if(!is_numeric($delOccid)) return true; + + $stage = $LANG['ERROR_CREATING_TRANSACTION']; + try { //Archive data, first grab occurrence data $archiveArr = array(); $sql = 'SELECT * FROM omoccurrences WHERE occid = '.$delOccid; - //echo $sql; exit; + $stage = $LANG['ERROR_FETCHING_OCCURRENCE_DATA']; $rs = $this->conn->query($sql); if($r = $rs->fetch_assoc()){ foreach($r as $k => $v){ @@ -1306,6 +1449,7 @@ public function deleteOccurrence($delOccid){ if($archiveArr){ //Archive determinations history $sql = 'SELECT * FROM omoccurdeterminations WHERE occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_DET_HISTORY']; $rs = $this->conn->query($sql); while($r = $rs->fetch_assoc()){ $detId = $r['detid']; @@ -1317,6 +1461,7 @@ public function deleteOccurrence($delOccid){ //Archive image history $sql = 'SELECT * FROM images WHERE occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_IMG_HISTORY']; if($rs = $this->conn->query($sql)){ $imgidStr = ''; while($r = $rs->fetch_assoc()){ @@ -1328,6 +1473,7 @@ public function deleteOccurrence($delOccid){ } $rs->free(); //Delete images + $stage = $LANG['ERROR_DELETING_IMGS']; if($imgidStr){ $imgidStr = trim($imgidStr, ', '); //Remove any OCR text blocks linked to the image @@ -1346,8 +1492,9 @@ public function deleteOccurrence($delOccid){ } //Archive paleo - if($this->paleoActivated){ + if(isset($this->collMap['paleoActivated'])){ $sql = 'SELECT * FROM omoccurpaleo WHERE occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_PALEO']; if($rs = $this->conn->query($sql)){ if($r = $rs->fetch_assoc()){ foreach($r as $k => $v){ @@ -1364,6 +1511,7 @@ public function deleteOccurrence($delOccid){ 'FROM omexsiccatiocclink l INNER JOIN omexsiccatinumbers n ON l.omenid = n.omenid '. 'INNER JOIN omexsiccatititles t ON n.ometid = t.ometid '. 'WHERE l.occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_EXSICCATI']; if($rs = $this->conn->query($sql)){ if($r = $rs->fetch_assoc()){ foreach($r as $k => $v){ @@ -1375,9 +1523,10 @@ public function deleteOccurrence($delOccid){ //Archive associations info $sql = 'SELECT * FROM omoccurassociations WHERE occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_ASSOC']; if($rs = $this->conn->query($sql)){ while($r = $rs->fetch_assoc()){ - $id = $r['associd']; + $id = $r['associd']; foreach($r as $k => $v){ if($v) $archiveArr['assoc'][$id][$k] = $this->encodeStrTargeted($v,$CHARSET,'utf8'); } @@ -1387,6 +1536,7 @@ public function deleteOccurrence($delOccid){ //Archive Material Sample info $sql = 'SELECT * FROM ommaterialsample WHERE occid = '.$delOccid; + $stage = $LANG['ERROR_ARCHIVING_MAT_SAMPLE']; if($rs = $this->conn->query($sql)){ while($r = $rs->fetch_assoc()){ foreach($r as $k => $v){ @@ -1400,6 +1550,7 @@ public function deleteOccurrence($delOccid){ //Archive complete occurrence record $archiveArr['dateDeleted'] = date('r').' by '.$USER_DISPLAY_NAME; $archiveObj = json_encode($archiveArr); + $stage = 'Create Archive'; $sqlArchive = 'INSERT INTO omoccurarchive(archiveobj, occid, catalogNumber, occurrenceID, recordID) '. 'VALUES ("'.$this->cleanInStr($this->encodeStrTargeted($archiveObj,'utf8',$CHARSET)).'", '.$delOccid.','. (isset($archiveArr['catalogNumber']) && $archiveArr['catalogNumber']?'"'.$this->cleanInStr($archiveArr['catalogNumber']).'"':'NULL').', '. @@ -1409,18 +1560,38 @@ public function deleteOccurrence($delOccid){ } //Go ahead and delete - //Associated records will be deleted from: omexsiccatiocclink, omoccurdeterminations, fmvouchers - $sqlDel = 'DELETE FROM omoccurrences WHERE (occid = '.$delOccid.')'; - if($this->conn->query($sqlDel)){ - //Update collection stats - $this->conn->query('UPDATE omcollectionstats SET recordcnt = recordcnt - 1 WHERE collid = '.$this->collId); - } - else{ - $this->errorArr[] = $LANG['ERROR_TRYING_TO_DELETE'].': '.$this->conn->error; - $status = false; - } + // Associated records will be deleted from: + // omexsiccatiocclink + // omoccurdeterminations + // fmvouchers + // omoccurpaleo + // omoccuridentifiers + // omogenetic + // omoccuridentifiers + // omoccurloanslink + // (Note this list can and like is not comprehensive of all the Cascade Keys) + $sqlDel = <<conn, $sqlDel, [$delOccid]); + + $sql = <<conn, $sql, [$this->collId]); + + return true; + } catch (\Throwable $th) { + error_log( + "Error deleting occid " . $delOccid + . ", Line: " . $th->getLine() + . " : " . $th->getMessage() + ); + $this->errorArr[] = $stage . ': ' . $th->getMessage(); + return false; } - return $status; } public function cloneOccurrence($postArr){ @@ -1473,6 +1644,7 @@ public function cloneOccurrence($postArr){ return $retArr; } + // Note source is record that started duplicate lookup and is deleted up success public function mergeRecords($targetOccid,$sourceOccid){ global $LANG; $status = true; @@ -1485,154 +1657,212 @@ public function mergeRecords($targetOccid,$sourceOccid){ return false; } - $oArr = array(); - //Merge records - $sql = 'SELECT * FROM omoccurrences WHERE occid = '.$targetOccid.' OR occid = '.$sourceOccid; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_assoc()){ - $tempArr = array_change_key_case($r); - $id = $tempArr['occid']; - unset($tempArr['occid']); - unset($tempArr['collid']); - unset($tempArr['dbpk']); - unset($tempArr['datelastmodified']); - $oArr[$id] = $tempArr; - } - $rs->free(); - - $tArr = $oArr[$targetOccid]; - $sArr = $oArr[$sourceOccid]; - $sqlFrag = ''; - foreach($sArr as $k => $v){ - if(($v != '') && $tArr[$k] == ''){ - $sqlFrag .= ','.$k.'="'.$this->cleanInStr($v).'"'; - } - } - if($sqlFrag){ - //Remap source to target - $sqlIns = 'UPDATE omoccurrences SET '.substr($sqlFrag,1).' WHERE occid = '.$targetOccid; - //echo $sqlIns; - if(!$this->conn->query($sqlIns)){ - $this->errorArr[] = $LANG['ABORT_DUE_TO_ERROR'].': '.$this->conn->error; - return false; - } - } - - //Remap determinations - $sql = 'UPDATE IGNORE omoccurdeterminations SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - //$this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_DETS'].': '.$this->conn->error; - //$status = false; - } - - //Remap images - $sql = 'UPDATE images SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_IMAGES'].': '.$this->conn->error; - $status = false; - } - - //Remap paleo - if($this->paleoActivated){ - $sql = 'UPDATE omoccurpaleo SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - //$this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_PALEOS'].': '.$this->conn->error; - //$status = false; - } - } - - //Delete source occurrence edits - $sql = 'DELETE FROM omoccuredits WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_OCC_EDITS'].': '.$this->conn->error; - $status = false; - } - - //Remap associations - $sql = 'UPDATE omoccurassociations SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_ASSOCS_1'].': '.$this->conn->error; - $status = false; - } - $sql = 'UPDATE omoccurassociations SET occidAssociate = '.$targetOccid.' WHERE occidAssociate = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_ASSOCS_2'].': '.$this->conn->error; - $status = false; - } - - //Remap comments - $sql = 'UPDATE omoccurcomments SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_COMMENTS'].': '.$this->conn->error; - $status = false; - } - - //Remap genetic resources - $sql = 'UPDATE omoccurgenetic SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_GENETIC'].': '.$this->conn->error; - $status = false; - } - - //Remap identifiers - $sql = 'UPDATE omoccuridentifiers SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - //$this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_OCCIDS'].': '.$this->conn->error; - //$status = false; - } + /* Start transaction */ + // This will autocommit if not rollback explicitly + $this->conn->begin_transaction(); - //Remap exsiccati - $sql = 'UPDATE omexsiccatiocclink SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - if(strpos($this->conn->error,'Duplicate') !== false){ - $this->conn->query('DELETE FROM omexsiccatiocclink WHERE occid = '.$sourceOccid); - } - else{ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_EXS'].': '.$this->conn->error; - $status = false; - } - } + //Add Latest Determination if missing + $this->addLatestIdentToDetermination($targetOccid); + $this->addLatestIdentToDetermination($sourceOccid); - //Remap occurrence dataset links - $sql = 'UPDATE omoccurdatasetlink SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - if(strpos($this->conn->error,'Duplicate') !== false){ - $this->conn->query('DELETE FROM omoccurdatasetlink WHERE occid = '.$sourceOccid); - } - else{ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_DATASET'].': '.$this->conn->error; - $status = false; - } - } - - //Remap loans - $sql = 'UPDATE omoccurloanslink SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - if(strpos($this->conn->error,'Duplicate') !== false){ - $this->conn->query('DELETE FROM omoccurloanslink WHERE occid = '.$sourceOccid); - } - else{ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_LOANS'].': '.$this->conn->error; - $status = false; + //Add otherCatalogNumbers to Identifiers if missing + $this->addLegacyIdentifers($targetOccid); + $this->addLegacyIdentifers($sourceOccid); + $stage = ''; + try { + $oArr = array(); + //Merge records + $sql = 'SELECT * FROM omoccurrences WHERE occid = '.$targetOccid.' OR occid = '.$sourceOccid; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_assoc()){ + $tempArr = array_change_key_case($r); + $id = $tempArr['occid']; + unset($tempArr['occid']); + unset($tempArr['collid']); + unset($tempArr['dbpk']); + unset($tempArr['datelastmodified']); + $oArr[$id] = $tempArr; } - } + $rs->free(); - //Remap checklists voucher links - $sql = 'UPDATE fmvouchers SET occid = '.$targetOccid.' WHERE occid = '.$sourceOccid; - if(!$this->conn->query($sql)){ - if(strpos($this->conn->error,'Duplicate') !== false){ - $this->conn->query('DELETE FROM fmvouchers WHERE occid = '.$sourceOccid); - } - else{ - $this->errorArr[] .= '; '.$LANG['ERROR_REMAPPING_VOUCHER'].': '.$this->conn->error; - $status = false; + $tArr = $oArr[$targetOccid]; + $sArr = $oArr[$sourceOccid]; + $sqlFrag = ''; + + foreach($sArr as $k => $v){ + if(($v != '') && $tArr[$k] == ''){ + $sqlFrag .= ','.$k.'="'.$this->cleanInStr($v).'"'; + } + } + if($sqlFrag){ + $sqlIns = 'UPDATE IGNORE omoccurrences SET ' . substr($sqlFrag,1) . ' WHERE occid = ?'; + $stage = $LANG['ABORT_DUE_TO_ERROR']; + SymbUtil::execute_query($this->conn, $sqlIns, [$targetOccid]); + } + + // Anon function for util of merging determinations + $get_current_determinations = function ($occid) { + $sql =<<<'SQL' + SELECT detid FROM omoccurdeterminations where occid = ? and isCurrent = 1; + SQL; + $result = SymbUtil::execute_query($this->conn, $sql, [$occid]); + return array_map(fn($v) => $v[0], $result->fetch_all()); + }; + + //Fetch List of Old Current Determinations + $currentDeterminations = $get_current_determinations($targetOccid); + + //Remap determinations + $sql = <<<'SQL' + UPDATE omoccurdeterminations + SET occid = ? WHERE occid = ? + AND detid NOT IN ( + SELECT source.detid FROM omoccurdeterminations source + JOIN omoccurdeterminations target ON target.occid = ? + WHERE source.occid = ? + AND source.sciname = target.sciname + AND source.dateIdentified = target.dateIdentified + AND source.identifiedBy = target.identifiedBy + ); + SQL; + SymbUtil::execute_query($this->conn, $sql, [ + //Update To This Occid + $targetOccid, + //From Options of This Occid + $sourceOccid, + //Check This Occid Determinations for Duplicates + $targetOccid, + //Of this Occid Record + $sourceOccid + ]); + + //Downgrade old determinations if new determinations have a current determination + if(count($currentDeterminations) > 0) { + + $parameters = str_repeat('?,', count($currentDeterminations) - 1) . '?'; + $sql = <<<"SQL" + UPDATE omoccurdeterminations + SET isCurrent = 0 + WHERE occid = ? AND isCurrent = 1 AND detid NOT IN ($parameters); + SQL; + SymbUtil::execute_query($this->conn, $sql, array_merge([$targetOccid], $currentDeterminations)); + } + + // Get New Current determination and updateBaseOccurrence to match + $currentDeterminations = $get_current_determinations($targetOccid); + if(is_array($currentDeterminations) && count($currentDeterminations) > 0) { + $this->updateBaseOccurrence($currentDeterminations[0]); + } + + //Remap images + $sql = <<<'SQL' + UPDATE images SET occid = ? WHERE occid = ?; + SQL; + $stage = $LANG['ERROR_REMAPPING_IMAGES']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap paleo + if(isset($this->collMap['paleoActivated'])){ + $sql = <<<'SQL' + UPDATE IGNORE omoccurpaleo SET occid = ? WHERE occid = ?; + SQL; + $stage = $LANG['ERROR_REMAPPING_PALEOS']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + } + + //Delete source occurrence edits + $sql = <<<'SQL' + DELETE FROM omoccuredits WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_OCC_EDITS']; + SymbUtil::execute_query($this->conn, $sql, [$sourceOccid]); + + //Remap associations + $sql = <<<'SQL' + UPDATE IGNORE omoccurassociations SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_ASSOCS_1']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + $sql = <<<'SQL' + UPDATE IGNORE omoccurassociations SET occidAssociate = ? WHERE occidAssociate = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_ASSOCS_2']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap comments + $sql = <<<'SQL' + UPDATE IGNORE omoccurcomments SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_COMMENTS']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap genetic resources + $sql = <<<'SQL' + UPDATE IGNORE omoccurgenetic SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_GENETIC']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap identifiers + $sql = <<<'SQL' + UPDATE IGNORE omoccuridentifiers SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_OCCIDS']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap exsiccati + $sql = <<<'SQL' + UPDATE IGNORE omexsiccatiocclink SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_EXS']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap occurrence dataset links + $sql = <<<'SQL' + UPDATE IGNORE omoccurdatasetlink SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_DATASET']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap loans + $sql = <<<'SQL' + UPDATE IGNORE omoccurloanslink SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_LOANS']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + //Remap checklists voucher links + $sql = <<<'SQL' + UPDATE IGNORE fmvouchers SET occid = ? WHERE occid = ? + SQL; + $stage = $LANG['ERROR_REMAPPING_VOUCHER']; + SymbUtil::execute_query($this->conn, $sql, [$targetOccid, $sourceOccid]); + + if(!$this->deleteOccurrence($sourceOccid)){ + error_log( + 'Error: Could not delete ' . $sourceOccid + . ' while trying to merge into '. $targetOccid + ); + $this->errorArr[] = $LANG['ERROR_DELETING_OCCURRENCE']; + return false; } - } - if(!$this->deleteOccurrence($sourceOccid)){ - $status = false; + $this->conn->commit(); + return true; + } catch (\Throwable $th) { + error_log( + 'Error: Merging Record ' . $sourceOccid . ' into '. $targetOccid + . ' at '. $stage + .', line: ' . $th->getLine() + . ' : ' . $th->getMessage() + ); + $this->errorArr[] = $stage . ' : ' . $th->getMessage(); + return false; + } finally { + //Explicit Rollback if not committed + $this->conn->rollback(); } - return $status; } public function transferOccurrence($targetOccid,$transferCollid){ @@ -1667,7 +1897,7 @@ public function getLoanData(){ } private function setPaleoData(){ - if($this->paleoActivated){ + if(isset($this->collMap['paleoActivated'])){ $sql = 'SELECT '.implode(',',$this->fieldArr['omoccurpaleo']).' FROM omoccurpaleo WHERE occid = '.$this->occid; //echo $sql; $rs = $this->conn->query($sql); @@ -1765,7 +1995,7 @@ public function batchUpdateField($fieldName,$oldValue,$newValue,$buMatch){ $statusStr = $LANG['ERROR_ADDING_UPDATE'].': '.$this->conn->error; } //Apply edits to core tables - if($this->paleoActivated && array_key_exists($fn, $this->fieldArr['omoccurpaleo'])){ + if(isset($this->collMap['paleoActivated']) && array_key_exists($fn, $this->fieldArr['omoccurpaleo'])){ $sql = 'UPDATE omoccurpaleo SET '.$fn.' = '.$nvSqlFrag.' '.$sqlWhere; } else{ @@ -1814,8 +2044,8 @@ private function getBatchUpdateWhere($fn,$ov,$buMatch){ } public function carryOverValues($fArr){ - $locArr = Array('recordedby','associatedcollectors','eventdate','eventdate2','verbatimeventdate','month','day','year', - 'startdayofyear','enddayofyear','country','stateprovince','county','municipality','locationid','locality','decimallatitude','decimallongitude', + $locArr = Array('recordedby','associatedcollectors','eventdate','eventdate2','verbatimeventdate', + 'country','stateprovince','county','municipality','locationid','locality','decimallatitude','decimallongitude', 'verbatimcoordinates','coordinateuncertaintyinmeters','footprintwkt','geodeticdatum','georeferencedby','georeferenceprotocol', 'georeferencesources','georeferenceverificationstatus','georeferenceremarks', 'minimumelevationinmeters','maximumelevationinmeters','verbatimelevation','minimumdepthinmeters','maximumdepthinmeters','verbatimdepth', @@ -2089,18 +2319,19 @@ public function getImageMap($imgId = 0){ $imageMap[$row->imgid]['url'] = $row->url; $imageMap[$row->imgid]['tnurl'] = $row->thumbnailurl; $imageMap[$row->imgid]['origurl'] = $row->originalurl; - $imageMap[$row->imgid]['caption'] = $this->cleanOutStr($row->caption); - $imageMap[$row->imgid]['photographer'] = $this->cleanOutStr($row->photographer); + $imageMap[$row->imgid]['caption'] = $row->caption; + $imageMap[$row->imgid]['photographer'] = $row->photographer; $imageMap[$row->imgid]['photographeruid'] = $row->photographeruid; $imageMap[$row->imgid]['sourceurl'] = $row->sourceurl; - $imageMap[$row->imgid]['copyright'] = $this->cleanOutStr($row->copyright); - $imageMap[$row->imgid]['notes'] = $this->cleanOutStr($row->notes); + $imageMap[$row->imgid]['copyright'] = $row->copyright; + $imageMap[$row->imgid]['notes'] = $row->notes; $imageMap[$row->imgid]['occid'] = $row->occid; - $imageMap[$row->imgid]['username'] = $this->cleanOutStr($row->username); + $imageMap[$row->imgid]['username'] = $row->username; $imageMap[$row->imgid]['sort'] = $row->sortoccurrence; } $result->free(); } + $this->cleanOutArr($imageMap); return $imageMap; } @@ -2324,7 +2555,7 @@ public function getCollectionList($limitToUser = true){ public function getPaleoGtsTerms(){ $retArr = array(); - if($this->paleoActivated){ + if(isset($this->collMap['paleoActivated'])){ $sql = 'SELECT gtsterm, rankid FROM omoccurpaleogts '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ @@ -2487,39 +2718,17 @@ public function getErrorStr(){ } //Misc functions - private function encodeStrTargeted($inStr,$inCharset,$outCharset){ + private function encodeStrTargeted($inStr, $inCharset, $outCharset){ if($inCharset == $outCharset) return $inStr; $retStr = $inStr; - if($inCharset == "latin" && $outCharset == 'utf8'){ - if(mb_detect_encoding($retStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($retStr); - } - } - elseif($inCharset == "utf8" && $outCharset == 'latin'){ - if(mb_detect_encoding($retStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($retStr); - } - } + $retStr = mb_convert_encoding($retStr, $outCharset, mb_detect_encoding($retStr)); return $retStr; } protected function encodeStr($inStr){ - global $CHARSET; $retStr = $inStr; - if($inStr){ - if(strtolower($CHARSET) == "utf-8" || strtolower($CHARSET) == "utf8"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1',true) == "ISO-8859-1"){ - $retStr = utf8_encode($inStr); - //$retStr = iconv("ISO-8859-1//TRANSLIT","UTF-8",$inStr); - } - } - elseif(strtolower($CHARSET) == "iso-8859-1"){ - if(mb_detect_encoding($inStr,'UTF-8,ISO-8859-1') == "UTF-8"){ - $retStr = utf8_decode($inStr); - //$retStr = iconv("UTF-8","ISO-8859-1//TRANSLIT",$inStr); - } - } + $retStr = mb_convert_encoding($retStr, $GLOBALS['CHARSET'], mb_detect_encoding($retStr)); } return $retStr; } @@ -2532,22 +2741,28 @@ protected function cleanOutArr(&$arr){ } protected function cleanOutStr($str){ - $newStr = str_replace('"',""",$str); - $newStr = str_replace("'","'",$newStr); - return $newStr; + if(!is_string($str) && !is_numeric($str) && !is_bool($str)) $str = ''; + $str = htmlspecialchars($str, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE); + return $str; } protected function cleanInStr($str){ - $newStr = trim($str); - $newStr = preg_replace('/\s\s+/', ' ',$newStr); - $newStr = $this->conn->real_escape_string($newStr); + $newStr = $str; + if($newStr){ + $newStr = trim($str); + $newStr = preg_replace('/\s\s+/', ' ',$newStr); + $newStr = $this->conn->real_escape_string($newStr); + } return $newStr; } protected function cleanRawFragment($str){ - $newStr = trim($str); - $newStr = $this->encodeStr($newStr); - $newStr = $this->conn->real_escape_string($newStr); + $newStr = $str; + if($newStr){ + $newStr = trim($str); + $newStr = $this->encodeStr($newStr); + $newStr = $this->conn->real_escape_string($newStr); + } return $newStr; } } diff --git a/classes/OccurrenceEditorMaterialSample.php b/classes/OccurrenceEditorMaterialSample.php deleted file mode 100644 index 15f28342e3..0000000000 --- a/classes/OccurrenceEditorMaterialSample.php +++ /dev/null @@ -1,142 +0,0 @@ -occid; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_assoc()){ - $retArr[$r['matSampleID']] = $r; - } - $rs->free(); - } - return $retArr; - } - - public function insertMaterialSample($postArr){ - $status = false; - if($this->occid){ - $reqArr = $this->getRequestArr($postArr); - $sql = 'INSERT INTO ommaterialsample(occid, sampleType, catalogNumber, guid, sampleCondition, disposition, preservationType, preparationDetails, preparationDate, - preparedByUid, individualCount, sampleSize, storageLocation, remarks) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('issssssssissss', $this->occid, $reqArr['sampleType'], $reqArr['catalogNumber'], $reqArr['guid'], $reqArr['sampleCondition'], $reqArr['disposition'], - $reqArr['preservationType'], $reqArr['preparationDetails'], $reqArr['preparationDate'], $reqArr['preparedByUid'], $reqArr['individualCount'], - $reqArr['sampleSize'], $reqArr['storageLocation'], $reqArr['remarks']); - $stmt->execute(); - if($stmt->affected_rows || !$stmt->error) $status = $stmt->insert_id; - else $this->errorMessage = 'ERROR inserting new material sample record into database: '.$stmt->error; - $stmt->close(); - } - else $this->errorMessage = 'ERROR preparing statement for MS insert: '.$this->conn->error; - if(!$status) echo $this->errorMessage; - } - return $status; - } - - public function updateMaterialSample($postArr){ - $status = false; - if(is_numeric($this->matSampleID)){ - $reqArr = $this->getRequestArr($postArr); - $sql = 'UPDATE ommaterialsample SET sampleType = ?, catalogNumber = ?, guid = ?, sampleCondition = ?, disposition = ?, - preservationType = ?, preparationDetails = ?, preparationDate = ?, preparedByUid = ?, individualCount = ?, sampleSize = ?, - storageLocation = ?, remarks = ? WHERE matSampleID = ?'; - if($stmt = $this->conn->prepare($sql)) { - $stmt->bind_param('ssssssssissssi', $reqArr['sampleType'], $reqArr['catalogNumber'], $reqArr['guid'], $reqArr['sampleCondition'], $reqArr['disposition'], - $reqArr['preservationType'], $reqArr['preparationDetails'], $reqArr['preparationDate'], $reqArr['preparedByUid'], $reqArr['individualCount'], - $reqArr['sampleSize'], $reqArr['storageLocation'], $reqArr['remarks'], $this->matSampleID); - $stmt->execute(); - if($stmt->affected_rows || !$stmt->error) $status = true; - else $this->errorMessage = 'ERROR updating material sample record in database: '.$stmt->error; - $stmt->close(); - } - else $this->errorMessage = 'ERROR preparing statement for MS update: '.$this->conn->error; - } - return $status; - } - - public function deleteMaterialSample(){ - if(is_numeric($this->matSampleID)){ - $sql = 'DELETE FROM ommaterialsample WHERE matSampleID = '.$this->matSampleID; - if($this->conn->query($sql)){ - return true; - } - else{ - $this->errorMessage = 'ERROR updating material sample record into database: '.$this->conn->error; - return false; - } - } - } - - private function getRequestArr($postArr){ - $retArr = array(); - $retArr['sampleType'] = ($postArr['ms_sampleType']?trim($postArr['ms_sampleType']):NULL); - $retArr['catalogNumber'] = ($postArr['ms_catalogNumber']?trim($postArr['ms_catalogNumber']):NULL); - $retArr['guid'] = ($postArr['ms_guid']?trim($postArr['ms_guid']):NULL); - $retArr['sampleCondition'] = ($postArr['ms_sampleCondition']?trim($postArr['ms_sampleCondition']):NULL); - $retArr['disposition'] = ($postArr['ms_disposition']?trim($postArr['ms_disposition']):NULL); - $retArr['preservationType'] = ($postArr['ms_preservationType']?trim($postArr['ms_preservationType']):NULL); - $retArr['preparationDetails'] = ($postArr['ms_preparationDetails']?trim($postArr['ms_preparationDetails']):NULL); - $retArr['preparationDate'] = ($postArr['ms_preparationDate']?trim($postArr['ms_preparationDate']):NULL); - $retArr['preparedByUid'] = (is_numeric($postArr['ms_preparedByUid'])?$postArr['ms_preparedByUid']:NULL); - $retArr['individualCount'] = ($postArr['ms_individualCount']?trim($postArr['ms_individualCount']):NULL); - $retArr['sampleSize'] = ($postArr['ms_sampleSize']?trim($postArr['ms_sampleSize']):NULL); - $retArr['storageLocation'] = ($postArr['ms_storageLocation']?trim($postArr['ms_storageLocation']):NULL); - $retArr['remarks'] = ($postArr['ms_remarks']?trim($postArr['ms_remarks']):NULL); - return $retArr; - } - - //Data lookup functions - public function getMSTypeControlValues(){ - $retArr = array(); - $sql = 'SELECT v.tableName, v.fieldName, t.term, v.limitToList FROM ctcontrolvocabterm t INNER JOIN ctcontrolvocab v ON t.cvID = v.cvID - WHERE v.tableName IN("ommaterialsample","ommaterialsampleextended") ORDER BY t.term'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $retArr[$r->tableName][$r->fieldName]['t'][] = $r->term; - $retArr[$r->tableName][$r->fieldName]['l'] = $r->limitToList; - } - return $retArr; - } - - //Misc support functions - public function cleanFormData(&$postArr){ - foreach($postArr as $k => $v){ - if(substr($k,0,3) == 'ms_') $postArr[$k] = filter_var($v,FILTER_SANITIZE_STRING); - } - } - - //Setters and getters - public function setOccid($id){ - if(is_numeric($id)) $this->occid = $id; - } - - public function getOccid(){ - return $this->occid; - } - - public function setMatSampleID($id){ - if(is_numeric($id)) $this->matSampleID = $id; - } - - public function getMatSampleID(){ - return $this->matSampleID; - } -} -?> \ No newline at end of file diff --git a/classes/OccurrenceEditorResource.php b/classes/OccurrenceEditorResource.php index 0ca2829604..3dbf5cabb9 100644 --- a/classes/OccurrenceEditorResource.php +++ b/classes/OccurrenceEditorResource.php @@ -1,11 +1,14 @@ assocManager = new OmAssociations($this->conn); } public function __destruct(){ @@ -15,145 +18,72 @@ public function __destruct(){ //Occurrence relationships public function getOccurrenceRelationships(){ $retArr = array(); - $relOccidArr = array(); - $uidArr = array(); - $sql = 'SELECT assocID, occid, occidAssociate, relationship, subType, resourceUrl, identifier, verbatimSciname, tid, locationOnHost, notes, dynamicProperties, '. - 'IFNULL(modifiedUid,createdUid) as uid, IFNULL(modifiedTimestamp, initialTimestamp) as ts '. - 'FROM omoccurassociations '. - 'WHERE (occid = '.$this->occid.') OR (occidAssociate = '.$this->occid.')'; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_object()){ - $relOccid = $r->occidAssociate; - $relationship = $r->relationship; - if($this->occid == $r->occidAssociate){ - $relOccid = $r->occid; - $relationship = $this->getInverseRelationship($relationship); - } - if($relOccid) $relOccidArr[$relOccid][] = $r->assocID; - $retArr[$r->assocID]['occidAssociate'] = $relOccid; - $retArr[$r->assocID]['relationship'] = $relationship; - $retArr[$r->assocID]['subType'] = $r->subType; - $retArr[$r->assocID]['resourceUrl'] = $r->resourceUrl; - $retArr[$r->assocID]['identifier'] = $r->identifier; - $retArr[$r->assocID]['sciname'] = $r->verbatimSciname; - $retArr[$r->assocID]['tid'] = $r->tid; - $retArr[$r->assocID]['locationOnHost'] = $r->locationOnHost; - $retArr[$r->assocID]['notes'] = $r->notes; - $retArr[$r->assocID]['dynamicProperties'] = $r->dynamicProperties; - $retArr[$r->assocID]['ts'] = $r->ts; - if(!$retArr[$r->assocID]['identifier'] && $retArr[$r->assocID]['resourceUrl']) $retArr[$r->assocID]['identifier'] = 'identifier undefined'; - if($r->uid) $uidArr[$r->uid][] = $r->assocID; - } - $rs->free(); - if($uidArr){ - //Update modifiedBy data - $sql = 'SELECT uid, CONCAT_WS("; ",lastname, firstname) as username FROM users WHERE uid IN('.implode(',',array_keys($uidArr)).')'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - foreach($uidArr[$r->uid] as $targetAssocID){ - $retArr[$targetAssocID]['definedBy'] = $r->username; - } - } - $rs->free(); - } - if($relOccidArr){ - $sql = 'SELECT o.occid, CONCAT_WS("-",IFNULL(o.institutioncode,c.institutioncode),IFNULL(o.collectioncode,c.collectioncode)) as collcode, IFNULL(o.catalogNumber,o.otherCatalogNumbers) as catnum '. - 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid '. - 'WHERE o.occid IN('.implode(',',array_keys($relOccidArr)).')'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - foreach($relOccidArr[$r->occid] as $targetAssocID){ - $retArr[$targetAssocID]['identifier'] = $r->collcode.':'.$r->catnum; - } - } - $rs->free(); - } + $this->assocManager->setOccid($this->occid); + $retArr = $this->assocManager->getAssociationArr('FULL'); + foreach($retArr as $assocID => $assocArr){ + $createdBy = ''; + if(!empty($assocArr['modifiedBy'])) $createdBy = $assocArr['modifiedBy']; + elseif(!empty($assocArr['createdBy'])) $createdBy = $assocArr['createdBy']; + $retArr[$assocID]['definedBy'] = $createdBy; } return $retArr; } - private function getInverseRelationship($relationship){ - if(!$this->relationshipArr) $this->setRelationshipArr(); - if(array_key_exists($relationship, $this->relationshipArr)) return $this->relationshipArr[$relationship]; - return $relationship; - } - - private function setRelationshipArr(){ - if(!$this->relationshipArr){ - $sql = 'SELECT t.term, t.inverseRelationship FROM ctcontrolvocabterm t INNER JOIN ctcontrolvocab v ON t.cvid = v.cvid '. - 'WHERE v.tableName = "omoccurassociations" AND v.fieldName = "relationship" '; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_object()){ - $this->relationshipArr[$r->term] = $r->inverseRelationship; - } - $rs->free(); - } - $this->relationshipArr = array_merge($this->relationshipArr,array_flip($this->relationshipArr)); - ksort($this->relationshipArr); - } + public function addAssociation($postArr){ + $status = true; + $this->assocManager->setOccid($postArr['occid']); + $status = $this->assocManager->insertAssociation($postArr); + if(!$status) $this->errorArr[] = $this->assocManager->getErrorMessage(); + return $status; } - public function addAssociation($postArr){ + public function updateAssociation($postArr){ $status = true; - $sql = 'INSERT INTO omoccurassociations(occid, occidAssociate, relationship, subType, identifier, basisOfRecord, resourceUrl, verbatimSciname, locationOnHost, notes, createdUid) '. - 'VALUES('.$postArr['occid'].','.(isset($postArr['occidAssoc']) && $postArr['occidAssoc']?$this->cleanInStr($postArr['occidAssoc']):'NULL').','. - ($postArr['relationship']?'"'.$this->cleanInStr($postArr['relationship']).'"':'NULL').','. - ($postArr['subtype']?'"'.$this->cleanInStr($postArr['subtype']).'"':'NULL').','. - ($postArr['identifier']?'"'.$this->cleanInStr($postArr['identifier']).'"':'NULL').','. - ($postArr['basisofrecord']?'"'.$this->cleanInStr($postArr['basisofrecord']).'"':'NULL').','. - ($postArr['resourceurl']?'"'.$this->cleanInStr($postArr['resourceurl']).'"':'NULL').','. - ($postArr['verbatimsciname']?'"'.$this->cleanInStr($postArr['verbatimsciname']).'"':'NULL').','. - ($postArr['locationonhost']?'"'.$this->cleanInStr($postArr['locationonhost']).'"':'NULL').','. - ($postArr['notes']?'"'.$this->cleanInStr($postArr['notes']).'"':'NULL').','. - $GLOBALS['SYMB_UID'].')'; - if(!$this->conn->query($sql)){ - $this->errorArr = 'ERROR saving occurrence association: '.$this->conn->error; - $status = false; - } + $this->assocManager->setOccid($postArr['occid']); + $this->assocManager->setAssocID($postArr['assocID']); + $status = $this->assocManager->updateAssociation($postArr); + if(!$status) $this->errorArr[] = $this->assocManager->getErrorMessage(); return $status; } public function deleteAssociation($assocID){ $status = true; if(is_numeric($assocID)){ - $sql = 'DELETE FROM omoccurassociations WHERE associd = '.$assocID; - if(!$this->conn->query($sql)){ - $this->errorArr = 'ERROR deleting occurrence association: '.$this->conn->error; - $status = false; - } + $this->assocManager->setAssocID($assocID); + $status = $this->assocManager->deleteAssociation(); + if(!$status) $this->errorArr[] = $this->assocManager->getErrorMessage(); } return $status; } + public function getAssociationTypeArr(){ + return $this->assocManager->getControlledVocab('associationType'); + } + public function getRelationshipArr(){ - if(!$this->relationshipArr) $this->setRelationshipArr(); - return $this->relationshipArr; + return array_keys($this->assocManager->getControlledVocab('relationship')); + } + + public function getResourceRelationshipArr(){ + return array_keys($this->assocManager->getControlledVocab('relationship', 'associationType:resource')); } public function getSubtypeArr(){ - $retArr = array(); - $sql = 'SELECT t.term FROM ctcontrolvocabterm t INNER JOIN ctcontrolvocab v ON t.cvid = v.cvid WHERE v.tableName = "omoccurassociations" AND v.fieldName = "subType" ORDER BY t.term'; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_object()){ - $retArr[] = $r->term; - } - $rs->free(); - } - return $retArr; + return $this->assocManager->getControlledVocab('subType'); } + //RPC calls: Used within occurrence editor rpc getAssocOccurrence AJAX call public function getOccurrenceByIdentifier($id,$target,$collidTarget){ - //Used within occurrence editor rpc getAssocOccurrence AJAX call $retArr = array(); $id = $this->cleanInStr($id); $sqlWhere = ''; if($target == 'occid'){ if(is_numeric($id)) $sqlWhere .= 'AND (occid = '.$id.') '; } - else $sqlWhere .= 'AND ((catalogNumber = "'.$id.'") OR (othercatalognumbers = "'.$id.'")) '; + else $sqlWhere .= 'AND ((catalogNumber LIKE "'.$id.'") OR (othercatalognumbers = "'.$id.'")) '; if($sqlWhere){ $sql = 'SELECT o.occid, o.catalogNumber, o.otherCatalogNumbers, o.recordedBy, o.recordNumber, IFNULL(o.eventDate,o.verbatimEventDate) as eventDate, '. - 'CONCAT_WS("-",c.institutionCode,c.collectionCode) AS collcode '. + 'CONCAT_WS("-",c.institutionCode,c.collectionCode) AS collcode, o.sciname, o.tidInterpreted '. 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid WHERE '.substr($sqlWhere, 4); if($collidTarget && is_numeric($collidTarget)) $sql .= ' AND (o.collid = '.$collidTarget.') '; $rs = $this->conn->query($sql); @@ -167,6 +97,8 @@ public function getOccurrenceByIdentifier($id,$target,$collidTarget){ } $retArr[$r->occid]['catnum'] = $catNum; $retArr[$r->occid]['collinfo'] = $r->recordedBy.($r->recordNumber?' ('.$r->recordNumber.')':'').' '.$r->eventDate; + $retArr[$r->occid]['sciname'] = $r->sciname; + $retArr[$r->occid]['tid'] = $r->tidInterpreted; } $rs->free(); } diff --git a/classes/OccurrenceExsiccatae.php b/classes/OccurrenceExsiccatae.php index 604ec8f6a5..397dfb3f5f 100644 --- a/classes/OccurrenceExsiccatae.php +++ b/classes/OccurrenceExsiccatae.php @@ -246,7 +246,8 @@ public function exportExsiccatiAsCsv($searchTerm, $specimenOnly, $imagesOnly, $c fputcsv($out, array_keys($fieldArr)); while($r = $rs->fetch_assoc()){ foreach($r as $k => $v){ - $r[$k] = utf8_decode($v); + if($v) $v = mb_convert_encoding($v, 'ISO-8859-1', $GLOBALS['CHARSET']); + $r[$k] = $v; } fputcsv($out, $r); } @@ -591,7 +592,7 @@ public function batchImport($targetCollid,$postArr){ } if($transferCnt){ $statusStr = 'SUCCESS transferring '.$transferCnt.' records '; - //if($datasetId) $statusStr = '
    Records linked to dataset: '.$datasetTitle.''; + //if($datasetId) $statusStr = '
    Records linked to dataset: ' . htmlspecialchars($datasetTitle, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''; } return $statusStr; } @@ -720,13 +721,14 @@ public function getCollArr($ometid = 0){ } public function getTargetCollArr(){ + global $USER_RIGHTS; $retArr = array(); $collArr = array(); - if(isset($GLOBALS['CollAdmin'])){ - $collArr = $GLOBALS['CollAdmin']; + if(isset($USER_RIGHTS['CollAdmin'])){ + $collArr = $USER_RIGHTS['CollAdmin']; } - if(isset($GLOBALS['CollEditor'])){ - $collArr = array_merge($collArr,$GLOBALS['CollEditor']); + if(isset($USER_RIGHTS['CollEditor'])){ + $collArr = array_merge($collArr, $USER_RIGHTS['CollEditor']); } if($collArr){ $sql ='SELECT DISTINCT c.collid, c.collectionname, c.institutioncode, c.collectioncode '. @@ -761,7 +763,7 @@ public function getExsTableRow($occid,$oArr,$omenid,$targetCollid){ $retStr .= '
    '; $retStr .= '
    CntNameActions
    '; + echo '
    '; echo ''.($indent?'':'').htmlspecialchars($sciname, ENT_QUOTES, 'UTF-8').($indent?'':'').''; - echo ' '; - echo '
    #'.$oArr['exsnum'].''; $retStr .= ''.$oArr['collcode'].', '; - $retStr .= ''.$oArr['recby'].' '.($oArr['recnum']?$oArr['recnum']:'s.n.').''; + $retStr .= '' . htmlspecialchars($oArr['recby'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ' ' . htmlspecialchars(($oArr['recnum']?$oArr['recnum']:'s.n.'), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ''; $retStr .= ($oArr['eventdate']?', '.$oArr['eventdate']:''); $retStr .= ', '.$oArr['sciname'].' '.$oArr['author']; $retStr .= $oArr['country'].', '.$oArr['state'].', '.$oArr['county'].', '.(strlen($oArr['locality'])>75?substr($oArr['locality'],0,75).'...':$oArr['locality']); diff --git a/classes/OccurrenceGeorefTools.php b/classes/OccurrenceGeorefTools.php index 861b7298e8..3cec0289e0 100644 --- a/classes/OccurrenceGeorefTools.php +++ b/classes/OccurrenceGeorefTools.php @@ -82,15 +82,15 @@ public function getLocalityArr(){ $countryStr='';$stateStr='';$countyStr='';$municipalityStr='';$localityStr='';$verbCoordStr = '';$decLatStr='';$decLngStr=''; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $localityStrNew = trim($r->locality,' .,;'); - $verbCoordStrNew = trim($r->verbatimcoordinates,' .,;'); + $localityStrNew = trim($r->locality ?? '',' .,;'); + $verbCoordStrNew = trim($r->verbatimcoordinates ?? '',' .,;'); if($localityStrNew || $verbCoordStrNew){ - if($countryStr != trim($r->country) || $stateStr != trim($r->stateprovince) || $countyStr != trim($r->county) || $municipalityStr != trim($r->municipality) + if($countryStr != trim($r->country ?? '') || $stateStr != trim($r->stateprovince ?? '') || $countyStr != trim($r->county ?? '') || $municipalityStr != trim($r->municipality ?? '') || $localityStr != $localityStrNew || $verbCoordStr != $verbCoordStrNew || $decLatStr != $r->decimallatitude || $decLngStr != $r->decimallongitude){ - $countryStr = trim($r->country); - $stateStr = trim($r->stateprovince); - $countyStr = trim($r->county); - $municipalityStr = trim($r->municipality); + $countryStr = trim($r->country ?? ''); + $stateStr = trim($r->stateprovince ?? ''); + $countyStr = trim($r->county ?? ''); + $municipalityStr = trim($r->municipality ?? ''); $localityStr = $localityStrNew; $verbCoordStr = $verbCoordStrNew; $decLatStr = $r->decimallatitude; @@ -253,7 +253,7 @@ public function getGeorefClones($locality, $country, $state, $county, $searchTyp } else{ //Exact search - $sqlWhere .= 'AND o.locality = "'.trim($this->cleanInStr($locality), " .").'" '; + $sqlWhere .= 'AND o.locality = "'.trim($this->cleanInStr($locality) ?? '', " .").'" '; } if($country){ $country = $this->cleanInStr($country); @@ -333,7 +333,7 @@ public function getCountryArr(){ $sql = 'SELECT DISTINCT country FROM omoccurrences WHERE decimalLatitude IS NULL AND collid IN('.$this->collStr.')'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $cStr = trim($r->country); + $cStr = trim($r->country ?? ''); if($cStr) $retArr[] = $cStr; } $rs->free(); @@ -346,7 +346,7 @@ public function getStateArr(){ $sql = 'SELECT DISTINCT stateprovince FROM omoccurrences WHERE decimalLatitude IS NULL AND collid IN('.$this->collStr.') '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $sStr = trim($r->stateprovince); + $sStr = trim($r->stateprovince ?? ''); if($sStr) $retArr[] = $sStr; } $rs->free(); @@ -360,7 +360,7 @@ public function getCountyArr(){ //echo $sql; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $cStr = trim($r->county); + $cStr = trim($r->county ?? ''); if($cStr) $retArr[] = $cStr; } $rs->free(); @@ -374,7 +374,7 @@ public function getMunicipalityArr(){ //echo $sql; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $mStr = trim($r->municipality); + $mStr = trim($r->municipality ?? ''); if($mStr) $retArr[] = $mStr; } $rs->free(); @@ -403,7 +403,7 @@ private function cleanInArr($arr){ return $retArr; } private function cleanInStr($str){ - $newStr = trim($str); + $newStr = trim($str ?? ''); $newStr = preg_replace('/\s\s+/', ' ',$newStr); $newStr = $this->conn->real_escape_string($newStr); return $newStr; diff --git a/classes/OccurrenceImport.php b/classes/OccurrenceImport.php new file mode 100644 index 0000000000..67e54a3c55 --- /dev/null +++ b/classes/OccurrenceImport.php @@ -0,0 +1,449 @@ +setVerboseMode(2); + set_time_limit(2000); + } + + function __destruct(){ + parent::__destruct(); + } + + public function loadData($postArr){ + global $LANG; + $status = false; + if($this->fileName && isset($postArr['tf'])){ + $this->fieldMap = array_flip($postArr['tf']); + if($this->setTargetPath()){ + if($this->getHeaderArr()){ // Advance past header row, set file handler, and define delimiter + $cnt = 1; + while($recordArr = $this->getRecordArr()){ + $identifierArr = array(); + if(isset($this->fieldMap['occurrenceid'])){ + if($recordArr[$this->fieldMap['occurrenceid']]) $identifierArr['occurrenceID'] = $recordArr[$this->fieldMap['occurrenceid']]; + } + if(isset($this->fieldMap['catalognumber'])){ + if($recordArr[$this->fieldMap['catalognumber']]) $identifierArr['catalogNumber'] = $recordArr[$this->fieldMap['catalognumber']]; + } + if(isset($this->fieldMap['othercatalognumbers'])){ + if($recordArr[$this->fieldMap['othercatalognumbers']]) $identifierArr['otherCatalogNumbers'] = $recordArr[$this->fieldMap['othercatalognumbers']]; + } + $this->logOrEcho('#'.$cnt.': '.$LANG['PROCESSING_CATNUM'].': '.implode(', ', $identifierArr)); + if($occidArr = $this->getOccurrencePK($identifierArr)){ + $status = $this->insertRecord($recordArr, $occidArr, $postArr); + } + $cnt++; + } + $occurMain = new OccurrenceMaintenance($this->conn); + $this->logOrEcho($LANG['VALUES_SET']); + $this->logOrEcho($LANG['UPDATING_STATS'].'...'); + if(!$occurMain->updateCollectionStatsBasic($this->collid)){ + $errorArr = $occurMain->getErrorArr(); + foreach($errorArr as $errorStr){ + $this->logOrEcho($errorStr,1); + } + } + } + $this->deleteImportFile(); + } + } + return $status; + } + + private function insertRecord($recordArr, $occidArr, $postArr){ + global $LANG; + $status = false; + if($this->importType == self::IMPORT_IMAGE_MAP){ + $importManager = new ImageShared($this->conn); + if(!isset($this->fieldMap['originalurl']) || !$recordArr[$this->fieldMap['originalurl']]){ + $this->errorMessage = 'large url (originalUrl) is null (required)'; + return false; + } + foreach($occidArr as $occid){ + $importManager->setOccid($occid); + //$importManager->setTid($tid); + $importManager->setImgLgUrl($recordArr[$this->fieldMap['originalurl']]); + if(isset($this->fieldMap['url']) && $recordArr[$this->fieldMap['url']]) $importManager->setImgWebUrl($recordArr[$this->fieldMap['url']]); + if(isset($this->fieldMap['thumbnailurl']) && $recordArr[$this->fieldMap['thumbnailurl']]) $importManager->setImgTnUrl($recordArr[$this->fieldMap['thumbnailurl']]); + if(isset($this->fieldMap['archiveurl']) && $recordArr[$this->fieldMap['archiveurl']]) $importManager->setArchiveUrl($recordArr[$this->fieldMap['archiveurl']]); + if(isset($this->fieldMap['referenceurl']) && $recordArr[$this->fieldMap['referenceurl']]) $importManager->setReferenceUrl($recordArr[$this->fieldMap['referenceurl']]); + if(isset($this->fieldMap['photographer']) && $recordArr[$this->fieldMap['photographer']]) $importManager->setPhotographer($recordArr[$this->fieldMap['photographer']]); + if(isset($this->fieldMap['photographeruid']) && $recordArr[$this->fieldMap['photographeruid']]) $importManager->setPhotographerUid($recordArr[$this->fieldMap['photographeruid']]); + if(isset($this->fieldMap['caption']) && $recordArr[$this->fieldMap['caption']]) $importManager->setCaption($recordArr[$this->fieldMap['caption']]); + if(isset($this->fieldMap['owner']) && $recordArr[$this->fieldMap['owner']]) $importManager->setOwner($recordArr[$this->fieldMap['owner']]); + if(isset($this->fieldMap['anatomy']) && $recordArr[$this->fieldMap['anatomy']]) $importManager->setAnatomy($recordArr[$this->fieldMap['anatomy']]); + if(isset($this->fieldMap['notes']) && $recordArr[$this->fieldMap['notes']]) $importManager->setNotes($recordArr[$this->fieldMap['notes']]); + if(isset($this->fieldMap['format']) && $recordArr[$this->fieldMap['format']]) $importManager->setFormat($recordArr[$this->fieldMap['format']]); + if(isset($this->fieldMap['sourceidentifier']) && $recordArr[$this->fieldMap['sourceidentifier']]) $importManager->setSourceIdentifier($recordArr[$this->fieldMap['sourceidentifier']]); + if(isset($this->fieldMap['hashfunction']) && $recordArr[$this->fieldMap['hashfunction']]) $importManager->setHashFunction($recordArr[$this->fieldMap['hashfunction']]); + if(isset($this->fieldMap['hashvalue']) && $recordArr[$this->fieldMap['hashvalue']]) $importManager->setHashValue($recordArr[$this->fieldMap['hashvalue']]); + if(isset($this->fieldMap['mediamd5']) && $recordArr[$this->fieldMap['mediamd5']]) $importManager->setMediaMD5($recordArr[$this->fieldMap['mediamd5']]); + if(isset($this->fieldMap['copyright']) && $recordArr[$this->fieldMap['copyright']]) $importManager->setCopyright($recordArr[$this->fieldMap['copyright']]); + if(isset($this->fieldMap['accessrights']) && $recordArr[$this->fieldMap['accessrights']]) $importManager->setAccessRights($recordArr[$this->fieldMap['accessrights']]); + if(isset($this->fieldMap['rights']) && $recordArr[$this->fieldMap['rights']]) $importManager->setRights($recordArr[$this->fieldMap['rights']]); + if(isset($this->fieldMap['sortoccurrence']) && $recordArr[$this->fieldMap['sortoccurrence']]) $importManager->setSortOccurrence($recordArr[$this->fieldMap['sortoccurrence']]); + if($importManager->insertImage()){ + $this->logOrEcho($LANG['IMAGE_LOADED'].': '.$occid.'', 1); + $status = true; + } + else{ + $this->logOrEcho('ERROR loading image: '.$importManager->getErrStr(), 1); + } + $importManager->reset(); + } + } + elseif($this->importType == self::IMPORT_DETERMINATIONS){ + $detManager = new OmDeterminations($this->conn); + foreach($occidArr as $occid){ + $detManager->setOccid($occid); + $fieldArr = array_keys($detManager->getSchemaMap()); + $detArr = array(); + foreach($fieldArr as $field){ + $fieldLower = strtolower($field); + if(isset($this->fieldMap[$fieldLower]) && !empty($recordArr[$this->fieldMap[$fieldLower]])) $detArr[$field] = $recordArr[$this->fieldMap[$fieldLower]]; + } + if (empty($detArr['sciname'])) { + $this->logOrEcho ('ERROR loading determination: Scientific name is empty.', 1); + continue; + } + if (empty($detArr['identifiedBy'])) { + $paramArr['identifiedBy'] = 'unknown'; + } + if (empty($detArr['dateIdentified'])) { + $paramArr['dateIdentified'] = 's.d.'; + } + if($detManager->insertDetermination($detArr)){ + $this->logOrEcho($LANG['DETERMINATION_ADDED'].': '.$occid.'', 1); + $status = true; + } + else{ + $this->logOrEcho('ERROR loading determination: '.$detManager->getErrorMessage(), 1); + } + } + } + elseif($this->importType == self::IMPORT_ASSOCIATIONS){ + $importManager = new OmAssociations($this->conn); + foreach($occidArr as $occid){ + $importManager->setOccid($occid); + $fieldArr = array_keys($importManager->getSchemaMap()); + $fieldArr[] = 'object-occurrenceID'; + $fieldArr[] = 'object-catalogNumber'; + $assocArr = array(); + foreach($fieldArr as $field){ + $fieldLower = strtolower($field); + if(isset($this->fieldMap[$fieldLower])) $assocArr[$field] = $recordArr[$this->fieldMap[$fieldLower]]; + } + if($assocArr){ + if(!empty($postArr['associationType']) && !empty($postArr['relationship'])){ + $assocArr['associationType'] = $postArr['associationType']; + $assocArr['relationship'] = $postArr['relationship']; + if(isset($postArr['subType']) && empty($assocArr['subType'])) $assocArr['subType'] = $postArr['subType']; + if(!empty($postArr['replace'])){ + $existingAssociation = null; + if(!empty($assocArr['instanceID'])){ + $existingAssociation = $importManager->getAssociationArr(array('associationType' => $assocArr['associationType'], 'recordID' => $assocArr['instanceID'])); + if($existingAssociation){ + //instanceID is recordID, thus don't add to instanceID + unset($assocArr['instanceID']); + } + if(!$existingAssociation){ + $existingAssociation = $importManager->getAssociationArr(array('associationType' => $assocArr['associationType'], 'instanceID' => $assocArr['instanceID'])); + } + } + if(!$existingAssociation && !empty($assocArr['resourceUrl'])){ + $existingAssociation = $importManager->getAssociationArr(array('associationType' => $assocArr['associationType'], 'resourceUrl' => $assocArr['resourceUrl'])); + } + if(!$existingAssociation && !empty($assocArr['objectID'])){ + $existingAssociation = $importManager->getAssociationArr(array('associationType' => $assocArr['associationType'], 'objectID' => $assocArr['objectID'])); + } + if($existingAssociation){ + if($assocID = key($existingAssociation)){ + $importManager->setAssocID($assocID); + if($assocArr['relationship'] == 'DELETE'){ + if($importManager->deleteAssociation()){ + $this->logOrEcho($LANG['ASSOC_DELETED'].': '.$occid.'', 1); + } + else{ + $this->logOrEcho($LANG['ERROR_DELETING'] . ': '.$importManager->getErrorMessage(), 1); + } + } + else{ + if($importManager->updateAssociation($assocArr)){ + $this->logOrEcho($LANG['ASSOC_UPDATED'].': '.$occid.'', 1); + $status = true; + } + else{ + $this->logOrEcho($LANG['ERROR_UPDATING'] . ': '.$importManager->getErrorMessage(), 1); + } + } + } + } + else{ + $this->logOrEcho($LANG['TARGET_NOT_FOUND'], 1); + } + } + elseif($importManager->insertAssociation($assocArr)){ + $this->logOrEcho($LANG['ASSOC_ADDED'].': '.$occid.'', 1); + $status = true; + } + else{ + $this->logOrEcho($LANG['ERROR_ADDING'] . ': '.$importManager->getErrorMessage(), 1); + } + } + } + } + } + elseif($this->importType == self::IMPORT_MATERIAL_SAMPLE){ + $importManager = new OmMaterialSample($this->conn); + foreach($occidArr as $occid){ + $importManager->setOccid($occid); + $fieldArr = array_keys($importManager->getSchemaMap()); + $msArr = array(); + foreach($fieldArr as $field){ + $fieldLower = strtolower($field); + if(isset($this->fieldMap[$fieldLower]) && !empty($recordArr[$this->fieldMap[$fieldLower]])) $msArr[$field] = $recordArr[$this->fieldMap[$fieldLower]]; + } + if(isset($msArr['ms_catalogNumber']) && $msArr['ms_catalogNumber']){ + $msArr['catalogNumber'] = $msArr['ms_catalogNumber']; + unset($msArr['ms_catalogNumber']); + } + if($importManager->insertMaterialSample($msArr)){ + $this->logOrEcho($LANG['MAT_SAMPLE_ADDED'].': '.$occid.'', 1); + $status = true; + } + else{ + $this->logOrEcho('ERROR loading Material Sample: '.$importManager->getErrorMessage(), 1); + } + } + } + return $status; + } + + //Identifier and occid functions + protected function getOccurrencePK($identifierArr){ + $retArr = array(); + $sql = 'SELECT DISTINCT o.occid FROM omoccurrences o '; + $sqlConditionArr = array(); + if(isset($identifierArr['occurrenceID'])){ + $occurrenceID = $this->cleanInStr($identifierArr['occurrenceID']); + $sqlConditionArr[] = '(o.occurrenceID = "'.$occurrenceID.'" OR o.recordID = "'.$occurrenceID.'")'; + } + if(isset($identifierArr['catalogNumber'])){ + $sqlConditionArr[] = '(o.catalogNumber = "'.$this->cleanInStr($identifierArr['catalogNumber']).'")'; + } + if(isset($identifierArr['otherCatalogNumbers'])){ + $otherCatalogNumbers = $this->cleanInStr($identifierArr['otherCatalogNumbers']); + $sqlConditionArr[] = '(o.othercatalognumbers = "'.$otherCatalogNumbers.'" OR i.identifierValue = "'.$otherCatalogNumbers.'")'; + $sql .= 'LEFT JOIN omoccuridentifiers i ON o.occid = i.occid '; + } + if($sqlConditionArr){ + $sql .= 'WHERE (o.collid = '.$this->collid.') AND ('.implode(' OR ', $sqlConditionArr).') '; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $retArr[] = $r->occid; + } + $rs->free(); + } + if(!$retArr){ + if($this->createNewRecord){ + $newOccid = $this->insertNewOccurrence($identifierArr); + if($newOccid) $retArr[] = $newOccid; + } + else $this->logOrEcho('SKIPPED: Unable to find record matching identifier: '.implode(', ', $identifierArr), 1); + } + + return $retArr; + } + + protected function insertNewOccurrence($identifierArr){ + $newOccid = 0; + if(isset($identifierArr['occurrenceID'])){ + $this->logOrEcho('SKIPPED: Unable to create new record based on occurrenceID', 1); + return false; + } + $catNum = null; + if(isset($identifierArr['catalogNumber'])) $catNum = $identifierArr['catalogNumber']; + $sql = 'INSERT INTO omoccurrences(collid, catalogNumber, recordID, processingstatus, recordEnteredBy, dateentered) VALUES(?, ?, ?, "unprocessed", ?, now())'; + if($stmt = $this->conn->prepare($sql)){ + $recordID = UuidFactory::getUuidV4(); + $stmt->bind_param('isss', $this->collid, $catNum, $recordID, $GLOBALS['USERNAME']); + $stmt->execute(); + $newOccid = $stmt->insert_id; + $stmt->close(); + } + if($newOccid){ + if(isset($identifierArr['otherCatalogNumbers'])) $this->insertAdditionalIdentifier($newOccid, $identifierArr['otherCatalogNumbers']); + $this->logOrEcho('Unable to find record with matching '.implode(',', $identifierArr).'; new occurrence record created',1); + } + return $newOccid; + } + + protected function insertAdditionalIdentifier($occid, $identifierValue){ + $status = false; + $sql = 'INSERT INTO omoccuridentifiers(occid, identifierValue, modifiedUid) VALUES(?, ?, ?) '; + if($stmt = $this->conn->prepare($sql)) { + $stmt->bind_param('iss', $occid, $identifierValue, $GLOBALS['SYMB_UID']); + $stmt->execute(); + if($stmt->affected_rows || !$stmt->error) $status = true; + else $this->errorMessage = 'ERROR inserting additional identifier: '.$stmt->error; + $stmt->close(); + } + else $this->errorMessage = 'ERROR preparing statement for inserting additional identifier: '.$this->conn->error; + return $status; + } + + //Mapping functions + public function setTargetFieldArr($associationType = null){ + $this->targetFieldMap['catalognumber'] = 'subject identifier: catalogNumber'; + $this->targetFieldMap['othercatalognumbers'] = 'subject identifier: otherCatalogNumbers'; + $this->targetFieldMap['occurrenceid'] = 'subject identifier: occurrenceID'; + $this->targetFieldMap[''] = '------------------------------------'; + $fieldArr = array(); + if($this->importType == self::IMPORT_IMAGE_MAP){ + $fieldArr = array('url', 'originalUrl', 'thumbnailUrl', 'archiveUrl', 'referenceUrl', 'photographer', 'photographerUid', 'caption', 'owner', 'anatomy', 'notes', + 'format', 'sourceIdentifier', 'hashFunction', 'hashValue', 'mediaMD5', 'copyright', 'rights', 'accessRights', 'sortOccurrence'); + } + elseif($this->importType == self::IMPORT_ASSOCIATIONS){ + $fieldArr = array('relationshipID', 'objectID', 'basisOfRecord', 'establishedDate', 'notes', 'accordingTo'); + if($associationType == 'resource'){ + $fieldArr[] = 'resourceUrl'; + } + elseif($associationType == 'internalOccurrence'){ + $this->targetFieldMap['object-catalognumber'] = 'object identifier: catalogNumber'; + $this->targetFieldMap['object-occurrenceid'] = 'object identifier: occurrenceID'; + $this->targetFieldMap['occidassociate'] = 'object identifier: occid'; + $this->targetFieldMap['0'] = '------------------------------------'; + } + elseif($associationType == 'externalOccurrence'){ + $fieldArr[] = 'verbatimSciname'; + $fieldArr[] = 'resourceUrl'; + } + elseif($associationType == 'observational'){ + $fieldArr[] = 'verbatimSciname'; + } + } + elseif($this->importType == self::IMPORT_DETERMINATIONS){ + $detManager = new OmDeterminations($this->conn); + $schemaMap = $detManager->getSchemaMap(); + unset($schemaMap['appliedStatus']); + unset($schemaMap['detType']); + $fieldArr = array_keys($schemaMap); + } + elseif($this->importType == self::IMPORT_MATERIAL_SAMPLE){ + $fieldArr = array('sampleType', 'ms_catalogNumber', 'guid', 'sampleCondition', 'disposition', 'preservationType', 'preparationDetails', 'preparationDate', + 'preparedByUid', 'individualCount', 'sampleSize', 'storageLocation', 'remarks'); + } + sort($fieldArr); + foreach($fieldArr as $field){ + $this->targetFieldMap[strtolower($field)] = $field; + } + } + + private function defineTranslationMap(){ + if($this->translationMap === null){ + if($this->importType == self::IMPORT_IMAGE_MAP){ + $this->translationMap = array('web' => 'url', 'webviewoptional' => 'url', 'thumbnail' => 'thumbnailurl','thumbnailoptional' => 'thumbnailurl', + 'largejpg' => 'originalurl', 'large' => 'originalurl', 'imageurl' => 'url', 'accessuri' => 'url'); + } + elseif($this->importType == self::IMPORT_ASSOCIATIONS){ + $this->translationMap = array(); + } + elseif($this->importType == self::IMPORT_DETERMINATIONS){ + $this->translationMap = array('identificationid' => 'sourceIdentifier'); + } + elseif($this->importType == self::IMPORT_MATERIAL_SAMPLE){ + $this->translationMap = array(); + } + } + } + + //Data set functions + private function setCollMetaArr(){ + $sql = 'SELECT institutionCode, collectionCode, collectionName, dynamicProperties FROM omcollections WHERE collid = '.$this->collid; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $this->collMetaArr['instCode'] = $r->institutionCode; + $this->collMetaArr['collCode'] = $r->collectionCode; + $this->collMetaArr['collName'] = $r->collectionName; + if($r->dynamicProperties){ + if(strpos($r->dynamicProperties , '"matSample":{"status":1')) $this->collMetaArr['materialSample'] = 1; + } + } + $rs->free(); + } + + public function materialSampleModuleActive(){ + if(!$this->collMetaArr) $this->setCollMetaArr(); + if(isset($this->collMetaArr['matsample'])) return true; + return false; + } + + public function getControlledVocabulary($tableName, $fieldName, $filterVariable = ''){ + $retArr = array(); + $sql = 'SELECT t.term, t.termDisplay + FROM ctcontrolvocab v INNER JOIN ctcontrolvocabterm t ON v.cvID = t.cvID + WHERE tableName = ? AND fieldName = ? AND filterVariable = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('sss', $tableName, $fieldName, $filterVariable); + $stmt->execute(); + $term = ''; $termDisplay = ''; + $stmt->bind_result($term, $termDisplay); + while ($stmt->fetch()) { + if(!$termDisplay) $termDisplay = $term; + $retArr[$term] = $termDisplay; + } + $stmt->close(); + } + asort($retArr); + return $retArr; + } + + //Basic setters and getters + public function setCollid($id){ + if(is_numeric($id)) $this->collid = $id; + } + + public function getCollid(){ + return $this->collid; + } + + public function getCollMeta($field){ + $fieldValue = ''; + if(!$this->collMetaArr) $this->setCollMetaArr(); + if(isset($this->collMetaArr[$field])) return $this->collMetaArr[$field]; + return $fieldValue; + } + + public function setCreateNewRecord($b){ + if($b) $this->createNewRecord = true; + else $this->createNewRecord = false; + } + + public function setImportType($importType){ + if(is_numeric($importType)) $this->importType = $importType; + $this->defineTranslationMap(); + } +} +?> \ No newline at end of file diff --git a/classes/OccurrenceIndividual.php b/classes/OccurrenceIndividual.php index 0de7a1b621..a6fffe9948 100644 --- a/classes/OccurrenceIndividual.php +++ b/classes/OccurrenceIndividual.php @@ -14,8 +14,8 @@ class OccurrenceIndividual extends Manager{ private $relationshipArr; private $activeModules = array(); - public function __construct($type='readonly') { - parent::__construct(null,$type); + public function __construct($type = 'readonly') { + parent::__construct(null, $type); } public function __destruct(){ @@ -24,76 +24,83 @@ public function __destruct(){ private function loadMetadata(){ if($this->collid){ - //$sql = 'SELECT institutioncode, collectioncode, collectionname, colltype, homepage, individualurl, contact, email, icon, publicedits, rights, rightsholder, accessrights, guidtarget FROM omcollections WHERE collid = '.$this->collid; - $sql = 'SELECT c.*, s.uploadDate FROM omcollections c INNER JOIN omcollectionstats s ON c.collid = s.collid WHERE c.collid = '.$this->collid; - if($rs = $this->conn->query($sql)){ - $this->metadataArr = array_change_key_case($rs->fetch_assoc()); - if(isset($this->metadataArr['contactjson'])){ - //Test to see if contact is a JSON object or a simple string - if($contactArr = json_decode($this->metadataArr['contactjson'],true)){ - $contactStr = ''; - foreach($contactArr as $cArr){ - if(!$contactStr || isset($cArr['centralContact'])){ - if(isset($cArr['firstName']) && $cArr['firstName']) $contactStr = $cArr['firstName'].' '; - $contactStr .= $cArr['lastName']; - if(isset($cArr['role']) && $cArr['role']) $contactStr .= ', '.$cArr['role']; - $this->metadataArr['contact'] = $contactStr; - if(isset($cArr['email']) && $cArr['email']) $this->metadataArr['email'] = $cArr['email']; - if(isset($cArr['centralContact'])) break; + //$sql = 'SELECT institutioncode, collectioncode, collectionname, colltype, homepage, individualurl, contact, email, icon, publicedits, rights, rightsholder, accessrights, guidtarget FROM omcollections WHERE collid = ?'; + $sql = 'SELECT c.*, s.uploadDate FROM omcollections c INNER JOIN omcollectionstats s ON c.collid = s.collid WHERE c.collid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->collid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + $this->metadataArr = array_change_key_case($rs->fetch_assoc()); + if(isset($this->metadataArr['contactjson'])){ + //Test to see if contact is a JSON object or a simple string + if($contactArr = json_decode($this->metadataArr['contactjson'],true)){ + $contactStr = ''; + foreach($contactArr as $cArr){ + if(!$contactStr || isset($cArr['centralContact'])){ + if(isset($cArr['firstName']) && $cArr['firstName']) $contactStr = $cArr['firstName'].' '; + $contactStr .= $cArr['lastName']; + if(isset($cArr['role']) && $cArr['role']) $contactStr .= ', ' . $cArr['role']; + $this->metadataArr['contact'] = $contactStr; + if(isset($cArr['email']) && $cArr['email']) $this->metadataArr['email'] = $cArr['email']; + if(isset($cArr['centralContact'])) break; + } } } } - } - if($this->metadataArr['dynamicproperties']){ - if($propArr = json_decode($this->metadataArr['dynamicproperties'], true)) { - if(isset($propArr['editorProps']['modules-panel'])) { - foreach($propArr['editorProps']['modules-panel'] as $k => $modArr) { - if(isset($modArr['paleo']['status'])) $this->activeModules['paleo'] = true; - elseif (isset($modArr['matSample']['status'])) $this->activeModules['matSample'] = true; + if($this->metadataArr['dynamicproperties']){ + if($propArr = json_decode($this->metadataArr['dynamicproperties'], true)) { + if(isset($propArr['editorProps']['modules-panel'])) { + foreach($propArr['editorProps']['modules-panel'] as $k => $modArr) { + if(isset($modArr['paleo']['status'])) $this->activeModules['paleo'] = true; + elseif (isset($modArr['matSample']['status'])) $this->activeModules['matSample'] = true; + } } } } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to set collection metadata; '.$this->conn->error,E_USER_ERROR); + else{ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } } } public function getMetadata(){ - return $this->metadataArr; + return $this->cleanOutArray($this->metadataArr); } public function setGuid($guid){ - $guid = $this->cleanInStr($guid); if(!$this->occid){ //Check occurrence recordID - $sql = 'SELECT occid FROM omoccurrences WHERE (occurrenceid = "'.$guid.'") OR (recordID = "'.$guid.'") '; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $this->occid = $r->occid; + $sql = 'SELECT occid FROM omoccurrences WHERE (occurrenceid = ?) OR (recordID = ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ss', $guid, $guid); + $stmt->execute(); + $stmt->bind_result($this->occid); + $stmt->close(); } - $rs->free(); } if(!$this->occid){ //Check image recordID - $sql = 'SELECT occid FROM images WHERE recordID = "'.$guid.'"'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $this->occid = $r->occid; + $sql = 'SELECT occid FROM images WHERE recordID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('s', $guid); + $stmt->execute(); + $stmt->bind_result($this->occid); + $stmt->close(); } - $rs->free(); } if(!$this->occid){ //Check identification recordID - $sql = 'SELECT occid FROM omoccurdeterminations WHERE recordID = "'.$guid.'" '; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $this->occid = $r->occid; + $sql = 'SELECT occid FROM omoccurdeterminations WHERE recordID = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('s', $guid); + $stmt->execute(); + $stmt->bind_result($this->occid); + $stmt->close(); } - $rs->free(); } return $this->occid; } @@ -106,10 +113,11 @@ public function getOccData($fieldKey = ""){ return false; } } - return $this->occArr; + return $this->cleanOutArray($this->occArr); } public function setOccurData(){ + $status = false; /* $sql = 'SELECT o.occid, o.collid, o.institutioncode, o.collectioncode, o.occurrenceid, o.catalognumber, o.occurrenceremarks, o.tidinterpreted, o.family, o.sciname, @@ -123,57 +131,65 @@ public function setOccurData(){ o.othercatalognumbers, o.disposition, o.informationwithheld, o.modified, o.observeruid, o.recordenteredby, o.dateentered, o.recordid, o.datelastmodified FROM omoccurrences o '; */ - $sql = 'SELECT o.*, MAKEDATE(YEAR(o.eventDate),o.enddayofyear) AS eventdateend FROM omoccurrences o '; - if($this->occid) $sql .= 'WHERE (o.occid = '.$this->occid.')'; - elseif($this->collid && $this->dbpk) $sql .= 'WHERE (o.collid = '.$this->collid.') AND (o.dbpk = "'.$this->dbpk.'")'; + $sql = 'SELECT o.*, MAKEDATE(YEAR(o.eventDate), o.enddayofyear) AS eventdateend FROM omoccurrences o '; + if($this->occid) $sql .= 'WHERE (o.occid = ?)'; + elseif($this->collid && $this->dbpk) $sql .= 'WHERE (o.collid = ?) AND (o.dbpk = ?)'; else{ $this->errorMessage = 'NULL identifier'; return false; } - if($rs = $this->conn->query($sql)){ - if($occArr = $rs->fetch_assoc()){ - $rs->free(); - $this->occArr = array_change_key_case($occArr); - if(!$this->occid) $this->occid = $this->occArr['occid']; - if(!$this->collid) $this->collid = $this->occArr['collid']; - $this->loadMetadata(); - if($this->occArr['institutioncode']){ - if(!$this->metadataArr['institutioncode']) $this->metadataArr['institutioncode'] = $this->occArr['institutioncode']; - elseif($this->metadataArr['institutioncode'] != $this->occArr['institutioncode']) $this->metadataArr['institutioncode'] .= '-'.$this->occArr['institutioncode']; - } - if($this->occArr['collectioncode']){ - if(!$this->metadataArr['collectioncode']) $this->metadataArr['collectioncode'] = $this->occArr['institutioncode']; - elseif($this->metadataArr['collectioncode'] != $this->occArr['collectioncode']) $this->metadataArr['collectioncode'] .= '-'.$this->occArr['institutioncode']; - } - if(!$this->occArr['occurrenceid']){ - //Set occurrence GUID based on GUID target, but only if occurrenceID field isn't already populated - if($this->metadataArr['guidtarget'] == 'catalogNumber'){ - $this->occArr['occurrenceid'] = $this->occArr['catalognumber']; + if($stmt = $this->conn->prepare($sql)){ + if($this->occid){ + $stmt->bind_param('i', $this->occid); + } + elseif($this->collid && $this->dbpk){ + $stmt->bind_param('is', $this->collid, $this->dbpk); + } + $stmt->execute(); + if($rs = $stmt->get_result()){ + if($occArr = $rs->fetch_assoc()){ + $rs->free(); + $this->occArr = array_change_key_case($occArr); + if(!$this->occid) $this->occid = $this->occArr['occid']; + if(!$this->collid) $this->collid = $this->occArr['collid']; + $this->loadMetadata(); + if($this->occArr['institutioncode']){ + if($this->metadataArr['institutioncode'] != $this->occArr['institutioncode']) $this->metadataArr['institutioncode'] = $this->occArr['institutioncode']; } - elseif($this->metadataArr['guidtarget'] == 'symbiotaUUID'){ - if(isset($this->occArr['recordid'])) $this->occArr['occurrenceid'] = $this->occArr['recordid']; + if($this->occArr['collectioncode']){ + if($this->metadataArr['collectioncode'] != $this->occArr['collectioncode']) $this->metadataArr['collectioncode'] = $this->occArr['collectioncode']; } + if(!$this->occArr['occurrenceid']){ + //Set occurrence GUID based on GUID target, but only if occurrenceID field isn't already populated + if($this->metadataArr['guidtarget'] == 'catalogNumber'){ + $this->occArr['occurrenceid'] = $this->occArr['catalognumber']; + } + elseif($this->metadataArr['guidtarget'] == 'symbiotaUUID'){ + if(isset($this->occArr['recordid'])) $this->occArr['occurrenceid'] = $this->occArr['recordid']; + } + } + $this->setAdditionalIdentifiers(); + $this->setPaleo(); + $this->setLoan(); + $this->setOccurrenceRelationships(); + $this->setReferences(); + $this->setMaterialSamples(); + $this->setSource(); } - $this->setAdditionalIdentifiers(); - $this->setPaleo(); - $this->setLoan(); - $this->setOccurrenceRelationships(); - $this->setReferences(); - $this->setMaterialSamples(); - $this->setSource(); - } - //Set access statistics - $accessType = 'view'; - if(in_array($this->displayFormat,array('json','xml','rdf','turtle'))) $accessType = 'api'.strtoupper($this->displayFormat); - $statsManager = new OccurrenceAccessStats(); - $statsManager->recordAccessEvent($this->occid, $accessType); - return true; - } - else{ - $this->errorMessage = 'SQL error: '.$this->conn->error; - return false; + //Set access statistics + $accessType = 'view'; + if(in_array($this->displayFormat, array('json', 'xml', 'rdf', 'turtle'))) $accessType = 'api' . strtoupper($this->displayFormat); + $statsManager = new OccurrenceAccessStats(); + $statsManager->recordAccessEvent($this->occid, $accessType); + $status = true; + } + else{ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } + return $status; } public function applyProtections($isSecuredReader){ @@ -194,17 +210,17 @@ public function applyProtections($isSecuredReader){ $protectLocality = true; $this->occArr['localsecure'] = 1; $redactArr = array('recordnumber','eventdate','verbatimeventdate','locality','locationid','decimallatitude','decimallongitude','verbatimcoordinates', - 'locationremarks', 'georeferenceremarks', 'geodeticdatum', 'coordinateuncertaintyinmeters', 'minimumelevationinmeters', 'maximumelevationinmeters', - 'verbatimelevation', 'habitat', 'associatedtaxa'); + 'locationremarks', 'georeferenceremarks', 'geodeticdatum', 'coordinateuncertaintyinmeters', 'minimumelevationinmeters', 'maximumelevationinmeters', + 'verbatimelevation', 'habitat', 'associatedtaxa'); $infoWithheld = ''; foreach($redactArr as $term){ if($this->occArr[$term]){ $this->occArr[$term] = ''; - $infoWithheld .= ', '.$term; + $infoWithheld .= ', ' . $term; } } - if($this->occArr['informationwithheld']) $infoWithheld = $this->occArr['informationwithheld'].'; '.$infoWithheld; - $this->occArr['informationwithheld'] = trim($infoWithheld,', '); + if($this->occArr['informationwithheld']) $infoWithheld = $this->occArr['informationwithheld'] . '; ' . $infoWithheld; + $this->occArr['informationwithheld'] = trim($infoWithheld, ', '); } if(!$protectTaxon) $this->setDeterminations(); if(!$protectLocality && !$protectTaxon) $this->setImages(); @@ -215,162 +231,197 @@ public function applyProtections($isSecuredReader){ private function setDeterminations(){ $sql = 'SELECT detid, dateidentified, identifiedby, sciname, scientificnameauthorship, identificationqualifier, identificationreferences, identificationremarks, iscurrent FROM omoccurdeterminations - WHERE (occid = '.$this->occid.') AND appliedstatus = 1 + WHERE (occid = ?) AND appliedstatus = 1 ORDER BY sortsequence'; - $rs = $this->conn->query($sql); - if($rs){ - while($row = $rs->fetch_object()){ - $detId = $row->detid; - $this->occArr['dets'][$detId]['date'] = $row->dateidentified; - $this->occArr['dets'][$detId]['identifiedby'] = $row->identifiedby; - $this->occArr['dets'][$detId]['sciname'] = $row->sciname; - $this->occArr['dets'][$detId]['author'] = $row->scientificnameauthorship; - $this->occArr['dets'][$detId]['qualifier'] = $row->identificationqualifier; - $this->occArr['dets'][$detId]['ref'] = $row->identificationreferences; - $this->occArr['dets'][$detId]['notes'] = $row->identificationremarks; - $this->occArr['dets'][$detId]['iscurrent'] = $row->iscurrent; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($row = $rs->fetch_object()){ + $detId = $row->detid; + $this->occArr['dets'][$detId]['date'] = $row->dateidentified; + $this->occArr['dets'][$detId]['identifiedby'] = $row->identifiedby; + $this->occArr['dets'][$detId]['sciname'] = $row->sciname; + $this->occArr['dets'][$detId]['author'] = $row->scientificnameauthorship; + $this->occArr['dets'][$detId]['qualifier'] = $row->identificationqualifier; + $this->occArr['dets'][$detId]['ref'] = $row->identificationreferences; + $this->occArr['dets'][$detId]['notes'] = $row->identificationremarks; + $this->occArr['dets'][$detId]['iscurrent'] = $row->iscurrent; + } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to setDeterminations; '.$this->conn->error,E_USER_NOTICE); + else{ + $this->warningArr[] = $stmt->error; + } + $stmt->close(); } } private function setImages(){ - global $imageDomain; - $sql = 'SELECT i.imgid, i.url, i.thumbnailurl, i.originalurl, i.sourceurl, i.notes, i.caption, CONCAT_WS(" ",u.firstname,u.lastname) as innerPhotographer, i.photographer '. - 'FROM images i LEFT JOIN users u ON i.photographeruid = u.uid '. - 'WHERE (i.occid = '.$this->occid.') ORDER BY i.sortoccurrence,i.sortsequence'; - $rs = $this->conn->query($sql); - if($rs){ - while($row = $rs->fetch_object()){ - $imgId = $row->imgid; - $url = $row->url; - $tnUrl = $row->thumbnailurl; - $lgUrl = $row->originalurl; - if($imageDomain){ - if(substr($url,0,1)=="/") $url = $imageDomain.$url; - if($lgUrl && substr($lgUrl,0,1)=="/") $lgUrl = $imageDomain.$lgUrl; - if($tnUrl && substr($tnUrl,0,1)=="/") $tnUrl = $imageDomain.$tnUrl; + global $IMAGE_DOMAIN; + $sql = 'SELECT i.imgid, i.url, i.thumbnailurl, i.originalurl, i.sourceurl, i.notes, i.caption, + CONCAT_WS(" ",u.firstname,u.lastname) as innerPhotographer, i.photographer, i.rights, i.accessRights, i.copyright + FROM images i LEFT JOIN users u ON i.photographeruid = u.uid + WHERE (i.occid = ?) ORDER BY i.sortoccurrence,i.sortsequence'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($row = $rs->fetch_object()){ + $imgId = $row->imgid; + $url = $row->url; + $tnUrl = $row->thumbnailurl; + $lgUrl = $row->originalurl; + if($IMAGE_DOMAIN){ + if(substr($url,0,1)=='/') $url = $IMAGE_DOMAIN . $url; + if($lgUrl && substr($lgUrl, 0, 1) == '/') $lgUrl = $IMAGE_DOMAIN . $lgUrl; + if($tnUrl && substr($tnUrl, 0, 1) == '/') $tnUrl = $IMAGE_DOMAIN . $tnUrl; + } + if((!$url || $url == 'empty') && $lgUrl) $url = $lgUrl; + if(!$tnUrl && $url) $tnUrl = $url; + $this->occArr['imgs'][$imgId]['url'] = $url; + $this->occArr['imgs'][$imgId]['tnurl'] = $tnUrl; + $this->occArr['imgs'][$imgId]['lgurl'] = $lgUrl; + $this->occArr['imgs'][$imgId]['sourceurl'] = $row->sourceurl; + $this->occArr['imgs'][$imgId]['caption'] = $row->caption; + $this->occArr['imgs'][$imgId]['photographer'] = $row->photographer; + $this->occArr['imgs'][$imgId]['rights'] = $row->rights; + $this->occArr['imgs'][$imgId]['accessrights'] = $row->accessRights; + $this->occArr['imgs'][$imgId]['copyright'] = $row->copyright; + if($row->innerPhotographer) $this->occArr['imgs'][$imgId]['photographer'] = $row->innerPhotographer; } - if((!$url || $url == 'empty') && $lgUrl) $url = $lgUrl; - if(!$tnUrl && $url) $tnUrl = $url; - $this->occArr['imgs'][$imgId]['url'] = $url; - $this->occArr['imgs'][$imgId]['tnurl'] = $tnUrl; - $this->occArr['imgs'][$imgId]['lgurl'] = $lgUrl; - $this->occArr['imgs'][$imgId]['sourceurl'] = $row->sourceurl; - $this->occArr['imgs'][$imgId]['caption'] = $row->caption; - $this->occArr['imgs'][$imgId]['photographer'] = $row->photographer; - if($row->innerPhotographer) $this->occArr['imgs'][$imgId]['photographer'] = $row->innerPhotographer; + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to set images; '.$this->conn->error,E_USER_WARNING); + else{ + $this->warningArr[] = $stmt->error; + } + $stmt->close(); } } private function setAdditionalIdentifiers(){ $retArr = array(); - $sql = 'SELECT idomoccuridentifiers, occid, identifiervalue, identifiername FROM omoccuridentifiers WHERE (occid = '.$this->occid.') ORDER BY sortBy'; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $identifierTag = $r->identifiername; - if(!$identifierTag) $identifierTag = 0; - $retArr[$identifierTag][] = $r->identifiervalue; + $sql = 'SELECT idomoccuridentifiers, occid, identifiervalue, identifiername FROM omoccuridentifiers WHERE (occid = ?) ORDER BY sortBy'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $identifierTag = $r->identifiername; + if(!$identifierTag) $identifierTag = 0; + $retArr[$r->idomoccuridentifiers]['name'] = $identifierTag; + $retArr[$r->idomoccuridentifiers]['value'] = $r->identifiervalue; + } + $rs->free(); } - $rs->free(); } - if($retArr) $this->occArr['othercatalognumbers'] = json_encode($retArr); + if($retArr) $this->occArr['othercatalognumbers'] = $retArr; + elseif($this->occArr['othercatalognumbers']){ + $this->occArr['othercatalognumbers'] = array(array('value' => $this->occArr['othercatalognumbers'])); + } } private function setPaleo(){ if(isset($this->activeModules['paleo']) && $this->activeModules['paleo']){ - $sql = 'SELECT paleoid, eon, era, period, epoch, earlyinterval, lateinterval, absoluteage, storageage, stage, localstage, biota, '. - 'biostratigraphy, lithogroup, formation, taxonenvironment, member, bed, lithology, stratremarks, element, slideproperties, geologicalcontextid '. - 'FROM omoccurpaleo WHERE occid = '.$this->occid; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_assoc()){ - $this->occArr = array_merge($this->occArr,$r); + $sql = 'SELECT paleoid, eon, era, period, epoch, earlyinterval, lateinterval, absoluteage, storageage, stage, localstage, biota, + biostratigraphy, lithogroup, formation, taxonenvironment, member, bed, lithology, stratremarks, element, slideproperties, geologicalcontextid + FROM omoccurpaleo WHERE occid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_assoc()){ + $this->occArr = array_merge($this->occArr, $r); + } + $rs->free(); } - $rs->free(); + $stmt->close(); } } } private function setLoan(){ - $sql = 'SELECT l.loanIdentifierOwn, i.institutioncode '. - 'FROM omoccurloanslink llink INNER JOIN omoccurloans l ON llink.loanid = l.loanid '. - 'INNER JOIN institutions i ON l.iidBorrower = i.iid '. - 'WHERE (llink.occid = '.$this->occid.') AND (l.dateclosed IS NULL) AND (llink.returndate IS NULL)'; - $rs = $this->conn->query($sql); - if($rs){ - while($row = $rs->fetch_object()){ - $this->occArr['loan']['identifier'] = $row->loanIdentifierOwn; - $this->occArr['loan']['code'] = $row->institutioncode; + $sql = 'SELECT l.loanIdentifierOwn, i.institutioncode + FROM omoccurloanslink llink INNER JOIN omoccurloans l ON llink.loanid = l.loanid + INNER JOIN institutions i ON l.iidBorrower = i.iid + WHERE (llink.occid = ?) AND (l.dateclosed IS NULL) AND (llink.returndate IS NULL)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($row = $rs->fetch_object()){ + $this->occArr['loan']['identifier'] = $row->loanIdentifierOwn; + $this->occArr['loan']['code'] = $row->institutioncode; + } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to load loan info; '.$this->conn->error,E_USER_WARNING); + else{ + $this->warningArr[] = $stmt->error; + } + $stmt->close(); } } private function setExsiccati(){ - $sql = 'SELECT t.title, t.editor, n.omenid, n.exsnumber '. - 'FROM omexsiccatititles t INNER JOIN omexsiccatinumbers n ON t.ometid = n.ometid '. - 'INNER JOIN omexsiccatiocclink l ON n.omenid = l.omenid '. - 'WHERE (l.occid = '.$this->occid.')'; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $this->occArr['exs']['title'] = $r->title; - $this->occArr['exs']['omenid'] = $r->omenid; - $this->occArr['exs']['exsnumber'] = $r->exsnumber; + $sql = 'SELECT t.title, t.editor, n.omenid, n.exsnumber + FROM omexsiccatititles t INNER JOIN omexsiccatinumbers n ON t.ometid = n.ometid + INNER JOIN omexsiccatiocclink l ON n.omenid = l.omenid + WHERE (l.occid = ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $this->occArr['exs']['title'] = $r->title; + $this->occArr['exs']['omenid'] = $r->omenid; + $this->occArr['exs']['exsnumber'] = $r->exsnumber; + } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to set exsiccati info; '.$this->conn->error,E_USER_WARNING); + else{ + $this->warningArr[] = $stmt->error; + } + $stmt->close(); } } private function setOccurrenceRelationships(){ $relOccidArr = array(); - $sql = 'SELECT assocID, occid, occidAssociate, relationship, subType, resourceUrl, identifier, dynamicProperties, verbatimSciname, tid '. - 'FROM omoccurassociations '. - 'WHERE occid = '.$this->occid.' OR occidAssociate = '.$this->occid; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $relOccid = $r->occidAssociate; - $relationship = $r->relationship; - if($this->occid == $r->occidAssociate){ - $relOccid = $r->occid; - $relationship = $this->getInverseRelationship($relationship); + $sql = 'SELECT assocID, occid, occidAssociate, relationship, subType, resourceUrl, objectID, dynamicProperties, verbatimSciname, tid + FROM omoccurassociations + WHERE occid = ? OR occidAssociate = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('ii', $this->occid, $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $relOccid = $r->occidAssociate; + $relationship = $r->relationship; + if($this->occid == $r->occidAssociate){ + $relOccid = $r->occid; + $relationship = $this->getInverseRelationship($relationship); + } + if($relOccid) $relOccidArr[$relOccid][] = $r->assocID; + $this->occArr['relation'][$r->assocID]['relationship'] = $relationship; + $this->occArr['relation'][$r->assocID]['subtype'] = $r->subType; + $this->occArr['relation'][$r->assocID]['occidassoc'] = $relOccid; + $this->occArr['relation'][$r->assocID]['resourceurl'] = $r->resourceUrl; + $this->occArr['relation'][$r->assocID]['objectID'] = $r->objectID; + $this->occArr['relation'][$r->assocID]['sciname'] = $r->verbatimSciname; } - if($relOccid) $relOccidArr[$relOccid][] = $r->assocID; - $this->occArr['relation'][$r->assocID]['relationship'] = $relationship; - $this->occArr['relation'][$r->assocID]['subtype'] = $r->subType; - $this->occArr['relation'][$r->assocID]['occidassoc'] = $relOccid; - $this->occArr['relation'][$r->assocID]['resourceurl'] = $r->resourceUrl; - $this->occArr['relation'][$r->assocID]['identifier'] = $r->identifier; - $this->occArr['relation'][$r->assocID]['sciname'] = $r->verbatimSciname; - if(!$r->identifier && $r->resourceUrl) $this->occArr['relation'][$r->assocID]['identifier'] = 'unknown ID'; + $rs->free(); } - $rs->free(); } if($relOccidArr){ - $sql = 'SELECT o.occid, CONCAT_WS("-",IFNULL(o.institutioncode,c.institutioncode),IFNULL(o.collectioncode,c.collectioncode)) as collcode, IFNULL(o.catalogNumber,o.otherCatalogNumbers) as catnum '. - 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid '. - 'WHERE o.occid IN('.implode(',',array_keys($relOccidArr)).')'; + $sql = 'SELECT o.occid, o.sciname, + CONCAT_WS("-",IFNULL(o.institutioncode, c.institutioncode), IFNULL(o.collectioncode, c.collectioncode)) as collcode, IFNULL(o.catalogNumber, o.otherCatalogNumbers) as catnum + FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid + WHERE o.occid IN(' . implode(',', array_keys($relOccidArr)) . ')'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ foreach($relOccidArr[$r->occid] as $targetAssocID){ - $this->occArr['relation'][$targetAssocID]['identifier'] = $r->collcode.':'.$r->catnum; + $this->occArr['relation'][$targetAssocID]['objectID'] = $r->collcode . ':' . $r->catnum; + $this->occArr['relation'][$targetAssocID]['sciname'] = $r->sciname; } } $rs->free(); @@ -397,20 +448,24 @@ private function setRelationshipArr(){ } private function setReferences(){ - $sql = 'SELECT r.refid, r.title, r.secondarytitle, r.shorttitle, r.tertiarytitle, r.pubdate, r.edition, r.volume, r.numbervolumnes, r.number, '. - ' r.pages, r.section, r.placeofpublication, r.publisher, r.isbn_issn, r.url, r.guid, r.cheatauthors, r.cheatcitation '. - 'FROM referenceobject r INNER JOIN referenceoccurlink l ON r.refid = l.refid '. - 'WHERE (l.occid = '.$this->occid.')'; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $this->occArr['ref'][$r->refid]['display'] = $r->cheatcitation; - $this->occArr['ref'][$r->refid]['url'] = $r->url; + $sql = 'SELECT r.refid, r.title, r.secondarytitle, r.shorttitle, r.tertiarytitle, r.pubdate, r.edition, r.volume, r.numbervolumnes, r.number, + r.pages, r.section, r.placeofpublication, r.publisher, r.isbn_issn, r.url, r.guid, r.cheatauthors, r.cheatcitation + FROM referenceobject r INNER JOIN referenceoccurlink l ON r.refid = l.refid + WHERE (l.occid = ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $this->occArr['ref'][$r->refid]['display'] = $r->cheatcitation; + $this->occArr['ref'][$r->refid]['url'] = $r->url; + } + $rs->free(); } - $rs->free(); - } - else{ - $this->warningArr[] = 'Unable to set occurrence references: '.$this->conn->error; + else{ + $this->warningArr[] = $stmt->error; + } + $stmt->close(); } } @@ -418,12 +473,18 @@ private function setMaterialSamples(){ if(isset($this->activeModules['matSample']) && $this->activeModules['matSample']){ $sql = 'SELECT m.matSampleID, m.sampleType, m.catalogNumber, m.guid, m.sampleCondition, m.disposition, m.preservationType, m.preparationDetails, m.preparationDate, m.preparedByUid, CONCAT_WS(", ",u.lastname,u.firstname) as preparedBy, m.individualCount, m.sampleSize, m.storageLocation, m.remarks, m.dynamicFields, m.recordID, m.initialTimestamp - FROM ommaterialsample m LEFT JOIN users u ON m.preparedByUid = u.uid WHERE m.occid = '.$this->occid; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_assoc()){ - $this->occArr['matSample'][$r['matSampleID']] = $r; + FROM ommaterialsample m LEFT JOIN users u ON m.preparedByUid = u.uid WHERE m.occid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_assoc()){ + $this->occArr['matSample'][$r['matSampleID']] = $r; + } + $rs->free(); + } + $stmt->close(); } - $rs->free(); } } @@ -432,24 +493,34 @@ private function setSource(){ $sql = 'SELECT o.remoteOccid, o.refreshTimestamp, o.verification, i.urlRoot, i.portalName FROM portaloccurrences o INNER JOIN portalpublications p ON o.pubid = p.pubid INNER JOIN portalindex i ON p.portalID = i.portalID - WHERE (o.occid = '.$this->occid.') AND (p.direction = "import")'; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_object()){ - $this->occArr['source']['type'] = 'symbiota'; - $this->occArr['source']['url'] = $r->urlRoot.'/collections/individual/index.php?occid='.$r->remoteOccid; - $this->occArr['source']['sourceName'] = $r->portalName; - $this->occArr['source']['refreshTimestamp'] = $r->refreshTimestamp; - $this->occArr['source']['sourceID'] = $r->remoteOccid; + WHERE (o.occid = ?) AND (p.direction = "import")'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $this->occArr['source']['type'] = 'symbiota'; + $this->occArr['source']['url'] = $r->urlRoot.'/collections/individual/index.php?occid='.$r->remoteOccid; + $this->occArr['source']['sourceName'] = $r->portalName; + $this->occArr['source']['refreshTimestamp'] = $r->refreshTimestamp; + $this->occArr['source']['sourceID'] = $r->remoteOccid; + } + $rs->free(); } - $rs->free(); + $stmt->close(); } if(isset($this->occArr['source'])){ - $sql2 = 'SELECT uploadDate FROM omcollectionstats WHERE collid = '.$this->collid; - if($rs2 = $this->conn->query($sql2)){ - if($r2 = $rs2->fetch_object()){ - if($r2->uploadDate > $this->occArr['source']['refreshTimestamp']) $this->occArr['source']['refreshTimestamp'] = $r2->uploadDate.' (batch update)'; + $sql2 = 'SELECT uploadDate FROM omcollectionstats WHERE collid = ?'; + if($stmt = $this->conn->prepare($sql2)){ + $stmt->bind_param('i', $this->collid); + $stmt->execute(); + if($rs2 = $stmt->get_result()){ + if($r2 = $rs2->fetch_object()){ + if($r2->uploadDate > $this->occArr['source']['refreshTimestamp']) $this->occArr['source']['refreshTimestamp'] = $r2->uploadDate.' (batch update)'; + } + $rs2->free(); } - $rs2->free(); + $stmt->close(); } } } @@ -475,8 +546,8 @@ private function setSource(){ } elseif(strpos($iUrl,'--OTHERCATALOGNUMBERS--') !== false && $this->occArr['othercatalognumbers']){ if(substr($this->occArr['othercatalognumbers'],0,1) == '{'){ - if($ocnArr = json_decode($this->occArr['othercatalognumbers'],true)){ - foreach($ocnArr as $idKey => $idArr){ + if($this->occArr['othercatalognumbers']){ + foreach($this->occArr['othercatalognumbers'] as $idKey => $idArr){ if(!$displayStr || $idKey == 'NEON sampleID' || $idKey == 'NEON sampleCode (barcode)'){ $displayStr = $idArr[0]; if($idKey == 'NEON sampleCode (barcode)') $iUrl = str_replace('sampleTag','barcode',$iUrl); @@ -512,62 +583,75 @@ public function getDuplicateArr(){ 'o.scientificnameauthorship AS author, o.identifiedby, o.dateidentified, o.recordedby, o.recordnumber, o.eventdate, IFNULL(i.thumbnailurl, i.url) AS url '; //Get exsiccati duplicates if(isset($this->occArr['exs'])){ - $sql = $sqlBase.'FROM omexsiccatiocclink l INNER JOIN omexsiccatiocclink l2 ON l.omenid = l2.omenid '. - 'INNER JOIN omoccurrences o ON l2.occid = o.occid '. - 'INNER JOIN omcollections c ON o.collid = c.collid '. - 'LEFT JOIN images i ON o.occid = i.occid '. - 'WHERE (o.occid != l.occid) AND (l.occid = '.$this->occid.')'; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_assoc()){ - $retArr['exs'][$r['occid']] = array_change_key_case($r); + $sql = $sqlBase.'FROM omexsiccatiocclink l INNER JOIN omexsiccatiocclink l2 ON l.omenid = l2.omenid + INNER JOIN omoccurrences o ON l2.occid = o.occid + INNER JOIN omcollections c ON o.collid = c.collid + LEFT JOIN images i ON o.occid = i.occid + WHERE (o.occid != l.occid) AND (l.occid = ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_assoc()){ + $retArr['exs'][$r['occid']] = array_change_key_case($r); + } + $rs->free(); } - $rs->free(); + $stmt->close(); } } //Get specimen duplicates - $sql = $sqlBase.'FROM omoccurduplicatelink d INNER JOIN omoccurduplicatelink d2 ON d.duplicateid = d2.duplicateid '. - 'INNER JOIN omoccurrences o ON d2.occid = o.occid '. - 'INNER JOIN omcollections c ON o.collid = c.collid '. - 'LEFT JOIN images i ON o.occid = i.occid '. - 'WHERE (d.occid = '.$this->occid.') AND (d.occid != d2.occid) '; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_assoc()){ - if(!isset($retArr['exs'][$r['occid']])) $retArr['dupe'][$r['occid']] = array_change_key_case($r); + $sql = $sqlBase.'FROM omoccurduplicatelink d INNER JOIN omoccurduplicatelink d2 ON d.duplicateid = d2.duplicateid + INNER JOIN omoccurrences o ON d2.occid = o.occid + INNER JOIN omcollections c ON o.collid = c.collid + LEFT JOIN images i ON o.occid = i.occid + WHERE (d.occid = ?) AND (d.occid != d2.occid) '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_assoc()){ + if(!isset($retArr['exs'][$r['occid']])) $retArr['dupe'][$r['occid']] = array_change_key_case($r); + } + $rs->free(); } - $rs->free(); + $stmt->close(); } - return $retArr; + return $this->cleanOutArray($retArr); } //Occurrence trait and attribute functions public function getTraitArr(){ $retArr = array(); if($this->occid){ - $sql = 'SELECT t.traitid, t.traitName, t.traitType, t.description AS t_desc, t.refUrl AS t_url, s.stateid, s.stateName, s.description AS s_desc, s.refUrl AS s_url, d.parentstateid '. - 'FROM tmattributes a INNER JOIN tmstates s ON a.stateid = s.stateid '. - 'INNER JOIN tmtraits t ON s.traitid = t.traitid '. - 'LEFT JOIN tmtraitdependencies d ON t.traitid = d.traitid '. - 'WHERE t.isPublic = 1 AND a.occid = '.$this->occid.' ORDER BY t.traitName, s.sortSeq'; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $retArr[$r->traitid]['name'] = $r->traitName; - $retArr[$r->traitid]['desc'] = $r->t_desc; - $retArr[$r->traitid]['url'] = $r->t_url; - $retArr[$r->traitid]['type'] = $r->traitType; - $retArr[$r->traitid]['depStateID'] = $r->parentstateid; - $retArr[$r->traitid]['state'][$r->stateid]['name'] = $r->stateName; - $retArr[$r->traitid]['state'][$r->stateid]['desc'] = $r->s_desc; - $retArr[$r->traitid]['state'][$r->stateid]['url'] = $r->s_url; + $sql = 'SELECT t.traitid, t.traitName, t.traitType, t.description AS t_desc, t.refUrl AS t_url, s.stateid, s.stateName, s.description AS s_desc, s.refUrl AS s_url, d.parentstateid + FROM tmattributes a INNER JOIN tmstates s ON a.stateid = s.stateid + INNER JOIN tmtraits t ON s.traitid = t.traitid + LEFT JOIN tmtraitdependencies d ON t.traitid = d.traitid + WHERE t.isPublic = 1 AND a.occid = ? ORDER BY t.traitName, s.sortSeq'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $retArr[$r->traitid]['name'] = $r->traitName; + $retArr[$r->traitid]['desc'] = $r->t_desc; + $retArr[$r->traitid]['url'] = $r->t_url; + $retArr[$r->traitid]['type'] = $r->traitType; + $retArr[$r->traitid]['depStateID'] = $r->parentstateid; + $retArr[$r->traitid]['state'][$r->stateid]['name'] = $r->stateName; + $retArr[$r->traitid]['state'][$r->stateid]['desc'] = $r->s_desc; + $retArr[$r->traitid]['state'][$r->stateid]['url'] = $r->s_url; + } + $rs->free(); } - $rs->free(); + $stmt->close(); } if($retArr){ //Set dependent traits - $sql = 'SELECT DISTINCT s.traitid AS parentTraitID, d.parentStateID, d.traitid AS depTraitID '. - 'FROM tmstates s INNER JOIN tmtraitdependencies d ON s.stateid = d.parentstateid '. - 'WHERE s.traitid IN('.implode(',',array_keys($retArr)).')'; - //echo $sql.'
    '; + $sql = 'SELECT DISTINCT s.traitid AS parentTraitID, d.parentStateID, d.traitid AS depTraitID + FROM tmstates s INNER JOIN tmtraitdependencies d ON s.stateid = d.parentstateid + WHERE s.traitid IN(' . implode(',', array_keys($retArr)) . ')'; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ $retArr[$r->parentTraitID]['state'][$r->parentStateID]['depTraitID'][] = $r->depTraitID; @@ -575,13 +659,13 @@ public function getTraitArr(){ $rs->free(); } } - return $retArr; + return $this->cleanOutArray($retArr); } public function echoTraitDiv($traitArr, $targetID, $ident = 15){ if(array_key_exists($targetID,$traitArr)){ $tArr = $traitArr[$targetID]; - foreach($tArr['state'] as $stateID => $sArr){ + foreach($tArr['state'] as $sArr){ $label = ''; if($tArr['type'] == 'TF') $label = $traitArr[$targetID]['name']; $this->echoTraitUnit($sArr, $label, $ident); @@ -597,13 +681,13 @@ public function echoTraitDiv($traitArr, $targetID, $ident = 15){ public function echoTraitUnit($outArr, $label = '', $indent=0){ if(isset($outArr['name'])){ echo '
    '; - if(!empty($outArr['url'])) echo ''; + if(!empty($outArr['url'])) echo ''; echo ''; if(!empty($label)) echo $label.' '; - echo $outArr['name']; + echo $this->cleanOutStr($outArr['name']); echo ''; if(!empty($outArr['url'])) echo ''; - if(!empty($outArr['desc'])) echo ': '.$outArr['desc']; + if(!empty($outArr['desc'])) echo ': '.$this->cleanOutStr($outArr['desc']); echo '
    '; } } @@ -612,99 +696,118 @@ public function echoTraitUnit($outArr, $label = '', $indent=0){ public function getCommentArr($isEditor){ $retArr = array(); if($this->occid){ - $sql = 'SELECT c.comid, c.comment, u.username, c.reviewstatus, c.initialtimestamp FROM omoccurcomments c INNER JOIN users u ON c.uid = u.uid WHERE (c.occid = '.$this->occid.') '; + $sql = 'SELECT c.comid, c.comment, u.username, c.reviewstatus, c.initialtimestamp FROM omoccurcomments c INNER JOIN users u ON c.uid = u.uid WHERE (c.occid = ?) '; if(!$isEditor) $sql .= 'AND c.reviewstatus IN(1,3) '; $sql .= 'ORDER BY c.initialtimestamp'; - //echo $sql.'

    '; - $rs = $this->conn->query($sql); - if($rs){ - while($row = $rs->fetch_object()){ - $comId = $row->comid; - $retArr[$comId]['comment'] = $row->comment; - $retArr[$comId]['reviewstatus'] = $row->reviewstatus; - $retArr[$comId]['username'] = $row->username; - $retArr[$comId]['initialtimestamp'] = $row->initialtimestamp; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($row = $rs->fetch_object()){ + $comId = $row->comid; + $retArr[$comId]['comment'] = $row->comment; + $retArr[$comId]['reviewstatus'] = $row->reviewstatus; + $retArr[$comId]['username'] = $row->username; + $retArr[$comId]['initialtimestamp'] = $row->initialtimestamp; + } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to set comments; '.$this->conn->error,E_USER_WARNING); + else{ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } } - return $retArr; + return $this->cleanOutArray($retArr); } public function addComment($commentStr){ $status = false; - if(isset($GLOBALS['SYMB_UID']) && $GLOBALS['SYMB_UID']){ - $con = MySQLiConnectionFactory::getCon("write"); - $sql = 'INSERT INTO omoccurcomments(occid,comment,uid,reviewstatus) VALUES('.$this->occid.',"'.$this->cleanInStr(strip_tags($commentStr)).'",'.$GLOBALS['SYMB_UID'].',1)'; - //echo 'sql: '.$sql; - if($con->query($sql)){ - $status = true; - } - else{ - $status = false; - $this->errorMessage = 'ERROR adding comment: '.$con->error; + if(is_numeric($GLOBALS['SYMB_UID'])){ + $commentStr = strip_tags($commentStr); + $sql = 'INSERT INTO omoccurcomments(occid, comment, uid, reviewstatus) VALUES(?, ?, ?, 1)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('isi', $this->occid, $commentStr, $GLOBALS['SYMB_UID']); + $stmt->execute(); + if($stmt->affected_rows){ + $status = true; + } + elseif($stmt->error){ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } - $con->close(); } return $status; } public function deleteComment($comId){ $status = true; - $con = MySQLiConnectionFactory::getCon("write"); if(is_numeric($comId)){ - $sql = 'DELETE FROM omoccurcomments WHERE comid = '.$comId; - if(!$con->query($sql)){ - $status = false; - $this->errorMessage = 'ERROR deleting comment: '.$con->error; + $sql = 'DELETE FROM omoccurcomments WHERE comid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $comId); + $stmt->execute(); + if($stmt->affected_rows){ + $status = true; + } + elseif($stmt->error){ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } } - $con->close(); return $status; } public function reportComment($repComId){ - $status = true; - if(!is_numeric($repComId)) return false; + global $LANG; + $status = false; if(isset($GLOBALS['ADMIN_EMAIL'])){ - //Set Review status to supress - $con = MySQLiConnectionFactory::getCon("write"); - if(!$con->query('UPDATE omoccurcomments SET reviewstatus = 2 WHERE comid = '.$repComId)){ - $this->errorMessage = 'ERROR changing comment status to needing review, Err msg: '.$con->error; - $status = false; + $sql = 'UPDATE omoccurcomments SET reviewstatus = 2 WHERE comid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $repComId); + $stmt->execute(); + if($stmt->affected_rows){ + $status = true; + } + elseif($stmt->error){ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } - $con->close(); //Email to portal admin $emailAddr = $GLOBALS['ADMIN_EMAIL']; - $comUrl = $this->getDomain().$GLOBALS['CLIENT_ROOT'].'/collections/individual/index.php?occid='.$this->occid.'#commenttab'; - $subject = $GLOBALS['DEFAULT_TITLE'].' inappropriate comment reported
    '; - $bodyStr = 'The following comment has been recorted as inappropriate:
    '.$comUrl.''; - $headerStr = "MIME-Version: 1.0 \r\nContent-type: text/html \r\nTo: ".$emailAddr." \r\nFrom: Admin <".$emailAddr."> \r\n"; + $comUrl = $this->getDomain().$GLOBALS['CLIENT_ROOT'].'/collections/individual/index.php?occid=' . $this->occid . '#commenttab'; + $subject = $GLOBALS['DEFAULT_TITLE'] . ' '. $LANG['INAPPROPRIATE'] . '
    '; + $bodyStr = $LANG['REPORTED_AS_INAPPROPRIATE'] . ':
    ' . $comUrl . ''; + $headerStr = "MIME-Version: 1.0 \r\nContent-type: text/html \r\nTo: " . $emailAddr . " \r\nFrom: Admin <" . $emailAddr . "> \r\n"; if(!mail($emailAddr,$subject,$bodyStr,$headerStr)){ - $this->errorMessage = 'ERROR sending email to portal manager, error unknown'; $status = false; } } else{ - $this->errorMessage = 'ERROR: Portal admin email not defined in central configuration file '; + $this->errorMessage = $LANG['EMAIL_NOT_DEFINED']; $status = false; } return $status; } public function makeCommentPublic($comId){ - $status = true; - if(!is_numeric($comId)) return false; - $con = MySQLiConnectionFactory::getCon("write"); - if(!$con->query('UPDATE omoccurcomments SET reviewstatus = 1 WHERE comid = '.$comId)){ - $this->errorMessage = 'ERROR making comment public, err msg: '.$con->error; - $status = false; + $status = false; + $sql = 'UPDATE omoccurcomments SET reviewstatus = 1 WHERE comid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $comId); + $stmt->execute(); + if($stmt->affected_rows){ + $status = true; + } + elseif($stmt->error){ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } - $con->close(); return $status; } @@ -712,81 +815,94 @@ public function makeCommentPublic($comId){ public function getGeneticArr(){ $retArr = array(); if($this->occid){ - $sql = 'SELECT idoccurgenetic, identifier, resourcename, locus, resourceurl, notes FROM omoccurgenetic WHERE occid = '.$this->occid; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $retArr[$r->idoccurgenetic]['id'] = $r->identifier; - $retArr[$r->idoccurgenetic]['name'] = $r->resourcename; - $retArr[$r->idoccurgenetic]['locus'] = $r->locus; - $retArr[$r->idoccurgenetic]['resourceurl'] = $r->resourceurl; - $retArr[$r->idoccurgenetic]['notes'] = $r->notes; + $sql = 'SELECT idoccurgenetic, identifier, resourcename, locus, resourceurl, notes FROM omoccurgenetic WHERE occid = ?'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $retArr[$r->idoccurgenetic]['id'] = $r->identifier; + $retArr[$r->idoccurgenetic]['name'] = $r->resourcename; + $retArr[$r->idoccurgenetic]['locus'] = $r->locus; + $retArr[$r->idoccurgenetic]['resourceurl'] = $r->resourceurl; + $retArr[$r->idoccurgenetic]['notes'] = $r->notes; + } + $rs->free(); } - $rs->free(); - } - else{ - trigger_error('Unable to get genetic data; '.$this->conn->error,E_USER_WARNING); + else{ + $this->errorMessage = $stmt->error; + } + $stmt->close(); } } - return $retArr; + return $this->cleanOutArray($retArr); } public function getEditArr(){ $retArr = array(); - $sql = 'SELECT e.ocedid, e.fieldname, e.fieldvalueold, e.fieldvaluenew, e.reviewstatus, e.appliedstatus, '. - 'CONCAT_WS(", ",u.lastname,u.firstname) as editor, e.initialtimestamp '. - 'FROM omoccuredits e INNER JOIN users u ON e.uid = u.uid '. - 'WHERE e.occid = '.$this->occid.' ORDER BY e.initialtimestamp DESC '; - $rs = $this->conn->query($sql); - if($rs){ - while($r = $rs->fetch_object()){ - $k = substr($r->initialtimestamp,0,16); - if(!isset($retArr[$k])){ - $retArr[$k]['editor'] = $r->editor; - $retArr[$k]['ts'] = $r->initialtimestamp; - $retArr[$k]['reviewstatus'] = $r->reviewstatus; - } - $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['fieldname'] = $r->fieldname; - $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['old'] = $r->fieldvalueold; - $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['new'] = $r->fieldvaluenew; - $currentCode = 0; - if(isset($this->occArr[strtolower($r->fieldname)])){ - $fName = $this->occArr[strtolower($r->fieldname)]; - if($fName == $r->fieldvaluenew) $currentCode = 1; - elseif($fName == $r->fieldvalueold) $currentCode = 2; + $sql = 'SELECT e.ocedid, e.fieldname, e.fieldvalueold, e.fieldvaluenew, e.reviewstatus, e.appliedstatus, + CONCAT_WS(", ",u.lastname,u.firstname) as editor, e.initialtimestamp + FROM omoccuredits e INNER JOIN users u ON e.uid = u.uid + WHERE e.occid = ? ORDER BY e.initialtimestamp DESC '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $k = substr($r->initialtimestamp,0,16); + if(!isset($retArr[$k])){ + $retArr[$k]['editor'] = $r->editor; + $retArr[$k]['ts'] = $r->initialtimestamp; + $retArr[$k]['reviewstatus'] = $r->reviewstatus; + } + $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['fieldname'] = $r->fieldname; + $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['old'] = $r->fieldvalueold; + $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['new'] = $r->fieldvaluenew; + $currentCode = 0; + if(isset($this->occArr[strtolower($r->fieldname)])){ + $fName = $this->occArr[strtolower($r->fieldname)]; + if($fName == $r->fieldvaluenew) $currentCode = 1; + elseif($fName == $r->fieldvalueold) $currentCode = 2; + } + $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['current'] = $currentCode; } - $retArr[$k]['edits'][$r->appliedstatus][$r->ocedid]['current'] = $currentCode; + $rs->free(); } - $rs->free(); + $stmt->close(); } - return $retArr; + return $this->cleanOutArray($retArr); } public function getExternalEditArr(){ $retArr = Array(); - $sql = 'SELECT r.orid, r.oldvalues, r.newvalues, r.externalsource, r.externaleditor, r.reviewstatus, r.appliedstatus, '. - 'CONCAT_WS(", ",u.lastname,u.firstname) AS username, r.externaltimestamp, r.initialtimestamp '. - 'FROM omoccurrevisions r LEFT JOIN users u ON r.uid = u.uid '. - 'WHERE (r.occid = '.$this->occid.') ORDER BY r.initialtimestamp DESC '; - //echo '
    '.$sql.'
    '; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $editor = $r->externaleditor; - if($r->username) $editor .= ' ('.$r->username.')'; - $retArr[$r->orid][$r->appliedstatus]['editor'] = $editor; - $retArr[$r->orid][$r->appliedstatus]['source'] = $r->externalsource; - $retArr[$r->orid][$r->appliedstatus]['reviewstatus'] = $r->reviewstatus; - $retArr[$r->orid][$r->appliedstatus]['ts'] = $r->initialtimestamp; - - $oldValues = json_decode($r->oldvalues,true); - $newValues = json_decode($r->newvalues,true); - foreach($oldValues as $fieldName => $value){ - $retArr[$r->orid][$r->appliedstatus]['edits'][$fieldName]['old'] = $value; - $retArr[$r->orid][$r->appliedstatus]['edits'][$fieldName]['new'] = (isset($newValues[$fieldName])?$newValues[$fieldName]:'ERROR'); + $sql = 'SELECT r.orid, r.oldvalues, r.newvalues, r.externalsource, r.externaleditor, r.reviewstatus, r.appliedstatus, + CONCAT_WS(", ",u.lastname,u.firstname) AS username, r.externaltimestamp, r.initialtimestamp + FROM omoccurrevisions r LEFT JOIN users u ON r.uid = u.uid + WHERE (r.occid = ?) ORDER BY r.initialtimestamp DESC '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $editor = $r->externaleditor; + if($r->username) $editor .= ' ('.$r->username.')'; + $retArr[$r->orid][$r->appliedstatus]['editor'] = $editor; + $retArr[$r->orid][$r->appliedstatus]['source'] = $r->externalsource; + $retArr[$r->orid][$r->appliedstatus]['reviewstatus'] = $r->reviewstatus; + $retArr[$r->orid][$r->appliedstatus]['ts'] = $r->initialtimestamp; + + $oldValues = json_decode($r->oldvalues,true); + $newValues = json_decode($r->newvalues,true); + foreach($oldValues as $fieldName => $value){ + $retArr[$r->orid][$r->appliedstatus]['edits'][$fieldName]['old'] = $value; + $retArr[$r->orid][$r->appliedstatus]['edits'][$fieldName]['new'] = (isset($newValues[$fieldName])?$newValues[$fieldName]:'ERROR'); + } + } + $rs->free(); } + $stmt->close(); } - $rs->free(); - return $retArr; + return $this->cleanOutArray($retArr); } public function getAccessStats(){ @@ -794,51 +910,61 @@ public function getAccessStats(){ if(isset($GLOBALS['STORE_STATISTICS'])){ $sql = 'SELECT year(s.accessdate) as accessdate, s.accesstype, s.cnt FROM omoccuraccesssummary s INNER JOIN omoccuraccesssummarylink l ON s.oasid = l.oasid - WHERE (l.occid = '.$this->occid.') GROUP BY s.accessdate, s.accesstype'; - //echo '
    '.$sql.'
    '; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $retArr[$r->accessdate][$r->accesstype] = $r->cnt; + WHERE (l.occid = ?) GROUP BY s.accessdate, s.accesstype'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $retArr[$r->accessdate][$r->accesstype] = $r->cnt; + } + $rs->free(); + } + $stmt->close(); } - $rs->free(); } - return $retArr; + return $this->cleanOutArray($retArr); } //Voucher management public function getVoucherChecklists(){ - global $USER_RIGHTS; + global $USER_RIGHTS, $LANG; $returnArr = Array(); if($this->occid){ $sql = 'SELECT c.clid, c.name, c.access, v.voucherID FROM fmchecklists c INNER JOIN fmchklsttaxalink cl ON c.clid = cl.clid INNER JOIN fmvouchers v ON cl.clTaxaID = v.clTaxaID - WHERE v.occid = '.$this->occid.' '; - if(array_key_exists("ClAdmin",$USER_RIGHTS)){ - $sql .= 'AND (c.access = "public" OR c.clid IN('.implode(',',$USER_RIGHTS['ClAdmin']).')) '; + WHERE v.occid = ? '; + if(array_key_exists('ClAdmin', $USER_RIGHTS)){ + $sql .= 'AND (c.access = "public" OR c.clid IN(' . $this->cleanInStr(implode(',', $USER_RIGHTS['ClAdmin'])) . ')) '; } else{ $sql .= 'AND (c.access = "public") '; } $sql .= 'ORDER BY c.name'; - if($rs = $this->conn->query($sql)){ - while($r = $rs->fetch_object()){ - $nameStr = $r->name; - if($r->access == 'private') $nameStr .= ' (private status)'; - $returnArr[$r->clid]['name'] = $nameStr; - $returnArr[$r->clid]['voucherID'] = $r->voucherID; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs = $stmt->get_result()){ + while($r = $rs->fetch_object()){ + $nameStr = $r->name; + if($r->access == 'private') $nameStr .= ' (' . $LANG['PRIVATE_STATUS'] . ')'; + $returnArr[$r->clid]['name'] = $nameStr; + $returnArr[$r->clid]['voucherID'] = $r->voucherID; + } + $rs->free(); } - $rs->free(); + $stmt->close(); } } - return $returnArr; + return $this->cleanOutArray($returnArr); } public function linkVoucher($postArr){ $status = false; if($this->occid && is_numeric($postArr['vclid'])){ if(isset($GLOBALS['USER_RIGHTS']['ClAdmin']) && in_array($postArr['vclid'], $GLOBALS['USER_RIGHTS']['ClAdmin'])){ - $voucherManager = new ChecklistVoucherAdmin($this->conn); + $voucherManager = new ChecklistVoucherAdmin(); $voucherManager->setClid($postArr['vclid']); if($voucherManager->linkVoucher($postArr['vtid'], $this->occid, '', $postArr['veditnotes'], $postArr['vnotes'])){ $status = true; @@ -850,12 +976,11 @@ public function linkVoucher($postArr){ } public function deleteVoucher($voucherID){ + global $LANG; $status = false; $clid = 0; //Make sure user has checklist admin permission for checklist - $sql = 'SELECT c.clid - FROM fmvouchers v INNER JOIN fmchklsttaxalink c ON v.clTaxaID = c.clTaxaID - WHERE v.voucherID = ?'; + $sql = 'SELECT c.clid FROM fmvouchers v INNER JOIN fmchklsttaxalink c ON v.clTaxaID = c.clTaxaID WHERE v.voucherID = ?'; if($stmt = $this->conn->prepare($sql)){ $stmt->bind_param('i', $voucherID); $stmt->execute(); @@ -864,7 +989,7 @@ public function deleteVoucher($voucherID){ $stmt->close(); } if(!$clid){ - $this->errorMessage = 'ERROR deleting voucher: unable to verify target checklist for voucher'; + $this->errorMessage = $LANG['UNABLE_TO_VERIFY_TARGET']; return false; } if(isset($GLOBALS['USER_RIGHTS']['ClAdmin']) && in_array($clid, $GLOBALS['USER_RIGHTS']['ClAdmin'])){ @@ -878,7 +1003,7 @@ public function deleteVoucher($voucherID){ } } else{ - $this->errorMessage = 'ERROR deleting voucher: permission error'; + $this->errorMessage = $LANG['PERMISSION_ERROR']; return false; } return $status; @@ -888,17 +1013,22 @@ public function deleteVoucher($voucherID){ public function getDatasetArr(){ $retArr = array(); $roleArr = array(); - if($GLOBALS['SYMB_UID']){ - $sql1 = 'SELECT tablepk, role FROM userroles WHERE (tablename = "omoccurdatasets") AND (uid = '.$GLOBALS['SYMB_UID'].') '; - $rs1 = $this->conn->query($sql1); - while($r1 = $rs1->fetch_object()){ - $roleArr[$r1->tablepk] = $r1->role; + if(is_numeric($GLOBALS['SYMB_UID'])){ + $sql = 'SELECT tablepk, role FROM userroles WHERE (tablename = "omoccurdatasets") AND (uid = ?) '; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $GLOBALS['SYMB_UID']); + $stmt->execute(); + $tablePK = ''; $role = ''; + $stmt->bind_result($tablePK, $role); + while($stmt->fetch()){ + $roleArr[$tablePK] = $role; + } + $stmt->close(); } - $rs1->free(); } $sql2 = 'SELECT datasetid, name, uid FROM omoccurdatasets '; - if(!$GLOBALS['IS_ADMIN']){ + if(!$GLOBALS['IS_ADMIN'] && is_numeric($GLOBALS['SYMB_UID'])){ //Only get datasets for current user. Once we have appied isPublic tag, we can extend display to all public datasets $sql2 .= 'WHERE (uid = '.$GLOBALS['SYMB_UID'].') '; if($roleArr) $sql2 .= 'OR (datasetid IN('.implode(',',array_keys($roleArr)).')) '; @@ -917,41 +1047,44 @@ public function getDatasetArr(){ } else $this->errorMessage = 'ERROR: Unable to set datasets for user: '.$this->conn->error; - $sql3 = 'SELECT datasetid, notes FROM omoccurdatasetlink WHERE occid = '.$this->occid; - $rs3 = $this->conn->query($sql3); - if($rs3){ - while($r3 = $rs3->fetch_object()){ - if(isset($retArr[$r3->datasetid])){ - //Only display datasets linked to current user, at least for now. Once isPublic option is activated, we'll open this up further. - $retArr[$r3->datasetid]['linked'] = 1; - if($r3->notes) $retArr[$r3->datasetid]['notes'] = $r3->notes; + $sql3 = 'SELECT datasetid, notes FROM omoccurdatasetlink WHERE occid = ?'; + if($stmt = $this->conn->prepare($sql3)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + if($rs3 = $stmt->get_result()){ + while($r3 = $rs3->fetch_object()){ + if(isset($retArr[$r3->datasetid])){ + //Only display datasets linked to current user, at least for now. Once isPublic option is activated, we'll open this up further. + $retArr[$r3->datasetid]['linked'] = 1; + if($r3->notes) $retArr[$r3->datasetid]['notes'] = $r3->notes; + } } + $rs3->free(); } - $rs3->free(); + else $this->errorMessage = $stmt->conn->error; + $stmt->close(); } - else $this->errorMessage = 'Unable to get related datasets: '.$this->conn->error; - return $retArr; + return $this->cleanOutArray($retArr); } public function getChecklists($clidExcludeArr){ global $USER_RIGHTS; - if(!array_key_exists("ClAdmin",$USER_RIGHTS)) return null; - $returnArr = Array(); - $targetArr = array_diff($USER_RIGHTS["ClAdmin"],$clidExcludeArr); + if(!array_key_exists('ClAdmin', $USER_RIGHTS)) return null; + $retArr = Array(); + $targetArr = array_diff($USER_RIGHTS['ClAdmin'], $clidExcludeArr); if($targetArr){ - $sql = 'SELECT name, clid FROM fmchecklists WHERE clid IN('.implode(",",$targetArr).') ORDER BY Name'; - //echo $sql; + $sql = 'SELECT name, clid FROM fmchecklists WHERE clid IN(' . implode(',', $targetArr) . ') ORDER BY Name'; if($rs = $this->conn->query($sql)){ while($row = $rs->fetch_object()){ - $returnArr[$row->clid] = $row->name; + $retArr[$row->clid] = $row->name; } $rs->free(); } else{ - trigger_error('Unable to get checklist data; '.$this->conn->error,E_USER_WARNING); + $this->errorMessage = $this->conn->error; } } - return $returnArr; + return $this->cleanOutArray($retArr); } public function checkArchive($guid){ @@ -969,7 +1102,7 @@ public function checkArchive($guid){ $stmt->close(); } } - if(!$retArr && $guid){ + if(!$archiveObject && $guid){ $sql .= 'WHERE (occurrenceid = ?) OR (recordID = ?) '; if($stmt = $this->conn->prepare($sql)){ $stmt->bind_param('ss', $guid, $guid); @@ -983,20 +1116,21 @@ public function checkArchive($guid){ $retArr['obj'] = json_decode($archiveObject, true); $retArr['notes'] = $notes; } - return $retArr; + return $this->cleanOutArray($retArr); } public function restoreRecord(){ if($this->occid){ $jsonStr = ''; - $sql = 'SELECT archiveobj FROM omoccurarchive WHERE (occid = '.$this->occid.')'; - $rs = $this->conn->query($sql); - while($r = $rs->fetch_object()){ - $jsonStr = $r->archiveobj; + $sql = 'SELECT archiveobj FROM omoccurarchive WHERE (occid = ?)'; + if($stmt = $this->conn->prepare($sql)){ + $stmt->bind_param('i', $this->occid); + $stmt->execute(); + $stmt->bind_result($jsonStr); + $stmt->fetch(); + $stmt->close(); } - $rs->free(); if(!$jsonStr){ - $this->errorMessage = 'ERROR restoring record: archive empty'; return false; } $recArr = json_decode($jsonStr,true); @@ -1012,13 +1146,13 @@ public function restoreRecord(){ $sql2 = 'VALUES('; foreach($recArr as $field => $value){ if(in_array(strtolower($field),$occurFieldArr)){ - $sql1 .= $field.','; - $sql2 .= '"'.$this->cleanInStr($value).'",'; + $sql1 .= $field . ','; + $sql2 .= '"' . $this->cleanInStr($value) . '",'; } } $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring occurrence record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error . ' (' . $sql . ')'; return false; } @@ -1030,18 +1164,18 @@ public function restoreRecord(){ $detFieldArr[] = strtolower($rDet->Field); } $rsDet->free(); - foreach($recArr['dets'] as $pk => $secArr){ + foreach($recArr['dets'] as $secArr){ $sql1 = 'INSERT INTO omoccurdeterminations('; $sql2 = 'VALUES('; foreach($secArr as $f => $v){ if(in_array(strtolower($f),$detFieldArr)){ - $sql1 .= $f.','; - $sql2 .= '"'.$this->cleanInStr($v).'",'; + $sql1 .= $f . ','; + $sql2 .= '"' . $this->cleanInStr($v) . '",'; } } - $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; + $sql = trim($sql1, ' ,') . ') ' . trim($sql2, ' ,') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring determination record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error; return false; } } @@ -1060,13 +1194,13 @@ public function restoreRecord(){ $sql2 = 'VALUES('; foreach($secArr as $f => $v){ if(in_array(strtolower($f),$imgFieldArr)){ - $sql1 .= $f.','; - $sql2 .= '"'.$this->cleanInStr($v).'",'; + $sql1 .= $f . ','; + $sql2 .= '"' . $this->cleanInStr($v) . '",'; } } - $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; + $sql = trim($sql1, ' ,') . ') ' . trim($sql2, ' ,') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring image record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error; return false; } } @@ -1089,9 +1223,9 @@ public function restoreRecord(){ $sql2 .= '"'.$this->cleanInStr($v).'",'; } } - $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; + $sql = trim($sql1, ' ,') . ') ' . trim($sql2, ' ,') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring paleo record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error . ' (' . $sql . ')'; return false; } } @@ -1099,11 +1233,11 @@ public function restoreRecord(){ //Restore exsiccati if(isset($recArr['exsiccati']) && $recArr['exsiccati']){ - $sql = 'INSERT INTO omexsiccatiocclink(omenid, occid, ranking, notes) VALUES('.$recArr['exsiccati']['ometid'].','.$recArr['exsiccati']['occid'].','. - (isset($recArr['exsiccati']['ranking'])?$recArr['exsiccati']['ranking']:'NULL').',' - (isset($recArr['exsiccati']['notes'])?'"'.$this->cleanInStr($recArr['exsiccati']['notes']).'"':'NULL').')'; + $sql = 'INSERT INTO omexsiccatiocclink(omenid, occid, ranking, notes) VALUES(' . $recArr['exsiccati']['ometid'] . ',' . $recArr['exsiccati']['occid'] . ','. + (isset($recArr['exsiccati']['ranking']) ? $recArr['exsiccati']['ranking'] : 'NULL') . ',' + (isset($recArr['exsiccati']['notes']) ? '"' . $this->cleanInStr($recArr['exsiccati']['notes']) . '"' : 'NULL') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring exsiccati record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error; return false; } } @@ -1120,14 +1254,14 @@ public function restoreRecord(){ $sql1 = 'INSERT INTO omoccurassociations('; $sql2 = 'VALUES('; foreach($secArr as $f => $v){ - if(in_array(strtolower($f),$assocFieldArr)){ - $sql1 .= $f.','; - $sql2 .= '"'.$this->cleanInStr($v).'",'; + if(in_array(strtolower($f), $assocFieldArr)){ + $sql1 .= $f . ','; + $sql2 .= '"' . $this->cleanInStr($v) . '",'; } } - $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; + $sql = trim($sql1, ' ,') . ') ' . trim($sql2, ' ,') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring association record: '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error; return false; } } @@ -1145,14 +1279,14 @@ public function restoreRecord(){ $sql1 = 'INSERT INTO ommaterialsample('; $sql2 = 'VALUES('; foreach($secArr as $f => $v){ - if(in_array(strtolower($f),$msFieldArr)){ - $sql1 .= $f.','; - $sql2 .= '"'.$this->cleanInStr($v).'",'; + if(in_array(strtolower($f), $msFieldArr)){ + $sql1 .= $f . ','; + $sql2 .= '"' . $this->cleanInStr($v) . '",'; } } - $sql = trim($sql1,' ,').') '.trim($sql2,' ,').')'; + $sql = trim($sql1, ' ,') . ') ' . trim($sql2, ' ,') . ')'; if(!$this->conn->query($sql)){ - $this->errorMessage = 'ERROR restoring material sample record '.$this->conn->error.' ('.$sql.')'; + $this->errorMessage = $this->conn->error; return false; } } @@ -1170,14 +1304,18 @@ public function isTaxonomicEditor(){ //Grab taxonomic node id and geographic scopes $editTidArr = array(); - $sqlut = 'SELECT idusertaxonomy, tid, geographicscope FROM usertaxonomy WHERE editorstatus = "OccurrenceEditor" AND uid = '.$GLOBALS['SYMB_UID']; - //echo $sqlut; - $rsut = $this->conn->query($sqlut); - while($rut = $rsut->fetch_object()){ - //Is a taxonomic editor, but not explicitly approved for this collection - $editTidArr[$rut->tid] = $rut->geographicscope; + $sqlut = 'SELECT tid, geographicscope FROM usertaxonomy WHERE editorstatus = "OccurrenceEditor" AND uid = ?'; + if($stmt = $this->conn->prepare($sqlut)){ + $stmt->bind_param('i', $GLOBALS['SYMB_UID']); + $stmt->execute(); + $tid = ''; + $geographicScope = ''; + $stmt->bind_result($tid, $geographicScope); + while($stmt->fetch()){ + $editTidArr[$tid] = $geographicScope; + } + $stmt->close(); } - $rsut->free(); //Get relevant tids for active occurrence if($editTidArr){ @@ -1185,7 +1323,7 @@ public function isTaxonomicEditor(){ $sql = ''; if($this->occArr['tidinterpreted']){ $occTidArr[] = $this->occArr['tidinterpreted']; - $sql = 'SELECT parenttid FROM taxaenumtree WHERE (taxauthid = 1) AND (tid = '.$this->occArr['tidinterpreted'].')'; + $sql = 'SELECT parenttid FROM taxaenumtree WHERE (taxauthid = 1) AND (tid = ' . $this->cleanInStr($this->occArr['tidinterpreted']) . ')'; } elseif($this->occArr['sciname'] || $this->occArr['family']){ //Get all relevant tids within the taxonomy hierarchy @@ -1193,14 +1331,14 @@ public function isTaxonomicEditor(){ if($this->occArr['sciname']){ //Try to isolate genus $taxon = $this->occArr['sciname']; - $tok = explode(' ',$this->occArr['sciname']); + $tok = explode(' ', $this->occArr['sciname']); if(count($tok) > 1){ if(strlen($tok[0]) > 2) $taxon = $tok[0]; } - $sql .= 'AND (t.sciname = "'.$this->cleanInStr($taxon).'") '; + $sql .= 'AND (t.sciname = "' . $this->cleanInStr($taxon) . '") '; } elseif($this->occArr['family']){ - $sql .= 'AND (t.sciname = "'.$this->cleanInStr($this->occArr['family']).'") '; + $sql .= 'AND (t.sciname = "' . $this->cleanInStr($this->occArr['family']) . '") '; } } if($sql){ @@ -1211,7 +1349,7 @@ public function isTaxonomicEditor(){ $rs2->free(); } if($occTidArr){ - if(array_intersect(array_keys($editTidArr),$occTidArr)){ + if(array_intersect(array_keys($editTidArr), $occTidArr)){ $isEditor = 3; //TODO: check to see if specimen is within geographic scope } @@ -1221,12 +1359,12 @@ public function isTaxonomicEditor(){ } public function activateOrcidID($inStr){ - $retStr = $inStr; + $retStr = $this->cleanOutStr($inStr); $m = array(); - if(preg_match('#((https://orcid.org/)?\d{4}-\d{4}-\d{4}-\d{3}[0-9X])#', $inStr, $m)){ + if(preg_match('#((https://orcid.org/)?\d{4}-\d{4}-\d{4}-\d{3}[0-9X])#', $retStr, $m)){ $orcidAnchor = $m[1]; - if(substr($orcidAnchor,5) != 'https') $orcidAnchor = 'https://orcid.org/'.$orcidAnchor; - $orcidAnchor = ''.$m[1].''; + if(substr($orcidAnchor,5) != 'https') $orcidAnchor = 'https://orcid.org/' . $orcidAnchor; + $orcidAnchor = '' . $m[1] . ''; $retStr = str_replace($m[1], $orcidAnchor, $retStr); } return $retStr; @@ -1258,8 +1396,8 @@ public function setDbpk($pk){ } public function setDisplayFormat($f){ - if(!in_array($f,array('json','xml','rdf','turtle','html'))) $f = 'html'; + if(!in_array($f, array('json', 'xml', 'rdf', 'turtle', 'html'))) $f = 'html'; $this->displayFormat = $f; } } -?> \ No newline at end of file +?> diff --git a/classes/OccurrenceLabel.php b/classes/OccurrenceLabel.php index 472ecffafc..0b27517a59 100644 --- a/classes/OccurrenceLabel.php +++ b/classes/OccurrenceLabel.php @@ -18,7 +18,7 @@ public function __destruct(){ } //Label functions - public function queryOccurrences($postArr){ + public function queryOccurrences($postArr, $limit){ global $USER_RIGHTS; $canReadRareSpp = false; if($GLOBALS['IS_ADMIN'] || array_key_exists('CollAdmin', $USER_RIGHTS) || array_key_exists('RareSppAdmin', $USER_RIGHTS) || array_key_exists('RareSppReadAll', $USER_RIGHTS)){ @@ -137,7 +137,7 @@ public function queryOccurrences($postArr){ if($sqlWhere) $sql .= 'WHERE '.substr($sqlWhere, 4); if($sqlOrderBy) $sql .= ' ORDER BY '.substr($sqlOrderBy,1); else $sql .= ' ORDER BY (o.recordnumber+1)'; - $sql .= ' LIMIT 400'; + $sql .= ' LIMIT ' . $limit; //echo '
    '.$sql.'
    '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ @@ -175,7 +175,7 @@ public function getLabelArray($occidArr, $speciesAuthors = false){ //echo $sql1; exit; if($rs1 = $this->conn->query($sql1)){ while($row1 = $rs1->fetch_object()){ - $authorArr[$row1->occid] = $row1->author; + $authorArr[$row1->occid] = $row1->author ?? ''; } $rs1->free(); } @@ -199,12 +199,12 @@ public function getLabelArray($occidArr, $speciesAuthors = false){ $cnt = 0; while($r = $rs->fetch_object()){ $otherCatArr[$r->occid][$cnt]['v'] = $r->identifiervalue; - $otherCatArr[$r->occid][$cnt]['n'] = $r->identifiername; + $otherCatArr[$r->occid][$cnt]['n'] = $r->identifiername ?? ''; $cnt++; } $rs->free(); foreach($otherCatArr as $occid => $ocnArr){ - $verbIdStr = $retArr[$occid]['othercatalognumbers']; + $verbIdStr = $retArr[$occid]['othercatalognumbers'] ?? ''; $ocnStr = ''; foreach($ocnArr as $idArr){ $ocnStr .= '; '.($idArr['n']?$idArr['n'].': ':'').$idArr['v']; @@ -231,6 +231,9 @@ public function exportLabelCsvFile($postArr){ $labelArr = $this->getLabelArray($occidArr, $speciesAuthors); if($labelArr){ $fileName = 'labeloutput_'.time().".csv"; + ob_start(); + ob_clean(); + ob_end_flush(); header('Content-Description: Symbiota Label Output File'); header ('Content-Type: text/csv'); header ('Content-Disposition: attachment; filename="'.$fileName.'"'); @@ -299,7 +302,7 @@ public function getLabelBlock($blockArr,$occArr){ $fieldDivStr = ''; foreach($bArr['fieldBlock'] as $fieldArr){ $fieldName = strtolower($fieldArr['field']); - $fieldValue = trim($occArr[$fieldName]); + $fieldValue = trim($occArr[$fieldName] ?? ''); if($fieldValue){ if($delimiter && $cnt) $fieldDivStr .= $delimiter; $fieldDivStr .= ''; @@ -413,7 +416,7 @@ public function getLabelFormatArr($annotated = false){ else $retArr['g'] = array('labelFormats'=>array()); //Add collection defined label formats if($this->collid){ - $collFormatArr = json_decode($this->collArr['dynprops'],true); + $collFormatArr = json_decode($this->collArr['dynprops'] ?? '[]',true); if($annotated){ if(isset($collFormatArr['labelFormats'])){ foreach($collFormatArr['labelFormats'] as $k => $labelObj){ @@ -434,7 +437,7 @@ public function getLabelFormatArr($annotated = false){ $dynPropStr = $r->dynamicProperties; } $rs->free(); - $dynPropArr = json_decode($dynPropStr,true); + $dynPropArr = json_decode($dynPropStr ?? '',true); if($annotated){ if(isset($dynPropArr['labelFormats'])){ foreach($dynPropArr['labelFormats'] as $k => $labelObj){ diff --git a/classes/OccurrenceListManager.php b/classes/OccurrenceListManager.php index 194a606a20..271ce89796 100644 --- a/classes/OccurrenceListManager.php +++ b/classes/OccurrenceListManager.php @@ -27,7 +27,7 @@ public function getSpecimenMap($pageRequest,$cntPerPage){ $sqlWhere = $this->getSqlWhere(); if(!$this->recordCount || $this->reset) $this->setRecordCnt($sqlWhere); $sql = 'SELECT o.occid, c.collid, c.institutioncode, c.collectioncode, c.collectionname, c.icon, o.institutioncode AS instcodeoverride, o.collectioncode AS collcodeoverride, '. - 'o.catalognumber, o.family, o.sciname, o.scientificnameauthorship, o.tidinterpreted, o.recordedby, o.recordnumber, o.eventdate, o.year, o.startdayofyear, o.enddayofyear, '. + 'o.catalognumber, o.family, o.sciname, o.scientificnameauthorship, o.tidinterpreted, o.recordedby, o.recordnumber, o.eventdate, '. 'o.country, o.stateprovince, o.county, o.locality, o.decimallatitude, o.decimallongitude, o.localitysecurity, o.localitysecurityreason, '. 'o.habitat, o.substrate, o.minimumelevationinmeters, o.maximumelevationinmeters, o.observeruid, c.sortseq '. 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid '; @@ -39,8 +39,8 @@ public function getSpecimenMap($pageRequest,$cntPerPage){ $sql .= 'ORDER BY c.sortseq, c.collectionname '; $pageRequest = ($pageRequest - 1)*$cntPerPage; } - $sql .= ' LIMIT '.$pageRequest.",".$cntPerPage; - //echo "
    Spec sql: ".$sql."
    "; + $sql .= ' LIMIT ' . $pageRequest . ',' . $cntPerPage; + //echo '
    Spec sql: ' . $sql . '
    '; exit; $result = $this->conn->query($sql); if($result){ $securityCollArr = array(); @@ -84,7 +84,8 @@ public function getSpecimenMap($pageRequest,$cntPerPage){ $retArr[$row->occid]['obsuid'] = $row->observeruid; $retArr[$row->occid]['localitysecurity'] = $row->localitysecurity; if($securityClearance || $row->localitysecurity != 1){ - $retArr[$row->occid]['locality'] = str_replace('.,',',',$this->cleanOutStr(trim($row->locality,' ,;'))); + $locStr = $row->locality ?? ''; + $retArr[$row->occid]['locality'] = str_replace('.,',',',$this->cleanOutStr(trim($locStr,' ,;'))); $retArr[$row->occid]['declat'] = $row->decimallatitude; $retArr[$row->occid]['declong'] = $row->decimallongitude; $retArr[$row->occid]['collnum'] = $this->cleanOutStr($row->recordnumber); diff --git a/classes/OccurrenceLoans.php b/classes/OccurrenceLoans.php index af32991796..38e4b59d76 100644 --- a/classes/OccurrenceLoans.php +++ b/classes/OccurrenceLoans.php @@ -939,7 +939,7 @@ public function generateNextID($idType){ } if($rs = $this->conn->query($sql)){ - $maxnum = 1; + $maxnum = 0; while($r = $rs->fetch_object()){ $num = ''; diff --git a/classes/OccurrenceMaintenance.php b/classes/OccurrenceMaintenance.php index 334c8a5f60..c642c6ab8e 100644 --- a/classes/OccurrenceMaintenance.php +++ b/classes/OccurrenceMaintenance.php @@ -104,6 +104,52 @@ public function generalOccurrenceCleaning(){ $rs->free(); if(isset($occidArr)) $this->batchUpdateAuthor($occidArr); + //Update date fields - first look for bad year + $occidArr = array(); + $this->outputMsg('Updating individual date fields (e.g. day, month, year, startDayOfYear, endDayOfYear)... ',1); + $sql = 'SELECT occid FROM omoccurrences WHERE eventDate BETWEEN "1500-01-01" AND CURDATE() AND (year IS NULL OR year != YEAR(eventDate)) '; + if($this->collidStr) $sql .= 'AND collid IN('.$this->collidStr.')'; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $occidArr[] = $r->occid; + if(count($occidArr) > 1000){ + $this->batchUpdateDateFields($occidArr); + unset($occidArr); + } + } + $rs->free(); + if(isset($occidArr)) $this->batchUpdateDateFields($occidArr); + + //Then look for records with bad month + $occidArr = array(); + $sql = 'SELECT occid FROM omoccurrences WHERE eventDate BETWEEN "1500-01-01" AND CURDATE() AND (month IS NULL OR month != MONTH(eventDate)) '; + if($this->collidStr) $sql .= 'AND collid IN('.$this->collidStr.')'; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $occidArr[] = $r->occid; + if(count($occidArr) > 1000){ + $this->batchUpdateDateFields($occidArr); + unset($occidArr); + } + } + $rs->free(); + if(isset($occidArr)) $this->batchUpdateDateFields($occidArr); + + //Then look for records with bad day + $occidArr = array(); + $sql = 'SELECT occid FROM omoccurrences WHERE eventDate BETWEEN "1500-01-01" AND CURDATE() AND (day IS NULL OR day != DAY(eventDate)) '; + if($this->collidStr) $sql .= 'AND collid IN('.$this->collidStr.')'; + $rs = $this->conn->query($sql); + while($r = $rs->fetch_object()){ + $occidArr[] = $r->occid; + if(count($occidArr) > 1000){ + $this->batchUpdateDateFields($occidArr); + unset($occidArr); + } + } + $rs->free(); + if(isset($occidArr)) $this->batchUpdateDateFields($occidArr); + return $status; } @@ -276,6 +322,25 @@ private function batchUpdateAuthor($occidArr){ return $status; } + private function batchUpdateDateFields($occidArr){ + $status = false; + if($occidArr){ + //Update all date fields, no matter which date field was tested as bad + $sql = 'UPDATE omoccurrences + SET year = YEAR(eventDate), month = MONTH(eventDate), day = day(eventDate), startDayOfYear = DAYOFYEAR(eventDate), endDayOfYear = DAYOFYEAR(IFNULL(eventDate2,eventDate)) + WHERE occid IN(' . implode(',', $occidArr) . ')'; + if($this->conn->query($sql)){ + $status = true; + } + else{ + $this->errorArr[] = 'WARNING: unable to update date fields; '.$this->conn->error; + $this->outputMsg($this->errorArr,2); + $status = false; + } + } + return $status; + } + public function batchUpdateGeoreferenceIndex(){ $status = false; $this->outputMsg('Updating georeference index... ',1); @@ -312,9 +377,10 @@ public function protectGlobalSpecies(){ $sensitiveArr = $this->getSensitiveTaxa(); if($sensitiveArr){ - $sql = 'UPDATE omoccurrences '. - 'SET LocalitySecurity = 1 '. - 'WHERE (LocalitySecurity IS NULL OR LocalitySecurity = 0) AND (localitySecurityReason IS NULL) AND (tidinterpreted IN('.implode(',',$sensitiveArr).')) '; + $sql = 'UPDATE omoccurrences + SET localitySecurity = 1 + WHERE (localitySecurity IS NULL OR localitySecurity = 0) AND (localitySecurityReason IS NULL) + AND (cultivationStatus = 0 OR cultivationStatus IS NULL) AND (tidinterpreted IN(' . implode(',', $sensitiveArr) . ')) '; if($this->collidStr) $sql .= 'AND collid IN('.$this->collidStr.')'; if($this->conn->query($sql)){ $status += $this->conn->affected_rows; @@ -363,13 +429,13 @@ public function batchProtectStateRareSpecies(){ return $status; } - public function protectStateRareSpecies($clid,$locality){ + public function protectStateRareSpecies($clid, $locality){ $status = 0; $occArr = array(); $sql = 'SELECT o.occid FROM omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid '. 'INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '. 'INNER JOIN fmchklsttaxalink cl ON ts2.tid = cl.tid '. - 'WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (o.localitySecurityReason IS NULL) '. + 'WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (o.localitySecurityReason IS NULL) AND (o.cultivationStatus = 0 OR o.cultivationStatus IS NULL) '. 'AND (o.stateprovince = "'.$locality.'") AND (cl.clid = '.$clid.') AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ @@ -399,7 +465,7 @@ public function getStateProtectionCount($clid, $state){ 'FROM omoccurrences o INNER JOIN taxstatus ts1 ON o.tidinterpreted = ts1.tid '. 'INNER JOIN taxstatus ts2 ON ts1.tidaccepted = ts2.tidaccepted '. 'INNER JOIN fmchklsttaxalink cl ON ts2.tid = cl.tid '. - 'WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (o.localitySecurityReason IS NULL) '. + 'WHERE (o.localitysecurity IS NULL OR o.localitysecurity = 0) AND (o.localitySecurityReason IS NULL) AND (o.cultivationStatus = 0 OR o.cultivationStatus IS NULL) '. 'AND (o.stateprovince = "'.$state.'") AND (cl.clid = '.$clid.') AND (ts1.taxauthid = 1) AND (ts2.taxauthid = 1) '; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ @@ -471,8 +537,8 @@ public function updateCollectionStatsFull(){ WHERE o.collid = '.$collid.' GROUP BY o.family '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $family = str_replace(array('"',"'"),"",$r->family); - if($family){ + if($r->family){ + $family = str_replace(array('"', "'"), '', $r->family); $statsArr['families'][$family]['SpecimensPerFamily'] = $r->SpecimensPerFamily; $statsArr['families'][$family]['GeorefSpecimensPerFamily'] = $r->GeorefSpecimensPerFamily; $statsArr['families'][$family]['IDSpecimensPerFamily'] = $r->IDSpecimensPerFamily; @@ -489,8 +555,8 @@ public function updateCollectionStatsFull(){ WHERE o.collid = '.$collid.' GROUP BY o.country '; $rs = $this->conn->query($sql); while($r = $rs->fetch_object()){ - $country = str_replace(array('"',"'"),"",$r->country); - if($country){ + if($r->country){ + $country = str_replace(array('"', "'"), "", $r->country); $statsArr['countries'][$country]['CountryCount'] = $r->CountryCount; $statsArr['countries'][$country]['GeorefSpecimensPerCountry'] = $r->GeorefSpecimensPerCountry; $statsArr['countries'][$country]['IDSpecimensPerCountry'] = $r->IDSpecimensPerCountry; diff --git a/classes/OccurrenceManager.php b/classes/OccurrenceManager.php index 00f111fa16..5716de54f3 100644 --- a/classes/OccurrenceManager.php +++ b/classes/OccurrenceManager.php @@ -14,11 +14,18 @@ class OccurrenceManager extends OccurrenceTaxaManager { private $occurSearchProjectExists = 0; protected $searchSupportManager = null; protected $errorMessage; + private $LANG; public function __construct($type='readonly'){ parent::__construct($type); if(array_key_exists('reset',$_REQUEST) && $_REQUEST['reset']) $this->reset(); $this->readRequestVariables(); + $langTag = ''; + if(!empty($GLOBALS['LANG_TAG'])) $langTag = $GLOBALS['LANG_TAG']; + if($langTag != 'en' && file_exists($GLOBALS['SERVER_ROOT'] . '/content/lang/classes/OccurrenceManager.' . $langTag . '.php')) + include_once($GLOBALS['SERVER_ROOT'] . '/content/lang/classes/OccurrenceManager.' . $langTag . '.php'); + else include_once($GLOBALS['SERVER_ROOT'] . '/content/lang/classes/OccurrenceManager.en.php'); + $this->LANG = $LANG; } public function __destruct(){ @@ -51,6 +58,9 @@ public function getSqlWhere(){ protected function setSqlWhere(){ $sqlWhere = ''; if(array_key_exists("targetclid",$this->searchTermArr) && is_numeric($this->searchTermArr["targetclid"])){ + if(!$this->voucherManager){ + $this->setChecklistVariables($this->searchTermArr['targetclid']); + } $voucherVariableArr = $this->voucherManager->getQueryVariableArr(); if($voucherVariableArr){ if(isset($voucherVariableArr['collid'])) $this->searchTermArr['db'] = $voucherVariableArr['collid']; @@ -65,7 +75,7 @@ protected function setSqlWhere(){ if(!isset($voucherVariableArr['excludecult'])) $this->searchTermArr['includecult'] = 1; if(isset($voucherVariableArr['onlycoord'])){ //Include details to limit to coordinates - $this->displaySearchArr[] = 'Only include occurrences with coordinates'; + $this->displaySearchArr[] = $this->LANG['OCCURRENCES_WITH_COORDS']; $sqlWhere .= 'AND (o.decimallatitude IS NOT NULL) '; } } @@ -89,22 +99,28 @@ protected function setSqlWhere(){ //$this->displaySearchArr[] = $this->voucherManager->getQueryVariableStr(); } elseif(array_key_exists('clid',$this->searchTermArr) && preg_match('/^[0-9,]+$/', $this->searchTermArr['clid'])){ - if(isset($this->searchTermArr["cltype"]) && $this->searchTermArr["cltype"] == 'all'){ - $sqlWhere .= 'AND (cl.clid IN('.$this->searchTermArr['clid'].')) '; + $clidStr = $this->getClidStrWithChildren($this->searchTermArr['clid']); + if(isset($this->searchTermArr['cltype']) && $this->searchTermArr['cltype'] == 'all'){ + $sqlWhere .= 'AND (cl.clid IN(' . $clidStr . ')) '; } else{ - $sqlWhere .= 'AND (ctl.clid IN('.$this->searchTermArr['clid'].')) '; + $sqlWhere .= 'AND (ctl.clid IN(' . $clidStr . ')) '; } - $this->displaySearchArr[] = 'Checklist ID: '.$this->searchTermArr['clid']; + $this->displaySearchArr[] = $this->LANG['CHECKLIST_ID'] . ': ' . $this->searchTermArr['clid']; } elseif(array_key_exists('db',$this->searchTermArr) && $this->searchTermArr['db']){ - $sqlWhere .= OccurrenceSearchSupport::getDbWhereFrag($this->cleanInStr($this->searchTermArr['db'])); + $pattern = '/[^\d,]/'; + if (preg_match($pattern, $this->searchTermArr['db'])==0) { + $sqlWhere .= OccurrenceSearchSupport::getDbWhereFrag($this->cleanInStr($this->searchTermArr['db'])); + } } if(array_key_exists('datasetid',$this->searchTermArr)){ + $sqlWhere .= 'AND (ds.datasetid IN('.$this->searchTermArr['datasetid'].')) '; - $this->displaySearchArr[] = 'Dataset(s): '.$this->getDatasetTitle($this->searchTermArr['datasetid']); + $this->displaySearchArr[] = $this->LANG['DATASETS'] . ': ' . $this->getDatasetTitle($this->searchTermArr['datasetid']); } $sqlWhere .= $this->getTaxonWhereFrag(); + if(array_key_exists('country',$this->searchTermArr)){ $countryArr = explode(";",$this->searchTermArr["country"]); $tempArr = Array(); @@ -118,7 +134,7 @@ protected function setSqlWhere(){ } } $sqlWhere .= 'AND ('.implode(' OR ',$tempArr).') '; - $this->displaySearchArr[] = implode(' OR ',$countryArr); + $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $countryArr); } if(array_key_exists("state",$this->searchTermArr)){ $stateAr = explode(";",$this->searchTermArr["state"]); @@ -133,7 +149,7 @@ protected function setSqlWhere(){ } } $sqlWhere .= 'AND ('.implode(' OR ',$tempArr).') '; - $this->displaySearchArr[] = implode(' OR ',$stateAr); + $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $stateAr); } if(array_key_exists("county",$this->searchTermArr)){ $countyArr = explode(";",$this->searchTermArr["county"]); @@ -146,11 +162,12 @@ protected function setSqlWhere(){ else{ $term = $this->cleanInStr(trim(str_ireplace(' county',' ',$value),'%')); //if(strlen($term) < 4) $term .= ' '; + $tempArr[] = '(o.county LIKE "'.$term.'%")'; } } $sqlWhere .= 'AND ('.implode(' OR ',$tempArr).') '; - $this->displaySearchArr[] = implode(' OR ',$countyArr); + $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $countyArr); } if(array_key_exists('local',$this->searchTermArr)){ $localArr = explode(';',$this->searchTermArr['local']); @@ -193,11 +210,11 @@ protected function setSqlWhere(){ } else{ $tempSqlArr[] = '(MATCH(f.locality) AGAINST("'.implode(' ',$fullTextArr).'")) '; - $this->displaySearchArr[] = implode(' OR ',$fullTextArr); + $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $fullTextArr); } } $sqlWhere .= 'AND ('.implode(' OR ',$tempSqlArr).') '; - if($tempTermArr) $this->displaySearchArr[] = implode(' OR ',$tempTermArr); + if($tempTermArr) $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $tempTermArr); } if(array_key_exists("elevlow",$this->searchTermArr) || array_key_exists("elevhigh",$this->searchTermArr)){ $elevlow = -200; @@ -206,7 +223,7 @@ protected function setSqlWhere(){ if(array_key_exists("elevhigh",$this->searchTermArr)) $elevhigh = $this->searchTermArr["elevhigh"]; $sqlWhere .= 'AND (( minimumElevationInMeters >= '.$elevlow.' AND maximumElevationInMeters <= '.$elevhigh.' ) OR ' . '( maximumElevationInMeters is null AND minimumElevationInMeters >= '.$elevlow.' AND minimumElevationInMeters <= '.$elevhigh.' )) '; - $this->displaySearchArr[] = 'Elev: '.$elevlow.($elevhigh?' - '.$elevhigh:''); + $this->displaySearchArr[] = $this->LANG['ELEV'] . ': ' . $elevlow . ($elevhigh ? ' - ' . $elevhigh : ''); } if(array_key_exists("llbound",$this->searchTermArr)){ $llboundArr = explode(";",$this->searchTermArr["llbound"]); @@ -217,7 +234,7 @@ protected function setSqlWhere(){ $rLng = $llboundArr[3]; //$sqlWhere .= 'AND (o.DecimalLatitude BETWEEN '.$llboundArr[1].' AND '.$llboundArr[0].' AND o.DecimalLongitude BETWEEN '.$llboundArr[2].' AND '.$llboundArr[3].') '; $sqlWhere .= 'AND (ST_Within(p.point,GeomFromText("POLYGON(('.$uLat.' '.$rLng.','.$bLat.' '.$rLng.','.$bLat.' '.$lLng.','.$uLat.' '.$lLng.','.$uLat.' '.$rLng.'))"))) '; - $this->displaySearchArr[] = 'Lat: '.$llboundArr[1].' - '.$llboundArr[0].' Long: '.$llboundArr[2].' - '.$llboundArr[3]; + $this->displaySearchArr[] = $this->LANG['LAT'] . ': ' . $llboundArr[1] . ' - ' . $llboundArr[0] . ' ' . $this->LANG['LONG'] . ': '.$llboundArr[2].' - '.$llboundArr[3]; } } elseif(array_key_exists("llpoint",$this->searchTermArr)){ @@ -239,20 +256,12 @@ protected function setSqlWhere(){ //Add a more percise circular definition that will run on bounding box points $sqlWhere .= 'AND (( 3959 * acos( cos( radians('.$lat.') ) * cos( radians( o.DecimalLatitude ) ) * cos( radians( o.DecimalLongitude )'. ' - radians('.$lng.') ) + sin( radians('.$lat.') ) * sin(radians(o.DecimalLatitude)) ) ) < '.$radius.') '; - /* - if($this->hasFullSpatialSupport()){ - - } - else{ - - } - */ } - $this->displaySearchArr[] = $pointArr[0]." ".$pointArr[1]." +- ".$pointArr[2].$pointArr[3]; + $this->displaySearchArr[] = $pointArr[0] . ' ' . $pointArr[1] . ' +- ' . $pointArr[2] . $pointArr[3]; } elseif(array_key_exists('footprintwkt',$this->searchTermArr)){ $sqlWhere .= 'AND (ST_Within(p.point,GeomFromText("'.$this->searchTermArr['footprintwkt'].'"))) '; - $this->displaySearchArr[] = 'Polygon search (not displayed)'; + $this->displaySearchArr[] = $this->LANG['POLYGON_SEARCH']; } if(array_key_exists('collector',$this->searchTermArr)){ $collectorArr = explode(';',$this->searchTermArr['collector']); @@ -260,7 +269,7 @@ protected function setSqlWhere(){ $tempCollTextArr = Array(); if(count($collectorArr) == 1 && $collectorArr[0] == 'NULL'){ $tempCollSqlArr[] = '(o.recordedBy IS NULL)'; - $tempCollTextArr[] = 'Collector IS NULL'; + $tempCollTextArr[] = $this->LANG['COLLECTOR_IS_NULL']; } else{ $fullCollArr = array(); @@ -286,7 +295,7 @@ protected function setSqlWhere(){ } } if($tempCollSqlArr) $sqlWhere .= 'AND ('.implode(' OR ',$tempCollSqlArr).') '; - $this->displaySearchArr[] = implode(' OR ',$tempCollTextArr); + $this->displaySearchArr[] = implode(' ' . $this->LANG['OR'] . ' ', $tempCollTextArr); } if(array_key_exists("collnum",$this->searchTermArr)){ $collNumArr = explode(";",$this->cleanInStr($this->searchTermArr["collnum"])); @@ -331,7 +340,7 @@ protected function setSqlWhere(){ } if($dateArr[0] == 'NULL'){ $sqlWhere .= 'AND (o.eventdate IS NULL) '; - $this->displaySearchArr[] = 'Date IS NULL'; + $this->displaySearchArr[] = $this->LANG['DATE_IS_NULL']; } elseif($eDate1 = $this->cleanInStr($this->formatDate($dateArr[0]))){ $eDate2 = (count($dateArr)>1?$this->cleanInStr($this->formatDate($dateArr[1])):''); @@ -358,7 +367,7 @@ protected function setSqlWhere(){ $sqlWhere .= 'AND (o.eventdate = "'.$eDate1.'") '; } } - $this->displaySearchArr[] = $this->searchTermArr['eventdate1'].(isset($this->searchTermArr['eventdate2'])?' to '.$this->searchTermArr['eventdate2']:''); + $this->displaySearchArr[] = $this->searchTermArr['eventdate1'].(isset($this->searchTermArr['eventdate2'])?' - '.$this->searchTermArr['eventdate2']:''); } } if(array_key_exists('catnum',$this->searchTermArr)){ @@ -421,29 +430,39 @@ protected function setSqlWhere(){ $sqlWhere .= 'AND ('.substr($catWhere,3).') '; $this->displaySearchArr[] = $this->searchTermArr['catnum']; } - if(array_key_exists("typestatus",$this->searchTermArr)){ - $sqlWhere .= "AND (o.typestatus IS NOT NULL) "; - $this->displaySearchArr[] = 'is type'; + if(!empty($this->searchTermArr['materialsampletype'])){ + if($this->searchTermArr['materialsampletype'] == 'all-ms'){ + $sqlWhere .= 'AND (o.occid IN(SELECT occid FROM ommaterialsample)) '; + $this->displaySearchArr[] = $this->LANG['HAS_MATERIAL_SAMPLE']; + } + else{ + $sqlWhere .= 'AND (o.occid IN(SELECT occid FROM ommaterialsample WHERE sampleType = "' . $this->cleanInStr($this->searchTermArr['materialsampletype']) . '")) '; + $this->displaySearchArr[] = $this->LANG['MATERIAL_SAMPLE'] . ': ' . $this->searchTermArr['materialsampletype']; + } + } + if(array_key_exists('typestatus', $this->searchTermArr)){ + $sqlWhere .= 'AND (o.typestatus IS NOT NULL) '; + $this->displaySearchArr[] = $this->LANG['IS_TYPE']; } - if(array_key_exists("hasimages",$this->searchTermArr)){ - $sqlWhere .= "AND (o.occid IN(SELECT occid FROM images)) "; - $this->displaySearchArr[] = 'has images'; + if(array_key_exists('hasimages', $this->searchTermArr)){ + $sqlWhere .= 'AND (o.occid IN(SELECT occid FROM images)) '; + $this->displaySearchArr[] = $this->LANG['HAS_IMAGES']; } - if(array_key_exists("hasgenetic",$this->searchTermArr)){ - $sqlWhere .= "AND (o.occid IN(SELECT occid FROM omoccurgenetic)) "; - $this->displaySearchArr[] = 'has genetic data'; + if(array_key_exists('hasgenetic', $this->searchTermArr)){ + $sqlWhere .= 'AND (o.occid IN(SELECT occid FROM omoccurgenetic)) '; + $this->displaySearchArr[] = $this->LANG['HAS_GENETIC_DATA']; } - if(array_key_exists("hascoords",$this->searchTermArr)){ - $sqlWhere .= "AND (o.decimalLatitude IS NOT NULL) "; - $this->displaySearchArr[] = 'has geocoordinates'; + if(array_key_exists('hascoords', $this->searchTermArr)){ + $sqlWhere .= 'AND (o.decimalLatitude IS NOT NULL) '; + $this->displaySearchArr[] = $this->LANG['HAS_COORDINATES']; } if($sqlWhere){ - if(!array_key_exists("includecult",$this->searchTermArr)){ - $sqlWhere .= "AND (o.cultivationStatus IS NULL OR o.cultivationStatus = 0) "; - $this->displaySearchArr[] = 'excluding cultivated/captive occurrences'; + if(!array_key_exists('includecult', $this->searchTermArr)){ + $sqlWhere .= 'AND (o.cultivationStatus IS NULL OR o.cultivationStatus = 0) '; + $this->displaySearchArr[] = $this->LANG['EXCLUDE_CULTIVATED']; } else{ - $this->displaySearchArr[] = 'includes cultivated/captive occurrences'; + $this->displaySearchArr[] = $this->LANG['INCLUDE_CULTIVATED']; } } if(array_key_exists('attr',$this->searchTermArr)){ @@ -457,9 +476,9 @@ protected function setSqlWhere(){ $rs->free(); $displayStr = ''; foreach($traitArr as $traitName => $stateName){ - $displayStr .= $traitName.': '.implode(', ',$stateName).'; '; + $displayStr .= $traitName . ': ' . implode(', ', $stateName) . '; '; } - $this->displaySearchArr[] = trim($displayStr,'; '); + $this->displaySearchArr[] = trim($displayStr, '; '); } $sqlWhere .= 'AND (o.occid IN(SELECT occid FROM tmattributes WHERE stateid IN(' . $this->searchTermArr['attr'] . '))) '; } @@ -487,6 +506,27 @@ private function getAdditionIdentifiers($identFrag){ return $retArr; } + public function getClidStrWithChildren($clid){ + $retStr = $clid; + if(is_numeric($clid)){ + $sqlBase = 'SELECT ch.clidchild + FROM fmchecklists cl INNER JOIN fmchklstchildren ch ON cl.clid = ch.clid + INNER JOIN fmchecklists cl2 ON ch.clidchild = cl2.clid + WHERE (cl2.type != "excludespp") AND (ch.clid != ch.clidchild) AND cl.clid IN('; + $sql = $sqlBase . $clid . ')'; + do{ + $childStr = ''; + $rsChild = $this->conn->query($sql); + while($r = $rsChild->fetch_object()){ + $childStr .= ',' . $r->clidchild; + $retStr .= ',' . $r->clidchild; + } + $sql = $sqlBase . substr($childStr, 1) . ')'; + }while($childStr); + } + return $retStr; + } + protected function formatDate($inDate){ $retDate = OccurrenceUtilities::formatDate($inDate); return $retDate; @@ -494,34 +534,36 @@ protected function formatDate($inDate){ protected function getTableJoins($sqlWhere){ $sqlJoin = ''; - if(array_key_exists('clid',$this->searchTermArr) && $this->searchTermArr['clid']){ - if(strpos($sqlWhere,'ctl.clid')){ - $sqlJoin .= 'INNER JOIN fmvouchers v ON o.occid = v.occid INNER JOIN fmchklsttaxalink ctl ON v.clTaxaID = ctl.clTaxaID '; + if($sqlWhere){ + if(array_key_exists('clid',$this->searchTermArr) && $this->searchTermArr['clid']){ + if(strpos($sqlWhere,'ctl.clid')){ + $sqlJoin .= 'INNER JOIN fmvouchers v ON o.occid = v.occid INNER JOIN fmchklsttaxalink ctl ON v.clTaxaID = ctl.clTaxaID '; + } + else{ + $sqlJoin .= 'INNER JOIN fmchklsttaxalink cl ON o.tidinterpreted = cl.tid '; + } } - else{ - $sqlJoin .= 'INNER JOIN fmchklsttaxalink cl ON o.tidinterpreted = cl.tid '; + if(strpos($sqlWhere,'MATCH(f.recordedby)') || strpos($sqlWhere,'MATCH(f.locality)')){ + $sqlJoin .= 'INNER JOIN omoccurrencesfulltext f ON o.occid = f.occid '; } + if(strpos($sqlWhere,'e.taxauthid')){ + $sqlJoin .= 'INNER JOIN taxaenumtree e ON o.tidinterpreted = e.tid '; + } + if(strpos($sqlWhere,'ts.')){ + $sqlJoin .= 'LEFT JOIN taxstatus ts ON o.tidinterpreted = ts.tid '; + } + if(strpos($sqlWhere,'ds.datasetid')){ + $sqlJoin .= 'INNER JOIN omoccurdatasetlink ds ON o.occid = ds.occid '; + } + if(array_key_exists('polycoords',$this->searchTermArr) || strpos($sqlWhere,'p.point')){ + $sqlJoin .= 'INNER JOIN omoccurpoints p ON o.occid = p.occid '; + } + /* + if(array_key_exists('includeothercatnum',$this->searchTermArr)){ + $sqlJoin .= 'LEFT JOIN omoccuridentifiers oi ON o.occid = oi.occid '; + } + */ } - if(strpos($sqlWhere,'MATCH(f.recordedby)') || strpos($sqlWhere,'MATCH(f.locality)')){ - $sqlJoin .= 'INNER JOIN omoccurrencesfulltext f ON o.occid = f.occid '; - } - if(strpos($sqlWhere,'e.taxauthid')){ - $sqlJoin .= 'INNER JOIN taxaenumtree e ON o.tidinterpreted = e.tid '; - } - if(strpos($sqlWhere,'ts.family')){ - $sqlJoin .= 'LEFT JOIN taxstatus ts ON o.tidinterpreted = ts.tid '; - } - if(strpos($sqlWhere,'ds.datasetid')){ - $sqlJoin .= 'INNER JOIN omoccurdatasetlink ds ON o.occid = ds.occid '; - } - if(array_key_exists('polycoords',$this->searchTermArr) || strpos($sqlWhere,'p.point')){ - $sqlJoin .= 'INNER JOIN omoccurpoints p ON o.occid = p.occid '; - } - /* - if(array_key_exists('includeothercatnum',$this->searchTermArr)){ - $sqlJoin .= 'LEFT JOIN omoccuridentifiers oi ON o.occid = oi.occid '; - } - */ return $sqlJoin; } @@ -531,9 +573,9 @@ public function getFullCollectionList($catId = ''){ return $this->searchSupportManager->getFullCollectionList($catId); } - public function outputFullCollArr($collGrpArr, $targetCatID = 0, $displayIcons = true, $displaySearchButtons = true){ + public function outputFullCollArr($collGrpArr, $targetCatID = 0, $displayIcons = true, $displaySearchButtons = true, $collTypeLabel = '', $uniqGrouping=''){ if(!$this->searchSupportManager) $this->searchSupportManager = new OccurrenceSearchSupport($this->conn); - $this->searchSupportManager->outputFullCollArr($collGrpArr, $targetCatID, $displayIcons, $displaySearchButtons); + $this->searchSupportManager->outputFullCollArr($collGrpArr, $targetCatID, $displayIcons, $displaySearchButtons, $collTypeLabel, $uniqGrouping); } public function getOccurVoucherProjects(){ @@ -614,12 +656,19 @@ public function getQueryTermStr(){ $retStr = ''; foreach($this->searchTermArr as $k => $v){ if(is_array($v)) $v = implode(',', $v); - if($v) $retStr .= '&'.$this->cleanOutStr($k).'='.$this->cleanOutStr($v); + if($v) $retStr .= '&'. $this->cleanOutStr($k) . '=' . $this->cleanOutStr($v); } if(isset($this->taxaArr['search'])){ - $retStr .= '&taxa='.$this->cleanOutStr($this->taxaArr['search']); + $patternTaxonChars = '/^[a-zA-Z0-9\s\-\,\.׆]*$/'; + if (preg_match($patternTaxonChars, $this->getTaxaSearchTerm())==1) { + $retStr .= '&taxa=' . $this->getTaxaSearchTerm(); + } if($this->taxaArr['usethes']) $retStr .= '&usethes=1'; - $retStr .= '&taxontype='.$this->taxaArr['taxontype']; + if(is_numeric($this->taxaArr['taxontype'])) { + $retStr .= '&taxontype=' . intval($this->taxaArr['taxontype']); + } else { + $retStr .= '&taxontype=1'; + } } return substr($retStr, 1); } @@ -650,7 +699,8 @@ protected function readRequestVariables(){ if(array_key_exists('searchvar',$_REQUEST)){ $parsedArr = array(); $taxaArr = array(); - parse_str($_REQUEST['searchvar'], $parsedArr); + $searchVar = str_replace('&', '&', $_REQUEST['searchvar']); + parse_str($searchVar, $parsedArr); if(isset($parsedArr['taxa'])){ $taxaArr['taxa'] = $this->cleanInputStr($parsedArr['taxa']); @@ -687,7 +737,7 @@ protected function readRequestVariables(){ $this->searchTermArr['clid'] = $clidStr; } elseif(array_key_exists('db',$_REQUEST) && $_REQUEST['db']){ - $dbStr = $this->cleanInputStr(OccurrenceSearchSupport::getDbRequestVariable($_REQUEST)); + $dbStr = $this->cleanInputStr(OccurrenceSearchSupport::getDbRequestVariable()); if(preg_match('/^[0-9,;]+$/', $dbStr)) $this->searchTermArr['db'] = $dbStr; } if(array_key_exists('datasetid',$_REQUEST) && $_REQUEST['datasetid']){ @@ -726,9 +776,9 @@ protected function readRequestVariables(){ $state = $this->cleanInputStr($_REQUEST['state']); if($state){ if(strlen($state) == 2 && (!isset($this->searchTermArr['country']) || stripos($this->searchTermArr['country'],'USA') !== false)){ - $sql = 'SELECT s.statename, c.countryname '. - 'FROM lkupstateprovince s INNER JOIN lkupcountry c ON s.countryid = c.countryid '. - 'WHERE c.countryname IN("USA","United States") AND (s.abbrev = "'.$state.'")'; + $sql = 'SELECT s.geoTerm AS statename, c.geoTerm AS countryname + FROM geographicthesaurus s INNER JOIN geographicthesaurus c ON s.parentID = c.geoThesID + WHERE c.geoTerm IN("USA","United States") AND (s.abbreviation = "'.$state.'")'; $rs = $this->conn->query($sql); if($r = $rs->fetch_object()){ $state = $r->statename; @@ -823,6 +873,15 @@ protected function readRequestVariables(){ unset($this->searchTermArr['catnum']); } } + if(array_key_exists('materialsampletype',$_REQUEST)){ + if($matSample = $this->cleanInputStr($_REQUEST['materialsampletype'])){ + $this->searchTermArr['materialsampletype'] = $matSample; + + } + else{ + unset($this->searchTermArr['materialsampletype']); + } + } if(array_key_exists('typestatus',$_REQUEST)){ if($_REQUEST['typestatus']) $this->searchTermArr['typestatus'] = true; else unset($this->searchTermArr['typestatus']); @@ -926,31 +985,22 @@ protected function readRequestVariables(){ } private function setChecklistVariables($clid){ - $this->voucherManager = new ChecklistVoucherAdmin($this->conn); + $this->voucherManager = new ChecklistVoucherAdmin(); $this->voucherManager->setClid($clid); $this->voucherManager->setCollectionVariables(); } //Misc support functions - private function hasFullSpatialSupport(){ - $serverStr = ''; - if(mysqli_get_server_info($this->conn)) $serverStr = mysqli_get_server_info($this->conn); - else $serverStr = shell_exec('mysql -V'); - if($serverStr){ - if(strpos($serverStr,'MariaDB') !== false) return true; - else{ //db = mysql; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@',$serverStr,$m); - $mysqlVerNums = explode('.', $m[0]); - if($mysqlVerNums[0] > 5) return true; - elseif($mysqlVerNums[0] == 5){ - if($mysqlVerNums[1] > 6) return true; - elseif($mysqlVerNums[1] == 6){ - if($mysqlVerNums[2] >= 1) return true; - } - } + public function getMaterialSampleTypeArr(){ + $retArr = array(); + $sql = 'SELECT DISTINCT sampleType FROM ommaterialsample ORDER BY sampleType'; + if($rs = $this->conn->query($sql)){ + while($r = $rs->fetch_object()){ + $retArr[] = $r->sampleType; } + $rs->close(); } - return false; + return $retArr; } //Setters and getters diff --git a/classes/OccurrenceMapManager.php b/classes/OccurrenceMapManager.php index 837b97feff..3c2749400f 100644 --- a/classes/OccurrenceMapManager.php +++ b/classes/OccurrenceMapManager.php @@ -55,39 +55,42 @@ public function getCoordinateMap($start, $limit){ if($this->sqlWhere){ $statsManager = new OccurrenceAccessStats(); $sql = 'SELECT o.occid, CONCAT_WS(" ",o.recordedby,IFNULL(o.recordnumber,o.eventdate)) AS identifier, '. - 'o.sciname, o.family, o.tidinterpreted, o.DecimalLatitude, o.DecimalLongitude, o.collid, o.catalognumber, '. + 'o.sciname, IF(ts.family IS NULL, o.family, ts.family) as family, o.tidinterpreted, o.DecimalLatitude, o.DecimalLongitude, o.collid, o.catalognumber, '. 'o.othercatalognumbers, c.institutioncode, c.collectioncode, c.CollectionName '. 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid '; + + $this->sqlWhere .= 'AND ts.taxauthid = 1 '; + $sql .= $this->getTableJoins($this->sqlWhere); + $sql .= $this->sqlWhere; + if(is_numeric($start) && $limit){ $sql .= "LIMIT ".$start.",".$limit; } - //echo "
    SQL: ".$sql."
    "; exit; + //echo '//SQL: ' . $sql; $result = $this->conn->query($sql); $color = 'e69e67'; $occidArr = array(); while($row = $result->fetch_object()){ - if(($row->DecimalLongitude <= 180 && $row->DecimalLongitude >= -180) && ($row->DecimalLatitude <= 90 && $row->DecimalLatitude >= -90)){ - $occidArr[] = $row->occid; - $collName = $row->CollectionName; - $tidInterpreted = $this->htmlEntities($row->tidinterpreted); - $latLngStr = $row->DecimalLatitude.",".$row->DecimalLongitude; - $coordArr[$collName][$row->occid]["llStr"] = $latLngStr; - $coordArr[$collName][$row->occid]["collid"] = $this->htmlEntities($row->collid); - //$tidcode = strtolower(str_replace(" ", "",$tidInterpreted.$row->sciname)); - //$tidcode = preg_replace( "/[^A-Za-z0-9 ]/","",$tidcode); - //$coordArr[$collName][$occId]["ns"] = $this->htmlEntities($tidcode); - $coordArr[$collName][$row->occid]["tid"] = $tidInterpreted; - $coordArr[$collName][$row->occid]["fam"] = ($row->family?strtoupper($row->family):'undefined'); - $coordArr[$collName][$row->occid]["sn"] = $row->sciname; - $coordArr[$collName][$row->occid]["id"] = $this->htmlEntities($row->identifier); - //$coordArr[$collName][$occId]["icode"] = $this->htmlEntities($row->institutioncode); - //$coordArr[$collName][$occId]["ccode"] = $this->htmlEntities($row->collectioncode); - //$coordArr[$collName][$occId]["cn"] = $this->htmlEntities($row->catalognumber); - //$coordArr[$collName][$occId]["ocn"] = $this->htmlEntities($row->othercatalognumbers); - $coordArr[$collName]["c"] = $color; - } + $occidArr[] = $row->occid; + $collName = $row->CollectionName; + $tidInterpreted = $this->htmlEntities($row->tidinterpreted); + $latLngStr = $row->DecimalLatitude.",".$row->DecimalLongitude; + $coordArr[$collName][$row->occid]["llStr"] = $latLngStr; + $coordArr[$collName][$row->occid]["collid"] = $this->htmlEntities($row->collid); + //$tidcode = strtolower(str_replace(" ", "",$tidInterpreted.$row->sciname)); + //$tidcode = preg_replace( "/[^A-Za-z0-9 ]/","",$tidcode); + //$coordArr[$collName][$occId]["ns"] = $this->htmlEntities($tidcode); + $coordArr[$collName][$row->occid]["tid"] = $tidInterpreted; + $coordArr[$collName][$row->occid]["fam"] = ($row->family?strtoupper($row->family):'undefined'); + $coordArr[$collName][$row->occid]["sn"] = $row->sciname; + $coordArr[$collName][$row->occid]["id"] = $this->htmlEntities($row->identifier); + //$coordArr[$collName][$occId]["icode"] = $this->htmlEntities($row->institutioncode); + //$coordArr[$collName][$occId]["ccode"] = $this->htmlEntities($row->collectioncode); + //$coordArr[$collName][$occId]["cn"] = $this->htmlEntities($row->catalognumber); + //$coordArr[$collName][$occId]["ocn"] = $this->htmlEntities($row->othercatalognumbers); + $coordArr[$collName]["c"] = $color; } $statsManager->recordAccessEventByArr($occidArr, 'map'); if(array_key_exists('undefined',$coordArr)){ @@ -105,44 +108,41 @@ public function getMappingData($recLimit, $extraFieldArr = null){ $coordArr = array(); if($this->sqlWhere){ $statsManager = new OccurrenceAccessStats(); - $sql = 'SELECT DISTINCT o.occid, CONCAT_WS(" ",o.recordedby,IFNULL(o.recordnumber,o.eventdate)) AS collector, o.sciname, o.tidinterpreted, '. - 'o.decimallatitude, o.decimallongitude, o.catalognumber, o.othercatalognumbers, c.institutioncode, c.collectioncode, c.colltype '; + $sql = 'SELECT DISTINCT o.occid, CONCAT_WS(" ",o.recordedby,IFNULL(o.recordnumber,o.eventdate)) AS collector, o.sciname, o.tidinterpreted, + o.decimallatitude, o.decimallongitude, o.catalognumber, o.othercatalognumbers, c.institutioncode, c.collectioncode, c.colltype '; if(isset($extraFieldArr) && is_array($extraFieldArr)){ foreach($extraFieldArr as $fieldName){ - $sql .= ", o.".$fieldName." "; + $sql .= ', o.' . $fieldName . ' '; } } $sql .= 'FROM omoccurrences o INNER JOIN omcollections c ON o.collid = c.collid '; $sql .= $this->getTableJoins($this->sqlWhere); $sql .= $this->sqlWhere; - $sql .= 'AND (o.decimallatitude BETWEEN -90 AND 90) AND (o.decimallongitude BETWEEN -180 AND 180) '; if(is_numeric($start) && $recLimit && is_numeric($recLimit)) $sql .= "LIMIT ".$start.",".$recLimit; - //echo "
    SQL: ".$sql."
    "; + //echo '
    SQL: ' . $sql . '
    '; $rs = $this->conn->query($sql); $occidArr = array(); while($r = $rs->fetch_assoc()){ - if(($r['decimallongitude'] <= 180 && $r['decimallongitude'] >= -180) && ($r['decimallatitude'] <= 90 && $r['decimallatitude'] >= -90)){ - $sciname = $r['sciname']; - if(!$sciname) $sciname = 'undefined'; - $coordArr[$sciname][$r['occid']]['instcode'] = $r['institutioncode']; - if($r['collectioncode']) $coordArr[$sciname][$r['occid']]['collcode'] = $r['collectioncode']; - $collType = 'obs'; - if(stripos($r['colltype'],'specimen')) $collType = 'spec'; - $coordArr[$sciname][$r['occid']]['colltype'] = $collType; - if($r['catalognumber']) $coordArr[$sciname][$r['occid']]['catnum'] = $r['catalognumber']; - if($r['othercatalognumbers']) $coordArr[$sciname][$r['occid']]['ocatnum'] = $r['othercatalognumbers']; - if($r['tidinterpreted']) $coordArr[$sciname]['tid'] = $r['tidinterpreted']; - $coordArr[$sciname][$r['occid']]['collector'] = $r['collector']; - $coordArr[$sciname][$r['occid']]['lat'] = $r['decimallatitude']; - $coordArr[$sciname][$r['occid']]['lng'] = $r['decimallongitude']; - if(isset($extraFieldArr) && is_array($extraFieldArr)){ - reset($extraFieldArr); - foreach($extraFieldArr as $fieldName){ - if(isset($r[$fieldName])) $coordArr[$sciname][$r['occid']][$fieldName] = $r[$fieldName]; - } + $sciname = $r['sciname']; + if(!$sciname) $sciname = 'undefined'; + $coordArr[$sciname][$r['occid']]['instcode'] = $r['institutioncode']; + if($r['collectioncode']) $coordArr[$sciname][$r['occid']]['collcode'] = $r['collectioncode']; + $collType = 'obs'; + if(stripos($r['colltype'],'specimen')) $collType = 'spec'; + $coordArr[$sciname][$r['occid']]['colltype'] = $collType; + if($r['catalognumber']) $coordArr[$sciname][$r['occid']]['catnum'] = $r['catalognumber']; + if($r['othercatalognumbers']) $coordArr[$sciname][$r['occid']]['ocatnum'] = $r['othercatalognumbers']; + if($r['tidinterpreted']) $coordArr[$sciname]['tid'] = $r['tidinterpreted']; + $coordArr[$sciname][$r['occid']]['collector'] = $r['collector']; + $coordArr[$sciname][$r['occid']]['lat'] = $r['decimallatitude']; + $coordArr[$sciname][$r['occid']]['lng'] = $r['decimallongitude']; + if(isset($extraFieldArr) && is_array($extraFieldArr)){ + reset($extraFieldArr); + foreach($extraFieldArr as $fieldName){ + if(isset($r[$fieldName])) $coordArr[$sciname][$r['occid']][$fieldName] = $r[$fieldName]; } - $occidArr[] = $r['occid']; } + $occidArr[] = $r['occid']; } $rs->free(); $statsManager->recordAccessEventByArr($occidArr, 'map'); @@ -193,6 +193,7 @@ private function setRecordCnt(){ if($this->sqlWhere){ $sql = "SELECT COUNT(DISTINCT o.occid) AS cnt FROM omoccurrences o ".$this->getTableJoins($this->sqlWhere).$this->sqlWhere; //echo "
    Count sql: ".$sql."
    "; + $result = $this->conn->query($sql); if($result){ if($row = $result->fetch_object()){ @@ -210,36 +211,39 @@ public function getRecordCnt(){ //SQL where functions private function setGeoSqlWhere(){ global $USER_RIGHTS; + $sqlWhere = $this->getSqlWhere(); if($this->searchTermArr){ - $sqlWhere = $this->getSqlWhere(); - $sqlWhere .= ($sqlWhere?'AND ':'WHERE ').'(o.DecimalLatitude IS NOT NULL AND o.DecimalLongitude IS NOT NULL) '; if(array_key_exists('clid',$this->searchTermArr) && $this->searchTermArr['clid']){ if(isset($this->searchTermArr['cltype']) && $this->searchTermArr['cltype'] == 'all'){ - $sqlWhere .= "AND (ST_Within(p.point,GeomFromText('".$this->getClFootprintWkt()." '))) "; + $sqlWhere .= ($sqlWhere ? ' AND' : ' WHERE' ) . "(ST_Within(p.point,GeomFromText('".$this->getClFootprintWkt()." '))) "; } else{ //$sqlWhere .= "AND (v.clid IN(".$this->searchTermArr['clid'].")) "; } } elseif(array_key_exists("polycoords",$this->searchTermArr)){ - $sqlWhere .= "AND (ST_Within(p.point,GeomFromText('".$this->searchTermArr["polycoords"]." '))) "; - } - //Check and exclude records with sensitive species protections - if(array_key_exists('SuperAdmin',$USER_RIGHTS) || array_key_exists('CollAdmin',$USER_RIGHTS) || array_key_exists('RareSppAdmin',$USER_RIGHTS) || array_key_exists('RareSppReadAll',$USER_RIGHTS)){ - //Is global rare species reader, thus do nothing to sql and grab all records - } - elseif(isset($USER_RIGHTS['RareSppReader']) || isset($USER_RIGHTS['CollEditor'])){ - $securityCollArr = array(); - if(isset($USER_RIGHTS['CollEditor'])) $securityCollArr = $USER_RIGHTS['CollEditor']; - if(isset($USER_RIGHTS['RareSppReader'])) $securityCollArr = array_unique(array_merge($securityCollArr, $USER_RIGHTS['RareSppReader'])); - $sqlWhere .= ' AND (o.CollId IN ('.implode(',',$securityCollArr).') OR (o.LocalitySecurity = 0 OR o.LocalitySecurity IS NULL)) '; - } - else{ - $sqlWhere .= ' AND (o.LocalitySecurity = 0 OR o.LocalitySecurity IS NULL) '; + $sqlWhere .= ($sqlWhere ? ' AND' : ' WHERE' ) . " (ST_Within(p.point,GeomFromText('".$this->searchTermArr["polycoords"]." '))) "; } - $this->sqlWhere = $sqlWhere; - //echo '
    sql: '.$this->sqlWhere.'
    '; exit; } + //Check and exclude records with sensitive species protections + if(array_key_exists('SuperAdmin',$USER_RIGHTS) || array_key_exists('CollAdmin',$USER_RIGHTS) || array_key_exists('RareSppAdmin',$USER_RIGHTS) || array_key_exists('RareSppReadAll',$USER_RIGHTS)){ + //Is global rare species reader, thus do nothing to sql and grab all records + } + elseif(isset($USER_RIGHTS['RareSppReader']) || isset($USER_RIGHTS['CollEditor'])){ + $securityCollArr = array(); + if(isset($USER_RIGHTS['CollEditor'])) $securityCollArr = $USER_RIGHTS['CollEditor']; + if(isset($USER_RIGHTS['RareSppReader'])) $securityCollArr = array_unique(array_merge($securityCollArr, $USER_RIGHTS['RareSppReader'])); + $sqlWhere .= ($sqlWhere ? ' AND' : ' WHERE' ) . ' (o.CollId IN ('.implode(',',$securityCollArr).') OR (o.LocalitySecurity = 0 OR o.LocalitySecurity IS NULL)) '; + } + elseif(!empty($sqlWhere)){ + $sqlWhere .= ($sqlWhere ? ' AND' : ' WHERE' ) . ' (o.LocalitySecurity = 0 OR o.LocalitySecurity IS NULL) '; + } + + if($sqlWhere) { + $sqlWhere .= ' AND ((o.decimallatitude BETWEEN -90 AND 90) AND (o.decimallongitude BETWEEN -180 AND 180)) '; + } + $this->sqlWhere = $sqlWhere; + //echo '
    sql: '.$this->sqlWhere.'
    '; exit; } //Shape functions @@ -348,7 +352,7 @@ public function writeKMLFile($recLimit, $extraFieldArr = null){ $cnt = 0; $coordArr = $this->getMappingData($recLimit, $extraFieldArr); if($coordArr){ - $this->googleIconArr = array('pushpin/ylw-pushpin','pushpin/blue-pushpin','pushpin/grn-pushpin','pushpin/ltblu-pushpin', + $googleIconArr = array('pushpin/ylw-pushpin','pushpin/blue-pushpin','pushpin/grn-pushpin','pushpin/ltblu-pushpin', 'pushpin/pink-pushpin','pushpin/purple-pushpin', 'pushpin/red-pushpin','pushpin/wht-pushpin','paddle/blu-blank', 'paddle/grn-blank','paddle/ltblu-blank','paddle/pink-blank','paddle/wht-blank','paddle/blu-diamond','paddle/grn-diamond', 'paddle/ltblu-diamond','paddle/pink-diamond','paddle/ylw-diamond','paddle/wht-diamond','paddle/red-diamond','paddle/purple-diamond', @@ -360,10 +364,10 @@ public function writeKMLFile($recLimit, $extraFieldArr = null){ foreach($coordArr as $sciname => $snArr){ unset($snArr['tid']); $cnt++; - $iconStr = $this->googleIconArr[$cnt%44]; + $iconStr = $googleIconArr[$cnt%44]; echo "\n"; echo " - -
    +
    +

    IGSN Management: '.$guidManager->getCollectionName().''; + echo '

    ' . $LANG['IGSN_MANAGE'] . ': '.$guidManager->getCollectionName().'

    '; if($statusStr){ ?>
    - Error Panel +
    getProductionMode()){ - echo '

    -- In Development Mode --

    '; + echo '

    -- ' . $LANG['DEV_MODE'] . ' --

    '; } if($namespace){ $guidCnt = $guidManager->getGuidCount($collid); @@ -148,15 +155,15 @@ function verifyProfileForm(f){ $guidAllCollCnt = $guidManager->getGuidCount(); ?>
    - IGSN Profile Details & Statistics -

    IGSN Namespace:

    -

    IGSN generation method:

    -

    GUIDs within collection:

    -

    Occurrences without GUIDs:

    + +

    +

    +

    +

    $guidCnt){ ?> -

    GUIDs using above namespace across all collections:

    +

    @@ -165,10 +172,10 @@ function verifyProfileForm(f){ - + - +
    @@ -177,7 +184,7 @@ function verifyProfileForm(f){ - +
    @@ -188,33 +195,33 @@ function verifyProfileForm(f){ ?>
    - IGSN Registration Profile +

    - Username: - - + + +
    -
    Password:
    - +
    +

    @@ -223,11 +230,11 @@ function verifyProfileForm(f){ You are not authorized to access this page or collection identifier has not been set'; + else echo '

    ' . $LANG['NOT_AUTH'] . '

    '; ?> - \ No newline at end of file + diff --git a/collections/admin/igsnmapper.php b/collections/admin/igsnmapper.php index f1b31be590..24ff577e59 100644 --- a/collections/admin/igsnmapper.php +++ b/collections/admin/igsnmapper.php @@ -1,6 +1,10 @@ - + + - IGSN GUID Mapper + <?php echo $LANG['IGSN_GUID_MAPPER'] ?> - + + + + + + + + +
    +

    +

    getCollMeta('collName'); ?>

    +
    + : + +
    + ' . $LANG['ERR_NOT_AUTH'] . ''; + } + elseif(!$importManager->getCollMeta('collName')){ + echo '

    ' . $LANG['ERR_COLL_NOT_VALID'] . '

    '; + } + else{ + $actionStatus = false; + if($action == 'importData'){ + ?> +
    + + setCreateNewRecord($createNew); + echo '
      '; + echo '
    • '.$LANG['STARTING_PROCESS'].' '.$fileName.' ('.date('Y-m-d H:i:s').')
    • '; + if($importManager->loadData($_POST)){ + echo '
    • '.$LANG['DONE_PROCESSING'].' ('.date('Y-m-d H:i:s').')
    • '; + } + echo '
    '; + ?> +
    + importFile()){ + $importManager->setTargetFieldArr($associationType); + ?> + +
    + + +
    + + +
    + +
    + + +
    +
    + + +
    + +
    + getFieldMappingTable(); + ?> +
    + +
    + > + +
    + +
    + + +
    + +
    + + + + +
    +
    + + getErrorMessage(); + } + if(!$actionStatus){ + ?> +
    +
    + +
    + +
    +
    + + +
    + +
    + + + +
    +
    +
    + +
    + + + diff --git a/collections/admin/index.php b/collections/admin/index.php index eae7091dad..9ed2524774 100644 --- a/collections/admin/index.php +++ b/collections/admin/index.php @@ -3,7 +3,8 @@ header('Content-Type: text/html; charset=' . $CHARSET); header('Location: '.$CLIENT_ROOT.'/index.php'); ?> - + + Forbidden -
    -

    Forbidden

    +
    +

    Access Denied

    +

    Forbidden

    You don't have permission to access this page.
    - + - Forbidden + <?php echo $LANG['FORBIDDEN']; ?> @@ -16,13 +20,13 @@ include($SERVER_ROOT.'/includes/header.php'); ?> -
    -

    Forbidden

    +
    +

    - You don't have permission to access this page. +
    loadFieldMap(true); ?> - + + <?php echo $DEFAULT_TITLE.' '.(isset($LANG['RESTORE'])?$LANG['RESTORE']:'Restore Backup'); ?> @@ -91,8 +92,8 @@ - - + + - -
    +
    +

    Restore Collection from Backup File

    '.(isset($LANG['CAUTION'])?$LANG['CAUTION']:'Caution').': '.(isset($LANG['MATCH_REPLACE'])?$LANG['MATCH_REPLACE']:'Matching records will be replaced with incoming records'); if($isEditor){ @@ -167,7 +169,7 @@ function verifyFileSize(inputObj){
    @@ -263,24 +265,24 @@ function verifyFileSize(inputObj){ $reportArr = $duManager->getTransferReport(); echo '
    '.(isset($LANG['OCCS_TRANSFERING'])?$LANG['OCCS_TRANSFERING']:'Occurrences pending transfer').': '.$reportArr['occur']; if($reportArr['occur']){ - echo ' '; - echo ' '; + echo ' '; + echo ' '; } echo '
    '; echo '
    '; echo '
    Records to be updated: '; echo $reportArr['update']; if($reportArr['update']){ - echo ' '; - echo ' '; + echo ' '; + echo ' '; } echo '
    '; if($reportArr['new']){ echo '
    Records to be restored: '; echo $reportArr['new']; if($reportArr['new']){ - echo ' '; - echo ' '; + echo ' '; + echo ' '; } echo '
    '; } @@ -288,8 +290,8 @@ function verifyFileSize(inputObj){ echo '
    Previous loaded records not matching incoming records: '; echo $reportArr['exist']; if($reportArr['exist']){ - echo ' '; - echo ' '; + echo ' '; + echo ' '; } echo '
    '; echo '
    '; @@ -334,7 +336,7 @@ function verifyFileSize(inputObj){ $errStr = 'ERROR: Either you have tried to reach this page without going through the collection management menuor you have tried to upload a file that is too large. You may want to breaking the upload file into smaller files or compressing the file into a zip archive (.zip extension). You may want to contact portal administrator to request assistance in uploading the file (hint to admin: increasing PHP upload limits may help, current upload_max_filesize = '; - echo (isset($LANG['NO_SETTING'])?$LANG['NO_SETTING']:$errStr).ini_get("upload_max_filesize").'; post_max_size = '.ini_get("post_max_size"); + echo (isset($LANG['NO_SETTING'])?$LANG['NO_SETTING']:$errStr) . '. ' . ini_get("upload_max_filesize") . '; post_max_size = '.ini_get("post_max_size") . '. '; echo (isset($LANG['USE_BACK'])?$LANG['USE_BACK']:'Use the back arrows to get back to the file upload page.'); ?>
    diff --git a/collections/admin/specupload.php b/collections/admin/specupload.php index afe662da15..ad9ccb690e 100644 --- a/collections/admin/specupload.php +++ b/collections/admin/specupload.php @@ -1,20 +1,15 @@ setCollId($collid); $duManager->setUspid($uspid); -$duManager->setUploadType($uploadType); +$duManager->readUploadParameters(); +if($uploadType) $duManager->setUploadType($uploadType); +else $uploadType = $duManager->getUploadType(); $isEditor = 0; if($IS_ADMIN || (array_key_exists('CollAdmin',$USER_RIGHTS) && in_array($collid,$USER_RIGHTS['CollAdmin']))){ $isEditor = 1; } -$duManager->readUploadParameters(); if($uploadType == $IPTUPLOAD || $uploadType == $SYMBIOTA){ if($duManager->getPath()) header('Location: specuploadmap.php?uploadtype='.$uploadType.'&uspid='.$uspid.'&collid='.$collid); } @@ -36,16 +32,17 @@ header('Location: specuploadprocessor.php?uploadtype='.$uploadType.'&uspid='.$uspid.'&collid='.$collid); } ?> - + + - <?php echo $DEFAULT_TITLE.' '.(isset($LANG['SPEC_UPLOAD'])?$LANG['SPEC_UPLOAD']:'Specimen Uploader - file selector'); ?> + <?php echo $DEFAULT_TITLE . ' ' . $LANG['SPEC_UPLOAD']; ?> - - + + - -
    +
    +

    '.(isset($LANG['UP_MODULE'])?$LANG['UP_MODULE']:'Data Upload Module').''; if($isEditor && $collid){ //Grab collection name and last upload date and display for all echo '
    '.$duManager->getCollInfo('name').'
    '; - echo '
    Last Upload Date: '.($duManager->getCollInfo('uploaddate')?$duManager->getCollInfo('uploaddate'):(isset($LANG['NOT_REC'])?$LANG['NOT_REC']:'not recorded')).'
    '; + echo '
    ' . $LANG['LAST_UPLOAD_DATE'] . ': ' . ($duManager->getCollInfo('uploaddate')?$duManager->getCollInfo('uploaddate'):$LANG['NOT_REC']) . '
    '; ?>
    - getTitle().': '.(isset($LANG['ID_SOURCE'])?$LANG['ID_SOURCE']:'Identify Data Source'); ?> + getTitle() . ': ' . $LANG['ID_SOURCE']; ?>
    - +
    :
    - +
    '.(isset($LANG['AUTOMAP'])?$LANG['AUTOMAP']:'Automap fields').'
    '; + echo '
    '; ?>
    - + @@ -169,12 +166,12 @@ function verifyFileSize(inputObj){ '.(isset($LANG['NOT_AUTH'])?$LANG['NOT_AUTH']:'ERROR: you are not authorized to upload to this collection').'
    '; + if(!$isEditor || !$collid) echo '
    ' . $LANG['NOT_AUTH'] . '
    '; else{ echo '
    '; - echo (isset($LANG['PAGE_ERROR'])?$LANG['PAGE_ERROR']:'').' = '; + echo $LANG['PAGE_ERROR'] . ' = '; echo ini_get("upload_max_filesize").'; post_max_size = '.ini_get('post_max_size'); - echo (isset($LANG['USE_BACK'])?$LANG['USE_BACK']:'Use the back arrows to get back to the file upload page.'); + echo $LANG['USE_BACK']; echo '
    '; } } diff --git a/collections/admin/specuploadmanagement.php b/collections/admin/specuploadmanagement.php index c82aa5bd14..d047b21de8 100644 --- a/collections/admin/specuploadmanagement.php +++ b/collections/admin/specuploadmanagement.php @@ -1,19 +1,16 @@ editUploadProfile($_POST)){ - $statusStr = (isset($LANG['SUCCESS_IMP'])?$LANG['SUCCESS_IMP']:'SUCCESS: Edits to import profile have been applied'); + $statusStr = $LANG['SUCCESS_IMP']; } else{ $statusStr = $duManager->getErrorStr(); @@ -39,7 +36,7 @@ } elseif($action == 'createProfile'){ if($duManager->createUploadProfile($_POST)){ - $statusStr = (isset($LANG['SUCCESS_UP'])?$LANG['SUCCESS_UP']:'SUCCESS: New upload profile added'); + $statusStr = $LANG['SUCCESS_UP']; } else{ $statusStr = $duManager->getErrorStr(); @@ -48,7 +45,7 @@ } elseif($action == 'Delete Profile'){ if($duManager->deleteUploadProfile($uspid)){ - $statusStr = (isset($LANG['SUCCESS_DEL'])?$LANG['SUCCESS_DEL']:'SUCCESS: Upload Profile Deleted'); + $statusStr = $LANG['SUCCESS_DEL']; } else{ $statusStr = $duManager->getErrorStr(); @@ -58,11 +55,11 @@ } $duManager->readUploadParameters(); ?> - - + + - - <?php echo $DEFAULT_TITLE.' '.(isset($LANG['UP_PROF_MAN'])?$LANG['UP_PROF_MAN']:'Specimen Upload Profile Manager'); ?> + + <?= $DEFAULT_TITLE . ' ' . $LANG['UP_PROF_MAN'] ?> @@ -77,22 +74,22 @@ function checkUploadListForm(f){ if (f.uspid[counter].checked) return true; } } - alert(""); + alert(""); return false; } function checkParameterForm(f){ if(f.title.value == ""){ - alert(""); + alert(""); return false; } else if(f.uploadtype.value == ""){ - alert(""); + alert(""); return false; } else if(f.uploadtype.value == 8 || f.uploadtype.value == 13){ if(f.path.value == ""){ - alert(""); + alert(""); return false; } } @@ -158,32 +155,16 @@ function adjustParameterForm(){ - - - - + -
    -

    +
    +

    '; @@ -201,10 +182,10 @@ function adjustParameterForm(){ ?>
    - +
    '; + echo ''; ?>
    $v){ ?>
    - - - - + + + + <?= $LANG['IMG_EDIT'] ?> +
    - .
    - . + .
    + .
    - +
    View All '; + echo 'View All '; ?>
    - : + :
    - : + :
    - - + + - + - +
    @@ -348,13 +330,13 @@ function adjustParameterForm(){ -
    +
    - +
    - - - + + +
    @@ -369,7 +351,7 @@ function adjustParameterForm(){ else{ ?>
    - +
    loadFieldMap(); } ?> - + + - <?php echo $DEFAULT_TITLE.' '.(isset($LANG['SPEC_UPLOAD'])?$LANG['SPEC_UPLOAD']:'Specimen Uploader'); ?> + <?php echo $DEFAULT_TITLE.' '.(isset($LANG['SPEC_UPLOAD']) ? $LANG['SPEC_UPLOAD'] : 'Specimen Uploader'); ?> - - + + - - -
    +
    +

    '.(isset($LANG['UP_MODULE'])?$LANG['UP_MODULE']:'Data Upload Module').''; if($statusStr){ echo '
    '; echo '
    '.$statusStr.'
    '; echo '
    '; } - $recReplaceMsg = ''.(isset($LANG['CAUTION'])?$LANG['CAUTION']:'Caution').': '.(isset($LANG['REC_REPLACE'])?$LANG['REC_REPLACE']:'Matching records will be replaced with incoming records'); + $recReplaceMsg = ''.(isset($LANG['CAUTION']) ? $LANG['CAUTION'] : 'Caution').': '.(isset($LANG['REC_REPLACE']) ? $LANG['REC_REPLACE'] : 'Matching records will be replaced with incoming records'); if($isEditor && $collid){ //Grab collection name and last upload date and display for all echo '
    '.$duManager->getCollInfo('name').'
    '; - echo '
    Last Upload Date: '.($duManager->getCollInfo('uploaddate')?$duManager->getCollInfo('uploaddate'):(isset($LANG['NOT_REC'])?$LANG['NOT_REC']:'not recorded')).'
    '; + echo '
    Last Upload Date: '.($duManager->getCollInfo('uploaddate') ? $duManager->getCollInfo('uploaddate') : (isset($LANG['NOT_REC']) ? $LANG['NOT_REC'] : 'not recorded')).'
    '; $processingList = array('unprocessed' => 'Unprocessed', 'stage 1' => 'Stage 1', 'stage 2' => 'Stage 2', 'stage 3' => 'stage 3', 'pending review' => 'Pending Review', 'expert required' => 'Expert Required', 'pending review-nfn' => 'Pending Review-NfN', 'reviewed' => 'Reviewed', 'closed' => 'Closed'); if(!$ulPath) $ulPath = $duManager->uploadFile(); @@ -290,7 +291,7 @@ function pkChanged(selObj){
    getTitle();?>
    - (): + (): getDbpk(); $dbpkTitle = 'Core ID'; @@ -301,17 +302,17 @@ function pkChanged(selObj){
    - () + ()
    - /> + /> '.(isset($LANG['VIEW_DETS'])?$LANG['VIEW_DETS']:'view details').')'; + echo '(' . htmlspecialchars((isset($LANG['VIEW_DETS']) ? $LANG['VIEW_DETS'] : 'view details'), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ')'; ?>
    - /> - + /> + view details)'; @@ -404,20 +405,20 @@ function pkChanged(selObj){
    '.(isset($LANG['RESET_MAP'])?$LANG['RESET_MAP']:'Reset Field Mapping').''; - echo ' '; + if($uspid) echo ''; + echo ' '; if(!$uspid){ echo '
    @@ -250,18 +246,18 @@ } elseif($action == 'activateOccurrences' || $finalTransfer){ echo '
      '; - $duManager->setTargetFieldArr(filter_var($_POST['fieldlist'], FILTER_SANITIZE_STRING)); + $duManager->setTargetFieldArr($_POST['fieldlist']); $duManager->finalTransfer(); echo '
    '; } } else{ - if(!$isEditor || !$collid) echo '

    '.(isset($LANG['NOT_AUTH'])?$LANG['NOT_AUTH']:'ERROR: you are not authorized to upload to this collection').'

    '; + if(!$isEditor || !$collid) echo '

    '.(isset($LANG['NOT_AUTH']) ? $LANG['NOT_AUTH'] : 'ERROR: you are not authorized to upload to this collection').'

    '; else{ echo '

    '; - echo (isset($LANG['PAGE_ERROR'])?$LANG['PAGE_ERROR']:'').' = '; + echo (isset($LANG['PAGE_ERROR']) ? $LANG['PAGE_ERROR'] : '').' = '; echo ini_get("upload_max_filesize").'; post_max_size = '.ini_get('post_max_size'); - echo (isset($LANG['USE_BACK'])?$LANG['USE_BACK']:'Use the back arrows to get back to the file upload page.'); + echo (isset($LANG['USE_BACK']) ? $LANG['USE_BACK'] : 'Use the back arrows to get back to the file upload page.'); echo '

    '; } } diff --git a/collections/admin/uploadreviewer.php b/collections/admin/uploadreviewer.php index e6211122f0..49f6fec5fb 100644 --- a/collections/admin/uploadreviewer.php +++ b/collections/admin/uploadreviewer.php @@ -33,8 +33,8 @@ $navStr = '
    '; if($SYMB_UID){ if(($pageIndex) >= $recLimit){ - $navStr .= '|<< | '; - $navStr .= '<<'; + $navStr .= '|<< | '; + $navStr .= '<<'; } else{ $navStr .= '|<< | <<'; @@ -44,8 +44,8 @@ $navStr .= (($pageIndex*$recLimit)+1).'-'.($recCnt<$highRange?$recCnt:$highRange).' of '.$recCnt.' records'; $navStr .= ' | '; if($recCnt > $highRange){ - $navStr .= '>> | '; - $navStr .= '>>|'; + $navStr .= '>> | '; + $navStr .= '>>|'; } else{ $navStr .= '>> | >>|'; @@ -54,7 +54,8 @@ } */ ?> - + + <?php echo (isset($LANG['UP_PREVIEW'])?$LANG['UP_PREVIEW']:'Record Upload Preview'); ?> @@ -68,6 +69,7 @@ ?> +

    Data Upload Reviewer

    $v){ - if(trim($v) && !array_key_exists($k,$headerArr)){ + if($v && trim($v) && !array_key_exists($k,$headerArr)){ $headerArr[$k] = $k; } } @@ -103,7 +105,7 @@ 'datelastmodified' => 'dateLastModified','processingstatus' => 'processingStatus','recordenteredby' => 'recordEnteredBy', 'basisofrecord' => 'basisOfRecord','occid' => 'occid (Primary Key)','dbpk'=>'dbpk (Source Identifier)'); ?> - +
    $v){ @@ -120,13 +122,15 @@ echo "\n"; foreach($headerArr as $k => $v){ $displayStr = $occArr[$k]; - if(strlen($displayStr) > 60){ - $displayStr = substr($displayStr,0,60).'...'; + if($displayStr){ + if(strlen($displayStr) > 60){ + $displayStr = substr($displayStr,0,60).'...'; + } + if($k == 'occid' && $searchVar != 'new') { + $displayStr = ''.$displayStr.''; + } } - if($k == 'occid' && $displayStr && $searchVar != 'new') { - $displayStr = ''.$displayStr.''; - } - if(!$displayStr) $displayStr = ' '; + else $displayStr = ' '; echo ''."\n"; } echo "\n"; diff --git a/collections/checklist.php b/collections/checklist.php index 98f578d4f6..3074a56b86 100644 --- a/collections/checklist.php +++ b/collections/checklist.php @@ -24,7 +24,7 @@ ?> @@ -36,7 +36,7 @@ ?> @@ -76,7 +76,7 @@ echo '
    '.$family.'
    '; foreach($sciNameArr as $sciName => $tid){ echo ''; @@ -86,7 +86,7 @@ echo '
    '.$LANG['FAMILY_NOT_DEFINED'].'
    '; foreach($undFamilyArray as $sciName => $tid){ echo ''; diff --git a/collections/cleaning/coordinatevalidator.php b/collections/cleaning/coordinatevalidator.php index 0c2de0ebb2..a37b2a4d81 100644 --- a/collections/cleaning/coordinatevalidator.php +++ b/collections/cleaning/coordinatevalidator.php @@ -28,16 +28,17 @@ if($IS_ADMIN) $isEditor = 1; ?> - + +<?php echo $DEFAULT_TITLE; ?> Coordinate Validator - - - + + + + -
    +
    +

    Duplicate Catalog Number Cleaning Tool

    '.$LANG['SUPERADMIN_NOTICE'].'
    '; @@ -122,12 +127,12 @@ function batchSwitchTargetSpecimens(cbElem){ $href = 'duplicatesearch.php?collid='.$collid.'&action='.$action.'&start='.($start+$limit); echo ''; } - echo '
    '; + echo '
    '; echo '
    '.($start+1).' '.$LANG['TO'].' '.($start+$recCnt).' '.$LANG['DUP_CLUSTERS'].'
    '; ?>
    -
    '.$displayStr.'
    +
    @@ -177,7 +182,7 @@ function batchSwitchTargetSpecimens(cbElem){
    - +
    • @@ -199,7 +204,7 @@ function batchSwitchTargetSpecimens(cbElem){ foreach($_POST['dupid'] as $v){ $vArr = explode('|',$v); if(count($vArr) > 1){ - $target = $_POST['dup'.str_replace(' ', '_', $vArr[0]).'target']; + $target = $_POST['dup' . $vArr[0] . 'target']; if($target != $vArr[1]) $dupArr[$target][] = $vArr[1]; } } @@ -212,13 +217,13 @@ function batchSwitchTargetSpecimens(cbElem){ if((count($dupArr)+2)>$limit){ ?>
      - +
      - +
    + \ No newline at end of file diff --git a/collections/cleaning/fieldstandardization.php b/collections/cleaning/fieldstandardization.php index c59500a502..978594b55a 100644 --- a/collections/cleaning/fieldstandardization.php +++ b/collections/cleaning/fieldstandardization.php @@ -1,6 +1,9 @@ - + + - <?php echo $DEFAULT_TITLE; ?> Field Standardization + <?php echo $DEFAULT_TITLE; ?> <?php echo $LANG['FIELD_STANDARDIZATION'] ?> @@ -47,13 +51,14 @@ if(!$dupArr) include($SERVER_ROOT.'/includes/header.php'); ?> - + -
    +
    +

    Field Standardization

    @@ -76,38 +81,44 @@ } ?>
    - Country -
    - - -
    + +
    +
    + + +
    +
    + + +
    +
    - Replacement Value: - + +
    You are not authorized to access this page'; + echo '

    ' . $LANG['NOT_AUTHORIZED'] . '

    '; } ?>
    diff --git a/collections/cleaning/imagerecycler.php b/collections/cleaning/imagerecycler.php index 24f94c933f..601cba5925 100644 --- a/collections/cleaning/imagerecycler.php +++ b/collections/cleaning/imagerecycler.php @@ -3,6 +3,9 @@ ini_set('display_errors', '1'); include_once('../../config/symbini.php'); include_once($SERVER_ROOT.'/classes/ImageCleaner.php'); +if($LANG_TAG != 'en' && file_exists($SERVER_ROOT.'/content/lang/collections/cleaning/imagerecycler.' . $LANG_TAG . '.php')) include_once($SERVER_ROOT.'/content/lang/collections/cleaning/imagerecycler.' . $LANG_TAG . '.php'); +else include_once($SERVER_ROOT . '/content/lang/collections/cleaning/imagerecycler.en.php'); + header("Content-Type: text/html; charset=".$CHARSET); $action = array_key_exists("submitaction",$_POST)?$_POST["submitaction"]:""; @@ -27,9 +30,10 @@ } } ?> - + + - <?php echo $DEFAULT_TITLE; ?> Image Recycler + <?php echo $DEFAULT_TITLE; ?> <?php echo $LANG['IMAGE_RECYCLER'] ?> -
    +
    +

    Image Recycler

    - Batch Image Remover +
    - This tool will batch delete images based on submission of multiple image identifiers. +
    - Image Identifiers
    +
    - +
    @@ -78,9 +83,9 @@ function verifyRecycleForm(f){ ERROR: collection identifier is not set'; + echo '' . $LANG['ERROR_COLLECTION'] . ''; } include($SERVER_ROOT.'/includes/footer.php'); ?> - \ No newline at end of file + diff --git a/collections/cleaning/index.php b/collections/cleaning/index.php index 52f9e40337..9f51276654 100644 --- a/collections/cleaning/index.php +++ b/collections/cleaning/index.php @@ -1,9 +1,10 @@ setObsUid($SYMB_UID); } ?> - + + - <?php echo $DEFAULT_TITLE; ?> Occurrence Cleaner + <?php echo $DEFAULT_TITLE . ' ' . (isset($LANG['OCC_CLEANER']) ? $LANG['OCC_CLEANER'] : 'Occurrence Cleaner')?> - @@ -43,76 +46,84 @@ include($SERVER_ROOT.'/includes/header.php'); ?> - + -
    +
    '.$collMap['collectionname'].' ('.$collMap['code'].')'; + echo '

    Data Cleaning Tools: ' . $collMap['collectionname'] .' (' . $collMap['code'] . ')

    '; ?> -
    Downloading a backup of your collection data before running any batch updates is strongly recommended
    +
    -

    Duplicate Records

    +

    - These tools will assist in searching this collection of records for duplicate records of the same specimen. - If duplicate records exist, this feature offers the ability to merge record values, images, - and data relationships into a single record. +
    -
    - List Duplicates based on... +
    +

    + + + +

    -
    +
    -

    Political Geography

    +

    - These tools help standardize country, state/province, and county designations. - They are also useful for locating and correcting misspelled geographical political units, - and even mismatched units, such as a state designation that does not match the wrong country. +
    -
    - Statistics and Action Panel +
    +

    + + + +

    -
    +
    -

    Taxonomy

    +

    -
    +
    Geography Cleaning Tools: ' . $collMap['collectionname'] . ' (' . $collMap['code'] . ')'; if($statusStr){ ?>
    @@ -156,14 +158,13 @@ function verifyNullCountyForm(f){
    '.$collMap['collectionname'].' ('.$collMap['code'].')'; if($isEditor){ ?> -
    - +
    +

    '.$LANG['MAIN_MENU'].'
    '; + if($mode) echo ''; echo '
    '.$LANG['GEO_REPORT_EXPLAIN'].'
    '; if($mode == 'badcountry'){ $badCountryArr = $cleanManager->getBadCountryArr(); @@ -181,7 +182,7 @@ function verifyNullCountyForm(f){
    ('.$countryCnt.')'; ?> - + @@ -263,7 +264,7 @@ function verifyNullCountyForm(f){ ('.$stateCnt.')'; ?> - + @@ -314,7 +315,7 @@ function verifyNullCountyForm(f){ ('.$countyCnt.')'; ?> - + @@ -370,7 +371,7 @@ function verifyNullCountyForm(f){ ('.$countyCnt.')'; ?> - + @@ -426,7 +427,7 @@ function verifyNullCountyForm(f){ ('.$localityCnt.')'; ?> - + @@ -474,45 +475,45 @@ function verifyNullCountyForm(f){ echo '
    '.$LANG['QUESTION_COUNTRIES'].': '; $badCountryCnt = $cleanManager->getBadCountryCount(); echo $badCountryCnt; - if($badCountryCnt) echo ' => '.$LANG['LIST_COUNTRIES'].'...'; + if($badCountryCnt) echo ' => ' . htmlspecialchars($LANG['LIST_COUNTRIES'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; //Get Null country and not null state echo '
    '.$LANG['NULL_COUNTRY_NOT_STATE'].': '; $nullCountryCnt = $cleanManager->getNullCountryNotStateCount(); echo $nullCountryCnt; - if($nullCountryCnt) echo ' => '.$LANG['LIST_RECORDS'].'...'; + if($nullCountryCnt) echo ' => ' . htmlspecialchars($LANG['LIST_RECORDS'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; echo '
    '.$LANG['QUESTION_STATES'].': '; $badStateCnt = $cleanManager->getBadStateCount(); echo $badStateCnt; - if($badStateCnt) echo ' => '.$LANG['LIST_STATES'].'...'; + if($badStateCnt) echo ' => ' . htmlspecialchars($LANG['LIST_STATES'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; //Get Null state and not null county or municipality echo '
    '.$LANG['NULL_STATE_NOT_COUNTY'].': '; $nullStateCnt = $cleanManager->getNullStateNotCountyCount(); echo $nullStateCnt; - if($nullStateCnt) echo ' => '.$LANG['LIST_RECORDS'].'...'; + if($nullStateCnt) echo ' => ' . htmlspecialchars($LANG['LIST_RECORDS'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; echo '
    '.$LANG['QUESTION_COUNTIES'].': '; $badCountiesCnt = $cleanManager->getBadCountyCount(); echo $badCountiesCnt; - if($badCountiesCnt) echo ' => '.$LANG['LIST_COUNTIES'].'...'; + if($badCountiesCnt) echo ' => ' . htmlspecialchars($LANG['LIST_COUNTIES'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; //Get Null county and not null locality echo '
    '.$LANG['NULL_COUNTY_NOT_LOCALITY'].': '; $nullCountyCnt = $cleanManager->getNullCountyNotLocalityCount(); echo $nullCountyCnt; - if($nullCountyCnt) echo ' => '.$LANG['LIST_RECORDS'].'...'; + if($nullCountyCnt) echo ' => ' . htmlspecialchars($LANG['LIST_RECORDS'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . '...'; echo '
    '; } } ?> -
    + -
    +
    getCollMap(); if($collid){ @@ -174,27 +183,27 @@ function verifyCleanerForm(f){
    Taxonomy Cleaning Tool: ' . $collMap[$collid]['collectionname'].' ('.$collMap[$collid]['code'].')'; } else{ - echo $LANG['MULT_CLEAN_TOOL'].' '.'('.count($activeCollArr).' '.$LANG['COLS'].')'; + echo '

    ' . $LANG['MULT_CLEAN_TOOL'].' '.'(' . htmlspecialchars(count($activeCollArr), ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ' ' . htmlspecialchars($LANG['COLS'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE) . ')

    '; } ?>
    1 && $activeCollArr){ ?> -
    +
    <?php echo (isset($LANG['ADD_BUTTON']) ? $LANG['ADD_BUTTON'] : 'Add Button') ?>
    -
    -
    - +
    +
    +

    - '.$LANG['SPECS'].': '.$badSpecimenCount.'
    '; ?> - '.$LANG['SCINAMES'].': '.$badTaxaCount.'
    '; ?> + '.$LANG['SPECS'].': '.$badSpecimenCount.'
    '; ?> + '.$LANG['SCINAMES'].': '.$badTaxaCount.'
    '; ?>

    @@ -256,18 +265,19 @@ function verifyCleanerForm(f){
    - + getTaxonomicResourceList(); foreach($taxResourceList as $taKey => $taValue){ - echo ' '.$taValue.'
    '; + echo ' '; + echo '
    '; } ?>
    - : -
    - : + +
    - : -
    -
    -
    :
    -
    />
    -
    />
    + +
    -
    +
    + +
    />
    +
    />
    +
    +
    @@ -311,7 +323,7 @@ function verifyCleanerForm(f){
    --> -
    +
    -
    -
    - +
    +
    +

    *
    -
    + - \ No newline at end of file + diff --git a/collections/datasets/datapublisher.php b/collections/datasets/datapublisher.php index 31a25c3d32..0d2ce17b0b 100644 --- a/collections/datasets/datapublisher.php +++ b/collections/datasets/datapublisher.php @@ -34,6 +34,7 @@ $includeAttributes = 1; $includeMatSample = 1; $redactLocalities = 1; + if ($action == 'savekey' || (isset($_REQUEST['datasetKey']) && $_REQUEST['datasetKey'])) { $collManager->setAggKeys($_POST); $collManager->updateAggKeys(); @@ -65,24 +66,31 @@ } } ?> - + + - - - <?php echo $LANG['DWCA_PUBLISHER']; ?> - - - + + - + + - @@ -101,16 +102,16 @@ function toggle(target){ include($SERVER_ROOT.'/includes/header.php'); ?> -
    +
    +

    Duplicate Manager

    @@ -134,23 +136,22 @@ function toggle(target){ if($isEditor){ if(!$action){ ?> -
    - +
    - + -
    @@ -158,19 +159,19 @@ function toggle(target){ if(!empty($ACTIVATE_EXSICCATI) && $collMap['colltype'] == 'Preserved Specimens'){ ?>
    - + -
    - + -
    -
    + '; - if($start) $paginationStr .= ''; + if($start) $paginationStr .= ''; $paginationStr .= '<< '.$LANG['PREVIOUS']; if($start) $paginationStr .= ''; $paginationStr .= ''; $paginationStr .= ' || '.($start+1).' - '.(count($clusterArr)<$limit?$totalCnt:($start + $limit)).' || '; $paginationStr .= ''; - if($totalCnt >= ($start+$limit)) $paginationStr .= ''; + if($totalCnt >= ($start+$limit)) $paginationStr .= ''; $paginationStr .= $LANG['NEXT'].' >>'; if($totalCnt >= ($start+$limit)) $paginationStr .= ''; $paginationStr .= ''; @@ -216,7 +217,7 @@ function toggle(target){
    - +
    '.$dupArr['desc'].'
    '; @@ -244,7 +245,7 @@ function toggle(target){ - +
    @@ -257,7 +258,7 @@ function toggle(target){ ?>
    - => + =>
    @@ -297,7 +298,7 @@ function toggle(target){ } ?>
    - +
    - \ No newline at end of file + diff --git a/collections/datasets/emlhandler.php b/collections/datasets/emlhandler.php index 1edeee9af1..25aa689ef7 100644 --- a/collections/datasets/emlhandler.php +++ b/collections/datasets/emlhandler.php @@ -2,13 +2,16 @@ include_once('../../config/symbini.php'); include_once($SERVER_ROOT.'/classes/DwcArchiverCore.php'); -$collid = filter_var($_REQUEST['collid'], FILTER_SANITIZE_NUMBER_INT); +$collid = (isset($_REQUEST['collid']) && is_numeric($_REQUEST['collid'])) ? filter_var($_REQUEST['collid'], FILTER_SANITIZE_NUMBER_INT) : ''; -if($collid && is_numeric($collid)){ +if($collid){ $dwcaManager = new DwcArchiverCore(); $dwcaManager->setCollArr($collid); if($collArr = $dwcaManager->getCollArr()){ + ob_start(); + ob_clean(); + ob_end_flush(); header('Content-Description: '.$collArr[$collid]['collname'].' EML'); header('Content-Type: text/xml; charset=utf-8'); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); diff --git a/collections/datasets/index.php b/collections/datasets/index.php index f715fc9606..1d315d6d2c 100644 --- a/collections/datasets/index.php +++ b/collections/datasets/index.php @@ -1,15 +1,16 @@ - + + - <?php echo $DEFAULT_TITLE; ?> Occurrence Dataset Manager + <?php echo $DEFAULT_TITLE; ?> <?php echo (isset($LANG['OCC_DAT_MNG']) ? $LANG['OCC_DAT_MNG'] : 'Occurrence Dataset Manager') ?> - - + + - + + - + + - + + - + + + -
    +
    +

    '.$occManager->getCollName().''; ?> -
    -
    - -
    +
    +
    +

    +
    -
    - : - - -
    -
    - : +
    +
    + + +
    +
    + +
    +
    +
    +
    -
    - - - - -
    +
    +
    + + +
    +
    + + +
    +
    @@ -330,18 +347,18 @@ function openPopup(urlStr){ if($statusStr){ echo ''; } ?> -
    +