浏览代码

Fixed merge conflict with 7.x-3.x

Stephen Ficklin 4 年之前
父节点
当前提交
f2e3d2fbb1
共有 65 个文件被更改,包括 2607 次插入469 次删除
  1. 159 0
      .all-contributorsrc
  2. 1 1
      .travis.yml
  3. 1 1
      CONTRIBUTING.md
  4. 32 0
      README.md
  5. 392 117
      composer.lock
  6. 30 30
      docs/contributing/governance.rst
  7. 5 5
      docs/contributing/pull_requests.rst
  8. 6 6
      docs/dev_guide/custom_field/manual_field_creation.rst
  9. 4 0
      docs/extensions/3rd_party.rst
  10. 7 0
      docs/extensions/analysis.rst
  11. 4 0
      docs/extensions/data_input.rst
  12. 9 1
      docs/extensions/search.rst
  13. 6 2
      docs/user_guide.rst
  14. 5 4
      docs/user_guide/bulk_loader.rst
  15. 二进制
      docs/user_guide/drupal_overview.create_content1.png
  16. 二进制
      docs/user_guide/drupal_overview.create_content2.png
  17. 二进制
      docs/user_guide/drupal_overview.find_content.png
  18. 4 0
      docs/user_guide/drupal_overview.rst
  19. 11 9
      docs/user_guide/example_genomics/pub_import.rst
  20. 6 6
      docs/user_guide/install_tripal/manual_install/install_drupal.rst
  21. 20 1
      docs/user_guide/install_tripal/upgrade_from_tripal2.rst
  22. 1 1
      docs/user_guide/job_management.rst
  23. 6 6
      docs/user_guide/web_services.rst
  24. 18 10
      tests/DataFactory.php
  25. 168 0
      tests/tripal/entities/PermissionsTest.php
  26. 194 0
      tests/tripal_chado/api/ChadoQueryTest.php
  27. 117 3
      tests/tripal_ws/http/TripalWebServicesContentTest.php
  28. 9 3
      tripal/api/tripal.entities.api.inc
  29. 109 0
      tripal/includes/TripalBundleController.inc
  30. 1 0
      tripal/includes/TripalEntityCollection.inc
  31. 0 8
      tripal/includes/TripalFields/TripalField.inc
  32. 0 8
      tripal/includes/TripalFields/TripalFieldFormatter.inc
  33. 1 1
      tripal/includes/TripalJob.inc
  34. 2 2
      tripal/includes/tripal.fields.inc
  35. 417 0
      tripal/includes/tripal.registration.inc
  36. 20 1
      tripal/includes/tripal.unpublish_orphans.inc
  37. 2 5
      tripal/theme/js/tripal.js
  38. 37 1
      tripal/tripal.module
  39. 2 1
      tripal_chado/api/modules/tripal_chado.pub.api.inc
  40. 10 2
      tripal_chado/api/tripal_chado.api.inc
  41. 49 44
      tripal_chado/api/tripal_chado.query.api.inc
  42. 9 4
      tripal_chado/api/tripal_chado.schema.api.inc
  43. 0 55
      tripal_chado/includes/ChadoDatabaseConnection.inc
  44. 265 0
      tripal_chado/includes/ChadoPrefixExtender.inc
  45. 8 13
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc
  46. 4 3
      tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_formatter.inc
  47. 2 0
      tripal_chado/includes/TripalFields/sio__references/sio__references.inc
  48. 5 0
      tripal_chado/includes/TripalFields/sio__references/sio__references_formatter.inc
  49. 2 1
      tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary_widget.inc
  50. 2 2
      tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc
  51. 24 7
      tripal_chado/includes/TripalImporter/FASTAImporter.inc
  52. 25 22
      tripal_chado/includes/TripalImporter/GFF3Importer.inc
  53. 20 4
      tripal_chado/includes/loaders/tripal_chado.pub_importer_PMID.inc
  54. 66 0
      tripal_chado/includes/loaders/tripal_chado.pub_importers.inc
  55. 25 12
      tripal_chado/includes/setup/tripal_chado.setup.inc
  56. 7 2
      tripal_chado/includes/tripal_chado.bundle.inc
  57. 16 8
      tripal_chado/includes/tripal_chado.db.inc
  58. 4 2
      tripal_chado/includes/tripal_chado.fields.inc
  59. 1 1
      tripal_chado/includes/tripal_chado.mapping.inc
  60. 6 1
      tripal_chado/includes/tripal_chado.publish.inc
  61. 4 9
      tripal_chado/includes/tripal_chado.semweb.inc
  62. 217 39
      tripal_chado/tripal_chado.install
  63. 1 1
      tripal_chado/tripal_chado.module
  64. 28 3
      tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc
  65. 1 1
      tripal_ws/tripal_ws.module

+ 159 - 0
.all-contributorsrc

@@ -0,0 +1,159 @@
+{
+  "files": [
+    "README.md"
+  ],
+  "imageSize": 100,
+  "commit": false,
+  "contributors": [
+    {
+      "login": "spficklin",
+      "name": "Stephen Ficklin",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/1719352?v=4",
+      "profile": "https://github.com/spficklin",
+      "contributions": [
+        "code",
+        "eventOrganizing",
+        "doc",
+        "projectManagement",
+        "review"
+      ]
+    },
+    {
+      "login": "bradfordcondon",
+      "name": "Bradford Condon",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/7063154?v=4",
+      "profile": "http://www.bradfordcondon.com/",
+      "contributions": [
+        "code",
+        "doc",
+        "projectManagement",
+        "eventOrganizing",
+        "review"
+      ]
+    },
+    {
+      "login": "laceysanderson",
+      "name": "Lacey-Anne Sanderson",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/1566301?v=4",
+      "profile": "https://laceysanderson.github.io/",
+      "contributions": [
+        "code",
+        "doc",
+        "projectManagement",
+        "eventOrganizing",
+        "review"
+      ]
+    },
+    {
+      "login": "chunhuaicheng",
+      "name": "chunhuaicheng",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/14333886?v=4",
+      "profile": "https://github.com/chunhuaicheng",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "shawnawsu",
+      "name": "Shawna",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/24374002?v=4",
+      "profile": "https://github.com/shawnawsu",
+      "contributions": [
+        "code",
+        "content",
+        "doc",
+        "review"
+      ]
+    },
+    {
+      "login": "mboudet",
+      "name": "mboudet",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/17642511?v=4",
+      "profile": "https://github.com/mboudet",
+      "contributions": [
+        "bug"
+      ]
+    },
+    {
+      "login": "guignonv",
+      "name": "Valentin Guignon",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/7290244?v=4",
+      "profile": "https://github.com/guignonv",
+      "contributions": [
+        "bug"
+      ]
+    },
+    {
+      "login": "mestato",
+      "name": "Meg Staton",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/508122?v=4",
+      "profile": "https://github.com/mestato",
+      "contributions": [
+        "fundingFinding",
+        "eventOrganizing"
+      ]
+    },
+    {
+      "login": "abretaud",
+      "name": "Anthony Bretaudeau",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/238755?v=4",
+      "profile": "https://github.com/abretaud",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "colthom",
+      "name": "colthom",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/17720870?v=4",
+      "profile": "https://github.com/colthom",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "almasaeed2010",
+      "name": "Abdullah Almsaeed",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/1512664?v=4",
+      "profile": "http://almsaeedstudio.com",
+      "contributions": [
+        "code",
+        "review"
+      ]
+    },
+    {
+      "login": "btski",
+      "name": "btski",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/32686196?v=4",
+      "profile": "https://github.com/btski",
+      "contributions": [
+        "question"
+      ]
+    },
+    {
+      "login": "ekcannon",
+      "name": "ekcannon",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/3409057?v=4",
+      "profile": "https://github.com/ekcannon",
+      "contributions": [
+        "ideas",
+        "eventOrganizing"
+      ]
+    },
+    {
+      "login": "jlwegrzyn",
+      "name": "jlwegrzyn",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/50996590?v=4",
+      "profile": "https://github.com/jlwegrzyn",
+      "contributions": [
+        "fundingFinding"
+      ]
+    }
+  ],
+  "contributorsPerLine": 7,
+  "projectName": "tripal",
+  "projectOwner": "tripal",
+  "repoType": "github",
+  "repoHost": "https://github.com",
+  "commitConvention": "none"
+}

+ 1 - 1
.travis.yml

@@ -2,7 +2,7 @@ language: php
 
 services:
   - docker
-  - postgres
+  - postgresql
 
 sudo: required
 

+ 1 - 1
CONTRIBUTING.md

@@ -1 +1 @@
-Please see the [Tripal online documentation](https://tripal.readthedocs.io/en/latest/dev_guide/contributing.html) for more information about contributing to Tripal and Tripal governance.
+Please see the [Tripal online documentation](https://tripal.readthedocs.io/en/latest/contributing/pull_requests.html#pull-request-pr-guideline) for more information about contributing to Tripal and Tripal governance.

+ 32 - 0
README.md

@@ -1,4 +1,5 @@
 [![7.x-3.x Build Status](https://travis-ci.org/tripal/tripal.svg?branch=7.x-3.x)](https://travis-ci.org/tripal/tripal)
+[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors)
 [![Documentation Status](https://readthedocs.org/projects/tripal/badge/?version=latest)](https://tripal.readthedocs.io/en/latest/?badge=latest)
 
 [![DOI](https://zenodo.org/badge/42666405.svg)](https://zenodo.org/badge/latestdoi/42666405)
@@ -85,3 +86,34 @@ Then run PHPUnit from your root Tripal directory.
 PHPUnit tests will also be run in the Travis CI build.
 
 Read our [testing guidelines](tests/README.md)
+
+## Contributors
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
+<!-- prettier-ignore -->
+<table>
+  <tr>
+    <td align="center"><a href="https://github.com/spficklin"><img src="https://avatars0.githubusercontent.com/u/1719352?v=4" width="100px;" alt="Stephen Ficklin"/><br /><sub><b>Stephen Ficklin</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=spficklin" title="Code">💻</a> <a href="#eventOrganizing-spficklin" title="Event Organizing">📋</a> <a href="https://github.com/tripal/tripal/commits?author=spficklin" title="Documentation">📖</a> <a href="#projectManagement-spficklin" title="Project Management">📆</a> <a href="#review-spficklin" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="http://www.bradfordcondon.com/"><img src="https://avatars2.githubusercontent.com/u/7063154?v=4" width="100px;" alt="Bradford Condon"/><br /><sub><b>Bradford Condon</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=bradfordcondon" title="Code">💻</a> <a href="https://github.com/tripal/tripal/commits?author=bradfordcondon" title="Documentation">📖</a> <a href="#projectManagement-bradfordcondon" title="Project Management">📆</a> <a href="#eventOrganizing-bradfordcondon" title="Event Organizing">📋</a> <a href="#review-bradfordcondon" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="https://laceysanderson.github.io/"><img src="https://avatars3.githubusercontent.com/u/1566301?v=4" width="100px;" alt="Lacey-Anne Sanderson"/><br /><sub><b>Lacey-Anne Sanderson</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=laceysanderson" title="Code">💻</a> <a href="https://github.com/tripal/tripal/commits?author=laceysanderson" title="Documentation">📖</a> <a href="#projectManagement-laceysanderson" title="Project Management">📆</a> <a href="#eventOrganizing-laceysanderson" title="Event Organizing">📋</a> <a href="#review-laceysanderson" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="https://github.com/chunhuaicheng"><img src="https://avatars2.githubusercontent.com/u/14333886?v=4" width="100px;" alt="chunhuaicheng"/><br /><sub><b>chunhuaicheng</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=chunhuaicheng" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/shawnawsu"><img src="https://avatars1.githubusercontent.com/u/24374002?v=4" width="100px;" alt="Shawna"/><br /><sub><b>Shawna</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=shawnawsu" title="Code">💻</a> <a href="#content-shawnawsu" title="Content">🖋</a> <a href="https://github.com/tripal/tripal/commits?author=shawnawsu" title="Documentation">📖</a> <a href="#review-shawnawsu" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="https://github.com/mboudet"><img src="https://avatars0.githubusercontent.com/u/17642511?v=4" width="100px;" alt="mboudet"/><br /><sub><b>mboudet</b></sub></a><br /><a href="https://github.com/tripal/tripal/issues?q=author%3Amboudet" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="https://github.com/guignonv"><img src="https://avatars1.githubusercontent.com/u/7290244?v=4" width="100px;" alt="Valentin Guignon"/><br /><sub><b>Valentin Guignon</b></sub></a><br /><a href="https://github.com/tripal/tripal/issues?q=author%3Aguignonv" title="Bug reports">🐛</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/mestato"><img src="https://avatars1.githubusercontent.com/u/508122?v=4" width="100px;" alt="Meg Staton"/><br /><sub><b>Meg Staton</b></sub></a><br /><a href="#fundingFinding-mestato" title="Funding Finding">🔍</a> <a href="#eventOrganizing-mestato" title="Event Organizing">📋</a></td>
+    <td align="center"><a href="https://github.com/abretaud"><img src="https://avatars3.githubusercontent.com/u/238755?v=4" width="100px;" alt="Anthony Bretaudeau"/><br /><sub><b>Anthony Bretaudeau</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=abretaud" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/colthom"><img src="https://avatars0.githubusercontent.com/u/17720870?v=4" width="100px;" alt="colthom"/><br /><sub><b>colthom</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=colthom" title="Documentation">📖</a></td>
+    <td align="center"><a href="http://almsaeedstudio.com"><img src="https://avatars2.githubusercontent.com/u/1512664?v=4" width="100px;" alt="Abdullah Almsaeed"/><br /><sub><b>Abdullah Almsaeed</b></sub></a><br /><a href="https://github.com/tripal/tripal/commits?author=almasaeed2010" title="Code">💻</a> <a href="#review-almasaeed2010" title="Reviewed Pull Requests">👀</a></td>
+    <td align="center"><a href="https://github.com/btski"><img src="https://avatars1.githubusercontent.com/u/32686196?v=4" width="100px;" alt="btski"/><br /><sub><b>btski</b></sub></a><br /><a href="#question-btski" title="Answering Questions">💬</a></td>
+    <td align="center"><a href="https://github.com/ekcannon"><img src="https://avatars0.githubusercontent.com/u/3409057?v=4" width="100px;" alt="ekcannon"/><br /><sub><b>ekcannon</b></sub></a><br /><a href="#ideas-ekcannon" title="Ideas, Planning, & Feedback">🤔</a> <a href="#eventOrganizing-ekcannon" title="Event Organizing">📋</a></td>
+    <td align="center"><a href="https://github.com/jlwegrzyn"><img src="https://avatars1.githubusercontent.com/u/50996590?v=4" width="100px;" alt="jlwegrzyn"/><br /><sub><b>jlwegrzyn</b></sub></a><br /><a href="#fundingFinding-jlwegrzyn" title="Funding Finding">🔍</a></td>
+  </tr>
+</table>
+
+<!-- ALL-CONTRIBUTORS-LIST:END -->
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

+ 392 - 117
composer.lock

@@ -229,32 +229,37 @@
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "1.4.2",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
-                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.4.0",
-                "psr/http-message": "~1.0"
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
             },
             "provide": {
                 "psr/http-message-implementation": "1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.0"
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+            },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.4-dev"
+                    "dev-master": "1.6-dev"
                 }
             },
             "autoload": {
@@ -284,26 +289,27 @@
             "keywords": [
                 "http",
                 "message",
+                "psr-7",
                 "request",
                 "response",
                 "stream",
                 "uri",
                 "url"
             ],
-            "time": "2017-03-20T17:10:46+00:00"
+            "time": "2019-07-01T23:21:34+00:00"
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.8.1",
+            "version": "1.9.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8"
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
-                "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea",
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea",
                 "shasum": ""
             },
             "require": {
@@ -338,7 +344,7 @@
                 "object",
                 "object graph"
             ],
-            "time": "2018-06-11T23:09:50+00:00"
+            "time": "2019-08-09T12:45:53+00:00"
         },
         {
             "name": "phar-io/manifest",
@@ -444,35 +450,33 @@
         },
         {
             "name": "phpdocumentor/reflection-common",
-            "version": "1.0.1",
+            "version": "2.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
-                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+                "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
-                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
+                "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5"
+                "php": ">=7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.6"
+                "phpunit/phpunit": "~6"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "2.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": [
-                        "src"
-                    ]
+                    "phpDocumentor\\Reflection\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -494,30 +498,30 @@
                 "reflection",
                 "static analysis"
             ],
-            "time": "2017-09-11T18:02:19+00:00"
+            "time": "2018-08-07T13:53:10+00:00"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "4.3.0",
+            "version": "4.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
+                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
-                "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
+                "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.0",
-                "phpdocumentor/reflection-common": "^1.0.0",
-                "phpdocumentor/type-resolver": "^0.4.0",
+                "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
+                "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
                 "webmozart/assert": "^1.0"
             },
             "require-dev": {
-                "doctrine/instantiator": "~1.0.5",
+                "doctrine/instantiator": "^1.0.5",
                 "mockery/mockery": "^1.0",
                 "phpunit/phpunit": "^6.4"
             },
@@ -545,41 +549,40 @@
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2017-11-30T07:14:17+00:00"
+            "time": "2019-09-12T14:27:41+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "0.4.0",
+            "version": "1.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
-                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+                "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.5 || ^7.0",
-                "phpdocumentor/reflection-common": "^1.0"
+                "php": "^7.1",
+                "phpdocumentor/reflection-common": "^2.0"
             },
             "require-dev": {
-                "mockery/mockery": "^0.9.4",
-                "phpunit/phpunit": "^5.2||^4.8.24"
+                "ext-tokenizer": "^7.1",
+                "mockery/mockery": "~1",
+                "phpunit/phpunit": "^7.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "phpDocumentor\\Reflection\\": [
-                        "src/"
-                    ]
+                    "phpDocumentor\\Reflection\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -592,20 +595,21 @@
                     "email": "me@mikevanriel.com"
                 }
             ],
-            "time": "2017-07-14T14:27:02+00:00"
+            "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+            "time": "2019-08-22T18:11:29+00:00"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.8.0",
+            "version": "1.8.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
-                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
                 "shasum": ""
             },
             "require": {
@@ -626,8 +630,8 @@
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Prophecy\\": "src/"
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -655,7 +659,7 @@
                 "spy",
                 "stub"
             ],
-            "time": "2018-08-05T17:53:17+00:00"
+            "time": "2019-06-13T12:50:23+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -810,16 +814,16 @@
         },
         {
             "name": "phpunit/php-timer",
-            "version": "2.0.0",
+            "version": "2.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f"
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f",
-                "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
                 "shasum": ""
             },
             "require": {
@@ -831,7 +835,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "2.1-dev"
                 }
             },
             "autoload": {
@@ -855,20 +859,20 @@
             "keywords": [
                 "timer"
             ],
-            "time": "2018-02-01T13:07:23+00:00"
+            "time": "2019-06-07T04:22:29+00:00"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "3.0.0",
+            "version": "3.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace"
+                "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace",
-                "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
+                "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
                 "shasum": ""
             },
             "require": {
@@ -881,7 +885,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "3.1-dev"
                 }
             },
             "autoload": {
@@ -904,7 +908,7 @@
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2018-02-01T13:16:43+00:00"
+            "time": "2019-09-17T06:23:10+00:00"
         },
         {
             "name": "phpunit/phpunit",
@@ -1040,8 +1044,58 @@
                 "mock",
                 "xunit"
             ],
+            "abandoned": true,
             "time": "2018-05-29T13:54:20+00:00"
         },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
         {
             "name": "psr/http-message",
             "version": "1.0.1",
@@ -1092,6 +1146,46 @@
             ],
             "time": "2016-08-06T14:39:51+00:00"
         },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
         {
             "name": "sebastian/code-unit-reverse-lookup",
             "version": "1.0.1",
@@ -1203,23 +1297,23 @@
         },
         {
             "name": "sebastian/diff",
-            "version": "3.0.1",
+            "version": "3.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "366541b989927187c4ca70490a35615d3fef2dce"
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce",
-                "reference": "366541b989927187c4ca70490a35615d3fef2dce",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.0",
+                "phpunit/phpunit": "^7.5 || ^8.0",
                 "symfony/process": "^2 || ^3.3 || ^4"
             },
             "type": "library",
@@ -1255,7 +1349,7 @@
                 "unidiff",
                 "unified diff"
             ],
-            "time": "2018-06-10T07:54:39+00:00"
+            "time": "2019-02-04T06:01:07+00:00"
         },
         {
             "name": "sebastian/environment",
@@ -1309,16 +1403,16 @@
         },
         {
             "name": "sebastian/exporter",
-            "version": "3.1.0",
+            "version": "3.1.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
+                "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
                 "shasum": ""
             },
             "require": {
@@ -1345,6 +1439,10 @@
                 "BSD-3-Clause"
             ],
             "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
                 {
                     "name": "Jeff Welch",
                     "email": "whatthejeff@gmail.com"
@@ -1353,17 +1451,13 @@
                     "name": "Volker Dusch",
                     "email": "github@wallbash.com"
                 },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
                 {
                     "name": "Adam Harvey",
                     "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
                 }
             ],
             "description": "Provides the functionality to export PHP variables for visualization",
@@ -1372,7 +1466,7 @@
                 "export",
                 "exporter"
             ],
-            "time": "2017-04-03T13:19:02+00:00"
+            "time": "2019-09-14T09:02:43+00:00"
         },
         {
             "name": "sebastian/global-state",
@@ -1657,16 +1751,16 @@
         },
         {
             "name": "statonlab/tripal-test-suite",
-            "version": "1.5.1",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/tripal/TripalTestSuite.git",
-                "reference": "c652f62c0e8302e00cf25a39c95a605b0266656f"
+                "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tripal/TripalTestSuite/zipball/c652f62c0e8302e00cf25a39c95a605b0266656f",
-                "reference": "c652f62c0e8302e00cf25a39c95a605b0266656f",
+                "url": "https://api.github.com/repos/tripal/TripalTestSuite/zipball/3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4",
+                "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4",
                 "shasum": ""
             },
             "require": {
@@ -1701,40 +1795,47 @@
                     "email": "bcondon@utk.edu"
                 }
             ],
-            "time": "2018-09-25T19:08:14+00:00"
+            "time": "2019-04-09T18:52:51+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v4.1.4",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f"
+                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/ca80b8ced97cf07390078b29773dc384c39eee1f",
-                "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f",
+                "url": "https://api.github.com/repos/symfony/console/zipball/de63799239b3881b8a08f8481b22348f77ed7b36",
+                "reference": "de63799239b3881b8a08f8481b22348f77ed7b36",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.1.3",
-                "symfony/polyfill-mbstring": "~1.0"
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.8",
+                "symfony/service-contracts": "^1.1"
             },
             "conflict": {
                 "symfony/dependency-injection": "<3.4",
+                "symfony/event-dispatcher": "<4.3",
                 "symfony/process": "<3.3"
             },
+            "provide": {
+                "psr/log-implementation": "1.0"
+            },
             "require-dev": {
                 "psr/log": "~1.0",
                 "symfony/config": "~3.4|~4.0",
                 "symfony/dependency-injection": "~3.4|~4.0",
-                "symfony/event-dispatcher": "~3.4|~4.0",
+                "symfony/event-dispatcher": "^4.3",
                 "symfony/lock": "~3.4|~4.0",
-                "symfony/process": "~3.4|~4.0"
+                "symfony/process": "~3.4|~4.0",
+                "symfony/var-dumper": "^4.3"
             },
             "suggest": {
-                "psr/log-implementation": "For using the console logger",
+                "psr/log": "For using the console logger",
                 "symfony/event-dispatcher": "",
                 "symfony/lock": "",
                 "symfony/process": ""
@@ -1742,7 +1843,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.1-dev"
+                    "dev-master": "4.3-dev"
                 }
             },
             "autoload": {
@@ -1769,20 +1870,78 @@
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:24:31+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.9.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
-                "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
                 "shasum": ""
             },
             "require": {
@@ -1794,7 +1953,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
@@ -1828,20 +1987,136 @@
                 "portable",
                 "shim"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
+                "reference": "ea7263d6b6d5f798b56a45a5b8d686725f2719a3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-08-20T14:44:19+00:00"
         },
         {
             "name": "theseer/tokenizer",
-            "version": "1.1.0",
+            "version": "1.1.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/theseer/tokenizer.git",
-                "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
-                "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
                 "shasum": ""
             },
             "require": {
@@ -1868,28 +2143,28 @@
                 }
             ],
             "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
-            "time": "2017-04-07T12:08:54+00:00"
+            "time": "2019-06-13T22:48:21+00:00"
         },
         {
             "name": "webmozart/assert",
-            "version": "1.3.0",
+            "version": "1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
-                "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.3 || ^7.0"
+                "php": "^5.3.3 || ^7.0",
+                "symfony/polyfill-ctype": "^1.8"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.6",
-                "sebastian/version": "^1.0.1"
+                "phpunit/phpunit": "^4.8.36 || ^7.5.13"
             },
             "type": "library",
             "extra": {
@@ -1918,7 +2193,7 @@
                 "check",
                 "validate"
             ],
-            "time": "2018-01-29T19:49:41+00:00"
+            "time": "2019-08-24T08:43:50+00:00"
         }
     ],
     "aliases": [],

+ 30 - 30
docs/contributing/governance.rst

@@ -9,8 +9,8 @@ The Tripal project recognizes these roles:
  - **Extension Contributors**: they extend Tripal through modules, themes, views, data loaders, fields, and/or libraries.
  - **Core Code Contributors**: contribute code to the `Tripal "core" <https://github.com/tripal/tripal>`_, comments, discussion, questions, bug reports.
  - **Core Code Committers**: write access to the `Tripal "core" repository <https://github.com/tripal/tripal>`_.
- - **Tripal Project Management Committee (TPMC)**: make *code relevant* decisions, (i.e. ensure code standards, robustness, and long-term design objectives are maintained).
- - **Tripal Steering Committee (TSC)**: Provide guidance to the TPMC for *policy-level* and *future planning* recommendations.
+ - **Tripal Project Management Committee (PMC)**: make *code relevant* decisions, (i.e. ensure code standards, robustness, and long-term design objectives are maintained).
+ - **Tripal Advisory Committee (TAC)**: Provide guidance to the PMC for *policy-level* and *future planning* recommendations.
 
 Adopters
 ----------
@@ -33,7 +33,7 @@ Responsibilities include:
 Committers
 ------------
 
-These are dedicated Tripal developers who are trusted to commit pull requests directly to the Tripal core repository. They are encouraged to be active in the Tripal community and routinely review pull requests. Developers are added to to committers group by unanimous agreement from the TPMC.
+These are dedicated Tripal developers who are trusted to commit pull requests directly to the Tripal core repository. They are encouraged to be active in the Tripal community and routinely review pull requests. Developers are added to to committers group by unanimous agreement from the PMC.
 
 Responsibilities include:
  - Monitor Tripal core issue queue.
@@ -41,16 +41,16 @@ Responsibilities include:
 
 See the `guidelines for contributors <https://tripal.readthedocs.io/en/latest/dev_guide/contributing/pull_requests.html>`_ for more details.
 
-The Tripal Project Management Committee (TPMC)
+The Tripal Project Management Committee (PMC)
 ------------------------------------------------
 
 This group consists of experienced Tripal developers.
 
 Responsibilities include:
  - Ensure good practices, for example, submitting errors, questions, and requests via GitHub.
- - Monitor issue queue (though this responsibility isn't limited to the TPMC).
+ - Monitor issue queue (though this responsibility isn't limited to the PMC).
  - Resolve questions and differences of opinions among Contributors.
- - Work with the TSC to make decisions about significant new features. Examples:
+ - Work with the TAC to make decisions about significant new features. Examples:
      - a new core module,
      - designing a module-specific REST API,
      - new technologies or libraries used by the core code.
@@ -59,22 +59,22 @@ Responsibilities include:
  - Set coding standards.
  - Ensure Tripal remains generic and useful to a wide range of groups.
 
-The TPMC will strive to obtain consensus, and all members ensure that the Tripal community and the TSC are informed on decisions. Any individual member can call a meeting. The term will be two years with the possibility of extension. At least one member will serve on the TSC; this person will be elected by vote within the TPMC.
+The PMC will strive to obtain consensus, and all members ensure that the Tripal community and the TAC are informed on decisions. Any individual member can call a meeting. The term will be two years with the possibility of extension. At least one member will serve on the TAC; this person will be elected by vote within the PMC.
 
 Communication and Meetings
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The TPMC will meet as necessary. It is expected that frequent decisions will need to be made, some through GitHub issue comments, Slack, e-mail, or conference calls.
+The PMC will meet as necessary. It is expected that frequent decisions will need to be made, some through GitHub issue comments, Slack, e-mail, or conference calls.
 
-Tripal Steering Committee (TSC)
+Tripal Advisory Committee (TAC)
 ---------------------------------
-The Tripal Steering Committee (TSC) provides leadership, guidance, future planning, advocacy and coordination for Tripal. The TSC acts in an advisory capacity only by determining sets of recommendations for Tripal. All recommendations will be provided to the TPMC. Topics include recommended technologies, overall approach, software architecture, development priorities, timelines, funding strategies, best practices, support for a fair and focused open source development strategy.
+The Tripal Advisory Committee (TAC) provides leadership, guidance, future planning, advocacy and coordination for Tripal. The TAC acts in an advisory capacity only by determining sets of recommendations for Tripal. All recommendations will be provided to the PMC. Topics include recommended technologies, overall approach, software architecture, development priorities, timelines, funding strategies, best practices, support for a fair and focused open source development strategy.
 
-At least one member of the TPMC must be on the TSC to ensure that the reality of what is and is not feasible for the developers is not lost. Additionally, close communication between the TSC and TPMC is critical, as is transparency of the TSC discussions to the entire Tripal community. All members of the TPMC are welcome at TSC meetings.
+At least one member of the PMC must be on the TAC to ensure that the reality of what is and is not feasible for the developers is not lost. Additionally, close communication between the TAC and PMC is critical, as is transparency of the TAC discussions to the entire Tripal community. All members of the PMC are welcome at TAC meetings.
 
 Membership
 ^^^^^^^^^^^^
-The TSC should include "internal" and "external" members. Internal members are individuals who manage Tripal websites or lead teams engaged in active development, possibly with funding to do so. External members may be outside the Tripal community altogether, and may include government, non-profit, or industry representatives who are stakeholders for one or more Tripal databases (but not active managers of a Tripal site) and/or specialists in such disciplines as cyberinfrastructure, bioinformatics, genomics, breeding.
+The TAC should include "internal" and "external" members. Internal members are individuals who manage Tripal websites or lead teams engaged in active development, possibly with funding to do so. External members may be outside the Tripal community altogether, and may include government, non-profit, or industry representatives who are stakeholders for one or more Tripal databases (but not active managers of a Tripal site) and/or specialists in such disciplines as cyberinfrastructure, bioinformatics, genomics, breeding.
 
 - Terms are for two years.
 - Two year memberships can be renewed for individuals who wish to stay on to complete a particular objective.
@@ -87,50 +87,50 @@ The TSC should include "internal" and "external" members. Internal members are i
 - The minimum number of internal members is 3.
 - The number of internal members should not be less than 1/2.
 - The target number of external members is 5.
-- If the TSC decides to replace a leaving member, the current members will develop a list of possible candidates. The chair will contact each in turn until the membership slot is filled.
-- Members will be asked to serve by the current TSC.
+- If the TAC decides to replace a leaving member, the current members will develop a list of possible candidates. The chair will contact each in turn until the membership slot is filled.
+- Members will be asked to serve by the current TAC.
 
 Responsibilities include:
  - Serving a minimum two year term, beginning with the yearly board meeting (see below) in conjunction with the January Plant and Animal Genome Conference in San Diego.
- - Respond to issues in a timely manner when contacted directly. Members are strongly encouraged to become part of the TSC GitHub group, and if they wish to comment or discuss agenda items directly with the community, to do so in the GitHub issue queue (instead of the email list serve).
+ - Respond to issues in a timely manner when contacted directly. Members are strongly encouraged to become part of the TAC GitHub group, and if they wish to comment or discuss agenda items directly with the community, to do so in the GitHub issue queue (instead of the email list serve).
  - Attend the annual January meeting at PAG and at least three of the quarterly meetings.
  - Review agenda and supporting materials prior to meetings.
  - Stay informed about Tripal, its member databases, developers, and users.
 
 In addition, internal members are responsible for:
- - Actively communicating with the Tripal community, both to collect ideas and concerns and to inform the community about TSC plans for Tripal.
+ - Actively communicating with the Tripal community, both to collect ideas and concerns and to inform the community about TAC plans for Tripal.
  - Engaging in the Tripal Core GitHub Issue queue on “discussion” issues.
 
-TSC Chair
+TAC Chair
 ^^^^^^^^^^^
 
-The board will be led by a chair to be elected by TSC members at the January meeting annually (see below). One or more vice-chairs can be designated by the chair. The chair will ensure that the following is accomplished, delegating responsibilities as needed:
- - Organize, announce and lead TSC meetings.
+The board will be led by a chair to be elected by TAC members at the January meeting annually (see below). One or more vice-chairs can be designated by the chair. The chair will ensure that the following is accomplished, delegating responsibilities as needed:
+ - Organize, announce and lead TAC meetings.
  - Write the meeting agenda and post to Tripal.info.
- - Provide supporting materials for review at least 1 week before TSC meetings.
- - Ensure that the agenda items that would benefit from review by the community are posted to the GitHub Tripal core issue queue. Ensure that any GitHub issue discussions are linked on the agenda and available for review by the TSC.
+ - Provide supporting materials for review at least 1 week before TAC meetings.
+ - Ensure that the agenda items that would benefit from review by the community are posted to the GitHub Tripal core issue queue. Ensure that any GitHub issue discussions are linked on the agenda and available for review by the TAC.
  - Ensure meeting notes are taken by someone present at the meeting and posted to Tripal.info.
- - Call for votes on TSC recommendations when community voting is required.
+ - Call for votes on TAC recommendations when community voting is required.
  - Call additional meetings if needed.
- - Facilitate communication between the TSC and TPMC.
- - Filling vacant slots on the TSC.
+ - Facilitate communication between the TAC and PMC.
+ - Filling vacant slots on the TAC.
  - The chair has voting privileges.
 
-TSC Meeting Agenda Items
+TAC Meeting Agenda Items
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Strongly encouraged to be posted to the GitHub Tripal core issue queue as well as to tripal.info, to inform and solicit community comment. TSC meeting agendas will include issues tagged “TSC Next Meeting” on the GitHub Tripal core issue queue. Other agenda items may be added by the TSC chair or members, or by the TPMC. These issues will be closed after the meeting.
+Strongly encouraged to be posted to the GitHub Tripal core issue queue as well as to tripal.info, to inform and solicit community comment. TAC meeting agendas will include issues tagged “TAC Next Meeting” on the GitHub Tripal core issue queue. Other agenda items may be added by the TAC chair or members, or by the PMC. These issues will be closed after the meeting.
 
 Communication and Meetings
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The primary TSC meeting will be held in January of each year, at the Tripal codefest. In-person attendance is strongly encouraged, but a teleconference option will be provided. Each issue on the agenda will be discussed, and if needed, the chair will call for a vote to determine the final recommendation of the TSC. Votes carry based on simple majority. All discussion, votes and objections will be recorded in meeting notes, which will be posted on Tripal.info.
+The primary TAC meeting will be held in January of each year, at the Tripal codefest. In-person attendance is strongly encouraged, but a teleconference option will be provided. Each issue on the agenda will be discussed, and if needed, the chair will call for a vote to determine the final recommendation of the TAC. Votes carry based on simple majority. All discussion, votes and objections will be recorded in meeting notes, which will be posted on Tripal.info.
 
-Additional teleconference TSC meetings will be held once per quarter (April, July, October).  These could be held in place of the monthly Tripal User's Meeting to avoid meeting overload.
+Additional teleconference TAC meetings will be held once per quarter (April, July, October).  These could be held in place of the monthly Tripal User's Meeting to avoid meeting overload.
 
-TSC meetings outside the above schedule may be called by the TSC chair. These will only be called in urgent situations. In less urgent situations, the TSC chair or the TPMC can contact the internal members of the TSC and request a meeting or solicit comments via email, GitHub issue, or Slack.
+TAC meetings outside the above schedule may be called by the TAC chair. These will only be called in urgent situations. In less urgent situations, the TAC chair or the TPMC can contact the internal members of the TAC and request a meeting or solicit comments via email, GitHub issue, or Slack.
 
-At any time the TPMC may communicate with members of the TSC with expertise in specific areas if needed to assist in decision making.
+At any time the TPMC may communicate with members of the TAC with expertise in specific areas if needed to assist in decision making.
 
 Changes to this Document
 --------------------------

+ 5 - 5
docs/contributing/pull_requests.rst

@@ -61,11 +61,11 @@ Pull Request (PR) Guideline
 The goal of this document is to make it easy for **A)** contributors to make pull requests that will be accepted, and **B)** Tripal committers to determine if a pull request should be accepted.
 
 - PRs that address a specific issue **must** link to the related issue page.
-    - Really in almost every case, there should be an issue for a PR.  This allows feedback and discussion before the coding happens.  Not grounds to reject, but encourage users to create issues at start of their PR.  Better late than never :).
-- Each PR **must** be tested/approved by at least 2 users with at least one user being a "trusted committer."
-    - Testers **should** describe how the testing was performed if applicable (allows others to replicate the test).
-    - At the Project Management Committee's (PMC) discretion, a PR may be subject to only one review.  Generally these are small and obvious commits.
-    - While Tripal's review body is small, only one of the code reviews must be a thorough functional test.
+    - In almost every case, there should be an issue for a PR.  This allows feedback and discussion before the coding happens.  Not grounds to reject, but encourage users to create issues at start of their PR.  Better late than never :).
+- Each PR **must** be tested/approved by at least 1 contributor, if approved, a "trusted committer" will merge the PR.
+    - Testers **should** describe how the testing was performed if applicable (allows others to replicate the test).    
+    - While Tripal's review body is small, the code review must be a thorough functional test.
+    - At the Project Management Committee's (PMC) discretion, a PR may be subject to a non-functional review.  Generally these are small and obvious commits.
     - Tripal's guiding philosophy is to encourage open contribution.  With this in mind, committers should **work with contributors** to resolve issues in their PRs.  PRs that will not be merged should be closed, **transparently citing** the reason for closure.  In an ideal world, features that would be closed are discouraged at the **issue phase** before the code is written!
     - The pull request branch should be deleted after merging (if not from a forked repository) by the person who performs the merge.
 - PRs that include new functionality **must** also provide Unit Tests.

+ 6 - 6
docs/dev_guide/custom_field/manual_field_creation.rst

@@ -12,18 +12,18 @@ Before we create our class we must first create a proper directory structure.  T
 
 .. code-block:: bash
 
-  /sites/all/modules/[your_module]/includes/TripalField/[field_name]/[field_name].inc
-  /sites/all/modules/[your_module]/includes/TripalField/[field_name]/[field_name]_widget.inc
-  /sites/all/modules/[your_module]/includes/TripalField/[field_name]/[field_name]_formatter.inc
+  /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name].inc
+  /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name]_widget.inc
+  /sites/all/modules/[your_module]/includes/TripalFields/[field_name]/[field_name]_formatter.inc
 
 
 In the directories above the token [your_module] can be substituted with the name of your module and [field_name] is the name of your field.  You can name your field whatever you like, but you must use this name consistently in other locations throughout the modules.  Because all fields are defined by vocabulary terms, it is custom to name your fields with the vocabulary **short name** followed by two underscores followed by the **term name**, hence:  obi__organism.  Here the ChadoField implementation goes in the [field_name].inc file, the ChadoFieldWidget in the [field_name]_widget.inc file and the ChadoFieldFormatter in the [field_name]_formatter.inc.   All new fields must implement all three classes.   in the case of our obi__organism field the directory structure is as follows:
 
 .. code-block:: bash
 
-  /sites/all/modules/tripal/tripal_chado/includes/TripalField/obi__organism/obi__organism.inc
-  /sites/all/modules/tripal/tripal_chado/includes/TripalField/obi__organism/obi__organism_widget.inc
-  /sites/all/modules/tripal/tripal_chado/includes/TripalField/obi__organism/obi__organism_formatter.inc
+  /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc
+  /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc
+  /sites/all/modules/tripal/tripal_chado/includes/TripalFields/obi__organism/obi__organism_formatter.inc
 
 Anatomy of the ChadoField Class
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 4 - 0
docs/extensions/3rd_party.rst

@@ -14,6 +14,10 @@ This module provides a Breeding API end point on your Tripal site as well as a u
 Tripal Blast
 ------------
 
+.. image:: https://tripal.readthedocs.io/en/7.x-3.x/_images/Tripal-Gold.png
+  :target: https://tripal.readthedocs.io/en/7.x-3.x/extensions/module_rating.html#Gold
+  :alt: Tripal Rating: Gold
+  
 This module provides a basic interface to allow your users to utilize your server's NCBI BLAST+. There is a simple interface allowing users to paste or upload a query sequence and then select from available databases and a tabular results listing with alignment information and multiple download formats (HTML, TSV, GFF3, XML) available.
 
 `Documentation <https://github.com/tripal/tripal_blast/blob/7.x-1.x/README.md>`__

+ 7 - 0
docs/extensions/analysis.rst

@@ -41,3 +41,10 @@ Tripal CV-Xray maps content annotations onto controlled vocabulary trees.  The e
 
 `Documentation <https://github.com/statonlab/tripal_cv_xray/blob/master/README.md>`__
 `Repository <https://github.com/statonlab/tripal_cv_xray>`__
+
+Tripal OrthoQuery
+-----------------
+
+The OrthoQuery module identifies orthologous genes via a Tripal database, standardizes the data for comparative analysis, performs the OrthoFinder analysis through the Tripal Galaxy API, sends the data to the user’s database profile, and provides interactive visualizations. Visualization features focus on facilitating the interrogation of large gene families, examining relationships among families, and allowing direct query of the stored orthogroups and their function. Orthoquery is currently in development.
+
+`Repository <https://gitlab.com/TreeGenes/orthoquery>`__

+ 4 - 0
docs/extensions/data_input.rst

@@ -38,6 +38,10 @@ A BibTEX importer for Tripal Publications. Currently this module only provides a
 Tripal Plant PopGen Submission
 -------------------------------
 
+.. image:: https://tripal.readthedocs.io/en/7.x-3.x/_images/Tripal-Silver.png
+  :target: https://tripal.readthedocs.io/en/7.x-3.x/extensions/module_rating.html#Silver
+  :alt: Tripal Rating: Silver
+
 The Tripal Plant PopGen Submit (TPPS) Module supports a flexible submission interface for genotype, phenotype, environmental, and metadata for population, association, or landscape genetics studies. The portal walks the user through specific questions and collects georeferenced coordinates on plant accessions and also supports ontology standards, including the Minimal Information About a Plant Phenotyping Experiment (MIAPPE) (http://www.miappe.org/) and standard genotyping file formats, such as VCF.
 
 `Documentation <https://tpps.readthedocs.io/en/latest/>`__

+ 9 - 1
docs/extensions/search.rst

@@ -6,7 +6,7 @@ CartograTree
 
 CartograTree is a web-based application that allows researchers to identify, filter, compare, and visualize geo-referenced biotic and abiotic data. Its goal is to support numerous multi-disciplinary research endeavors including: phylogenetics, population structure, and association studies.
 
-`Documentation <https://gitlab.com/TreeGenes/CartograTree/blob/master/README.md>`__
+`Documentation <https://cartogratree.readthedocs.io/en/latest/index.html>`__
 `Repository <https://gitlab.com/TreeGenes/CartograTree>`__
 
 Mainlab Chado Search
@@ -25,6 +25,14 @@ The Tripal ElasticSearch module allows you to easily manage the indexing and dis
 `Documentation <https://github.com/tripal/tripal_elasticsearch/blob/master/docs/README.md>`__
 `Repository <https://github.com/tripal/tripal_elasticsearch>`__
 
+Tripal MegaSearch
+---------------------
+
+Tripal MegaSearch is a tool for downloading biological data stored in a Tripal/Chado database. The module was designed to be generic and flexible so it can be used on most Tripal sites. Site administrators may choose from 1) a set of predefined materialized views or 2) chado base tables as the data source to serve data. If neither data source is desired, developers may create their own materialized views and serve them through Tripal MegaSearch via a flexible dynamic query form. This form allows filters to be added dynamically and combined using 'AND/OR' operators. The filters correspond to the underlying data source columns so the user can filter data on each column.
+
+`Documentation <https://gitlab.com/mainlabwsu/tripal_megasearch/blob/master/README.md>`__
+`Repository <https://gitlab.com/mainlabwsu/tripal_megasearch>`__
+
 Tripal Sequence Similarity Search
 ----------------------------------
 

+ 6 - 2
docs/user_guide.rst

@@ -1,5 +1,9 @@
-User's Guide
-============
+Tripal v3 User's Guide
+======================
+
+.. note:: 
+
+  Looking for the `Tripal v2 User's Guide <http://tripal.info/tutorials/v2.x>`_? 
 
 
 .. toctree::

+ 5 - 4
docs/user_guide/bulk_loader.rst

@@ -5,14 +5,15 @@ Bulk Loader
 
   Remember you must set the ``$DRUPAL_HOME`` environment variable if you want to cut-and-paste the commands below. See :doc:`./install_tripal/drupal_home`
 
-The bulk loader is a tool that Tripal provides for loading of data contained in tab delimited files. Tripal supports loading of files in standard formats (e.g. ``FASTA``, ``GFF``, ``OBO``), but Chado can support a variety of different biological data types and there are often no community standard file formats for loading these data. For example, there is no file format for importing genotype and phenotype data. Those data can be stored in the feature, stock and natural diversity tables of Chado. The Bulk Loader was introduced in Tripal v1.1 and provides a web interface for building custom data loader. In short, the site developer creates the bulk loader "template". This template can then be used and re-used for any tab delimited file that follows the format described by the template. Additionally, bulk loading templates can be exported allowing Tripal sites to share loaders with one another.  Loading templates that have been shared are available on the Tripal website here: http://tripal.info/extensions/bulk-loader-templates.
+The bulk loader is a tool that Tripal provides for loading of data contained in tab delimited files. Tripal supports loading of files in standard formats (e.g. ``FASTA``, ``GFF``, ``OBO``), but Chado can support a variety of different biological data types and there are often no community standard file formats for loading these data. For example, there is no file format for importing genotype and phenotype data. Those data can be stored in the feature, stock and natural diversity tables of Chado. The Bulk Loader was introduced in Tripal v1.1 and provides a web interface for building custom data loader. In short, the site developer creates the bulk loader "template". This template can then be used and re-used for any tab delimited file that follows the format described by the template. Additionally, bulk loading templates can be exported allowing Tripal sites to share loaders with one another.  
 
 The following commands can be executed to install the Tripal Bulk Loader using Drush:
 
-.. code-block bash
+.. code-block:: bash
 
   cd /var/www/
   drush pm-enable tripal_bulk_loader
+  
 
 Plan How to Store Data
 ----------------------
@@ -21,7 +22,7 @@ To demonstrate use of the Bulk Loader, a brief example that imports a list of or
 
 * `Fragaria.txt <http://tripal.info/sites/default/files/book_pages/Fragaria_0.txt>`_
 
-.. code-block bash
+.. code-block:: bash
 
   cd $DRUPAL_HOME/sites/default/files
   wget http://tripal.info/sites/default/files/book_pages/Fragaria_0.txt
@@ -29,7 +30,7 @@ To demonstrate use of the Bulk Loader, a brief example that imports a list of or
 
 This file has three columns: NCBI taxonomy ID, genus and species:
 
-.. .. csv-table:: Fragaria sample file
+.. csv-table:: Fragaria sample file
 
   3747    "Fragaria"        "x ananassa"
   57918   "Fragaria"        "vesca"

二进制
docs/user_guide/drupal_overview.create_content1.png


二进制
docs/user_guide/drupal_overview.create_content2.png


二进制
docs/user_guide/drupal_overview.find_content.png


+ 4 - 0
docs/user_guide/drupal_overview.rst

@@ -27,6 +27,8 @@ You'll notice at the top a **Title** field and a **Body** text box. All pages re
 
 For practice, try to create two new pages. A **Home** page and an **About** page for our site. First, create the home page and second create the about page. Add whatever text you like for the body.
 
+In the screenshots above, you may have noticed the link **Add Tripal Content**.  Tripal content is different from the typical Drupal content types: **Basic Page** or **Article**.  Instead the content that Tripal provides is the biological and ancillary data that your site will provide to users.  Later this tutorial will describe how to add new biological data.
+
 Finding Content
 ---------------
 
@@ -36,6 +38,8 @@ To find any content that has been created on the site, click the **Find Content*
 
 You'll also notice a set of drop down boxes for filtering the content. For sites with many different content types and pages this helps to find content. You can use this list to click to view each page or to edit.
 
+Later in this guide, instructions will be provided for finding Tripal Content. Tripal content is different from the typical Drupal content types: **Basic Page** or **Article**.  Instead the content that Tripal provides is the biological and ancillary data that your site will provide to users.  It is also accessible via a link named **Find Tripal Content** found on the same menu bar as **Find Content**
+
 Site Administration
 -------------------
 

+ 11 - 9
docs/user_guide/example_genomics/pub_import.rst

@@ -17,17 +17,19 @@ We will add information about the Tripal publication. Enter the following values
 .. csv-table::
   :header: "Field Name", "Value"
 
-  "Title", "Tripal v1.1: a standards-based toolkit for construction of online genetic and genomic databases."
+  "Title", "Tripal v3: an ontology-based toolkit for construction of FAIR biological community databases."
+  "Volume", "baz077"
   "Series Name", "Database"
-  "Publication Year", "2013"
-  "Unique Local Identifier", "Tripal v1.1: a standards-based toolkit for construction of online genetic and genomic databases."
-  "Type	Journal", "Article"
-  "Publication Date", "2013 Oct 25"
+  "Publication Year", "2019"
+  "Unique Local Identifier", "Tripal v3: an ontology-based toolkit for construction of FAIR biological community databases."
+  "Publication Type", "Journal"
   "Cross Reference", "Database: PMID"
-  "Accession", "24163125"
-  "Authors", "Sanderson LA, Ficklin SP, Cheng CH, Jung S, Feltus FA, Bett KE, Main D"
-  "Citation", "Sanderson LA, Ficklin SP, Cheng CH, Jung S, Feltus FA, Bett KE, Main D. Tripal: a construction Toolkit for Online Genome Databases. Database, Oct 25 2013. bat075"
-  "Abstract", "Tripal is an open-source freely available toolkit for construction of online genomic and genetic databases. It aims to facilitate development of community-driven biological websites by integrating the GMOD Chado database schema with Drupal, a popular website creation and content management software. Tripal provides a suite of tools for interaction with a Chado database and display of content therein. The tools are designed to be generic to support the various ways in which data may be stored in Chado. Previous releases of Tripal have supported organisms, genomic libraries, biological stocks, stock collections and genomic features, their alignments and annotations. Also, Tripal and its extension modules provided loaders for commonly used file formats such as FASTA, GFF, OBO, GAF, BLAST XML, KEGG heir files and InterProScan XML. Default generic templates were provided for common views of biological data, which could be customized using an open Application Programming Interface to change the way data are displayed. Here, we report additional tools and functionality that are part of release v1.1 of Tripal. These include (i) a new bulk loader that allows a site curator to import data stored in a custom tab delimited format; (ii) full support of every Chado table for Drupal Views (a powerful tool allowing site developers to construct novel displays and search pages); (iii) new modules including ‘Feature Map’, ‘Genetic’, ‘Publication’, ‘Project’, ‘Contact’ and the ‘Natural Diversity’ modules. Tutorials, mailing lists, download and set-up instructions, extension modules and other documentation can be found at the Tripal website located at http://tripal.info."
+  "Accession", "31328773"
+  "Publication Date", "2019 Jul 22"
+  "Citation", "Spoor S, Cheng CH, Sanderson LA, Condon B, Almsaeed A, Chen M, Bretaudeau A, Rasche H, Jung S, Main D, Bett K, Staton M, Wegrzyn JL, Feltus FA, Ficklin SP. Tripal v3: an ontology-based toolkit for construction of FAIR biological community databases.  Database. July 2019, 2019: baz077"
+  "Authors", "Spoor S, Cheng CH, Sanderson LA, Condon B, Almsaeed A, Chen M, Bretaudeau A, Rasche H, Jung S, Main D, Bett K, Staton M, Wegrzyn JL, Feltus FA, Ficklin SP"
+  "Abstract", "Community biological databases provide an important online resource for both public and private data, analysis tools and community engagement. These sites house genomic, transcriptomic, genetic, breeding and ancillary data for specific species, families or clades. Due to the complexity and increasing quantities of these data, construction of online resources is increasingly difficult especially with limited funding and access to technical expertise. Furthermore, online repositories are expected to promote FAIR data principles (findable, accessible, interoperable and reusable) that presents additional challenges. The open-source Tripal database toolkit seeks to mitigate these challenges by creating both the software and an interactive community of developers for construction of online community databases. Additionally, through coordinated, distributed co-development, Tripal sites encourage community-wide sustainability. Here, we report the release of Tripal version 3 that improves data accessibility and data sharing through systematic use of controlled vocabularies (CVs). Tripal uses the community-developed Chado database as a default data store, but now provides tools to support other data stores, while ensuring that CVs remain the central organizational structure for the data. A new site developer can use Tripal to develop a basic site with little to no programming, with the ability to integrate other data types using extension modules and the Tripal application programming interface. A thorough online User’s Guide and Developer’s Handbook are available at http://tripal.info, providing download, installation and step-by-step setup instructions."
+
 
 To complete the page click the **Save** button at the bottom
 

+ 6 - 6
docs/user_guide/install_tripal/manual_install/install_drupal.rst

@@ -52,26 +52,26 @@ Substitute [user] for the name of the user that will own the web files.
 
   The apache web server runs as the user 'www-data'.  For security reasons you should chose a user other than 'www-data' to be the owner of the Drupal root directory.
 
-Tripal 3.x requires version 7.x of Drupal. Drupal can be freely downloaded from the http://www.drupal.org website. At the writing of this Tutorial the most recent version of Drupal 7 is version 7.64. The software can be downloaded manually from the Drupal website through a web browser or we can use the ``wget`` command to retrieve it:
+Tripal 3.x requires version 7.x of Drupal. Drupal can be freely downloaded from the http://www.drupal.org website. At the writing of this Tutorial the most recent version of Drupal 7 is version 7.69. The software can be downloaded manually from the Drupal website through a web browser or we can use the ``wget`` command to retrieve it:
 
 .. code-block:: bash
 
   cd $DRUPAL_HOME
-  wget http://ftp.drupal.org/files/projects/drupal-7.64.tar.gz
+  wget http://ftp.drupal.org/files/projects/drupal-7.69.tar.gz
 
 
 Next, we want to install Drupal. We will use the tar command to uncompress the software:
 
 .. code-block:: bash
 
-  tar -zxvf drupal-7.64.tar.gz
+  tar -zxvf drupal-7.69.tar.gz
 
-Notice that we now have a drupal-7.64 directory with all of the Drupal files. We want the Drupal files to be in our document root, not in a 'drupal-7.64' subdirectory. So, we'll move the contents of the directory up one level:
+Notice that we now have a drupal-7.69 directory with all of the Drupal files. We want the Drupal files to be in our document root, not in a 'drupal-7.69' subdirectory. So, we'll move the contents of the directory up one level:
 
 .. code-block:: bash
 
-  mv drupal-7.64/* ./
-  mv drupal-7.64/.htaccess ./
+  mv drupal-7.69/* ./
+  mv drupal-7.69/.htaccess ./
 
 If an index.html file is present (as is the case with Ubuntu installations) you can move it out of the way so that it does not interfere with Drupal by executing the following:
 

+ 20 - 1
docs/user_guide/install_tripal/upgrade_from_tripal2.rst

@@ -25,6 +25,8 @@ Step 1: Upgrade Tripal
   .. warning::
 
     If you have made customizations to Chado you may encounter problems during the upgrade.  It is not recommended to ever change any of the existing tables of Chado. However, if you have and if you do encounter such issues, please use the Tripal Issue queue to request help: https://github.com/tripal/tripal/issues
+    
+    If you have custom Drupal fields attached to Tripal nodes then the content in those fields will not automatically be migrated to the new Tripal v3 entities. Bradford Condon has provided some instructions to help migrate these fields after the site has been upgrade. You can find those instructions `here <https://gist.github.com/bradfordcondon/0dddfd015ff6ef1f545364c2ceff1f0b>`_.
 
 2. Put the site in maintenance mode. Before completing any upgrade you should put your site into "maintenance mode". This ensures that users are isolated from any temporary error messages generated through the process. To put the site in maintenance mode, navigate to **Administration > Configuration > Maintenance Mode** . Then click the **Put site into maintenance mode** checkbox and click **Save Configuration**. Additionally, there is a text area on this page that allows you to customize the message displayed to your users while your site is in maintenance mode.
 
@@ -184,8 +186,25 @@ You have now completed the migration process and can safely disable the Tripal v
 
 Troubleshooting
 ---------------
+1. Dealing with ``stack depth limit exceeded`` on Step 4 of the Migration.
 
-1. For sites that have upgrading from Drupal 6:
+When there is a large number of nodes, Drupal's search module fails to update the search_total table and gives the following error:
+
+.. code-block:: bash
+
+    Uncaught exception thrown in shutdown function. PDOException: SQLSTATE[54001]: Statement too complex: 7 ERROR:  stack depth limit exceeded
+    HINT:  Increase the configuration parameter &amp;quot;max_stack_depth
+
+
+You can avoid this problem by clearing out the Drupal search tables byu executing the following SQL commands:
+
+.. code-block:: sql
+
+    TRUNCATE search_total;
+    TRUNCATE search_index;
+
+
+2. For sites that have upgrading from Drupal 6:
 
   If your site was upgraded from Drupal 6, you'll need to add a new text format with a machine name called 'full_html' as this is the default formatter that Tripal v3 uses. As in Drupal 6, the 'Full HTML' text format has a numeric machine name (usually '2') that was later changed to 'full_html' in Drupal 7.
 

+ 1 - 1
docs/user_guide/job_management.rst

@@ -35,7 +35,7 @@ Next, we need the `PHP-Daemon Library version 2.0 <https://github.com/shaneharte
   cd $DRUPAL_HOME/sites/all/libraries
   wget https://github.com/shaneharter/PHP-Daemon/archive/v2.0.tar.gz
   tar -zxvf v2.0.tar.gz
-  mv v2.0.tar.gz PHP-Daemon
+  mv PHP-Daemon-2.0 PHP-Daemon
 
 Next, install the `Drush Daemon API <https://www.drupal.org/project/drushd>`_ module.
 

+ 6 - 6
docs/user_guide/web_services.rst

@@ -53,7 +53,7 @@ Structure of a Web Service Response
 
 The initial response in JSON is in the `JSON-LD format <https://json-ld.org/>`_ where the LD means Linked Data.   For example:
 
-.. code-block:: JSON
+.. code-block:: json
 
     {
     "@context": {,
@@ -80,7 +80,7 @@ A notable component of JSON-LD is the **@context** sub array.  It is within this
 
 There are two other special keys used in the JSON-LD response.  These are the **@id** and **@type** keys.  The **@id** indicates the unique URL for this resource and becomes a unique name for the resource.  In the example screenshot above, the **@id** of the initial page of web services is ``http://localhost/web-services``.  This URL will always refer to the initial page for Tripal web services on the given site.  The **@type** identifier specifies what type of resource this URL provides.  In this example, the type is **EntryPoint**.  If a client program is unsure as to what an **EntryPoint** is, then that information is provided in the @context section.  The following line indicates that the term **EntryPoint** expands to the vocabulary term:  vocab:EntryPoint
 
-.. code-block:: JSON
+.. code-block:: json
 
   "EntryPoint": "vocab:EntryPoint",
 
@@ -93,7 +93,7 @@ Primary Services
 
 By default, the only resource that Tripal provides at the initial response level is the content resource.  Any resource at this level is hereafter referred to as primary service.  Tripal is design to allow new web-services to be added to it.  These will be more formally described in the Tripal v3 Developer's Handbook.  In short, a primary service provides a variety of data and services for related content and function.   Each primary resource has a version number to help ensure backwards compatibility as new web services are developed and updated.  For example, the default content service currently has a version of v0.1:
 
-.. code-block:: JSON
+.. code-block:: json
 
   "content": "http://localhost/web-services/content/v0.1"
 
@@ -152,7 +152,7 @@ Currently, Tripal provides the ability to search for content via web services by
 
 Where {name} is the label assigned to the content type (See the Content Type Members section above).   Using this path, clients filter content to a specific content type.  But further refinement is possible.  As a reminder, each member (or entity) on the content type members collection appears similar to the following:
 
-.. code-block:: JSON
+.. code-block:: json
 
   {
     "@id": "http://localhost/web-services/content/v0.1/mRNA/691468",
@@ -164,7 +164,7 @@ Where {name} is the label assigned to the content type (See the Content Type Mem
 
 When retrieving the data for a specific entity something similar to the following (for our mRNA example) may be seen:
 
-.. code-block:: JSON
+.. code-block:: json
 
   "label": "LOC_Os01g01010.1",
   "ItemPage": "http://localhost/bio_data/691468",
@@ -187,7 +187,7 @@ When retrieving the data for a specific entity something similar to the followin
 
 As another reminder, when any of these attributes have a URL then further information about that attribute is obtained by following the URL. In the example below, the relationship term yields results similar to the following:
 
-.. code-block:: JSON
+.. code-block:: json
 
   {
       "@id": "http://localhost/web-services/content/v0.1/mRNA/691468/relationship/0",

+ 18 - 10
tests/DataFactory.php

@@ -11,7 +11,7 @@ use StatonLab\TripalTestSuite\Database\Factory;
  * @docs https://github.com/statonlab/TripalTestSuite
  */
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.cv', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
@@ -19,7 +19,7 @@ Factory::define('chado.cv', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.db', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
@@ -29,7 +29,7 @@ Factory::define('chado.db', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.dbxref', function (Faker\Generator $faker) {
   return [
     'db_id' => factory('chado.db')->create()->db_id,
@@ -39,7 +39,7 @@ Factory::define('chado.dbxref', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.cvterm', function (Faker\Generator $faker) {
   return [
     'cv_id' => factory('chado.cv')->create()->cv_id,
@@ -51,7 +51,7 @@ Factory::define('chado.cvterm', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.organism', function (Faker\Generator $faker) {
   return [
     'abbreviation' => $faker->name,
@@ -62,7 +62,7 @@ Factory::define('chado.organism', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.feature', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
@@ -73,7 +73,7 @@ Factory::define('chado.feature', function (Faker\Generator $faker) {
 });
 
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.stock', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
@@ -83,14 +83,13 @@ Factory::define('chado.stock', function (Faker\Generator $faker) {
   ];
 });
 
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('chado.project', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
   ];
 });
 
-
 Factory::define('chado.analysis', function (Faker\Generator $faker) {
   return [
     'name' => $faker->name,
@@ -104,7 +103,8 @@ Factory::define('chado.analysis', function (Faker\Generator $faker) {
     'sourceuri' => $faker->name,
   ];
 });
-/** @see  StatonLab\TripalTestSuite\Database\Factory::define() */
+
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
 Factory::define('tripal_jobs', function (Faker\Generator $faker) {
   return [
     'uid' => 1,
@@ -127,3 +127,11 @@ Factory::define('tripal_jobs', function (Faker\Generator $faker) {
     'priority' => $faker->numberBetween(1, 10),
   ];
 }, 'job_id');
+
+/** @see StatonLab\TripalTestSuite\Database\Factory::define() */
+Factory::define('chado.pub', function (Faker\Generator $faker) {
+  return [
+    'uniquename' => $faker->word,
+    'type_id' => factory('chado.cvterm')->create()->cvterm_id,
+  ];
+});

+ 168 - 0
tests/tripal/entities/PermissionsTest.php

@@ -0,0 +1,168 @@
+<?php
+namespace Tests\tripal\entities;
+
+use StatonLab\TripalTestSuite\DBTransaction;
+use StatonLab\TripalTestSuite\TripalTestCase;
+use Faker\Factory;
+
+class PermissionsTest extends TripalTestCase {
+  // Uncomment to auto start and rollback db transactions per test method.
+  use DBTransaction;
+
+  /**
+   * Test that our new permissions are available.
+   *
+   * @group permissions
+   */
+  public function testPermissionsAvailable() {
+
+    $permissions = module_invoke_all('permission');
+
+    // All bundle names are bio_data_##. Content types are created on install
+    // of tripal_chado and thus all sites should have them available.
+    $bundle_name = db_query('SELECT name FROM tripal_bundle limit 1')->fetchField();
+
+    // check `view [bundle name]`
+    $tripal_permissions = [
+      "view $bundle_name",
+      "create $bundle_name",
+      "edit $bundle_name",
+      "delete $bundle_name",
+    ];
+    foreach ($tripal_permissions as $permission_name) {
+      $this->assertArrayHasKey($permission_name, $permissions,
+        "Tripal permission, $permission_name, was not available.");
+    }
+  }
+
+  /**
+   * Test the permission for a given bundle.
+   *
+   * NOTE: We only test one bundle since it should be the same for all
+   * of them (done in a loop).
+   *
+   * @group permissions
+   */
+  public function testPermissionsForUser() {
+    $faker = Factory::create();
+
+    // Create organism entity for testing.
+    $bundle_id = db_query("SELECT bundle_id from {chado_bundle} where data_table='organism'")->fetchField();
+    $bundle_name = 'bio_data_' . $bundle_id;
+    $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
+
+    $genus = $faker->word(1, TRUE);
+    $species = $faker->word(2, TRUE);
+    $values = [
+      'bundle' => $bundle_name,
+      'term_id' => $bundle->term_id,
+      'chado_table' => 'organism',
+      'chado_column' => 'organism_id',
+    ];
+    $values['taxrank__genus']['und'][0] = [
+      'value' => $genus,
+      'chado-organism__genus' => $genus,
+    ];
+    $values['taxrank__species']['und'][0] = [
+      'value' => $species,
+      'chado-organism__species' => $species,
+    ];
+    $ec = entity_get_controller('TripalEntity');
+    $entity = $ec->create($values);
+    $entity = $entity->save();
+    $entity_id = $entity->id;
+
+    // For this test we are only testing entity permissions. Here we are
+    // we are testing a single bundle.
+    $tripal_permissions = [
+      'view' => "view $bundle_name",
+      'create' => "create $bundle_name",
+      'edit' => "edit $bundle_name",
+      'delete' => "delete $bundle_name",
+    ];
+
+    // All permissions are assigned to users via roles...
+    // Thus, create two new roles:
+    // 1) A role which cannot use any of the permissions.
+    $role_canNOT = new \stdClass();
+    $role_canNOT->name = $faker->name();
+    user_role_save($role_canNOT);
+    // 2) A role which can use all of them.
+    $role_can = new \stdClass();
+    $role_can->name = $faker->name();
+    user_role_save($role_can);
+    user_role_grant_permissions($role_can->rid, $tripal_permissions);
+
+    // Create our users:
+    // 1) a user without tripal permissions but who is still authenticated.
+    $email = $faker->email();
+    $user_canNOT = array(
+      'name' => $faker->name(),
+      'pass' => $faker->password(), // note: do not md5 the password
+      'mail' => $email,
+      'status' => 1,
+      'init' => $email,
+      'roles' => array(
+        DRUPAL_AUTHENTICATED_RID => 'authenticated user',
+        $role_canNOT->rid => $role_canNOT->name,
+      ),
+    );
+    $user_canNOT = user_save('', $user_canNOT); // 1st param blank so new user is created.
+    $user_canNOT_uid = $user_canNOT->uid;
+    // 2) A user with the role giving them all tripal permissions.
+    $email = $faker->email();
+    $user_can = array(
+      'name' => $faker->name(),
+      'pass' => $faker->password(), // note: do not md5 the password
+      'mail' => $email,
+      'status' => 1,
+      'init' => $email,
+      'roles' => array(
+        DRUPAL_AUTHENTICATED_RID => 'authenticated user',
+        $role_can->rid => $role_can->name,
+      ),
+    );
+    $user_can = user_save('', $user_can); // 1st param blank so new user is created.
+    $user_can_uid = $user_can->uid;
+
+    $entity_load = entity_load('TripalEntity', [$entity_id]);
+    $entity = $entity_load[$entity_id];
+
+    // Now we need to clear the user_access cache and re-load our users
+    // in order to see our newly assigned roles and permissions reflected.
+    drupal_static_reset('user_access');
+    unset($user_can, $user_canNOT);
+    $user_can = user_load($user_can_uid, TRUE);
+    $user_canNOT = user_load($user_canNOT_uid, TRUE);
+    cache_clear_all();
+
+    // Finally, for each Tripal permission...
+    foreach ($tripal_permissions as $op => $permission_name) {
+
+      // Check that our roles were assigned this permission correctly.
+      $all_roles_with_permission = user_roles(TRUE, $permission_name);
+      $this->assertArrayHasKey($role_can->rid, $all_roles_with_permission,
+        "Our newly created role  doesn't have the expected permission.");
+      $this->assertArrayNotHasKey($role_canNOT->rid, $all_roles_with_permission,
+        "The roles that shouldn't have the permission, does?");
+
+      // Check that the user who should be able to access the content, can.
+      $result = tripal_entity_access($op, $entity, $user_can);
+      $this->assertTrue($result,
+        "The current user does not have permission to $op the entity.");
+
+      // Check that the user who should NOT be able to access the content, can NOT.
+      // Note we can only check if this permission is not given to the authenticated user.
+      $has_authenticated = in_array(
+        'authenticated user',
+        $all_roles_with_permission
+      );
+      if ($has_authenticated == FALSE) {
+        $result = tripal_entity_access($op, $entity, $user_canNOT);
+        $this->assertFalse($result,
+          "The current user does but shouldn't have permission to $op the entity.");
+      }
+    }
+
+  }
+}

+ 194 - 0
tests/tripal_chado/api/ChadoQueryTest.php

@@ -66,7 +66,201 @@ class ChadoQueryTest extends TripalTestCase {
     $object = chado_generate_var('stock', $selector);
     $this->assertNotNull($object->stock_id);
     $this->assertEquals($stock->stock_id, $object->stock_id);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_chado_db_select_works_for_chado_tables() {
+    $analysis_record = factory('chado.analysis')->create();
+
+    $id = $analysis_record->analysis_id;
+
+
+    // Test passing a table name without brackets or braces.
+    $query = chado_db_select('analysis', 't');
+
+    $analysis = $query
+      ->condition('analysis_id', $id)
+      ->fields('t')
+      ->execute()
+      ->fetchObject();
+
+    $this->assertNotFalse($analysis);
+    $this->assertNotEmpty($analysis);
+    $this->assertEquals($id, $analysis->analysis_id);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_chado_db_select_should_throw_an_exception_if_table_is_undefined() {
+    $this->expectException(\Exception::class);
+    chado_db_select('some_nonexistent_table', 'd')->execute();
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_chado_db_select_recognizes_non_chado_tables() {
+    $query = chado_db_select('users', 'u');
+    $query->fields('u');
+    $query->range(0, 1);
+    $results = $query->execute()->fetchAll();
+
+    $this->assertNotEmpty($results);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_chado_db_select_handles_aliases_correctly() {
+    $query = chado_db_select('public.users');
+    $query->fields('public_users');
+    $query->range(0, 1);
+    $results = $query->execute()->fetchAll();
+
+    $this->assertNotEmpty($results);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_joining_chado_tables_in_chado_db_select() {
+    $feature = factory('chado.feature')->create();
+    $cvterm = factory('chado.cvterm')->create();
+    $pub = factory('chado.pub')->create();
+
+    $feature_cvterm = chado_insert_record('feature_cvterm', [
+      'feature_id' => $feature->feature_id,
+      'cvterm_id' => $cvterm->cvterm_id,
+      'pub_id' => $pub->pub_id,
+    ]);
+
+    $query = chado_db_select('feature', 'f');
+    $query->join('feature_cvterm', 'fcvt', 'f.feature_id = fcvt.feature_id');
+    $query->fields('f', ['name']);
+    $query->fields('fcvt', ['cvterm_id']);
+    $query->condition('f.feature_id', $feature->feature_id);
+    $found = $query->execute()->fetchObject();
 
+    $this->assertNotEmpty($found);
+    $this->assertEquals($feature_cvterm['cvterm_id'], $found->cvterm_id);
   }
 
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_left_joining_chado_tables_in_chado_db_select() {
+    $feature = factory('chado.feature')->create();
+    $cvterm = factory('chado.cvterm')->create();
+    $pub = factory('chado.pub')->create();
+
+    $feature_cvterm = chado_insert_record('feature_cvterm', [
+      'feature_id' => $feature->feature_id,
+      'cvterm_id' => $cvterm->cvterm_id,
+      'pub_id' => $pub->pub_id,
+    ]);
+
+    $query = chado_db_select('feature', 'f');
+    $query->leftJoin('feature_cvterm', 'fcvt', 'f.feature_id = fcvt.feature_id');
+    $query->fields('f', ['name']);
+    $query->fields('fcvt', ['cvterm_id']);
+    $query->condition('f.feature_id', $feature->feature_id);
+    $found = $query->execute()->fetchObject();
+
+    $this->assertNotEmpty($found);
+    $this->assertEquals($feature_cvterm['cvterm_id'], $found->cvterm_id);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_right_joining_chado_tables_in_chado_db_select() {
+    $feature = factory('chado.feature')->create();
+    $cvterm = factory('chado.cvterm')->create();
+    $pub = factory('chado.pub')->create();
+
+    $feature_cvterm = chado_insert_record('feature_cvterm', [
+      'feature_id' => $feature->feature_id,
+      'cvterm_id' => $cvterm->cvterm_id,
+      'pub_id' => $pub->pub_id,
+    ]);
+
+    $query = chado_db_select('feature', 'f');
+    $query->rightJoin('feature_cvterm', 'fcvt', 'f.feature_id = fcvt.feature_id');
+    $query->fields('f', ['name']);
+    $query->fields('fcvt', ['cvterm_id']);
+    $query->condition('f.feature_id', $feature->feature_id);
+    $found = $query->execute()->fetchObject();
+
+    $this->assertNotEmpty($found);
+    $this->assertEquals($feature_cvterm['cvterm_id'], $found->cvterm_id);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_inner_joining_chado_tables_in_chado_db_select() {
+    $feature = factory('chado.feature')->create();
+    $cvterm = factory('chado.cvterm')->create();
+    $pub = factory('chado.pub')->create();
+
+    $feature_cvterm = chado_insert_record('feature_cvterm', [
+      'feature_id' => $feature->feature_id,
+      'cvterm_id' => $cvterm->cvterm_id,
+      'pub_id' => $pub->pub_id,
+    ]);
+
+    $query = chado_db_select('feature', 'f');
+    $query->innerJoin('feature_cvterm', 'fcvt', 'f.feature_id = fcvt.feature_id');
+    $query->fields('f', ['name']);
+    $query->fields('fcvt', ['cvterm_id']);
+    $query->condition('f.feature_id', $feature->feature_id);
+    $found = $query->execute()->fetchObject();
+
+    $this->assertNotEmpty($found);
+    $this->assertEquals($feature_cvterm['cvterm_id'], $found->cvterm_id);
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_is_chado_table_returns_correct_results() {
+    $this->assertTrue(\ChadoPrefixExtender::isChadoTable('analysis'));
+    $this->assertTrue(\ChadoPrefixExtender::isChadoTable('feature_cvtermprop'));
+    $this->assertFalse(\ChadoPrefixExtender::isChadoTable('users'));
+  }
+
+  /**
+   * @group api
+   * @group chado
+   * @group chado_db_select
+   */
+  public function test_get_real_schema_returns_correct_results() {
+    $chado = chado_get_schema_name('chado');
+    $public = chado_get_schema_name('drupal');
+
+    $this->assertEquals($chado . '.analysis', \ChadoPrefixExtender::getRealSchema('chado.analysis'));
+    $this->assertEquals($public . '.users', \ChadoPrefixExtender::getRealSchema('public.users'));
+    $this->assertEquals('users', \ChadoPrefixExtender::getRealSchema('users'));
+  }
 }

+ 117 - 3
tests/tripal_ws/http/TripalWebServicesContentTest.php

@@ -12,6 +12,14 @@ class TripalWebServicesContentTest extends TripalTestCase {
 
   /** @test */
   public function testGettingMainContentList() {
+    // Grant user permission to all content.
+    $role_id = (user_is_anonymous()) ? DRUPAL_ANONYMOUS_RID : DRUPAL_AUTHENTICATED_RID;
+    $bundles = db_query('SELECT name FROM tripal_bundle');
+    foreach($bundles as $bundle) {
+      $bundle_name = 'view ' . $bundle->name;
+      user_role_grant_permissions($role_id, array($bundle_name));
+    }
+
     $response = $this->get('web-services/content/v0.1');
 
     // Make sure it returned valid json
@@ -38,10 +46,15 @@ class TripalWebServicesContentTest extends TripalTestCase {
    */
   public function testGettingListOfEntitiesInABundle() {
     // Get bundle label
-    $label = db_query('SELECT label FROM tripal_bundle LIMIT 1')->fetchField();
+    $label = db_query('SELECT label, name FROM tripal_bundle LIMIT 1')->fetchObject();
+
+    // Grant user permission to this content.
+    $role_id = (user_is_anonymous()) ? DRUPAL_ANONYMOUS_RID : DRUPAL_AUTHENTICATED_RID;
+    user_role_grant_permissions($role_id, array('view ' . $label->name));
 
     // Call /web-services/content/v0.1/[label]
-    $response = $this->get("web-services/content/v0.1/$label");
+    $ctype = preg_replace('/[^\w]/', '_', $label->label);
+    $response = $this->get("web-services/content/v0.1/" . $ctype);
 
     // Verify the returned JSON matches the structure
     $response->assertSuccessful();
@@ -56,6 +69,107 @@ class TripalWebServicesContentTest extends TripalTestCase {
 
     // Verify the collection is of the correct type
     $json = $response->json();
-    $this->assertEquals($json['label'], "$label Collection");
+    $this->assertEquals($json['label'], "$label->label Collection");
+  }
+
+  /**
+   * Tests the sanitizeFieldKeys() method.
+   * @group tripal_ws
+   * @group tripalWS-ServiceResource
+   */
+  public function testSanitizeFieldKeys() {
+
+    // We need a bundle in order to determine a valid base path.
+    $label = db_query('SELECT label FROM tripal_bundle LIMIT 1')->fetchField();
+    $bundle = tripal_load_bundle_entity(['label' => $label]);
+    $this->assertNotNull($bundle,
+      "Unable to load the associated bundle object.");
+
+    // We need a resource to add context to.
+    $base_path = "web-services/content/v0.1/$label";
+    $resource = new \TripalWebServiceResource($base_path);
+    $this->assertNotNull($resource,
+      "Unable to create a Tripal Web Service Resource for testing.");
+
+    // We need a ContentService object to call sanitizeFieldKeys().
+    module_load_include('inc', 'tripal_ws', 'includes/TripalWebService/TripalContentService_v0_1');
+    $web_service = new \TripalContentService_v0_1($base_path);
+    $this->assertNotNull($web_service,
+      "Unable to create a TripalContentService_v0_1 object for testing.");
+
+    // Now finally, we try to test it!
+    // - Associative array where keys are valid terms.
+    $value = [
+      'rdfs:type' => 'fake',
+    ];
+    $sanitized_value = $this->invokeMethod($web_service, 'sanitizeFieldKeys', [
+      $resource,
+      $value,
+      $bundle,
+      $base_path
+    ]);
+    $this->assertNotNull($sanitized_value,
+      "You should be able to sanitize a term-indexed array if terms are valid.");
+
+    // - Numeric keys to direct values.
+    $value = [
+      'fake',
+      'none',
+      5
+    ];
+    $sanitized_value = $this->invokeMethod($web_service, 'sanitizeFieldKeys', [
+      $resource,
+      $value,
+      $bundle,
+      $base_path
+    ]);
+    $this->assertNotNull($sanitized_value,
+      "You should be able to sanitize a numeric-indexed array if sub-elements are direct values.");
+
+    // - Numeric keys where value is an array with term keys.
+    $value = [
+      ['rdfs:type' => 'fake'],
+      ['rdfs:type' => 'none'],
+    ];
+    $sanitized_value = $this->invokeMethod($web_service, 'sanitizeFieldKeys', [
+      $resource,
+      $value,
+      $bundle,
+      $base_path
+    ]);
+    $this->assertNotNull($sanitized_value,
+      "You should be able to sanitize a numeric-indexed array if sub-elements are also arrays.");
+
+    // - Numeric keys where value is an array with random keys.
+    //   (random keys should be removed.)
+    $value = [
+      ['randomnotterm' => 'fake'],
+    ];
+    $sanitized_value = $this->invokeMethod($web_service, 'sanitizeFieldKeys', [
+      $resource,
+      $value,
+      $bundle,
+      $base_path
+    ]);
+    $this->assertEmpty($sanitized_value[0],
+      "You should be able to sanitize a numeric-indexed array if sub-elements arrays are not keyed with valid terms.");
+
+  }
+
+  /**
+   * Call protected/private method of a class.
+   *
+   * @param object &$object    Instantiated object that we will run method on.
+   * @param string $methodName Method name to call
+   * @param array  $parameters Array of parameters to pass into method.
+   *
+   * @return mixed Method return.
+   */
+  public function invokeMethod(&$object, $methodName, array $parameters = array()) {
+      $reflection = new \ReflectionClass(get_class($object));
+      $method = $reflection->getMethod($methodName);
+      $method->setAccessible(true);
+
+      return $method->invokeArgs($object, $parameters);
   }
 }

+ 9 - 3
tripal/api/tripal.entities.api.inc

@@ -433,6 +433,9 @@ function tripal_add_notification($title, $details, $type, $actions, $submitter_i
  *     - term_name: A human-readable name for this term.  This will became
  *       the name that appears for the content type.  In practice, this
  *       should be the name of the term. (E.g. the name for SO:0000704 is gene).
+ *     - label: An alternative bundle label.  This will be used instead of the
+ *       term name, for cases where the vocabulary term is not human readable.
+ *
  * @param $job
  *  The job ID if this is launched via a job.
  *
@@ -446,6 +449,7 @@ function tripal_create_bundle($args, $job = NULL) {
   $accession = $args['accession'];
   $term_name = $args['term_name'];
   $storage_args = $args['storage_args'];
+  $label = $args['label'];
 
   $message_args = [
     'job' => $job,
@@ -505,9 +509,11 @@ function tripal_create_bundle($args, $job = NULL) {
     if (!in_array($bundle_name, array_keys($einfo['bundles']))) {
       // Make the label for the content type have capitalized words.  The
       // exception is 'mRNA' which we know should not be uppercased.
-      $label = ucwords(preg_replace('/_/', ' ', $term_name));
-      if ($term_name == 'mRNA') {
-        $label = $term_name;
+      if (!$label) {
+        $label = ucwords(preg_replace('/_/', ' ', $term_name));
+        if ($term_name == 'mRNA') {
+          $label = $term_name;
+        }
       }
       // Insert the bundle.
       db_insert('tripal_bundle')

+ 109 - 0
tripal/includes/TripalBundleController.inc

@@ -162,6 +162,52 @@ class TripalBundleController extends EntityAPIControllerExportable {
     }
   }
 
+  /**
+   * Finds any orphaned entities associated with all bundles.
+   *
+   * An orphaned entity can occur if the module that created the entity
+   * unknowingly lost its underlying record in its data store.  Such a case
+   * could happen if someone directly removed the record from the data store
+   * outside of the module's control. This function allows each module
+   * to report if any orphans are missing for a given bundle type.
+   *
+   * @param bool $count
+   *   Set to TRUE to return only a count of orphans.
+   * @param integer $offset
+   *   For paging of entities set this to the offset within the total count.
+   * @param integer $limit
+   *   For paging of entities set this to the total number to return.
+   *
+   * @return array|integer
+   *  If $count == FALSE then an array of all entity IDs that are orphaned. If
+   *  $count == TRUE then a single integer count value is returned.
+   */
+  public function findAllOrphans($count = FALSE, $offset = 0, $limit = 0) {
+    $results = db_select('tripal_bundle', 'tb')
+      ->fields('tb')
+      ->orderBy('label')
+      ->execute();
+    if ($count) {
+      $response = 0;
+    }
+    else {
+      $response = "";
+    }
+    while (($bundle_record = $results->fetchObject())) {
+      $bid = $bundle_record->id;
+      $bundle_response = $this->findOrphans($bid, $count, $offset, $limit);
+      if (is_array($bundle_response)) {
+        foreach ($bundle_response as $key => $value) {
+          $response += $value;
+        }
+      }
+      else {
+        $response += $bundle_response;
+      }
+    }
+    return $response;
+  }
+
 
   /**
    * Deletes orphaned entities.
@@ -187,6 +233,9 @@ class TripalBundleController extends EntityAPIControllerExportable {
     $num_deleted = 0;
     $transaction = db_transaction();
     try {
+      if ($id === 0) {
+        return $this->deleteAllOrphans($job);
+      }
 
       // Get the list of entities that need cleanup.
       $eids = $this->findOrphans($id, FALSE, 0, 0);
@@ -227,4 +276,64 @@ class TripalBundleController extends EntityAPIControllerExportable {
     return $num_deleted;
 
   }
+
+  /**
+   * Deletes orphaned entities from all bundles.
+   *
+   * An orphaned entity can occur if the module that created the entity
+   * unknowingly lost its underlying record in its data store.  Such a case
+   * could happen if someone directly removed the record from the data store
+   * outside of the module's control. This function allows each module
+   * to report if any orphans are missing for a given bundle type.
+   *
+   * @param TripalJob $job
+   *   An optional Tripal Job object. This is provided when this function is
+   *   called using the Tripal Jobs system.
+   *
+   * @return integer
+   *   The number of entitites that were cleaned up.
+   */
+  public function deleteAllOrphans(TripalJob $job = NULL) {
+    $num_deleted = 0;
+    $transaction = db_transaction();
+
+    try {
+      $results = db_select('tripal_bundle', 'tb')
+        ->fields('tb')
+        ->orderBy('label')
+        ->execute();
+      $eids = $this->findAllOrphans(FALSE, 0, 0);
+      $num_entities = count($eids);
+      if ($job) {
+        $job->logMessage('Found !num orphaned entities.', ['!num' => $num_entities]);
+        $job->setInterval(1);
+        $job->setTotalItems($num_entities);
+      }
+
+      if ($num_entities == 0) {
+        return 0;
+      }
+
+      while (($bundle_record = $results->fetchObject())) {
+        $num_bundle_del = $this->deleteOrphans($bundle_record->id);
+        if ($job) {
+          $job->addItemsHandled($num_bundle_del);
+          $job->logMessage("Removed !num orphaned !label entities.", ['!num' => $num_bundle_del, '!label' => $bundle_record->label]);
+        }
+        $num_deleted += $num_bundle_del;
+      }
+    }
+    catch (Exception $e) {
+      $transaction->rollback();
+      $err_msg = "Failed to remove orphans: " . $e->getMessage();
+      if ($job) {
+        $job->logMessage($err_msg, [], 'error');
+      }
+      else {
+        drupal_set_message($err_msg, 'error');
+      }
+      return 0;
+    }
+    return $num_deleted;
+  }
 }

+ 1 - 0
tripal/includes/TripalEntityCollection.inc

@@ -527,6 +527,7 @@ class TripalEntityCollection {
     }
     if ($job) {
       $job->setTotalItems($total_entities);
+      $job->setInterval(1);
     }
 
     // Iterate through the bundles in this collection and get the entities.

+ 0 - 8
tripal/includes/TripalFields/TripalField.inc

@@ -208,14 +208,6 @@ class TripalField {
     return $this->instance;
   }
 
-  /**
-   * When constructing a pager for use by a field, all pagers must have
-   * a unique ID
-   */
-  protected function getPagerElementID() {
-    return $this->field['id'];
-  }
-
   public function getFieldTerm() {
     return $this->term;
   }

+ 0 - 8
tripal/includes/TripalFields/TripalFieldFormatter.inc

@@ -167,14 +167,6 @@ class TripalFieldFormatter {
 
   }
 
-  /**
-   * When constructing a pager for use by a field, all pagers must have
-   * a unique ID
-   */
-  protected function getPagerElementID() {
-    return $this->field['id'];
-  }
-
   /**
    * Updates a pager generated by theme('pager') for use with AJAX.
    *

+ 1 - 1
tripal/includes/TripalJob.inc

@@ -28,7 +28,7 @@ class TripalJob {
 
   /**
    * The interval when the job progress should be updated. Updating the job
-   * progress incurrs a database write which takes time and if it occurs to
+   * progress incurrs a database write which takes time and if it occurs too
    * frequently can slow down the loader.  This should be a value between
    * 0 and 100 to indicate a percent interval (e.g. 1 means update the
    * progress every time the num_handled increases by 1%).

+ 2 - 2
tripal/includes/tripal.fields.inc

@@ -765,8 +765,8 @@ function tripal_field_widget_form_validate($element, &$form_state, $form) {
   $field = $element['#field'];
   $instance = $element['#instance'];
 
-  $langcode = $element['#language'];
-  $delta = $element['#delta'];
+  $langcode = (isset($element['#language'])) ? $element['#language'] : LANGUAGE_NONE;
+  $delta = (isset($element['#delta'])) ? $element['#delta'] : 0;
 
   $widget_class = $instance['widget']['type'];
   tripal_load_include_field_class($widget_class);

+ 417 - 0
tripal/includes/tripal.registration.inc

@@ -0,0 +1,417 @@
+<?php
+/**
+ * @file
+ * Contains functions related to the display of Tripal registration
+ * form in a Tripal website.
+ */
+
+
+/**
+ * Provides the page for the Tripal registration page
+ *
+ */
+function tripal_registration_form($form, &$form_state) {
+  $form_data = unserialize(variable_get('tripal_site_registration', NULL));
+
+  $form['description'] = [
+    '#title' => 'Why Register your Site?',
+    '#type' => 'item',
+    '#markup' => t('Registering your site is important for continued improvements to the software.  You may opt-in by providing answers to the
+        following questions or opt-out by checking the box below. If you opt-in, your site will periodically
+        connect to the http://tripal.info website and provide updated registration details. If you opt-out, no
+        registration information is communictated. You can opt-out at any time.  If you want previously submitted information
+        deleted from the tripal.info database please email !admin.', ['!admin' => l('admin@tripal.info', 'mailto:admin@tripal.info')])
+  ];
+
+  $form['usage_details'] = [
+      '#title' => 'How will this data be used?',
+      '#type' => 'item',
+      '#markup' => t('Tripal is open-source, freely-available, but
+        dependent on external funding. When you register your site, it provides important details that can
+        be used to justify continued support from funding agencies. The information provided will not be shared publically.
+        Information about the Tripal modules installed on your site will be used to help justify continued development to
+        funding agencies.  Registration details may be shared with members of Tripal\'s Project Management Committee (PMC) and
+        Tripal\'s Steering Committee (TSC) and Tripal extension module usage may be shared with developers
+        of the the extension modules to aid in their funding requests.'),
+  ];
+
+  $form['disable_tripal_reporting'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Do not register this site (opt-out).'),
+    '#default_value' => isset($form_data['values']['disable_tripal_reporting']) ? $form_data['values']['disable_tripal_reporting'] : NULL,
+    '#description' => "If you do not want to register your site please check
+      this box. You will no longer be notified to register and no details will be submitted.  You can return later and register at any time.",
+    '#ajax' => array(
+      'callback' => 'custom_registration_ajax_disable_reg_callback',
+      'wrapper' => 'reg-details',
+    ),
+  );
+  $opt_out = $form_data['values']['disable_tripal_reporting'];
+  if (array_key_exists('values', $form_state) and
+      array_key_exists('disable_tripal_reporting', $form_state['values'])){
+        $opt_out = $form_state['values']['disable_tripal_reporting'] ? TRUE : FALSE;
+  }
+
+  if ($opt_out) {
+    $form['details'] = [
+        '#type' => 'markup',
+        '#prefix' => '<div id="reg-details">',
+        '#suffix' => '</div>',
+    ];
+    return $form;
+  }
+  $form['details'] = [
+    '#type' => 'fieldset',
+    '#title' => t('Registration Details'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#disabled' => $opt_out,
+    '#prefix' => '<div id="reg-details">',
+    '#suffix' => '</div>',
+  ];
+
+  $purpose = array(0 => t('Production'), 1 => t('Development'), 2 => t('Experimental'));
+  $form['details']['tripal_reg_site_purpose'] = array(
+      '#type' => 'radios',
+      '#title' => t('Site Status'),
+      '#default_value' => isset($form_data['values']['tripal_reg_site_purpose']) ? $form_data['values']['tripal_reg_site_purpose'] : NULL,
+      '#options' => $purpose,
+      '#required' => TRUE,
+      '#description' => t('Please register your site regardless if it is experimental (to explore tripal),
+       for development of a future site (or updates to an existing site), or a site currently
+       in production. For funding, it is important to know how many sites are active for each category.  If your site changes
+       status, such as from development to production, please remember to return and update the purpose.')
+  );
+
+  $form['details']['tripal_reg_site_modules'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => isset($form_data['values']['tripal_reg_site_modules']) ? $form_data['values']['tripal_reg_site_modules'] : 1,
+      '#title' => t('Report your installed Tripal Extensions.'),
+      '#description' => t('When checked, any Tripal extension modules that you have installed will be reported with your site\'s registration information.')
+  );
+
+  $form['details']['tripal_reg_site_description']= array(
+      '#type' => 'textarea',
+      '#title' => t('Description of the site'),
+      '#default_value' => isset($form_data['values']['tripal_reg_site_description']) ? $form_data['values']['tripal_reg_site_description'] : NULL,
+      '#required' => TRUE,
+      '#description' => t('Please provide a brief description of this site.  Consider including
+     details such as its purpose, the primary data types your site provides, and the
+     research community your site serves.')
+  );
+
+  $form['details']['principal_investigator'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Principal Investigator Contact Information'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+    '#description' => t('Please provide the name and email of this site\'s principal
+     investigator (PI). If the name and email are provided then the PI agrees to
+     receive periodic communication from either the Tripal Advisory Committee (TAC) or
+     Project Management Committee (PMC) for the purposes of engaging with the larger
+     Tripal user community. The PI will NOT be automatically subscribed to mailing lists.')
+  );
+
+  $form['details']['principal_investigator']['principal_investigator_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Name'),
+    '#default_value' => isset($form_data['values']['principal_investigator_name']) ? $form_data['values']['principal_investigator_name'] : NULL,
+    '#required' => FALSE,
+  );
+
+  $form['details']['principal_investigator']['principal_investigator_email'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Email'),
+    '#default_value' => isset($form_data['values']['principal_investigator_email']) ? $form_data['values']['principal_investigator_email'] : NULL,
+    '#required' => FALSE,
+  );
+  $form['details']['tripal_reg_site_admin'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Site Manager (if different from the principal investigator)'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Please provide the name and email of this site\'s manager if
+     different from the PI. Sometimes, site managers desire involvement in community
+     activites as well as the PI.  If the name and email are provided then the site manager agrees to
+     receive periodic communication from either the Tripal Advisory Committee (TAC) or
+     Project Management Committee (PMC) for the purposes of engaging with the larger
+     Tripal user community. The site manager will NOT be automatically subscribed to mailing lists.')
+  );
+
+  $form['details']['tripal_reg_site_admin']['tripal_reg_site_admin_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Name'),
+    '#default_value' => isset($form_data['values']['tripal_reg_site_admin_name']) ? $form_data['values']['tripal_reg_site_admin_name'] : NULL,
+    '#required' => FALSE,
+  );
+
+  $form['details']['tripal_reg_site_admin']['tripal_reg_site_admin_email'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Email'),
+    '#default_value' => isset($form_data['values']['tripal_reg_site_admin_email']) ? $form_data['values']['tripal_reg_site_admin_email'] : NULL,
+    '#required' => FALSE,
+  );
+
+  $form['details']['funding'] = array(
+    '#type' => 'container',
+    '#tree' => TRUE,
+    '#prefix' => '<div id="funding">',
+    '#suffix' => '</div>',
+  );
+
+  $num_sources = 1;
+  if (isset($form_data['values']['funding'])) {
+    $num_sources = count(array_keys($form_data['values']['funding'])) + 1;
+  }
+  if (array_key_exists('triggering_element', $form_state) and
+      $form_state['triggering_element']['#name'] == 'add_funding') {
+    $num_sources++;
+  }
+
+  for ($i = 1; $i <= $num_sources; $i++) {
+    $collapsed = TRUE;
+    $tripal_reg_site_agency = '';
+    $tripal_reg_site_grant = '';
+    $tripal_reg_site_amount = '';
+    $tripal_reg_site_start = date('Y', time());
+    $tripal_reg_site_end = date('Y', time());
+    if ($i == $num_sources) {
+      $collapsed = FALSE;
+    }
+    if ($i < $num_sources) {
+      $source = $form_data['values']['funding'][$i-1];
+      $tripal_reg_site_agency = $source['tripal_reg_site_agency'];
+      $tripal_reg_site_grant = $source['tripal_reg_site_grant'];
+      $tripal_reg_site_amount = $source['tripal_reg_site_amount'];
+      $tripal_reg_site_start = $source['funding_period']['tripal_reg_site_start'];
+      $tripal_reg_site_end = $source['funding_period']['tripal_reg_site_end'];
+    }
+    $form['details']['funding'][$i] = array(
+      '#type' => 'fieldset',
+      '#title' => t("Funding Source %index, %name", ['%index' => $i, '%name' => $tripal_reg_site_agency]),
+      '#tree' => TRUE,
+      '#collapsible' => TRUE,
+      '#collapsed' => $collapsed,
+      '#description' => t('When requesting funds for additional Tripal development,
+     it is important to report the breadth of of funding sources for Tripal sites.
+     Please consider sharing this information by providing the granting
+     agency, and funding periods.')
+    );
+
+    $form['details']['funding'][$i]['tripal_reg_site_agency'] = array(
+      '#type' => 'textfield',
+        '#default_value' => $tripal_reg_site_agency,
+      '#title' => t('Funding Agency'),
+    );
+    $form['details']['funding'][$i]['tripal_reg_site_grant'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $tripal_reg_site_grant,
+      '#title' => t('Grant/Award Number'),
+    );
+    $form['details']['funding'][$i]['tripal_reg_site_amount'] = array(
+      '#type' => 'textfield',
+        '#default_value' => $tripal_reg_site_amount,
+      '#title' => t('Funding Amount'),
+    );
+    $form['details']['funding'][$i]['funding_period'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Funding Period'),
+      '#tree' => TRUE,
+    );
+    $form['details']['funding'][$i]['funding_period']['tripal_reg_site_start'] = array(
+      '#type' => 'date_select',
+      '#title' => t("Start"),
+      '#default_value' => $tripal_reg_site_start,
+      '#date_year_range' => '-20:+20',
+      '#date_format' => 'Y',
+    );
+    $form['details']['funding'][$i]['funding_period']['tripal_reg_site_end'] = array(
+      '#type' => 'date_select',
+      '#title' => t('End'),
+      '#default_value' => $tripal_reg_site_end,
+      '#date_year_range' => '-20:+20',
+      '#date_format' => 'Y',
+    );
+  }
+
+  $form['details']['funding']['add_funding'] = array(
+    '#type' => 'button',
+    '#name' => 'add_funding',
+    '#value' => t('Add additional funding sources'),
+    '#href' => '',
+    '#limit_validation_errors' => [],
+    '#ajax' => array(
+      'callback' => 'custom_registration_ajax_add_funding',
+      'wrapper' => 'funding',
+    ),
+  );
+
+  $form['details']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Submit',
+  );
+
+  return $form;
+
+}
+
+function custom_registration_ajax_add_funding($form, $form_state) {
+  // Save the current state of the registration form.
+  tripal_registration_form_save_registration_info($form_state);
+
+  return $form['details']['funding'];
+}
+
+function custom_registration_ajax_disable_reg_callback($form, &$form_state) {
+  // Save the current state of the registration form.
+  tripal_registration_form_save_registration_info($form_state);
+
+  // Turn off the notice to register the site.
+  variable_set('disable_tripal_reporting', TRUE);
+  if ($form_state['values']['disable_tripal_reporting']) {
+    drupal_set_message('You have opted-out of registation.');
+  }
+  return $form['details'];
+}
+
+/**
+ * Implements validation from the Form API.
+ *
+ * @param $form
+ *   A structured array containing the elements and properties of the form.
+ * @param $form_state
+ *   An array that stores information about the form's current state
+ *   during processing.
+ */
+function tripal_registration_form_validate($form, &$form_state){
+  $mail_pi = array_key_exists('principal_investigator_email', $form_state['values']) ? $form_state['values']['principal_investigator_email'] : '';
+  $mail_sa = array_key_exists('tripal_reg_site_admin_email', $form_state['values']) ? $form_state['values']['tripal_reg_site_admin_email'] : '';
+
+  if ($form_state['values']['disable_tripal_reporting']) {
+    return;
+  }
+
+  if (!$mail_pi and !$mail_sa) {
+    form_set_error('principal_investigator_email', t('Please provide an email address for the principal investigator or the site admin.'));
+  }
+
+  if (!empty($mail_pi) && !valid_email_address($mail_pi)) {
+    form_set_error('principal_investigator_email', t('The email address for the principal investigator appears to be invalid.'));
+  }
+  if(!empty($mail_sa) && !valid_email_address($mail_sa)) {
+    form_set_error('tripal_reg_site_admin_email', t("The email address for the site administrator appears to be invalid."));
+  }
+
+}
+
+/**
+ * Saves the current state of the registration form as a drupal variable.
+ */
+function tripal_registration_form_save_registration_info($form_state) {
+  // Check for empty funding periods and remove them.
+  $sources = [];
+  foreach ($form_state['values']['funding'] as $funding_source) {
+    if (!empty($funding_source['tripal_reg_site_agency'])) {
+      $sources[] = $funding_source;
+    }
+  }
+  $form_state['values']['funding'] = $sources;
+
+  // Save the registration details.
+  $registration = serialize($form_state);
+  variable_set('tripal_site_registration', $registration);
+}
+/**
+ * Implements submit from the Form API.
+ *
+ * @param $form
+ *   A structured array containing the elements and properties of the form.
+ * @param $form_state
+ *   An array that stores information about the form's current state
+ *   during processing.
+ */
+function tripal_registration_form_submit($form, &$form_state) {
+  variable_set('disable_tripal_reporting', TRUE);
+  tripal_registration_form_save_registration_info($form_state);
+
+  // Now send the updated info to the Tripal Site.
+  // Only register with tripal.info if the user has not opt'd out.
+  if ($form_state['values']['disable_tripal_reporting'] == FALSE) {
+
+    $registration = variable_get('tripal_site_registration');
+    tripal_registration_remote_submit($registration);
+    drupal_set_message(t('Registration sent to tripal.info'), 'status');
+    drupal_set_message(t('Thank you for registering. You can update your details at any time.'), 'status');
+  }
+  else {
+    drupal_set_message(t('You are not registered with tripal.info. You can
+            change this at any time by unchecking the opt out checkbox and
+            submitting the form.'), 'status');
+  }
+}
+
+function tripal_registration_remote_submit($data) {
+  global $base_url;
+  $endpoint = 'http://tripal.info/registration/content/50619jdi8ciayjhygidf';
+  //Are we getting the modules?
+  $form_data = unserialize($data);
+  if ($form_data['values']['tripal_reg_site_modules']) {
+    // Get current list of modules.
+    $files = system_rebuild_module_data();
+
+    // Remove hidden modules from display list.
+    $visible_files = $files;
+    foreach ($visible_files as $filename => $file) {
+      if (!empty($file->info['hidden'])) {
+        unset($visible_files[$filename]);
+      }
+    }
+
+    // Iterate through each of the modules.
+    $tripal_modules = [];
+    foreach ($visible_files as $filename => $module) {
+      if ($module->info['package'] == 'Tripal Extensions') {
+        $module_name = $module->info['name'];
+        if (!in_array($module->info, $tripal_modules)) {
+          $tripal_modules[$module_name]['info'] = $module->info;
+          $tripal_modules[$module_name]['status'] = $module->status;
+        }
+      }
+    }
+  }
+
+  //Clean up form data
+  $outgoing_data['pi_name'] = $form_data['values']['principal_investigator_name'];
+  $outgoing_data['pi_email'] = $form_data['values']['principal_investigator_email'];
+  $outgoing_data['sa_name'] = $form_data['values']['tripal_reg_site_admin_name'];
+  $outgoing_data['sa_email'] = $form_data['values']['tripal_reg_site_admin_email'];
+  $outgoing_data['description'] = $form_data['values']['tripal_reg_site_description'];
+
+  $i = 0;
+  foreach ($form_data['values']['funding'] as $funding_source){
+    $outgoing_data['funding_period'][$i]['funding_agency'] = $funding_source['tripal_reg_site_agency'];
+    $outgoing_data['funding_period'][$i]['funding_grant']  = $funding_source['tripal_reg_site_grant'];
+    $outgoing_data['funding_period'][$i]['funding_start']  = $funding_source['funding_period']['tripal_reg_site_start'];
+    $outgoing_data['funding_period'][$i]['funding_end']  = $funding_source['funding_period']['tripal_reg_site_end'];
+    $outgoing_data['funding_period'][$i]['funding_amount']  = $funding_source['tripal_reg_site_amount'];
+    $i++;
+  }
+
+  $outgoing_data['type'] = $form_data['values']['tripal_reg_site_purpose'];
+
+  //Build the info to send out.
+  $outgoing_data['tripal_modules'] = $tripal_modules;
+  $outgoing_data['site_name'] = variable_get('site_name', 'Default');
+  $outgoing_data['site_url'] = $base_url;
+
+  //Send
+  $result = drupal_http_request($endpoint, array(
+    'method' => 'POST',
+    'headers' => array('Content-Type' => 'application/json', 'Accept' => 'application/json'),
+    'data' => json_encode($outgoing_data),
+  ));
+
+  variable_set('tripal_site_registration_last_update', time());
+
+  return $result;
+}

+ 20 - 1
tripal/includes/tripal.unpublish_orphans.inc

@@ -23,6 +23,7 @@ function tripal_unpublish_orphans_form($form, &$form_state) {
   while ($bundle = $results->fetchObject()) {
     $bundles[$bundle->id] = $bundle->label;
   }
+  $bundles[0] = "Delete all orphaned content";
   drupal_set_title('Unpublish Orphaned Content');
 
   $form['description'] = [
@@ -91,6 +92,22 @@ function tripal_unpublish_orphans_form($form, &$form_state) {
     }
   }
 
+  if ($selected_bundle_id === '0') {
+    $bundlec = entity_get_controller('TripalBundle');
+    $count = $bundlec->findAllOrphans(TRUE);
+    $form['bundle_info_fieldset']['message'] = [
+      '#type' => 'markup',
+      '#markup' => t('<p><strong>There are ' . $count . ' orphaned entities in all bundles.</strong></p>'),
+    ];
+
+    if ($count > 0) {
+      $form['bundle_info_fieldset']['submit'] = [
+        '#type' => 'submit',
+        '#value' => 'Unpublish Orphaned Entities',
+      ];
+    }
+  }
+
   return $form;
 }
 
@@ -104,7 +121,9 @@ function tripal_unpublish_orphans_form_validate($form, &$form_state) {
   $bundle_id = isset($form_state['values']['bundles']) ? $form_state['values']['bundles'] : NULL;
 
   if (empty($bundle_id) || !is_numeric($bundle_id)) {
-    form_set_error('bundles', t('Please select a valid bundle.'));
+    if (!isset($bundle_id) || $bundle_id !== '0') {
+      form_set_error('bundles', t('Please select a valid bundle.'));
+    }
   }
 }
 

+ 2 - 5
tripal/theme/js/tripal.js

@@ -208,18 +208,15 @@
 
 // Used for ajax update of fields by links in a pager.
 function tripal_navigate_field_pager(id, page) {
-  jQuery(document).ajaxStart(function () {
-    jQuery('#' + id + '-spinner').show();
-  }).ajaxComplete(function () {
-    jQuery('#' + id + '-spinner').hide();
-  });
 
+  jQuery('#' + id + '-spinner').show();
   jQuery.ajax({
     type   : 'GET',
     url    : Drupal.settings['basePath'] + 'bio_data/ajax/field_attach/' + id,
     data   : {'page': page},
     success: function (response) {
       jQuery('#' + id + ' .field-items').replaceWith(response['content']);
+      jQuery('#' + id + '-spinner').hide();
     }
   });
 }

+ 37 - 1
tripal/tripal.module

@@ -28,6 +28,7 @@ tripal_import_api();
 require_once "includes/tripal.field_storage.inc";
 require_once "includes/tripal.fields.inc";
 require_once "includes/tripal.entity.inc";
+require_once "includes/tripal.registration.inc";
 
 require_once "includes/TripalVocab.inc";
 require_once "includes/TripalVocabController.inc";
@@ -84,6 +85,16 @@ function tripal_init() {
   // will fail when inserting or updating a date column in a table.
   db_query("SET DATESTYLE TO :style", array(':style' => 'MDY'));
 
+  //Ask users to do the registration form
+  if (user_access('administer tripal')) {
+    if (empty(variable_get('tripal_site_registration', FALSE)) || !(variable_get('disable_tripal_reporting', FALSE))) {
+      if (current_path() != 'admin/tripal/register' and current_path() != 'system/ajax') {
+        drupal_set_message('Please register your Tripal Site. Registering provides important
+        information that will help secure funding for continued improvements to Tripal. ' .
+          l('Click to register now or opt out.', 'admin/tripal/register'), 'warning');
+      }
+    }
+  }
 }
 
 function tripal_menu_alter(&$items) {
@@ -109,6 +120,20 @@ function tripal_menu() {
     'file path' => drupal_get_path('module', 'system'),
   );
 
+  // Tripal registration form.
+  $items['admin/tripal/register'] = [
+    'title' => 'Registration',
+    'description' => t('Register your Tripal website. Registration of Tripal
+          websites gives the developers important information that allow us
+          to continue to secure funding for maintenance and expansion.'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_registration_form'),
+    'access arguments' => array('administer tripal'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 0,
+    'file' => 'includes/tripal.registration.inc',
+  ];
+
   $items['admin/content/bio_data/add'] = [
     'title' => 'Add Tripal Content',
     'description' => t('Create new Tripal Content.'),
@@ -1347,7 +1372,7 @@ function tripal_cron() {
     $modules = module_implements('tripal_cron_notification');
     foreach ($modules as $module) {
       $function = $module . '_tripal_cron_notification';
-      tripal_add_job("Cron: Checking for '$module' notifications.", 'tripal',                                         // Module Name
+      tripal_add_job("Cron: Checking for '$module' notifications.", 'tripal',
         $function, $args, 1, 1, $includes, TRUE);
     }
   }
@@ -1359,6 +1384,17 @@ function tripal_cron() {
   tripal_add_job('Cron: Checking expired files', 'tripal',
     'tripal_expire_files', $args, 1, 1, $includes, TRUE);
 
+  // Update the registration information every month.
+  if (variable_get('tripal_site_registration', FALSE)) {
+    $last_submit = variable_get('tripal_site_registration_last_update');
+    if ($last_submit) {
+      if ($last_submit <= strtotime('-1 month')) {
+        $registration = variable_get('tripal_site_registration', FALSE);
+        $success = tripal_registration_remote_submit($registration);
+        watchdog('Tripal Cron', 'Tripal registration has been successfull submitted to the remote service.', [], WATCHDOG_INFO);
+      }
+    }
+  }
 }
 
 /**

+ 2 - 1
tripal_chado/api/modules/tripal_chado.pub.api.inc

@@ -548,11 +548,12 @@ function chado_execute_pub_importer($import_id, $publish = TRUE,
         ['%num' => $num_pubs], $message_opts);
 
       $subset_report = tripal_pub_add_publications($pubs, $import->do_contact, $do_update, $job);
+      $countpubs = count($pubs);  // the following merge resets count($pubs) so save it
       foreach ($subset_report as $action => $pubs) {
         $report[$action] = array_merge($report[$action], $pubs);
       }
       $page++;
-    } while (count($pubs) == $num_to_retrieve);
+    } while ($countpubs == $num_to_retrieve);
 
     // Publish as requested by the caller.
     _chado_execute_pub_importer_publish($publish, $job, $message_type, $message_opts);

+ 10 - 2
tripal_chado/api/tripal_chado.api.inc

@@ -224,6 +224,8 @@ function chado_publish_records($values, $job = NULL) {
   // Perform the query in chunks.
   $sql = $select . $from . $where . ' LIMIT ' . $chunk_size;
   $more_records_to_publish = TRUE;
+  $num_actually_published = 0; // the full number of records published.
+  $i = 0; //reset on each chunk.
   while ($more_records_to_publish) {
 
     $records = chado_query($sql, $args);
@@ -236,6 +238,9 @@ function chado_publish_records($values, $job = NULL) {
     // are already in one.
     $transaction = db_transaction();
     try {
+      // Keep track of how many we've published so far
+      // before clearing our chunk,
+      $num_actually_published = $num_actually_published + $i;
       $i = 0;
       while ($record = $records->fetchObject()) {
 
@@ -293,7 +298,7 @@ function chado_publish_records($values, $job = NULL) {
       return FALSE;
     }
 
-    // If we get through the loop and haven't completed 100 records, then 
+    // If we get through the loop and haven't completed 100 records, then
     // we're done!
     if ($i < $chunk_size) {
       $more_records_to_publish = FALSE;
@@ -303,9 +308,12 @@ function chado_publish_records($values, $job = NULL) {
     unset($transaction);
   }
 
+  // Add the last number published to our total.
+  $num_actually_published = $num_actually_published + $i;
+
   tripal_report_error($message_type, TRIPAL_INFO,
     "Successfully published !count !type record(s).",
-    ['!count' => $i, '!type' => $bundle->label], $message_opts);
+    ['!count' => $num_actually_published, '!type' => $bundle->label], $message_opts);
 
   return TRUE;
 }

+ 49 - 44
tripal_chado/api/tripal_chado.query.api.inc

@@ -349,9 +349,9 @@ function chado_set_active($dbname = 'default') {
   $search_path = chado_get_schema_name('drupal');
 
   // Change only if 'chado' has been specified.
-  // Notice that we leave the active_db set as chado but use the possibly 
-  // user-altered  schema name for the actual search path. This is to keep 
-  // outward facing mentions of chado as "chado" while still allowing the user 
+  // Notice that we leave the active_db set as chado but use the possibly
+  // user-altered  schema name for the actual search path. This is to keep
+  // outward facing mentions of chado as "chado" while still allowing the user
   // to alter the schema name used.
   if ($dbname == 'chado') {
     $active_db = 'chado';
@@ -490,7 +490,7 @@ function chado_insert_record($table, $values, $options = []) {
   // that has all the values needed for insert with all foreign relationsihps
   // resolved.
   foreach ($values as $field => $value) {
-    // Make sure the field is in the table description. If not then return an 
+    // Make sure the field is in the table description. If not then return an
     // error message.
     if (!array_key_exists($field, $table_desc['fields'])) {
       tripal_report_error('tripal_chado', TRIPAL_ERROR,
@@ -640,7 +640,7 @@ function chado_insert_record($table, $values, $options = []) {
   if ($options['return_record'] == TRUE and $result) {
     if (array_key_exists('primary key', $table_desc) and is_array($table_desc['primary key'])) {
       foreach ($table_desc['primary key'] as $field) {
-        $sql = "SELECT CURRVAL('{" . $table . "_" . $field . "_seq}')";
+        $sql = "SELECT CURRVAL('{" . $table . "}_" . $field . "_seq')";
         $results = chado_query($sql);
         $value = $results->fetchField();
         if (!$value) {
@@ -910,7 +910,7 @@ function chado_update_record($table, $match, $values, $options = NULL) {
 
   // If we have a result then add primary keys to return array.
   if ($options['return_record'] == TRUE and $result) {
-    // Only if we have a single result do we want to add the primary keys to the 
+    // Only if we have a single result do we want to add the primary keys to the
     // values array.  If the update matched many records we can't add the pkeys.
 
     if (count($pkeys) == 1) {
@@ -1430,7 +1430,7 @@ function chado_select_record($table, $columns, $values, $options = NULL) {
         $where[] = $value;
       }
       // CASE 1c: If we have an integer indexed array and the first element is
-      // not an array then we have a simple array of values to be used for an 
+      // not an array then we have a simple array of values to be used for an
       // IN clause.
       elseif (is_int(key($value)) AND !is_array(current($value))) {
 
@@ -1453,7 +1453,7 @@ function chado_select_record($table, $columns, $values, $options = NULL) {
             $where[] = $subvalue;
           }
         }
-        // CASE 1e: We have a multi-dimensional array that doesn't fit any of 
+        // CASE 1e: We have a multi-dimensional array that doesn't fit any of
         // the above cases then we have a foreign key definition to follow.
         else {
 
@@ -1701,7 +1701,7 @@ function chado_select_record_check_value_type(&$op, &$value, $type) {
  * $args = array( ':feature_uniquename' => $form_state['values']['uniquename']
  *   );
  * $result = chado_query($sql, $args);
- * while ($r = $results->fetchObject()) { 
+ * while ($r = $results->fetchObject()) {
  *   // Do something with the record object $r
  * }
  * @endcode
@@ -1728,30 +1728,51 @@ function chado_query($sql, $args = []) {
     // Remove carriage returns from the SQL.
     $sql = preg_replace('/\n/', ' ', $sql);
 
+    // Get the current default Chado and Drupal schema prefixes.
+    $chado_schema_name = chado_get_schema_name('chado');
+    $drupal_schema_name = chado_get_schema_name('drupal');
+
     // Prefix the tables with their correct schema.
     // Chado tables should be enclosed in curly brackets (ie: {feature} )
     // and Drupal tables should be enclosed in square brackets
     // (ie: [tripal_jobs] ).
-    $chado_schema_name = chado_get_schema_name('chado');
-    $drupal_schema_name = chado_get_schema_name('drupal');
-    $sql = preg_replace('/\{(.*?)\}/', $chado_schema_name . '.$1', $sql);
-    $sql = preg_replace('/\[(\w+)\]/', $drupal_schema_name . '.$1', $sql);
+    $matches = [];
+    if (preg_match_all('/\{(.*?)\}/', $sql, $matches)) {
+      $matches = $matches[1];
+      $chado_tables = array_unique(array_keys(chado_get_table_names(TRUE)));
+      foreach ($matches as $match) {
+        if (in_array(strtolower($match), $chado_tables)) {
+          $sql = preg_replace("/\{$match\}/", $chado_schema_name . '.' . $match, $sql);
+        }
+      }
+    }
 
-    // Add an alter hook to allow module developers to change the query right 
+    // Now set the Drupal prefix if the table is surrounded by square brackets.
+    if (preg_match_all('/\[(.*?)\]/', $sql, $matches)) {
+      $matches = $matches[1];
+      $drupal_tables = array_unique(array_keys(drupal_get_schema()));
+      foreach ($matches as $match) {
+        if (in_array(strtolower($match), $drupal_tables)) {
+          $sql = preg_replace("/\[$match\]/", $drupal_schema_name . '.' . $match, $sql);
+        }
+      }
+    }
+
+    // Add an alter hook to allow module developers to change the query right
     // before it's  executed. Since all queriying of chado by Tripal eventually
     // goes through this function, we only need to provide an alter hook at this
-    // point in order to ensure developers have complete control over the query 
-    // being executed. For example, a module developer might want to remove 
-    // schema prefixing from queries and rely on the search path. This alter 
+    // point in order to ensure developers have complete control over the query
+    // being executed. For example, a module developer might want to remove
+    // schema prefixing from queries and rely on the search path. This alter
     // hook would allow them to do that by implementing
-    // mymodule_chado_query_alter($sql, $args) and using a regular expression 
+    // mymodule_chado_query_alter($sql, $args) and using a regular expression
     // to remove table prefixing from the query.
     // @see hook_chado_query_alter().
     drupal_alter('chado_query', $sql, $args);
 
     // The featureloc table has some indexes that use function that call other
-    // functions and those calls do not reference a schema, therefore, any 
-    // tables with featureloc must automaticaly have the chado schema set as 
+    // functions and those calls do not reference a schema, therefore, any
+    // tables with featureloc must automaticaly have the chado schema set as
     // active to find.
     if (preg_match('/' . $chado_schema_name . '.featureloc/i', $sql) or preg_match('/' . $chado_schema_name . '.feature/i', $sql)) {
       $previous_db = chado_set_active('chado');
@@ -1763,7 +1784,7 @@ function chado_query($sql, $args = []) {
         throw $e;
       }
     }
-    // For all other tables we should have everything in scope so just run the 
+    // For all other tables we should have everything in scope so just run the
     // query.
     else {
       $results = db_query($sql, $args);
@@ -1817,11 +1838,11 @@ function hook_chado_query_alter(&$sql, &$args) {
 
   // The following code is an example of how this alter function might be used.
   // Say you would like only a portion of node => feature connections available
-  // for a period of time or under a specific condition. To "hide" the other 
-  // connections you might create a temporary view of the chado_feature table 
+  // for a period of time or under a specific condition. To "hide" the other
+  // connections you might create a temporary view of the chado_feature table
   // that only includes the connections you would like to be available. In order
-  // to ensure this view is used rather than the original chado_feature table 
-  // you could alter all Tripal queries referring to chado_feature to instead 
+  // to ensure this view is used rather than the original chado_feature table
+  // you could alter all Tripal queries referring to chado_feature to instead
   //refer to your view.
   if (preg_match('/(\w+)\.chado_feature/', $sql, $matches)) {
 
@@ -2105,7 +2126,7 @@ function hook_chado_get_schema_name_alter($schema_name, $context) {
 }
 
 /**
- * A replacment for db_select when querying Chado.
+ * A replacement for db_select when querying Chado.
  *
  * Use this function instead of db_select when querying Chado tables.
  *
@@ -2121,24 +2142,8 @@ function hook_chado_get_schema_name_alter($schema_name, $context) {
  *   A new SelectQuery object for this connection.
  *
  * @ingroup tripal_chado_query_api
+ * @see \ChadoPrefixExtender::select()
  */
 function chado_db_select($table, $alias = NULL, array $options = []) {
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-
-  // We only want one connection for chado_db_select, so the first time we
-  // create it, we'll save it in the $GLOBALS array for use next time this
-  // function is called. If we don't do this, then the function will
-  // open too many connections and cause the database server to block.
-  $conn = NULL;
-  if (!array_key_exists('chado_db_select_connnection', $GLOBALS)) {
-    $conninfo = Database::getConnectionInfo();
-    $conn = new ChadoDatabaseConnection($conninfo['default']);
-    $GLOBALS['chado_db_select_connnection'] = $conn;
-  }
-  else {
-    $conn = $GLOBALS['chado_db_select_connnection'];
-  }
-  return $conn->select($table, $alias, $options);
+  return ChadoPrefixExtender::select($table, $alias, $options);
 }

+ 9 - 4
tripal_chado/api/tripal_chado.schema.api.inc

@@ -392,11 +392,16 @@ function chado_get_version($exact = FALSE, $warn_if_unsupported = FALSE) {
     $version = "1.11 or older";
   }
   else {
+    // We need to hardcode the schema here rather than rely on the db_query
+    // function to settle it because the db_query function needs to know the
+    // Chado version to make the table prefixes set properly (chicken and
+    // egg problem).
+    $chado_schema = chado_get_schema_name('chado');
     $sql = "
       SELECT value
-      FROM {chadoprop} CP
-        INNER JOIN {cvterm} CVT on CVT.cvterm_id = CP.type_id
-        INNER JOIN {cv} CV on CVT.cv_id = CV.cv_id
+      FROM $chado_schema.chadoprop CP
+        INNER JOIN $chado_schema.cvterm CVT on CVT.cvterm_id = CP.type_id
+        INNER JOIN $chado_schema.cv CV on CVT.cv_id = CV.cv_id
       WHERE CV.name = 'chado_properties' and CVT.name = 'version'
     ";
     if (!$is_local) {
@@ -498,7 +503,7 @@ function chado_get_table_names($include_custom = NULL) {
     }
   }
 
-  // now add in the custom tables too if requested
+  // Now add in the custom tables too if requested
   if ($include_custom) {
     $sql = "SELECT table_name FROM {tripal_custom_tables}";
     $resource = db_query($sql);

+ 0 - 55
tripal_chado/includes/ChadoDatabaseConnection.inc

@@ -1,55 +0,0 @@
-<?php
-
-/**
- * Overrides the DatabaseConnection_pgsql.
- *
- * The primary purpose of this class is to allow for prefixing of Chado tables.
- * By default the only way to support this is to add an array to the 'prefix'
- * key of the settings.php file.  But this is problematic. For example, what
- * if there is a contact table in the Drupal database as well as one in Chado.
- * The default prefix replacement would always rewrite it to be the one in
- * Chado.  This class is intended to be used when the Chado tables
- * are needed.
- *
- */
-class ChadoDatabaseConnection extends DatabaseConnection_pgsql {
-
-  /**
-   * A replacement constructor for DatabaseConnection_pgsql::__construct.
-   *
-   * The primary purpose for overiding the constructor is to dynamically add
-   * a set of prefixes for replacing. This will allow Chado tables to be
-   * prefixed with the 'chado.' schema prefix.  The alternative to overridding
-   * the DatabaseConnection_pgsql is to ask the end-user to add a prefix
-   * entry for every Chado table and custom table they create.  That's not
-   * very manageable.
-   */
-  function __construct(array $connection_options = []) {
-    parent::__construct($connection_options);
-
-
-    // Get the list of prefix search and replace that are set in the
-    // settings.php file. We'll need those later.
-    $psearch = $this->prefixSearch;
-    $preplace = $this->prefixReplace;
-
-    // Reset the prefix serach and replace
-    $this->prefixSearch = [];
-    $this->prefixReplace = [];
-
-    $tables = chado_get_table_names(TRUE);
-    foreach ($tables as $table) {
-      $this->prefixSearch[] = '{' . $table . '}';
-      $this->prefixReplace[] = 'chado.' . $table;
-    }
-    $this->prefixSearch = array_merge($this->prefixSearch, $psearch);
-    $this->prefixReplace = array_merge($this->prefixReplace, $preplace);
-  }
-
-  public function prefixTables($sql) {
-    $sql = str_replace($this->prefixSearch, $this->prefixReplace, $sql);
-    return $sql;
-  }
-
-
-}

+ 265 - 0
tripal_chado/includes/ChadoPrefixExtender.inc

@@ -0,0 +1,265 @@
+<?php
+
+/**
+ * ChadoPrefixExtender
+ *
+ * A query extender that for select queries. By extending the
+ * SelectQueryExtender class, we can make sure that chado tables
+ *
+ * @see https://www.drupal.org/docs/7/api/database-api/dynamic-queries/extenders
+ */
+class ChadoPrefixExtender extends SelectQueryExtender {
+
+  /**
+   * A static cache for Chado tables.
+   *
+   * @var array
+   */
+  protected static $chado_tables = [];
+
+  /**
+   * A replacement for db_select when querying Chado.
+   *
+   * Use this function instead of db_select when querying Chado tables.
+   *
+   * @param string|\SelectQuery $table
+   *   The base table for this query. May be a
+   *   string or another SelectQuery object. If a query object is passed, it
+   *   will be used as a subselect.
+   * @param string $alias
+   *   The alias for the base table of this query.
+   * @param array $options
+   *   An array of options to control how the query
+   *   operates.
+   *
+   * @return \SelectQuery
+   *   A new SelectQuery object for this connection.
+   *
+   * @ingroup tripal_chado_query_api
+   */
+  public static function select($table, $alias = NULL, array $options = []) {
+    // Since the table could also be a SelectQuery object, we should verify that
+    // it is a string first.
+    if (is_string($table)) {
+      $table = static::getTable($table);
+    }
+
+    // If the alias is null, determine a safe alias. db_select fails to generate
+    // a safe alias when the table name is prefixed with "public.".
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    // Create a select query
+    $query = db_select($table, $alias, $options);
+    return $query->extend('ChadoPrefixExtender');
+  }
+
+  /**
+   * @param $type
+   * @param $table
+   * @param null $alias
+   * @param null $condition
+   * @param array $arguments
+   *
+   * @return $this
+   *
+   * @see SelectQueryInterface::addJoin()
+   */
+  public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = []) {
+    $table = static::getTable($table);
+
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    $this->query->addJoin($type, $table, $alias, $condition, $arguments);
+
+    return $this;
+  }
+
+  /**
+   * Overwrites the join to prefix table names.
+   *
+   * @param string $table
+   *   Table to join.
+   * @param string $alias
+   *   Alias for joined table.
+   * @param string $condition
+   *   Operation for joining.
+   * @param array $arguments
+   *   Additional arguments.
+   *
+   * @return $this
+   *   The current object.
+   *
+   * @ingroup tripal_chado_query_api
+   */
+  public function join($table, $alias = NULL, $condition = NULL, $arguments = []) {
+    $table = static::getTable($table);
+
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    $this->query->join($table, $alias, $condition, $arguments);
+
+    return $this;
+  }
+
+  /**
+   * Overwrites the innerJoin to prefix table names.
+   *
+   * @param string $table
+   *   Table to join.
+   * @param string $alias
+   *   Alias for joined table.
+   * @param string $condition
+   *   Operation for joining.
+   * @param array $arguments
+   *   Additional arguments.
+   *
+   * @return $this
+   *   The current object.
+   *
+   * @ingroup tripal_chado_query_api
+   */
+  public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = []) {
+    $table = static::getTable($table);
+
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    $this->query->innerJoin($table, $alias, $condition, $arguments);
+
+    return $this;
+  }
+
+  public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = []) {
+    $table = static::getTable($table);
+
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    $this->query->leftJoin($table, $alias, $condition, $arguments);
+
+    return $this;
+  }
+
+  /**
+   * Overwrites the rightJoin to prefix table names.
+   *
+   * @param string $table
+   *   Table to join.
+   * @param string $alias
+   *   Alias for joined table.
+   * @param string $condition
+   *   Operation for joining.
+   * @param array $arguments
+   *   Additional arguments.
+   *
+   * @return $this
+   *   The current object.
+   *
+   * @ingroup tripal_chado_query_api
+   */
+  public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = []) {
+    $table = static::getTable($table);
+
+    if (is_null($alias)) {
+      $alias = static::makeAlias($table);
+    }
+
+    $this->query->rightJoin($table, $alias, $condition, $arguments);
+
+    return $this;
+  }
+
+  /**
+   * Checks if a table is a chado table.
+   *
+   * @param string $table The table name.
+   *
+   * @return bool
+   */
+  public static function isChadoTable($table) {
+    if (empty(static::$chado_tables)) {
+      static::$chado_tables = chado_get_table_names(TRUE);
+    }
+
+    return in_array(strtolower($table), static::$chado_tables);
+  }
+
+  /**
+   * If the table name has a schema name as a prefix, replace it with the
+   * correct schema name.
+   *
+   * @param string $table
+   *   The table name.
+   *
+   * @return string
+   *   The table with the correct prefix.
+   *
+   */
+  public static function getTable($table) {
+    $chado_schema_name = chado_get_schema_name('chado');
+    $drupal_schema_name = chado_get_schema_name('drupal');
+
+    // No schema was provided.
+    if (strpos($table, '.') === FALSE) {
+      // If this is a chado table, add the chado prefix. Otherwise, add the
+      // public prefix.
+      if (static::isChadoTable($table)) {
+        $table = $chado_schema_name . ".{$table}";
+      }
+      else {
+        $table = $drupal_schema_name . ".{$table}";
+      }
+    }
+
+    // Now that the schema has been set, we can replace it with the correct
+    // name. Note that schema names can be altered by developers so we need to
+    // to run the following function to obtain the final name.
+    $table = static::getRealSchema($table);
+
+    return $table;
+  }
+
+  /**
+   * Allows altered schema names to be replaces correctly.
+   *
+   * @param string $table
+   *     The table name with a prefix such as "chado." or "public."
+   *
+   * @return mixed
+   *    The table name with the correct prefix.
+   */
+  public static function getRealSchema($table) {
+
+    if (strpos($table, 'public.') === 0) {
+      $replace = chado_get_schema_name('drupal') . '.';
+      return str_replace('public.', $replace, $table);
+    }
+
+    if (strpos($table, 'chado.') === 0) {
+      $replace = chado_get_schema_name('chado') . '.';
+      return str_replace('chado.', $replace, $table);
+    }
+
+    return $table;
+  }
+
+  /**
+   * Create a safe alias.
+   *
+   * @param string $table Table name.
+   *
+   * @return string
+   *    The safe alias.
+   */
+  public static function makeAlias($table) {
+    return str_replace('.', '_', $table);
+  }
+}

+ 8 - 13
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship.inc

@@ -239,34 +239,29 @@ class sbo__relationship extends ChadoField {
     $this->base_type_column = 'table_name';
     switch ($instance['settings']['chado_table']) {
 
+      // TODO: note that Chado 1.4 will add types to, at least,
+      // project and analysis, at which point you should use the default instead.
       case 'acquisition_relationship':
       case 'analysis_relationship':
       case 'biomaterial_relationship':
       case 'cell_line_relationship':
       case 'quantification_relationship':
-        $this->base_type_column = 'table_name';
-        break;
       case 'element_relationship':
-        // RELATIONSHIP->subject_id_key->feature_id->name;
-        $this->base_name_columns = ['name'];
+      case 'project_relationship':
+      case 'pub_relationship':
         $this->base_type_column = 'table_name';
-        break;
+        $this->base_name_columns = ['name'];
+      break;
+
       case 'organism_relationship':
         $this->base_name_columns = ['genus', 'species'];
         $this->base_type_column = 'table_name';
         break;
-      case 'project_relationship':
-        $this->base_name_columns = ['name'];
-        $this->base_type_column = 'table_name';
-        break;
       case 'phylonode_relationship':
         $this->base_name_columns = ['label'];
         $this->base_type_column = 'table_name';
         break;
-      case 'pub_relationship':
-        $this->base_name_columns = ['name'];
-        $this->base_type_column = 'table_name';
-        break;
+
       case 'contact':
         $this->base_name_columns = ['name'];
         $this->base_type_column = 'type_id';

+ 4 - 3
tripal_chado/includes/TripalFields/sbo__relationship/sbo__relationship_formatter.inc

@@ -96,16 +96,17 @@ class sbo__relationship_formatter extends ChadoFieldFormatter {
       // Convert the object/subject to a link if an entity exists for it.
       if (array_key_exists('entity', $item['value']['local:relationship_object'])) {
         list($entity_type, $object_entity_id) = explode(':', $item['value']['local:relationship_object']['entity']);
+
         if ($object_entity_id != $entity->id) {
           $link = l($object_name, 'bio_data/' . $object_entity_id);
-          $phrase = preg_replace("/$object_name/", $link, $phrase);
+          $phrase = str_replace($object_name, $link, $phrase);
         }
       }
       if (array_key_exists('entity', $item['value']['local:relationship_subject'])) {
         list($entity_type, $subject_entity_id) = explode(':', $item['value']['local:relationship_subject']['entity']);
         if ($subject_entity_id != $entity->id) {
           $link = l($subject_name, 'bio_data/' . $subject_entity_id);
-          $phrase = preg_replace("/$subject_name/", $link, $phrase);
+          $phrase = str_replace($subject_name, $link, $phrase);
         }
       }
 
@@ -119,7 +120,7 @@ class sbo__relationship_formatter extends ChadoFieldFormatter {
     $items_per_page = array_key_exists('items_per_page', $this->instance['settings']) ? $this->instance['settings']['items_per_page'] : 10;
     $total_records = count($rows);
     $total_pages = (int) ($total_records / $items_per_page) + 1;
-    $pelement = 0; //$this->getPagerElementID();
+    $pelement = 0;
     $current_page = pager_default_initialize($total_records, $items_per_page, $pelement);
     $pager = theme('pager', [
       'tags' => [],

+ 2 - 0
tripal_chado/includes/TripalFields/sio__references/sio__references.inc

@@ -98,6 +98,7 @@ class sio__references extends ChadoField {
       $matches = [];
       if (preg_match('/^(.+?)_pub$/', $chado_table, $matches)) {
         $reference_table = $matches[1];
+
         // Find the base table this links to and get the fk columns that map it.
         $schema = chado_get_schema($chado_table);
         $fkeys = $schema['foreign keys'];
@@ -106,6 +107,7 @@ class sio__references extends ChadoField {
             $fkleft = array_keys($fk_details['columns'])[0];
             $fkright = $fk_details['columns'][$fkleft];
           }
+
         }
         // Iterate through all of the records in the linker table that
         // match the given pub ID.

+ 5 - 0
tripal_chado/includes/TripalFields/sio__references/sio__references_formatter.inc

@@ -16,6 +16,11 @@ class sio__references_formatter extends ChadoFieldFormatter {
     $field_name = $this->field['field_name'];
     $chado_table = $this->instance['settings']['chado_table'];
 
+    // Do we have an empty list? If so, just return.
+    if (!$items[0]['value']) {
+      return;
+    }
+
     // First, organize the values by their types.
     $ordered_items = [];
     foreach ($items as $delta => $item) {

+ 2 - 1
tripal_chado/includes/TripalFields/sio__vocabulary/sio__vocabulary_widget.inc

@@ -27,9 +27,10 @@ class sio__vocabulary_widget extends ChadoFieldWidget {
     // content managers to change it. Thus we need to look up the value for the
     // entity type and use it here.
     if (empty($items)) {
+
       // Use the bundle to get the cv_id choosen for this cvterm-based entity.
       // ASSUMPTION: the cv_id is saved as the "type_value" of the bundle.
-      $bundle = tripal_load_bundle_entity(['name' => $widget['#bundle']]);
+      $bundle = tripal_load_bundle_entity(['name' => $widget['#instance']['bundle']]);
       $cv = chado_get_cv(['cv_id' => $bundle->type_value]);
 
       // Now populate the items array with defaults based on the cv.

+ 2 - 2
tripal_chado/includes/TripalFields/so__genotype/so__genotype.inc

@@ -144,7 +144,7 @@ class so__genotype extends ChadoField {
       ],
     ];
     $record = chado_expand_var($record, 'table', $linker_table, $options);
-    $genotype_linkers = isset($record->$linker_table->$fkey_rcolumn) ? $record->$linker_table->$fkey_rcolumn : '';
+    $genotype_linkers = $record->$linker_table;
     if ($genotype_linkers) {
       foreach ($genotype_linkers as $i => $genotype_linker) {
         $genotype = $genotype_linker->genotype_id;
@@ -166,4 +166,4 @@ class so__genotype extends ChadoField {
     }
   }
 
-}
+}

+ 24 - 7
tripal_chado/includes/TripalImporter/FASTAImporter.inc

@@ -243,7 +243,7 @@ class FASTAImporter extends TripalImporter {
       '#description' => t('Enter the regular expression that will extract the unique
                          name needed to identify the existing sequence for which the
                          relationship type selected above will apply.  If no regular
-                         expression is provided, the parent unique name must be the 
+                         expression is provided, the parent unique name must be the
                          same as the loaded feature name.'),
       '#weight' => 6,
     ];
@@ -319,6 +319,20 @@ class FASTAImporter extends TripalImporter {
       form_set_error('db_id', t("Please select a database"));
     }
 
+    // Check to make sure the regexps are valid.
+    if ($re_name && @preg_match("/$re_name/", null) === false) {
+      form_set_error('re_name', t("please provide a valid regular expression for the feature name."));
+    }
+    if ($re_uname && @preg_match("/$re_uname/", null) === false) {
+      form_set_error('re_uname', t("please provide a valid regular expression for the feature unique name."));
+    }
+    if ($re_accession && @preg_match("/$re_accession/", null) === false) {
+      form_set_error('re_accession', t("please provide a valid regular expression for the external database accession."));
+    }
+    if ($re_subject && @preg_match("/$re_subject/", null) === false) {
+      form_set_error('re_subject', t("please provide a valid regular expression for the relationship parent."));
+    }
+
     // check to make sure the types exists
     $cvtermsql = "
       SELECT CVT.cvterm_id
@@ -498,7 +512,10 @@ class FASTAImporter extends TripalImporter {
     $num_seqs = 0;
     $prev_pos = 0;
     $set_start = FALSE;
+    $i = 0;
+
     while ($line = fgets($fh)) {
+      $i++;
       $num_read += strlen($line);
 
       // If we encounter a definition line then get the name, uniquename,
@@ -651,7 +668,7 @@ class FASTAImporter extends TripalImporter {
       ], $values);
       if (count($results) > 1) {
         $this->logMessage("Multiple features exist with the name '!name' of type '!type' for the organism.  skipping",
-          ['!name' => $name, '!type' => $type], TRIPAL_ERROR);
+          ['!name' => $name, '!type' => $cvterm->name], TRIPAL_ERROR);
         return 0;
       }
       if (count($results) == 1) {
@@ -670,7 +687,7 @@ class FASTAImporter extends TripalImporter {
       $results = chado_select_record('feature', ['feature_id'], $values);
       if (count($results) > 1) {
         $this->logMessage("Multiple features exist with the name '!name' of type '!type' for the organism.  skipping",
-          ['!name' => $name, '!type' => $type], TRIPAL_WARNING);
+          ['!name' => $name, '!type' => $cvterm->name], TRIPAL_WARNING);
         return 0;
       }
       if (count($results) == 1) {
@@ -711,7 +728,7 @@ class FASTAImporter extends TripalImporter {
       if (!$success) {
         $this->logMessage("Failed to insert feature '!name (!uname)'", [
           '!name' => $name,
-          '!uname' => $numane,
+          '!uname' => $uname,
         ], TRIPAL_ERROR);
         return 0;
       }
@@ -730,7 +747,7 @@ class FASTAImporter extends TripalImporter {
       else {
         $this->logMessage("Failed to retreive newly inserted feature '!name (!uname)'", [
           '!name' => $name,
-          '!uname' => $numane,
+          '!uname' => $uname,
         ], TRIPAL_ERRORR);
         return 0;
       }
@@ -772,7 +789,7 @@ class FASTAImporter extends TripalImporter {
               [
                 '!name' => $name,
                 '!uname' => $uname,
-                '!type' => $type,
+                '!type' => $cvterm->name,
               ], TRIPAL_ERROR);
             return 0;
           }
@@ -861,7 +878,7 @@ class FASTAImporter extends TripalImporter {
         }
         else {
           $this->logMessage("Failed to retreive newly inserted dbxref '!name (!uname)'",
-            ['!name' => $name, '!uname' => $numane], TRIPAL_ERROR);
+            ['!name' => $name, '!uname' => $uname], TRIPAL_ERROR);
           return 0;
         }
       }

+ 25 - 22
tripal_chado/includes/TripalImporter/GFF3Importer.inc

@@ -860,57 +860,60 @@ class GFF3Importer extends TripalImporter {
               throw new Exception(t("Cound not save record in temporary table, Cannot continue.", []));
             }
           }
-          // add/update the featureloc if the landmark and the ID are not the same
-          // if they are the same then this entry in the GFF is probably a landmark identifier
+          // Add/update the featureloc if the landmark and the ID are not the
+          // same if they are the same then this entry in the GFF is probably
+          // a landmark identifier.
           if (strcmp($landmark, $attr_uniquename) != 0) {
             $this->loadFeatureLoc($feature, $organism,
               $landmark, $fmin, $fmax, $strand, $phase, $attr_fmin_partial,
               $attr_fmax_partial, $attr_residue_info, $attr_locgroup);
           }
 
-          // add any aliases for this feature
+          // Add any aliases for this feature.
           if (array_key_exists('Alias', $tags)) {
             $this->loadAlias($feature, $tags['Alias']);
           }
-          // add any dbxrefs for this feature
+          // Add any dbxrefs for this feature.
           if (array_key_exists('Dbxref', $tags)) {
             $this->loadDbxref($feature, $tags['Dbxref']);
           }
-          // add any ontology terms for this feature
+          // Add any ontology terms for this feature.
           if (array_key_exists('Ontology_term', $tags)) {
             $this->loadOntology($feature, $tags['Ontology_term']);
           }
-          // add parent relationships
+          // Add parent relationships.
           if (array_key_exists('Parent', $tags)) {
             $this->loadParents($feature, $cvterm, $tags['Parent'],
               $feature_organism->organism_id, $strand, $phase, $fmin, $fmax);
           }
 
-          // add target relationships
+          // Add target relationships.
           if (array_key_exists('Target', $tags)) {
             $this->loadTarget($feature, $tags, $target_organism_id, $target_type, $create_target, $attr_locgroup);
           }
-          // add gap information.  This goes in simply as a property
+          // Add gap information.  This goes in simply as a property.
           if (array_key_exists('Gap', $tags)) {
             foreach ($tags['Gap'] as $value) {
               $this->loadProperty($feature, 'Gap', $value);
             }
           }
-          // add notes. This goes in simply as a property
+          // Add notes. This goes in simply as a property.
           if (array_key_exists('Note', $tags)) {
             foreach ($tags['Note'] as $value) {
               $this->loadProperty($feature, 'Note', $value);
             }
           }
-          // add the Derives_from relationship (e.g. polycistronic genes).
+          // Add the Derives_from relationship (e.g. polycistronic genes).
           if (array_key_exists('Derives_from', $tags)) {
             $this->loadDerivesFrom($feature, $cvterm, $tags['Derives_from'][0],
               $feature_organism, $fmin, $fmax);
           }
-          // add in the GFF3_source dbxref so that GBrowse can find the feature using the source column
+          // Add in the GFF3_source dbxref so that GBrowse can find the feature
+          // using the source column.
           $source_ref = ['GFF_source:' . $source];
           $this->loadDbxref($feature, $source_ref);
-          // add any additional attributes
+
+          // Add any additional attributes.
           if ($attr_others) {
             foreach ($attr_others as $tag_name => $values) {
               foreach ($values as $value) {
@@ -1430,7 +1433,7 @@ class GFF3Importer extends TripalImporter {
         $db = chado_select_record('db', ['db_id'], ['name' => "$dbname"]);
         if (sizeof($db) == 0) {
           $this->logMessage("Database, $dbname, is not present. Cannot associate term: $dbname:$accession.", [], TRIPAL_WARNING);
-          return 0;
+          continue;
         }
       }
       $db = $db[0];
@@ -1440,7 +1443,7 @@ class GFF3Importer extends TripalImporter {
         ['accession' => $accession, 'db_id' => $db->db_id]);
       if (sizeof($dbxref) == 0) {
         $this->logMessage("Accession, $accession is missing for reference: $dbname:$accession.", [], TRIPAL_WARNING);
-        return 0;
+        continue;
       }
       $dbxref = $dbxref[0];
 
@@ -1455,7 +1458,7 @@ class GFF3Importer extends TripalImporter {
         ]);
         if (sizeof($cvterm) == 0) {
           $this->logMessage("CV Term is missing for reference: $dbname:$accession.", [], TRIPAL_WARNING);
-          return 0;
+          continue;
         }
       }
       $cvterm = $cvterm[0];
@@ -1481,11 +1484,10 @@ class GFF3Importer extends TripalImporter {
 
         if (!$success) {
           $this->logMessage("Failed to insert ontology term: $dbname:$accession.", [], TRIPAL_WARNING);
-          return 0;
+          continue;
         }
       }
     }
-    return 1;
   }
 
   /**
@@ -1971,7 +1973,7 @@ class GFF3Importer extends TripalImporter {
    */
   private function loadProperty($feature, $property, $value) {
 
-    // first make sure the cvterm exists.  if not, then add it
+    // First make sure the cvterm exists.  if not, then add it.
     $select = [
       'name' => $property,
       'cv_id' => [
@@ -1980,14 +1982,15 @@ class GFF3Importer extends TripalImporter {
     ];
     $result = chado_select_record('cvterm', ['*'], $select);
 
-    // if we don't have a property like this already, then add it otherwise, just return
+    // If we don't have a property like this already, then add it otherwise,
+    // just return.
     if (count($result) == 0) {
       $term = [
-        'id' => "null:$property",
+        'id' => "local:$property",
         'name' => $property,
-        'namespace' => 'feature_property',
         'is_obsolete' => 0,
         'cv_name' => 'feature_property',
+        'db_name' => 'local',
         'is_relationship' => FALSE,
       ];
       $cvterm = (object) chado_insert_cvterm($term, ['update_existing' => FALSE]);
@@ -2001,7 +2004,7 @@ class GFF3Importer extends TripalImporter {
     }
 
 
-    // check to see if the property already exists for this feature
+    // Check to see if the property already exists for this feature
     // if it does but the value is unique then increment the rank and add it.
     // if the value is not unique then don't add it.
     $add = 1;

+ 20 - 4
tripal_chado/includes/loaders/tripal_chado.pub_importer_PMID.inc

@@ -208,12 +208,20 @@ function tripal_pub_PMID_search_init($search_str, $retmax) {
   // do a search for a single result so that we can establish a history, and get
   // the number of records. Once we have the number of records we can retrieve
   // those requested in the range.
-  $query_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" .
+  $query_url = "https://www.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" .
     "db=Pubmed" .
     "&retmax=$retmax" .
     "&usehistory=y" .
     "&term=" . urlencode($search_str);
 
+  $api_key = variable_get('tripal_pub_importer_ncbi_api_key', NULL);
+  $sleep_time = 333334;
+  if (!empty($api_key)) {
+    $query_url .= "&api_key=" . $api_key;
+    $sleep_time = 100000;
+  }
+
+  usleep($sleep_time);  // 1/3 of a second delay, NCBI limits requests to 3 / second without API key
   $rfh = fopen($query_url, "r");
   if (!$rfh) {
     drupal_set_message('Could not perform Pubmed query. Cannot connect to Entrez.', 'error');
@@ -292,7 +300,7 @@ function tripal_pub_PMID_fetch($query_key, $web_env, $rettype = 'null',
 
   // repeat the search performed previously (using WebEnv & QueryKey) to retrieve
   // the PMID's within the range specied.  The PMIDs will be returned as a text list
-  $fetch_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?" .
+  $fetch_url = "https://www.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?" .
     "rettype=$rettype" .
     "&retmode=$retmod" .
     "&retstart=$start" .
@@ -301,6 +309,13 @@ function tripal_pub_PMID_fetch($query_key, $web_env, $rettype = 'null',
     "&query_key=$query_key" .
     "&WebEnv=$web_env";
 
+  $api_key = variable_get('tripal_pub_importer_ncbi_api_key', NULL);
+  $sleep_time = 333334;
+  if (!empty($api_key)) {
+    $fetch_url .= "&api_key=" . $api_key;
+    $sleep_time = 100000;
+  }
+
   foreach ($args as $key => $value) {
     if (is_array($value)) {
       $fetch_url .= "&$key=";
@@ -313,6 +328,7 @@ function tripal_pub_PMID_fetch($query_key, $web_env, $rettype = 'null',
       $fetch_url .= "&$key=$value";
     }
   }
+  usleep($sleep_time);  // 1/3 of a second delay, NCBI limits requests to 3 / second without API key
   $rfh = fopen($fetch_url, "r");
   if (!$rfh) {
     drupal_set_message('ERROR: Could not perform PubMed query.', 'error');
@@ -338,10 +354,10 @@ function tripal_pub_PMID_fetch($query_key, $web_env, $rettype = 'null',
  * XML should contain only a single publication record.
  *
  * Information about the valid elements in the PubMed XML can be found here:
- * http://www.nlm.nih.gov/bsd/licensee/elements_descriptions.html
+ * https://www.nlm.nih.gov/bsd/licensee/elements_descriptions.html
  *
  * Information about PubMed's citation format can be found here
- * http://www.nlm.nih.gov/bsd/policy/cit_format.html
+ * https://www.nlm.nih.gov/bsd/policy/cit_format.html
  *
  * @param $pub_xml
  *  An XML string describing a single publication

+ 66 - 0
tripal_chado/includes/loaders/tripal_chado.pub_importers.inc

@@ -85,6 +85,8 @@ function tripal_pub_importers_list() {
           importer to run peridically by adding a cron job. </li> 
      </ol><br>");
 
+  $form = drupal_get_form('tripal_pub_importer_ncbi_api_key_form');
+  $page .= drupal_render($form);
 
   $table = [
     'header' => $headers,
@@ -451,6 +453,70 @@ function tripal_pub_importer_setup_form($form, &$form_state = NULL, $pub_import_
   return $form;
 }
 
+/**
+ * The form used for setting the optional NCBI API key.
+ *
+ * @param $form
+ *   The form element to be populated.
+ * @param $form_state
+ *   The state of the form element to be populated.
+ *
+ * @return array
+ *   The populated form element.
+ *
+ * @ingroup tripal_pub
+ */
+function tripal_pub_importer_ncbi_api_key_form($form, $form_state) {
+  $description = t('Tripal imports publications using NCBI\'s ') 
+    . l('EUtils API', 'https://www.ncbi.nlm.nih.gov/books/NBK25500/')
+    . t(', which limits users and programs to a maximum of 3 requests per second without an API key. '
+        . 'However, NCBI allows users and programs to an increased maximum of 10 requests per second if '
+        . 'they provide a valid API key. This is particularly useful in speeding up large publication imports. '
+        . 'For more information on NCBI API keys, please ')
+    . l('see here', 'https://www.ncbi.nlm.nih.gov/books/NBK25497/#chapter2.Coming_in_December_2018_API_Key', array(
+      'attributes' => array(
+        'target' => 'blank',
+      ),
+    )) . '.';
+
+  $form['ncbi_api_key'] = array(
+    '#type' => 'textfield',
+    '#title' => t('(Optional) NCBI API key:'),
+    '#description' => $description,
+    '#default_value' => variable_get('tripal_pub_importer_ncbi_api_key', NULL),
+    '#ajax' => array(
+      'callback' => 'tripal_pub_importer_set_ncbi_api_key',
+      'wrapper' => 'ncbi_api_key',
+    ),
+    '#prefix' => '<div id="ncbi_api_key">',
+    '#suffix' => '</div>',
+  );
+
+  return $form;
+}
+
+/**
+ * This function saves the NCBI API key to the database.
+ *
+ * It is called when the user makes a change to the NCBI API key field and then
+ * moves their cursor out of the field.
+ *
+ * @param $form
+ *   The new form element.
+ * @param $form_state
+ *   The state of the new form element.
+ *
+ * @return array
+ *   The new api key field.
+ *
+ * @ingroup tripal_pub
+ */
+function tripal_pub_importer_set_ncbi_api_key($form, $form_state) {
+  variable_set('tripal_pub_importer_ncbi_api_key', check_plain($form_state['values']['ncbi_api_key']));
+  drupal_set_message('NCBI API key has been saved successfully!');
+  return $form['ncbi_api_key'];
+}
+
 /**
  * A helper function for the importer setup form that adds the criteria to
  * the form that belong to the importer.

+ 25 - 12
tripal_chado/includes/setup/tripal_chado.setup.inc

@@ -348,7 +348,8 @@ function tripal_chado_prepare_general_types($job) {
   $args = [
     'vocabulary' => 'SIO',
     'accession' => '001066',
-    'term_name' => 'Study',
+    'term_name' => 'study',
+    'label' => 'Study',
     'storage_args' => [
       'data_table' => 'study',
     ],
@@ -501,7 +502,8 @@ function tripal_chado_prepare_genomic_types($job) {
   $args = [
     'vocabulary' => 'operation',
     'accession' => '0525',
-    'term_name' => 'Genome Assembly',
+    'term_name' => 'Genome assembly',
+    'label' => 'Genome Assembly',
     'storage_args' => [
       'data_table' => 'analysis',
       'type_linker_table' => 'analysisprop',
@@ -518,7 +520,8 @@ function tripal_chado_prepare_genomic_types($job) {
   $args = [
     'vocabulary' => 'operation',
     'accession' => '0362',
-    'term_name' => 'Genome Annotation',
+    'term_name' => 'Genome annotation',
+    'label' => 'Genome Annotation',
     'storage_args' => [
       'data_table' => 'analysis',
       'type_linker_table' => 'analysisprop',
@@ -574,7 +577,8 @@ function tripal_chado_prepare_expression_types($job) {
   $args = [
     'vocabulary' => 'OBI',
     'accession' => '0000070',
-    'term_name' => 'Assay',
+    'term_name' => 'assay',
+    'label' => 'Assay',
     'storage_args' => [
       'data_table' => 'assay',
     ],
@@ -628,7 +632,8 @@ function tripal_chado_prepare_germplasm_types($job) {
   $args = [
     'vocabulary' => 'CO_010',
     'accession' => '0000044',
-    'term_name' => 'Germplasm Accession',
+    'term_name' => 'accession',
+    'label' => 'Germplasm Accession',
     'storage_args' => [
       'data_table' => 'stock',
       'type_column' => 'type_id',
@@ -643,7 +648,8 @@ function tripal_chado_prepare_germplasm_types($job) {
   $args = [
     'vocabulary' => 'CO_010',
     'accession' => '0000255',
-    'term_name' => 'Generated germplasm (breeding cross)',
+    'label' => 'Generated Germplasm (Breeding Cross)',
+    'term_name' => 'generated germplasm',
     'storage_args' => [
       'data_table' => 'stock',
       'type_column' => 'type_id',
@@ -658,7 +664,8 @@ function tripal_chado_prepare_germplasm_types($job) {
   $args = [
     'vocabulary' => 'CO_010',
     'accession' => '0000029',
-    'term_name' => 'Cultivar (germplasm variety)',
+    'label' => 'Cultivar (Germplasm Variety)',
+    'term_name' => 'cultivar',
     'storage_args' => [
       'data_table' => 'stock',
       'type_column' => 'type_id',
@@ -673,7 +680,8 @@ function tripal_chado_prepare_germplasm_types($job) {
   $args = [
     'vocabulary' => 'CO_010',
     'accession' => '0000162',
-    'term_name' => 'Recombinant Inbred Line',
+    'label' => 'Recombinant Inbred Line',
+    'term_name' => '414 inbred line',
     'storage_args' => [
       'data_table' => 'stock',
       'type_column' => 'type_id',
@@ -695,7 +703,8 @@ function tripal_chado_prepare_genetic_types($job) {
   $args = [
     'vocabulary' => 'data',
     'accession' => '1278',
-    'term_name' => 'Genetic Map',
+    'label' => 'Genetic Map',
+    'term_name' => 'Genetic map',
     'storage_args' => [
       'data_table' => 'featuremap',
       'type_linker_table' => 'featuremapprop',
@@ -728,7 +737,8 @@ function tripal_chado_prepare_genetic_types($job) {
   $args = [
     'vocabulary' => 'SO',
     'accession' => '0001060',
-    'term_name' => 'Sequence Variant',
+    'label' => "Sequence Variant",
+    'term_name' => 'sequence_variant',
     'storage_args' => [
       'data_table' => 'feature',
       'type_column' => 'type_id',
@@ -743,7 +753,8 @@ function tripal_chado_prepare_genetic_types($job) {
   $args = [
     'vocabulary' => 'SO',
     'accession' => '0001645',
-    'term_name' => 'Genetic Marker',
+    'term_name' => 'genetic_marker',
+    'label' => "Genetic Marker",
     'storage_args' => [
       'data_table' => 'feature',
       'type_column' => 'type_id',
@@ -759,7 +770,9 @@ function tripal_chado_prepare_genetic_types($job) {
   $args = [
     'vocabulary' => 'SO',
     'accession' => '0001500',
-    'term_name' => 'Heritable Phenotypic Marker',
+    'term_name' => 'heritable_phenotypic_marker',
+    'label' => "Heritable Phenotypic Marker",
+
     'storage_args' => [
       'data_table' => 'feature',
       'type_column' => 'type_id',

+ 7 - 2
tripal_chado/includes/tripal_chado.bundle.inc

@@ -118,6 +118,11 @@ function tripal_chado_create_bundle_table($bundle) {
     throw new Exception('Cannot create entity linker table for chado.');
   }
   db_create_table($chado_entity_table, $schema);
+
+  // Ensure we clear the drupal schema cache after creating this table.
+  // This is needed so we can publish records directly after creating a new
+  // content type.
+  drupal_get_complete_schema(TRUE);
 }
 
 /**
@@ -155,9 +160,9 @@ function tripal_chado_bundle_find_orphans($bundle, $count = FALSE, $offset = 0,
   if (!$count and $limit) {
     $qlimit = "LIMIT $limit OFFSET $offset ";
   }
-  // Get the count 
+  // Get the count
   $query = "
-    SELECT $select  
+    SELECT $select
     FROM [$chado_bundle_table] CT
       LEFT JOIN {" . $bundle->data_table . "} BT ON CT.record_id = BT.$primary_key
     WHERE BT.$primary_key IS NULL

+ 16 - 8
tripal_chado/includes/tripal_chado.db.inc

@@ -84,8 +84,8 @@ function tripal_chado_db_edit_form($form, &$form_state) {
   ];
 
 
-  // if we don't have a db_id then we can  return the form, otherwise
-  // add in the other fields
+  // If we don't have a db_id then we can  return the form, otherwise
+  // add in the other fields.
   if ($dbid) {
     tripal_chado_add_db_form_fields($form, $form_state, $dbid);
 
@@ -93,11 +93,19 @@ function tripal_chado_db_edit_form($form, &$form_state) {
       '#type' => 'submit',
       '#value' => t('Update'),
     ];
-    $form['delete'] = [
-      '#type' => 'submit',
-      '#value' => t('Delete'),
-      '#attributes' => ['onclick' => 'if(!confirm("Really Delete?")){return false;}'],
-    ];
+
+    $dbxref_count = chado_query('select count(dbxref_id) from {dbxref} WHERE db_id = :db_id', [':db_id' => $dbid])->fetchField();
+
+    if ($dbxref_count == 0) {
+      $form['delete'] = [
+        '#type' => 'submit',
+        '#value' => t('Delete'),
+        '#attributes' => ['onclick' => 'if(!confirm("Really Delete?")){return false;}'],
+      ];
+    }
+    else {
+      tripal_set_message("You cannot delete this db without first deleting the ${dbxref_count} dbxrefs associated with it.", TRIPAL_NOTICE);
+    }
   }
   else {
     // if we don't have a dbid then this is the first time the form has
@@ -346,4 +354,4 @@ function tripal_chado_db_edit_form_ajax($form, $form_state) {
   //drupal_set_message('<pre>' . print_r($elements, TRUE) . '</pre>', "status");
 
   return $elements;
-}
+}

+ 4 - 2
tripal_chado/includes/tripal_chado.fields.inc

@@ -1067,9 +1067,8 @@ function tripal_chado_bundle_instances_info_base(&$info, $entity_type, $bundle,
         $base_info['widget']['settings']['increment'] = 1;
         $base_info['widget']['settings']['tz_handling'] = 'none';
         $base_info['widget']['settings']['collapsible'] = TRUE;
+        $base_info['widget']['settings']['year_range'] = '-10:+10';
 
-        // TODO: Add settings so that the minutes increment by 1.
-        // And turn off the timezone, as the Chado field doesn't support it.
         break;
     }
 
@@ -2520,6 +2519,9 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
   $prop_table = $table_name . 'prop';
   if (chado_table_exists($prop_table)) {
 
+    $schema = chado_get_schema($prop_table);
+    $pkey = $schema['primary key'][0];
+
     $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column, $cvterm_id, $type_value);
     foreach ($props as $term) {
 

+ 1 - 1
tripal_chado/includes/tripal_chado.mapping.inc

@@ -24,7 +24,7 @@ function tripal_chado_map_cvterms() {
         // Get the list of cvterm_ids from existing records in the table.
         $sql = "
           SELECT $local_id
-          FROM { " . $tablename . "}
+          FROM {" . $tablename . "}
           GROUP BY $local_id
         ";
         $results = chado_query($sql);

+ 6 - 1
tripal_chado/includes/tripal_chado.publish.inc

@@ -57,6 +57,12 @@ function tripal_chado_publish_form($form, &$form_state) {
         $field = field_info_field($field_name);
         $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
 
+        // Exclude data fields from the publish form.
+        // We do this because the lack of default causes errors.
+        if ($field['type'] == 'datetime'){
+          continue;
+        }
+
         // Get the Chado mapping information.
         $base_table = array_key_exists('base_table', $instance['settings']) ? $instance['settings']['base_table'] : '';
         $chado_table = array_key_exists('chado_table', $instance['settings']) ? $instance['settings']['chado_table'] : '';
@@ -193,4 +199,3 @@ function tripal_chado_publish_form_submit($form, &$form_state) {
 function tripal_chado_publish_form_ajax_callback($form, $form_state) {
   return $form;
 }
-

+ 4 - 9
tripal_chado/includes/tripal_chado.semweb.inc

@@ -759,7 +759,7 @@ function tripal_chado_populate_vocab_EFO() {
     'cv_name' => 'efo',
     'definition' => 'An instrument design which describes the design of the array.',
   ]);
-  chado_associate_semweb_term(NULL, 'arraydesign_id', $term);
+  chado_associate_semweb_term('assay', 'arraydesign_id', $term);
 
   $term = chado_insert_cvterm([
     'id' => 'EFO:0005522',
@@ -768,13 +768,7 @@ function tripal_chado_populate_vocab_EFO() {
     'definition' => 'Controlled terms for descriptors of types of array substrates.',
   ]);
   chado_associate_semweb_term('arraydesign', 'substratetype_id', $term);
-  $term = chado_insert_cvterm([
-    'id' => 'EFO:0001728',
-    'name' => 'array manufacturer',
-    'cv_name' => 'efo',
-    'definition' => '',
-  ]);
-  chado_associate_semweb_term('arraydesign', 'manufacturer_id', $term);
+
   $term = chado_insert_cvterm([
     'id' => 'EFO:0001728',
     'name' => 'array manufacturer',
@@ -785,10 +779,11 @@ function tripal_chado_populate_vocab_EFO() {
 
   $term = chado_insert_cvterm([
     'id' => 'EFO:0000269',
-    'name' => 'assay design',
+    'name' => 'array design',
     'cv_name' => 'efo',
     'definition' => 'An instrument design which describes the design of the array.',
   ]);
+  chado_associate_semweb_term('element', 'arraydesign_id', $term);
 }
 
 /**

+ 217 - 39
tripal_chado/tripal_chado.install

@@ -29,6 +29,10 @@ function tripal_chado_install() {
       )
     ";
     chado_query($sql);
+
+    // Fix the SOFP feature_property issue from the legacy feature_property.
+    tripal_chado_fix_legacy_SOFP_7338();
+
   }
 
   tripal_insert_variable('bundle_category', 'Bundles can be categorized to allow for grouping');
@@ -471,13 +475,7 @@ function tripal_chado_tripal_custom_tables_schema() {
       'mview_id' => array(
         'type' => 'int',
         'not NULL' => FALSE
-      ),
-      'is_base' => array(
-        'type' => 'int',
-        'not NULL' => FALSE,
-        'size' => 'tiny',
-        'default' => 0,
-      ),
+      )
     ),
     'indexes' => array(
       'table_id' => array('table_id'),
@@ -689,6 +687,63 @@ function tripal_chado_chado_cvterm_mapping_schema() {
   return $schema;
 }
 
+
+
+/**
+ * Fixes a problem with the legacy feature_property/SOFP
+ * ontology loaded with previous verions of Chado and all terms are
+ * relationships.
+ *
+ * This function is called by the tripal_chado_install() for a
+ * new Tripal setup, and the tripal_chado_update_7338 for an existing
+ * site.
+ */
+
+function tripal_chado_fix_legacy_SOFP_7338() {
+
+  $sofp =  chado_get_db(['name' => 'SOFP']);
+  $fp = chado_get_cv(['name' => 'feature_property']);
+
+  // No need to update unless the SOFP db exists
+  if (!$sofp || !$fp) {
+    return;
+  }
+  $terms = chado_select_record('cvterm', ['cvterm_id', 'name'], [
+    'dbxref_id' => [
+      'db_id' => [
+        'name' => 'SOFP',
+      ],
+    ],
+    'cv_id' => ['name' => 'feature_property'],
+  ]);
+
+  if (empty($terms)) {
+    return;
+  }
+
+  foreach ($terms as $term) {
+
+    $id = $term->cvterm_id;
+    $name = $term->name;
+
+    if ($name == 'linked_to') {
+      continue;
+    }
+    chado_update_record('cvterm', ['cvterm_id' => $id], ['is_relationshiptype' => 0]);
+  }
+
+  // Repopulate the mview.
+  $mview_id = chado_get_mview_id('db2cv_mview');
+  global $user;
+  tripal_add_job(
+    'Repopulating db2cv to fix legacy SOFP',
+    'tripal_chado',
+    'chado_populate_mview',
+    [$mview_id],
+    $user->uid
+  );
+}
+
 /**
  * Fixes the phase on the tripal_gffcds_temp table used for importing GFF files, and fixes the db.name term mapping.
  *
@@ -1839,58 +1894,181 @@ function tripal_chado_update_7335() {
  */
 function tripal_chado_update_7336() {
 
-  $bundles = field_info_instances('TripalEntity');
+  try {
+    $bundles = field_info_instances('TripalEntity');
 
-  foreach ($bundles as $bundle_name => $fields) {
+    foreach ($bundles as $bundle_name => $fields) {
 
-    $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
-    $base = $bundle->data_table;
+      $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
+      $base = $bundle->data_table;
 
-    $contact_table = $base . '_contact';
-    if (chado_table_exists($contact_table)) {
+      $contact_table = $base . '_contact';
+      if (chado_table_exists($contact_table)) {
 
-      $field_name = $base . '_contact';
-      $instance_info = field_info_instance('TripalEntity', $field_name, $bundle_name);
+        $field_name = $base . '_contact';
+        $instance_info = field_info_instance('TripalEntity', $field_name, $bundle_name);
 
-      if ($instance_info) {
-        $instance_info['type'] = 'chado_linker__contact';
-        $instance_info['widget']['type'] = 'chado_linker__contact_widget';
-        $instance_info['formatter']['type'] = 'chado_linker__contact_widget';
-        field_update_instance($instance_info);
+        if ($instance_info) {
+          $instance_info['type'] = 'chado_linker__contact';
+          $instance_info['widget']['type'] = 'chado_linker__contact_widget';
+          $instance_info['formatter']['type'] = 'chado_linker__contact_widget';
+          field_update_instance($instance_info);
+        }
       }
     }
   }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
 }
 
 /**
  * Update the NCBITaxon DB entry.
  */
 function tripal_chado_update_7337(){
-
-  chado_insert_db(array(
-    'name' => 'NCBITaxon',
-    'description' => 'NCBI organismal classification.',
-    'url' => 'http://www.berkeleybop.org/ontologies/ncbitaxon/',
-    'urlprefix' => 'https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id={accession}',
-  ));
+  try {
+    chado_insert_db(array(
+      'name' => 'NCBITaxon',
+      'description' => 'NCBI organismal classification.',
+      'url' => 'http://www.berkeleybop.org/ontologies/ncbitaxon/',
+      'urlprefix' => 'https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id={accession}',
+    ));
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
 }
 
 /**
- * Adds a "is_base" column to the "tripal_custom_tables" table.
+ * Modify the term name for Sequence Variant, Genetic Marker, and Heritable Phenotypic Marker to match the corresponding SO term.
  */
-function tripal_chado_update_7339(){
+function tripal_chado_update_7338() {
+
+  $terms = [
+    [
+      'vocabulary' => 'SO',
+      'label' => 'Sequence Variant',
+      'term' => 'sequence_variant',
+      'accession' => '0001060',
+    ],
+    [
+      'vocabulary' => 'SO',
+      'label' => 'Genetic Marker',
+      'term' => 'genetic_marker',
+      'accession' => '0001645',
+    ],
+    [
+      'vocabulary' => 'SO',
+      'label' => 'Heritable Phenotypic Marker',
+      'term' => 'heritable_phenotypic_marker',
+      'accession' => '0001500',
+    ],
+    [
+      'vocabulary' => 'SIO',
+      'label' => 'Study',
+      'term' => 'study',
+      'accession' => '001066',
+    ],
+    [
+      'vocabulary' => 'sep',
+      'label' => 'Biological Sample',
+      'term' => '	biological sample',
+      'accession' => '00195',
+    ],
+    [
+      'vocabulary' => 'OBI',
+      'label' => 'Assay',
+      'term' => 'assay',
+      'accession' => '0000070',
+    ],
+    [
+      'vocabulary' => 'data',
+      'label' => 'Genetic Map',
+      'term' => 'Genetic map',
+      'accession' => '1278',
+    ],
+    [
+      'vocabulary' => 'operation',
+      'label' => 'Genome Annotation',
+      'term' => 'Genome annotation',
+      'accession' => '0362',
+    ],
+    [
+      'vocabulary' => 'operation',
+      'label' => 'Genome Assembly',
+      'term' => 'Genome assembly',
+      'accession' => '0525',
+    ],
+    [
+      'vocabulary' => 'CO_010',
+      'label' => 'Generated Germplasm (Breeding Cross)',
+      'term' => 'generated germplasm',
+      'accession' => '0000255',
+    ],
+    [
+      'vocabulary' => 'CO_010',
+      'label' => 'Cultivar (Germplasm Variety)',
+      'term' => 'cultivar',
+      'accession' => '0000029',
+    ],
+
+    [
+      'vocabulary' => 'CO_010',
+      'label' => 'Germplasm Accession',
+      'term' => 'accession',
+      'accession' => '0000044',
+    ],
+    [
+      'vocabulary' => 'data',
+      'label' => 'Physical Map',
+      'term' => 'Physical map',
+      'accession' => '1280',
+    ],
+    [
+      'vocabulary' => 'sep',
+      'label' => 'Protocol',
+      'term' => '	protocol',
+      'accession' => '00101',
+    ],
+    [
+      'vocabulary' => 'CO_010',
+      'label' => 'Recombinant Inbred Line',
+      'term' => '414 inbred line',
+      'accession' => '0000162',
+    ],
+  ];
+
   try {
-    $spec = [
-      'type' => 'int',
-      'not NULL' => FALSE,
-      'size' => 'tiny',
-      'default' => 0,
-    ];
-    if (!db_field_exists('tripal_custom_tables', 'is_base')) {
-      db_add_field('tripal_custom_tables', 'is_base', $spec);
+    foreach ($terms as $term) {
+
+      $label = $term['label'];
+      $termName = $term['term'];
+      $accession = $term['accession'];
+      $vocabulary = $term['vocabulary']; #shortname, ie, chado.db
+      $term = tripal_load_term_entity([
+        'vocabulary' => $vocabulary,
+        'accession' => $accession
+      ]);
+
+      if (!$term) {
+        continue;
+      }
+      $term->name = $termName;
+      $term->save();
+
+      $bundle = tripal_load_bundle_entity(['term_id' => $term->id]);
+
+      if (!$bundle) {
+        continue;
+      }
+      $bundle->label = $label;
+      $bundle->save();
+
+
     }
-  }
-  catch (\PDOException $e) {
+  } catch (\PDOException $e) {
     $error = $e->getMessage();
     throw new DrupalUpdateException('Could not perform update: '. $error);
   }

+ 1 - 1
tripal_chado/tripal_chado.module

@@ -68,7 +68,7 @@ require_once "includes/TripalFields/ChadoField.inc";
 require_once "includes/TripalFields/ChadoFieldWidget.inc";
 require_once "includes/TripalFields/ChadoFieldFormatter.inc";
 
-require_once "includes/ChadoDatabaseConnection.inc";
+require_once "includes/ChadoPrefixExtender.inc";
 
 tripal_chado_set_globals();
 

+ 28 - 3
tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc

@@ -110,7 +110,7 @@ class TripalContentService_v0_1 extends TripalWebService {
    * Creates a resource for an expanded field of an entity.
    */
   private function doExpandedField($ctype, $entity_id, $expfield) {
-    $service_path = $this->getServicePath() . '/' . urlencode($ctype) . '/' . $entity_id;
+    $service_path = $this->getServicePath() . '/' . preg_replace('/[^\w]/', '_', $ctype) . '/' . $entity_id;
     $this->resource = new TripalWebServiceResource($service_path);
 
     // Get the TripalBundle, TripalTerm and TripalVocab for this type.
@@ -161,18 +161,27 @@ class TripalContentService_v0_1 extends TripalWebService {
       $field_name = $instance['field_name'];
       $field = field_info_field($field_name);
       $field_type = $field['type'];
+
       // Skip fields of remote data.
       if ($field_type == 'remote__data') {
         continue;
       }
+
       $vocabulary = $instance['settings']['term_vocabulary'];
       $accession = $instance['settings']['term_accession'];
       $temp_term = tripal_get_term_details($vocabulary, $accession);
+
+      // See if the name matches perfectly.
+      if (strtolower($temp_term['name']) == strtolower($expfield)) {
+        return [$field, $instance, $temp_term];
+      }
+
       // See if the name provided matches the field name after a bit of
       // cleanup.
       if (strtolower(preg_replace('/[^\w]/', '_', $temp_term['name'])) == strtolower($expfield)) {
         return [$field, $instance, $temp_term];
       }
+
       // Alternatively if the CV term accession matches then we're good too.
       if ($vocabulary . ':' . $accession == $expfield) {
         return [$field, $instance, $temp_term];
@@ -184,7 +193,7 @@ class TripalContentService_v0_1 extends TripalWebService {
    * Creates a resource for a single entity.
    */
   private function doEntity($ctype, $entity_id) {
-    $service_path = $this->getServicePath() . '/' . urlencode($ctype);
+    $service_path = $this->getServicePath() . '/' . preg_replace('/[^\w]/', '_', $ctype);
     $this->resource = new TripalWebServiceResource($service_path);
 
     // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
@@ -505,7 +514,11 @@ class TripalContentService_v0_1 extends TripalWebService {
             $temp[$key] = $v;
           }
           $term['name'] = $key;
-
+        }
+        elseif (is_numeric($k)) {
+          // Key is non-vocabulary term:
+          // Index number (0, 1, 2...) of a hydra:member array.
+          $temp[$k] = $this->sanitizeFieldKeys($resource, $v, $bundle, $service_path);
         }
         else {
           // TODO: this is an error, if we get here then we have
@@ -866,6 +879,13 @@ class TripalContentService_v0_1 extends TripalWebService {
 
     // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
     $bundle = tripal_load_bundle_entity(['label' => $ctype]);
+    
+    // Check that the user has access to this bundle.  If not then the
+    // function call will throw an error.
+    if (!user_access('view ' . $bundle->name)) {
+      throw new Exception("Permission Denied.");
+    }
+        
     $term = entity_load('TripalTerm', ['id' => $bundle->term_id]);
     $term = reset($term);
 
@@ -1034,6 +1054,11 @@ class TripalContentService_v0_1 extends TripalWebService {
     // Iterate through the terms and add an entry in the collection.
     $i = 0;
     while ($bundle = $bundles->fetchObject()) {
+      if (!user_access('view ' . $bundle->name)) {
+        // Show only content types users have access to and skip the rest.
+        continue;
+      }
+      
       $entity = entity_load('TripalTerm', ['id' => $bundle->term_id]);
       $term = reset($entity);
       $vocab = $term->vocab;

+ 1 - 1
tripal_ws/tripal_ws.module

@@ -39,7 +39,7 @@ require_once "includes/TripalFields/WebServicesFieldFormatter.inc";
 function tripal_ws_init() {
   global $base_url;
 
-  $api_url = $base_url . '/web-sevices/';
+  $api_url = $base_url . '/web-services/';
 
   $vocab = tripal_get_vocabulary_details('hydra');