Browse Source

Merged in 7.x-3.x and adjusted the update functions

Stephen Ficklin 7 years ago
parent
commit
286cd86104
100 changed files with 6772 additions and 3839 deletions
  1. 1 0
      .gitignore
  2. 339 0
      LICENSE.txt
  3. 0 94
      legacy/tripal_analysis/tripal_analysis.install
  4. 30 0
      legacy/tripal_analysis/tripal_analysis.module
  5. 1 1
      legacy/tripal_core/api/tripal_core.chado_nodes.api.inc
  6. 2 2
      legacy/tripal_core/api/tripal_core.chado_nodes.properties.api.inc
  7. 2 2
      legacy/tripal_core/api/tripal_core.chado_nodes.relationships.api.inc
  8. 1 1
      legacy/tripal_cv/api/tripal_cv.api.inc
  9. 3 3
      legacy/tripal_cv/includes/tripal_cv.cv_defaults.inc
  10. 1 1
      legacy/tripal_cv/theme/templates/tripal_cv_help.tpl.php
  11. 1 1
      legacy/tripal_feature/theme/templates/tripal_feature_help.tpl.php
  12. 0 191
      legacy/tripal_feature/tripal_feature.install
  13. 1 1
      legacy/tripal_library/includes/tripal_library.chado_node.inc
  14. 0 265
      legacy/tripal_phylogeny/includes/tripal_phylogeny.admin.inc
  15. 3 4
      legacy/tripal_phylogeny/includes/tripal_phylogeny.taxonomy.inc
  16. 1 1
      legacy/tripal_phylogeny/theme/css/tripal_phylogeny.css
  17. 1 1
      legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js
  18. 2 81
      legacy/tripal_phylogeny/theme/tripal_phylogeny.theme.inc
  19. 17 156
      legacy/tripal_phylogeny/tripal_phylogeny.module
  20. 0 45
      legacy/tripal_pub/includes/tripal_pub.admin.inc
  21. 1 1
      legacy/tripal_pub/includes/tripal_pub.chado_node.inc
  22. 2 2
      legacy/tripal_pub/theme/templates/tripal_pub_help.tpl.php
  23. 0 14
      legacy/tripal_pub/tripal_pub.module
  24. 1 1
      legacy/tripal_stock/includes/tripal_stock.chado_node.inc
  25. 16 9
      tripal/api/tripal.entities.api.inc
  26. 68 24
      tripal/api/tripal.importer.api.inc
  27. 30 4
      tripal/api/tripal.terms.api.inc
  28. 11 2
      tripal/includes/TripalEntityUIController.inc
  29. 1 1
      tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc
  30. 4 1
      tripal/includes/TripalFieldDownloaders/TripalNucFASTADownloader.inc
  31. 4 1
      tripal/includes/TripalFieldDownloaders/TripalProteinFASTADownloader.inc
  32. 4 2
      tripal/includes/TripalFields/TripalField.inc
  33. 26 17
      tripal/includes/TripalFields/TripalFieldFormatter.inc
  34. 34 17
      tripal/includes/TripalImporter.inc
  35. 7 17
      tripal/includes/TripalTerm.inc
  36. 25 2
      tripal/includes/tripal.entity.inc
  37. 27 25
      tripal/includes/tripal.fields.inc
  38. 55 4
      tripal/includes/tripal.jobs.inc
  39. 2 2
      tripal/theme/css/tripal.css
  40. 3 2
      tripal/theme/js/TripalUploader.js
  41. 175 30
      tripal/tripal.drush.inc
  42. 23 1
      tripal/tripal.install
  43. 30 7
      tripal/tripal.module
  44. 3 0
      tripal/tripal.views_default.inc
  45. 63 19
      tripal/views_handlers/tripal_views_handler_area_collections.inc
  46. 0 31
      tripal_chado/api/modules/tripal_chado.analysis.api.inc
  47. 277 332
      tripal_chado/api/modules/tripal_chado.cv.api.inc
  48. 81 2
      tripal_chado/api/modules/tripal_chado.organism.api.inc
  49. 5 5
      tripal_chado/api/modules/tripal_chado.phylotree.api.inc
  50. 1 1
      tripal_chado/api/tripal_chado.api.inc
  51. 4 6
      tripal_chado/api/tripal_chado.query.api.inc
  52. 8 5
      tripal_chado/api/tripal_chado.schema.api.inc
  53. 9 6
      tripal_chado/includes/TripalFields/data__accession/data__accession.inc
  54. 1 1
      tripal_chado/includes/TripalFields/local__source_data/local__source_data_widget.inc
  55. 105 0
      tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis.inc
  56. 54 0
      tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis_formatter.inc
  57. 11 0
      tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis_widget.inc
  58. 5 4
      tripal_chado/includes/TripalImporter/FASTAImporter.inc
  59. 42 56
      tripal_chado/includes/TripalImporter/OBOImporter.inc
  60. 917 0
      tripal_chado/includes/TripalImporter/TaxonomyImporter.inc
  61. 4 2
      tripal_chado/includes/loaders/tripal_chado.pub_importer_PMID.inc
  62. 13 11
      tripal_chado/includes/loaders/tripal_chado.pub_importers.inc
  63. 0 368
      tripal_chado/includes/loaders/tripal_chado.taxonomy_importer.inc
  64. 0 1
      tripal_chado/includes/setup/tripal_chado.chado_v1_1.inc
  65. 0 1
      tripal_chado/includes/setup/tripal_chado.chado_vx_x.inc
  66. 47 8
      tripal_chado/includes/setup/tripal_chado.setup.inc
  67. 13 1
      tripal_chado/includes/tripal_chado.entity.inc
  68. 40 2
      tripal_chado/includes/tripal_chado.field_storage.inc
  69. 50 0
      tripal_chado/includes/tripal_chado.fields.inc
  70. 1 2
      tripal_chado/includes/tripal_chado.mapping.inc
  71. 5 5
      tripal_chado/includes/tripal_chado.migrate.inc
  72. 514 0
      tripal_chado/includes/tripal_chado.phylotree.inc
  73. 214 165
      tripal_chado/includes/tripal_chado.pub_search.inc
  74. 62 23
      tripal_chado/includes/tripal_chado.semweb.inc
  75. 31 5
      tripal_chado/includes/tripal_chado.vocab_storage.inc
  76. 12 0
      tripal_chado/theme/css/tripal_phylogeny.css
  77. 402 0
      tripal_chado/theme/js/d3.phylogram.js
  78. 143 0
      tripal_chado/theme/js/tripal_phylogeny.js
  79. 140 2
      tripal_chado/tripal_chado.install
  80. 150 66
      tripal_chado/tripal_chado.module
  81. 11 11
      tripal_chado/tripal_chado.views_default.inc
  82. 5 179
      tripal_chado_views/api/tripal_chado_views.api.inc
  83. 0 3
      tripal_ds/includes/tripal_ds.ds.inc
  84. 2 2
      tripal_ds/theme/css/tripal_ds.css
  85. BIN
      tripal_ds/theme/fonts/font-awesome-4.7.0/.DS_Store
  86. 26 20
      tripal_ds/theme/js/tripal_ds.js
  87. 118 0
      tripal_ws/includes/TripalFields/WebServicesField.inc
  88. 118 0
      tripal_ws/includes/TripalFields/WebServicesFieldFormatter.inc
  89. 5 0
      tripal_ws/includes/TripalFields/WebServicesFieldWidget.inc
  90. 391 0
      tripal_ws/includes/TripalFields/remote__data/remote__data.inc
  91. 368 0
      tripal_ws/includes/TripalFields/remote__data/remote__data_formatter.inc
  92. 169 0
      tripal_ws/includes/TripalFields/remote__data/remote__data_widget.inc
  93. 473 7
      tripal_ws/includes/TripalWebService.inc
  94. 317 239
      tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc
  95. 124 115
      tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc
  96. 9 9
      tripal_ws/includes/TripalWebServiceCollection.inc
  97. 81 24
      tripal_ws/includes/TripalWebServiceResource.inc
  98. 78 0
      tripal_ws/includes/tripal_ws.field_storage.inc
  99. 95 0
      tripal_ws/includes/tripal_ws.fields.inc
  100. 0 1064
      tripal_ws/includes/tripal_ws.rest_v0.1.inc

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.DS_Store

+ 339 - 0
LICENSE.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 0 - 94
legacy/tripal_analysis/tripal_analysis.install

@@ -50,18 +50,12 @@ function tripal_analysis_requirements($phase) {
  */
 function tripal_analysis_install() {
 
-  // we may need the analysisfeatureprop table if it doesn't already exist
-  tripal_analysis_create_analysisfeatureprop();
-
   // add vocabularies
   tripal_analysis_add_cvs();
 
   // add cvterms
   tripal_analysis_add_cvterms();
 
-  // add materialized views
-  tripal_analysis_add_mview_analysis_organism();
-
   // set the default vocabularies
   tripal_set_default_cv('analysisprop', 'type_id', 'analysis_property');
 }
@@ -75,33 +69,6 @@ function tripal_analysis_uninstall() {
 
 }
 
-/**
- * Create a legacy custom chado table (analysisfeatureprop) to store properties of
- * analysisfeature links.
- *
- * @ingroup tripal_analysis
- */
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_analysis_create_analysisfeatureprop() {
-
-  // Create analysisfeatureprop table in chado.  This is needed for Chado
-  // version 1.11, the table exists in Chado 1.2.
-  if (!db_table_exists('chado.analysisfeatureprop')) {
-    $sql = "
-      CREATE TABLE {analysisfeatureprop} (
-        analysisfeatureprop_id SERIAL PRIMARY KEY,
-        analysisfeature_id     INTEGER NOT NULL,
-        type_id                INTEGER NOT NULL,
-        value                  TEXT,
-        rank                   INTEGER NOT NULL,
-        CONSTRAINT analysisfeature_id_type_id_rank UNIQUE (analysisfeature_id, type_id, rank),
-        CONSTRAINT analysisfeatureprop_analysisfeature_id_fkey FOREIGN KEY (analysisfeature_id) REFERENCES {analysisfeature}(analysisfeature_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
-        CONSTRAINT analysisfeatureprop_type_id_fkey FOREIGN KEY (type_id) REFERENCES {cvterm}(cvterm_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
-      )
-    ";
-    chado_query($sql);
-  }
-} */
 
 /**
  * Add cvs related to analyses
@@ -231,64 +198,3 @@ function tripal_analysis_schema() {
 
   return $schema;
 }
-
-/**
- * Creates a view showing the link between an organism & it's analysis through associated features.
- *
- * @ingroup tripal_analysis
- */
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_analysis_add_mview_analysis_organism() {
-  $view_name = 'analysis_organism';
-  $comment = t('This view is for associating an organism (via it\'s associated features) to an analysis.');
-
-  // this is the SQL used to identify the organism to which an analsysis
-  // has been used.  This is obtained though the analysisfeature -> feature -> organism
-  // joins
-  $sql = "
-    SELECT DISTINCT A.analysis_id, O.organism_id
-    FROM analysis A
-      INNER JOIN analysisfeature AF ON A.analysis_id = AF.analysis_id
-      INNER JOIN feature F          ON AF.feature_id = F.feature_id
-      INNER JOIN organism O         ON O.organism_id = F.organism_id
-  ";
-
-  // the schema array for describing this view
-  $schema = array(
-    'table' => $view_name,
-    'description' => $comment,
-    'fields' => array(
-      'analysis_id' => array(
-        'size' => 'big',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'organism_id' => array(
-        'size' => 'big',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'networkmod_qtl_indx0' => array('analysis_id'),
-      'networkmod_qtl_indx1' => array('organism_id'),
-    ),
-    'foreign keys' => array(
-      'analysis' => array(
-        'table' => 'analysis',
-        'columns' => array(
-          'analysis_id' => 'analysis_id',
-        ),
-      ),
-      'organism' => array(
-        'table' => 'organism',
-        'columns' => array(
-          'organism_id' => 'organism_id',
-        ),
-      ),
-    ),
-  );
-
-  // add the view
-  tripal_add_mview($view_name, 'tripal_analysis', $schema, $sql, $comment);
-} */

+ 30 - 0
legacy/tripal_analysis/tripal_analysis.module

@@ -249,5 +249,35 @@ function tripal_analysis_form_alter(&$form, &$form_state, $form_id) {
 }
 
 
+/**
+ * Register tripal_analysis_api sub-modules
+ *
+ * @param $modulename
+ *  The name of the module to be registered as a tripal analysis submodule
+ *
+ * @ingroup tripal_analysis_api
+ */
+function tripal_register_analysis_child($modulename) {
+  $sql = "SELECT * FROM {tripal_analysis} WHERE modulename = :modname";
+  if (!db_query($sql, array(':modname' => $modulename))->fetchField()) {
+    $sql = "INSERT INTO {tripal_analysis} (modulename) VALUES (:modname)";
+    db_query($sql, array(':modname' => $modulename));
+  }
+}
+
+/**
+ * Un-register a tripal analysis sub-module
+ *
+ * @param $modulename
+ *  The name of the module to un-register
+ *
+ * @ingroup tripal_analysis_api
+ */
+function tripal_unregister_analysis_child($modulename) {
+  if (db_table_exists('tripal_analysis')) {
+    $sql = "DELETE FROM {tripal_analysis} WHERE modulename = :modname";
+    db_query($sql, array(':modname' => $modulename));
+  }
+}
 
 

+ 1 - 1
legacy/tripal_core/api/tripal_core.chado_nodes.api.inc

@@ -881,7 +881,7 @@ function chado_node_sync_records($base_table, $max_sync = FALSE,
   if ($where) {
     $query .= $where;
   }
-  $query .- " ORDER BY " . $base_table_id;
+  $query .= " ORDER BY " . $base_table_id;
 
   // If Maximum number to Sync is supplied
   if ($max_sync) {

+ 2 - 2
legacy/tripal_core/api/tripal_core.chado_nodes.properties.api.inc

@@ -293,7 +293,7 @@ function chado_add_node_form_properties(&$form, &$form_state, $details) {
         to the %cv_name controlled vocabulary.',
         array(
           '%cv_name' => $details['cv_name'],
-          '@cvtermlink' => url('admin/tripal/loaders/chado_cv/' . $details['cv_id'] . '/cvterm/add')
+          '@cvtermlink' => url('admin/tripal/loaders/chado_vocabs/chado_cv/' . $details['cv_id'] . '/cvterm/add')
         )
       ),
       TRIPAL_NOTICE,
@@ -306,7 +306,7 @@ function chado_add_node_form_properties(&$form, &$form_state, $details) {
         a controlled vocabulary term</a> to the %cv_name controlled vocabulary.',
         array(
           '%cv_name' => $details['cv_name'],
-          '@cvtermlink' => url('admin/tripal/loaders/chado_cv/' . $details['cv_id'] . '/cvterm/add')
+          '@cvtermlink' => url('admin/tripal/loaders/chado_vocabs/chado_cv/' . $details['cv_id'] . '/cvterm/add')
         )
       ),
       TRIPAL_INFO,

+ 2 - 2
legacy/tripal_core/api/tripal_core.chado_nodes.relationships.api.inc

@@ -254,7 +254,7 @@ function chado_add_node_form_relationships(&$form, &$form_state, $details) {
         to the %cv_name controlled vocabulary.',
         array(
           '%cv_name' => $details['cv_name'],
-          '@cvtermlink' => url('admin/tripal/loaders/chado_cv/'.$details['cv_id'].'/cvterm/add')
+          '@cvtermlink' => url('admin/tripal/loaders/chado_vocabs/chado_cv/'.$details['cv_id'].'/cvterm/add')
         )
       ),
       TRIPAL_WARNING,
@@ -267,7 +267,7 @@ function chado_add_node_form_relationships(&$form, &$form_state, $details) {
         a controlled vocabulary term</a> to the %cv_name controlled vocabulary.',
         array(
           '%cv_name' => $details['cv_name'],
-          '@cvtermlink' => url('admin/tripal/loaders/chado_cv/' . $details['cv_id'] . '/cvterm/add')
+          '@cvtermlink' => url('admin/tripal/loaders/chado_vocabs/chado_cv/' . $details['cv_id'] . '/cvterm/add')
         )
       ),
       TRIPAL_INFO,

+ 1 - 1
legacy/tripal_cv/api/tripal_cv.api.inc

@@ -97,7 +97,7 @@ function tripal_get_cvterm_default_select_options($table, $field, $field_desc) {
     if (count($options) == 0) {
       tripal_set_message('There are no ' . $field_desc . '. Please ' .
           l('add terms',
-              'admin/tripal/loaders/chado_cv/' .$default_cv->cv_id. '/cvterm/add',
+              'admin/tripal/loaders/chado_vocabs/chado_cv/' .$default_cv->cv_id. '/cvterm/add',
               array('attributes' => array('target' => '_blank'))) . ' to the ' .
           $default_cv->name .' vocabulary.',
           TRIPAL_WARNING);

+ 3 - 3
legacy/tripal_cv/includes/tripal_cv.cv_defaults.inc

@@ -79,18 +79,18 @@ function tripal_cv_admin_set_defaults_form($form, &$form_state) {
     );
 
     // Actions
-    $view_terms = l('New Vocabulary', 'admin/tripal/loaders/chado_cv/add');
+    $view_terms = l('New Vocabulary', 'admin/tripal/loaders/chado_vocabs/chado_cv/add');
     $add_term = '';
     if (!empty($cv)) {
       $view_terms = l(
           'View Terms',
-          'admin/tripal/loaders/chado_cvterms',
+          'admin/tripal/loaders/chado_vocabs/chado_cvterms',
           array('query' => array('cv' => $cv->name))
       );
 
       $add_term = l(
           'Add Term',
-          'admin/tripal/loaders/chado_cv/' . $cv->cv_id . '/cvterm/add'
+          'admin/tripal/loaders/chado_vocabs/chado_cv/' . $cv->cv_id . '/cvterm/add'
       );
     }
     $form['settings']['existing'][$cv_default_id]["view-terms"] = array(

+ 1 - 1
legacy/tripal_cv/theme/templates/tripal_cv_help.tpl.php

@@ -15,7 +15,7 @@
    users access to view content and create a special role for creating, editing and other administrative tasks.</p></li>
 
       <li><p><b>Loading of Ontologies/Controlled Vocabularies</b>: You can access this loader at <?php
-        print l('Admin->Tripal Management->Tripal CV->Load Ontology With OBO File', 'admin/tripal/loaders/chado_obo_loader')
+        print l('Admin->Tripal Management->Tripal CV->Load Ontology With OBO File', 'admin/tripal/loaders/chado_vocabs/obo_loader')
         ?>. This loader allows you to choose from a list of common ontologies or
         enter the URL or location to an OBO file. Even the list of common
         ontologies is using a URL ensuring you get the most up to date ontology.</p>

+ 1 - 1
legacy/tripal_feature/theme/templates/tripal_feature_help.tpl.php

@@ -11,7 +11,7 @@
 
    <li><p><b>Loading of Ontologies</b>:
      Before loading genomic features you must also have several vocabularies loaded as well. Using the
-     <?php print l('OGO loader','admin/tripal/tripal_cv/obo_loader')?> you should load the following
+     <?php print l('OGO loader','admin/tripal/loaders/chado_vocabs/obo_loader')?> you should load the following
      ontologies:</p>
      <ul>
         <li>Sequence Ontology</li>

+ 0 - 191
legacy/tripal_feature/tripal_feature.install

@@ -57,14 +57,6 @@ function tripal_feature_install() {
   // $obo_id = tripal_insert_obo('Chado Feature Properties', $obo_path);
   // tripal_submit_obo_job(array('obo_id' => $obo_id));
 
-  // Add the materialized view.
-  tripal_feature_add_organism_count_mview();
-
-  // Add the custom tables.
-  tripal_feature_add_tripal_gff_temp_table();
-  tripal_feature_add_tripal_gffcds_temp_table();
-  tripal_feature_add_tripal_gffprotein_temp_table();
-
   // Add the vocabularies used by the feature module.
   tripal_feature_add_cvs();
 
@@ -82,119 +74,6 @@ function tripal_feature_install() {
 function tripal_feature_uninstall() {
 
 }
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_feature_add_tripal_gff_temp_table() {
-  $schema = array(
-    'table' => 'tripal_gff_temp',
-    'fields' => array(
-      'feature_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'organism_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'uniquename' => array(
-        'type' => 'text',
-        'not null' => TRUE,
-      ),
-      'type_name' => array(
-        'type' => 'varchar',
-        'length' => '1024',
-        'not null' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'tripal_gff_temp_idx0' => array('feature_id'),
-      'tripal_gff_temp_idx0' => array('organism_id'),
-      'tripal_gff_temp_idx1' => array('uniquename'),
-    ),
-    'unique keys' => array(
-      'tripal_gff_temp_uq0' => array('feature_id'),
-      'tripal_gff_temp_uq1' => array('uniquename', 'organism_id', 'type_name'),
-    ),
-  );
-  chado_create_custom_table('tripal_gff_temp', $schema, TRUE);
-} */
-
-/**
- *
- */
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_feature_add_tripal_gffcds_temp_table($skip_recreate = TRUE) {
-  $schema = array(
-    'table' => 'tripal_gffcds_temp',
-    'fields' => array(
-      'feature_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'parent_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'phase' => array(
-        'type' => 'int',
-        'not null' => FALSE,
-      ),
-      'strand' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'fmin' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'fmax' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'tripal_gff_temp_idx0' => array('feature_id'),
-      'tripal_gff_temp_idx0' => array('parent_id'),
-    ),
-  );
-  chado_create_custom_table('tripal_gffcds_temp', $schema, $skip_recreate);
-} */
-
-/**
- *
- */
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_feature_add_tripal_gffprotein_temp_table() {
-  $schema = array(
-    'table' => 'tripal_gffprotein_temp',
-    'fields' => array(
-      'feature_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'parent_id' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'fmin' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'fmax' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'tripal_gff_temp_idx0' => array('feature_id'),
-      'tripal_gff_temp_idx0' => array('parent_id'),
-    ),
-    'unique keys' => array(
-      'tripal_gff_temp_uq0' => array('feature_id'),
-    ),
-  );
-  chado_create_custom_table('tripal_gffprotein_temp', $schema, TRUE);
-} */
-
 /**
  * Implementation of hook_schema().
  *
@@ -240,76 +119,6 @@ function tripal_feature_schema() {
   return $schema;
 };
 
-/**
- * Creates a materialized view that stores the type & number of features per organism
- *
- * @ingroup tripal_feature
- */
-// This function was moved to tripal_chado/includes/setup/tripal_chado.setup.inc
-/* function tripal_feature_add_organism_count_mview() {
-  $view_name = 'organism_feature_count';
-  $comment = 'Stores the type and number of features per organism';
-
-  $schema = array(
-    'description' => $comment,
-    'table' => $view_name,
-    'fields' => array(
-      'organism_id' => array(
-        'size' => 'big',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'genus' => array(
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => TRUE,
-      ),
-      'species' => array(
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => TRUE,
-      ),
-      'common_name' => array(
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => FALSE,
-      ),
-      'num_features' => array(
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'cvterm_id' => array(
-        'size' => 'big',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'feature_type' => array(
-        'type' => 'varchar',
-        'length' => '255',
-        'not null' => TRUE,
-      ),
-    ),
-    'indexes' => array(
-      'organism_feature_count_idx1' => array('organism_id'),
-      'organism_feature_count_idx2' => array('cvterm_id'),
-      'organism_feature_count_idx3' => array('feature_type'),
-    ),
-  );
-
-  $sql = "
-    SELECT
-        O.organism_id, O.genus, O.species, O.common_name,
-        count(F.feature_id) as num_features,
-        CVT.cvterm_id, CVT.name as feature_type
-     FROM organism O
-        INNER JOIN feature F  ON O.Organism_id = F.organism_id
-        INNER JOIN cvterm CVT ON F.type_id     = CVT.cvterm_id
-     GROUP BY
-        O.Organism_id, O.genus, O.species, O.common_name, CVT.cvterm_id, CVT.name
-  ";
-
-  tripal_add_mview($view_name, 'tripal_feature', $schema, $sql, $comment);
-} */
 /**
  * Add cvs related to publications
  *

+ 1 - 1
legacy/tripal_library/includes/tripal_library.chado_node.inc

@@ -128,7 +128,7 @@ function chado_library_form($node, &$form_state) {
   $lt_message = tripal_set_message("To add additional items to the library type drop down list,
      add a term to the " .
      l($lt_cv->name . " controlled vocabulary",
-       "admin/tripal/loaders/chado_cv/" . $lt_cv->cv_id . "/cvterm/add",
+       "admin/tripal/loaders/chado_vocabs/chado_cv/" . $lt_cv->cv_id . "/cvterm/add",
        array('attributes' => array('target' => '_blank'))
       ),
      TRIPAL_INFO, array('return_html' => TRUE)

+ 0 - 265
legacy/tripal_phylogeny/includes/tripal_phylogeny.admin.inc

@@ -1,38 +1,4 @@
 <?php
-/**
- * @file
- * This file contains the functions used for administration of the module
- *
- */
-
-function tripal_phylogeny_admin_phylotrees_listing() {
-  $output = '';
-
-  // set the breadcrumb
-  $breadcrumb = array();
-  $breadcrumb[] = l('Home', '<front>');
-  $breadcrumb[] = l('Administration', 'admin');
-  $breadcrumb[] = l('Tripal', 'admin/tripal');
-  $breadcrumb[] = l('Chado', 'admin/tripal/legacy');
-  $breadcrumb[] = l('Phylotrees', 'admin/tripal/extension/tripal_phylogeny');
-  drupal_set_breadcrumb($breadcrumb);
-
-  // Add the view
-  $view = views_embed_view('tripal_phylogeny_admin_phylotree','default');
-  if (isset($view)) {
-    $output .= $view;
-  }
-  else {
-    $output .= '<p>The Phylotree module uses primarily views to provide an '
-      . 'administrative interface. Currently one or more views needed for this '
-      . 'administrative interface are disabled. <strong>Click each of the following links to '
-      . 'enable the pertinent views</strong>:</p>';
-    $output .= '<ul>';
-      $output .= '<li>'.l('Phylotree View', 'admin/tripal/extension/tripal_phylogeny/views/phylotree/enable').'</li>';
-    $output .= '</ul>';
-  }
-  return $output;
-}
 
 /**
  * Administrative settings form
@@ -85,235 +51,4 @@ function tripal_phylogeny_admin() {
 
   return system_settings_form($form);
 
-}
-
-/**
- *
- * @param unknown $form
- * @param unknown $form_state
- */
-function tripal_phylogeny_default_plots_form($form, &$form_state) {
-  $form = array();
-
-  $form['plot_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Plot Settings'),
-    '#description' => t('You can customize settings for each plot'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE
-  );
-
-  $form['plot_settings']['phylogram_width'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Tree Width',
-    '#description' => 'Please specify the width in pixels for the phylogram',
-    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
-    '#element_validate' => array(
-      'element_validate_integer_positive'
-    ),
-    '#size' => 5,
-  );
-
-  $form['node_settings'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Node Settings'),
-    '#description' => t('You can customize settings for the nodes on the trees.'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE
-  );
-  $form['node_settings']['root_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Root Node Size',
-    '#description' => 'Please specify a size for the root node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_root_node_size', 3),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-  $form['node_settings']['interior_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Interor Node Size',
-    '#description' => 'Please specify a size for the interior node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-  $form['node_settings']['leaf_node_size'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Leaf Node Size',
-    '#description' => 'Please specify a size for the leaf node size. If set to zero, the node will not appear.',
-    '#default_value' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
-    '#element_validate' => array(
-      'element_validate_integer'
-    ),
-    '#size' => 3,
-  );
-
-  // Get the number of organism colors that already exist. If the site admin
-  // has set colors then those settings will be in a Drupal variable which we
-  // will retrieve.  Otherwise the num_orgs defaults to 1 and a single
-  // set of fields is provided.
-  $num_orgs = variable_get("tripal_phylogeny_num_orgs", 1);
-  if (array_key_exists('values', $form_state) and array_key_exists('num_orgs', $form_state['values'])) {
-    $num_orgs = $form_state['values']['num_orgs'];
-  }
-  // The default values for each organism color are provided in a d
-  // Drupal variable that gets set when the form is set.
-  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
-
-  $form['node_settings']['desc'] = array(
-    '#type' => 'item',
-    '#title' => t('Node Colors by Organism'),
-    '#markup' => t('If the trees are associated with features (e.g. proteins)
-      then the nodes can be color-coded by their organism.  This helps the user
-      visualize which nodes belong to each organism.  Please enter the
-      name of the organism and it\'s corresponding color in HEX code (e.g. #FF0000 == red).
-      Organisms that are not given a color will be gray.'),
-  );
-  $form['node_settings']['org_table']['num_orgs'] = array(
-    '#type' => 'value',
-    '#value' => $num_orgs,
-  );
-
-  // Iterate through the number of organism colors and add a field for each one.
-  for ($i = 0; $i < $num_orgs; $i++) {
-    $form['node_settings']['org_table']['organism_' . $i] = array(
-      '#type' => 'textfield',
-      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['organism'] : '',
-      '#autocomplete_path' => "admin/tripal/legacy/tripal_organism/organism/auto_name",
-      '#description' => t('Please enter the name of the organism.'),
-      '#size' => 30,
-    );
-    $form['node_settings']['org_table']['color_' . $i] = array(
-      '#type' => 'textfield',
-      '#description' => t('Please provide a color in Hex format (e.g. #FF0000).'),
-      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['color'] : '',
-      '#suffix' => "<div id=\"color-box-$i\" style=\"width: 30px;\"></div>",
-      '#size' => 10,
-    );
-  }
-  $form['node_settings']['org_table']['add'] = array(
-    '#type' => 'submit',
-    '#name' => 'add',
-    '#value' => 'Add',
-    '#ajax' => array(
-      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
-      'wrapper'  => 'tripal_phylogeny_default_plots_form',
-      'effect'   => 'fade',
-      'method'   => 'replace',
-    ),
-  );
-  $form['node_settings']['org_table']['remove'] = array(
-    '#type' => 'submit',
-    '#name' => 'remove',
-    '#value' => 'Remove',
-    '#ajax' => array(
-      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
-      'wrapper'  => 'tripal_phylogeny_default_plots_form',
-      'effect'   => 'fade',
-      'method'   => 'replace',
-    ),
-  );
-  $form['node_settings']['org_table']['#theme'] = 'tripal_phylogeny_admin_org_color_tables';
-  $form['node_settings']['org_table']['#prefix'] = '<div id="tripal_phylogeny_default_plots_form">';
-  $form['node_settings']['org_table']['#suffix'] = '</div>';
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#name' => 'submit',
-    '#value' => 'Save Configuration',
-  );
-
-  $form['#submit'][] = 'tripal_phylogeny_default_plots_form_submit';
-
-  return $form;
-}
-
-/**
- * Validate the phylotree settings forms
- *
- * @ingroup tripal_phylogeny
- */
-function tripal_phylogeny_default_plots_form_validate($form, &$form_state) {
-
-}
-/**
- *
- * @param unknown $form
- * @param unknown $form_state
- */
-function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
-  // Rebuild this form after submission so that any changes are reflected in
-  // the flat tables.
-  $form_state['rebuild'] = TRUE;
-
-  if ($form_state['clicked_button']['#name'] == 'submit') {
-    variable_set('tripal_phylogeny_default_phylogram_width', $form_state['values']['phylogram_width']);
-
-    variable_set('tripal_phylogeny_default_root_node_size', $form_state['values']['root_node_size']);
-    variable_set('tripal_phylogeny_default_interior_node_size', $form_state['values']['interior_node_size']);
-    variable_set('tripal_phylogeny_default_leaf_node_size', $form_state['values']['leaf_node_size']);
-
-    $num_orgs = $form_state['values']['num_orgs'];
-    variable_set("tripal_phylogeny_num_orgs", $num_orgs);
-    $colors = array();
-    for ($i = 0; $i < $num_orgs ;$i++) {
-      $colors[$i] = array(
-        'organism' => $form_state['values']['organism_' . $i],
-        'color' => $form_state['values']['color_' . $i]
-      );
-    }
-    variable_set("tripal_phylogeny_org_colors", $colors);
-  }
-  if ($form_state['clicked_button']['#name'] == 'add') {
-    $form_state['values']['num_orgs']++;
-  }
-  if ($form_state['clicked_button']['#name'] == 'remove') {
-    $form_state['values']['num_orgs']--;
-  }
-}
-
-/**
- *
- * @param unknown $variables
- */
-function theme_tripal_phylogeny_admin_org_color_tables($variables){
-   $fields = $variables['element'];
-   $num_orgs = $fields['num_orgs']['#value'];
-   $headers = array('Organism', 'Color', '');
-   $rows = array();
-   for ($i = 0; $i < $num_orgs; $i++) {
-     $add_button = ($i == $num_orgs - 1) ? drupal_render($fields['add']) : '';
-     $del_button = ($i == $num_orgs - 1 and $i != 0) ? drupal_render($fields['remove']) : '';
-     $rows[] = array(
-       drupal_render($fields['organism_' . $i]),
-       drupal_render($fields['color_' . $i]),
-       $add_button . $del_button,
-     );
-   }
-   $table_vars = array(
-     'header' => $headers,
-     'rows' => $rows,
-     'attributes' => array(),
-     'sticky' => FALSE,
-     'colgroups' => array(),
-     'empty' => '',
-   );
-   $form['orgs']['num_orgs'] = $fields['num_orgs'];
-   return theme('table', $table_vars);
-}
-
-
-/**
- * Ajax callback function for the gensas_job_view_panel_form.
- *
- * @param $form
- * @param $form_state
- */
-function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
-
-  return $form['node_settings']['org_table'];
 }

+ 3 - 4
legacy/tripal_phylogeny/includes/tripal_phylogeny.taxonomy.inc

@@ -21,8 +21,8 @@ function tripal_phylogeny_taxonomy_view() {
       array(
         '@menu' => url('admin/structure/menu/manage/navigation'),
         '@taxloader' => url('admin/tripal/loaders/ncbi_taxonomy_loader'
-      ))
-  );
+            ))
+      );
   $admin_message = tripal_set_message($message, TRIPAL_INFO, array('return_html' => TRUE));
 
   $phylotree = chado_generate_var('phylotree', $values);
@@ -31,7 +31,7 @@ function tripal_phylogeny_taxonomy_view() {
     $node->phylotree = $phylotree;
 
     $html =  theme('tripal_phylogeny_taxonomic_tree', array('node' => $node)) .
-      $admin_message;
+    $admin_message;
     return $html;
   }
 
@@ -40,4 +40,3 @@ function tripal_phylogeny_taxonomy_view() {
     '#markup' => t('This site has not yet prepared the taxonomy for viewing.') . $admin_message,
   );
 }
-

+ 1 - 1
legacy/tripal_phylogeny/theme/css/tripal_phylogeny.css

@@ -399,4 +399,4 @@ if (!d3) { throw "d3 wasn't included!"};
     
     return {tree: tree, vis: vis}
   }
-}());
+}());

+ 1 - 1
legacy/tripal_phylogeny/theme/js/tripal_phylogeny.js

@@ -127,4 +127,4 @@
       return 22 * leafNodeCt;
     }
   });
-})(jQuery);
+})(jQuery);

+ 2 - 81
legacy/tripal_phylogeny/theme/tripal_phylogeny.theme.inc

@@ -1,86 +1,5 @@
 <?php
 
-function tripal_phylogeny_prepare_tree_viewer($phylotree) {
-
-  // Don't prepare for viewing more than once.
-  if (property_exists($phylotree, 'prepared_to_view') and
-      $phylotree->prepared_to_view == TRUE) {
-    return;
-  }
-
-  $module_path = drupal_get_path('module', 'tripal_phylogeny');
-
-  drupal_add_js('https://d3js.org/d3.v3.min.js', 'external');
-
-  drupal_add_js("$module_path/theme/js/d3.phylogram.js");
-  drupal_add_js("$module_path/theme/js/tripal_phylogeny.js");
-  drupal_add_css("$module_path/theme/css/tripal_phylogeny.css");
-
-  drupal_add_library('system', 'ui.dialog');
-
-  // Don't show tick marks for the taxonomy tree.
-  $skip_ticks = 0;
-  if ($phylotree->type_id->name == 'taxonomy') {
-    $skip_ticks = 1;
-  }
-
-  // Get the tree options as set by the administrator.
-  $options = json_encode(array(
-    'phylogram_width' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
-    'root_node_size' => variable_get('tripal_phylogeny_default_root_node_size', 3),
-    'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
-    'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
-    'skipTicks' => $skip_ticks,
-  ));
-
-  // Get the node colors as set by the administrator.
-  $colors = array();
-  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
-  foreach ($color_defaults as $i => $details) {
-    if ($details['organism']) {
-      $colors[$details['organism']] =  $details['color'];
-    }
-  }
-  $colors = json_encode($colors);
-
-  // Add javascript data needed for this tree.
-  drupal_add_js(
-    ' // var having URL of json data source for charting
-      var phylotreeDataURL =  baseurl + "/ajax/chado_phylotree/' . $phylotree->phylotree_id . '/json";
-
-      // var with path to our theme, for use by javascript functions.
-      var pathToTheme = baseurl + "/' . $module_path . '/theme";
-
-      // var with custom options
-      var treeOptions = ' . $options . ';
-
-      // var with the organism colors
-      var organismColors = ' . $colors . ';',
-    'inline'
-  );
-
-  if (!property_exists($phylotree, 'has_nodes')) {
-    // If the nodes haven't loaded then set a value so the template can
-    // choose not to show the phylogram.
-    $values  = array('phylotree_id' => $phylotree->phylotree_id);
-    $options = array('limit' => 1, 'offset' => 0, 'has_record' => 1);
-    $phylotree->has_nodes = chado_select_record('phylonode', array('phylonode_id'), $values, $options);
-  }
-  if (!property_exists($phylotree, 'has_features')) {
-    // If the nodes haven't loaded then set a value so the template can
-    // choose not to show the circular dendrogram. The chado_select_record()
-    // API call can't do this query so we have to do it manually.
-    $sql = "
-      SELECT count(*) as num_features
-      FROM {phylonode}
-      WHERE NOT feature_id IS NULL and phylotree_id = :phylotree_id
-      LIMIT 1 OFFSET 0
-    ";
-    $phylotree->has_features = chado_query($sql, array(':phylotree_id' => $phylotree->phylotree_id))->fetchField();
-  }
-
-  $phylotree->prepared_to_view = TRUE;
-}
 /**
  * Implements hook_preprocess_hook()
  *
@@ -88,6 +7,7 @@ function tripal_phylogeny_prepare_tree_viewer($phylotree) {
  */
 function tripal_phylogeny_preprocess_tripal_phylogeny_phylogram(&$variables) {
   $phylotree = $variables['node']->phylotree;
+  module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
   tripal_phylogeny_prepare_tree_viewer($phylotree);
 }
 
@@ -98,5 +18,6 @@ function tripal_phylogeny_preprocess_tripal_phylogeny_phylogram(&$variables) {
  */
 function tripal_phylogeny_preprocess_tripal_phylogeny_taxonomic_tree(&$variables) {
   $phylotree = $variables['node']->phylotree;
+  module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
   tripal_phylogeny_prepare_tree_viewer($phylotree);
 }

+ 17 - 156
legacy/tripal_phylogeny/tripal_phylogeny.module

@@ -53,6 +53,15 @@ function tripal_phylogeny_permission() {
 function tripal_phylogeny_menu() {
   $items = array();
 
+  $items['taxonomy_view'] = array(
+    'title' => 'Taxonomy',
+    'description' => 'Taxonomic view of the species available on this site.',
+    'page callback' => 'tripal_phylogeny_taxonomy_view',
+    'access arguments' => array('access taxonomy content'),
+    'file' => '/includes/tripal_phylogeny.taxonomy.inc',
+    'type' => MENU_NORMAL_ITEM,
+  );
+
   // administration landing page. currently has no content but is
   // apparently required for the Sync and Help links to work.
   $items['admin/tripal/legacy/tripal_phylogeny'] = array(
@@ -85,16 +94,6 @@ function tripal_phylogeny_menu() {
     'weight' => 1
   );
 
-  $items['admin/tripal/legacy/tripal_phylogeny/plots'] = array(
-    'title' => 'Plot Defaults',
-    'description' => 'Set defaults for the trees',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_phylogeny_default_plots_form'),
-    'access arguments' => array('administer tripal phylotree'),
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 2
-  );
-
   // sync menu item (will be rendered as a tab by tripal)
   $items['admin/tripal/legacy/tripal_phylogeny/sync'] = array(
     'title' => ' Sync',
@@ -115,22 +114,13 @@ function tripal_phylogeny_menu() {
     'type' => MENU_CALLBACK,
   );
 
-   $items['taxonomy_view'] = array(
-     'title' => 'Taxonomy',
-     'description' => 'Taxonomic view of the species available on this site.',
-     'page callback' => 'tripal_phylogeny_taxonomy_view',
-     'access arguments' => array('access taxonomy content'),
-     'file' => '/includes/tripal_phylogeny.taxonomy.inc',
-     'type' => MENU_NORMAL_ITEM,
-   );
-
-   // create a route for viewing json of all phylonodes having this phylotree_id
-   $items['ajax/chado_phylotree/%/json'] = array(
-     'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
-     'page arguments' => array(2),
-     // allow all anonymous http clients
-     'access callback' => TRUE
-   );
+  // create a route for viewing json of all phylonodes having this phylotree_id
+  $items['ajax/chado_phylotree/%/json'] = array(
+    'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
+    'page arguments' => array(2),
+    // allow all anonymous http clients
+    'access callback' => TRUE
+  );
 
   return $items;
 }
@@ -247,133 +237,4 @@ function tripal_phylogeny_help ($path, $arg) {
   if ($path == 'admin/help#tripal_phylogeny') {
     return theme('tripal_phylogeny_help', array());
   }
-}
-
-/**
- * Get json representation of a phylotree id.
- *
- * This function is meant to be called via AJAX.
- *
- * @param int $phylotree_id
- *   the ID of the phylotree node.
- *
- * @return string json
- *
- * @ingroup tripal_phylogeny
- */
-function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
-
-  $phylotree = chado_generate_var('phylotree', array('phylotree_id' => $phylotree_id));
-
-  // This SQL gets all of the phylonodes for a given tree as well as the
-  // features and organisms with which it is assocaited.  Each phylonode
-  // can be associated with an organism in one of two ways: 1) via a
-  // feature linked by the phylonode.feature_id field or 2) via a
-  // a record in the phylonde_organsim table.  Therefore both types of
-  // organism records are returned in the query below, but those
-  // retrieved via a FK link on features are prefixed with 'fo_'.
-  $sql = "
-    SELECT
-      n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
-      f.feature_id, f.name AS feature_name,
-      cvt.name AS cvterm_name,
-      o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
-      fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
-      fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species,
-      cf.nid AS feature_node_id,
-      fco.nid AS fo_organism_node_id,
-      co.nid AS organism_node_id
-    FROM {phylonode} n
-      LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
-      LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
-      LEFT OUTER JOIN [chado_feature] cf        ON cf.feature_id = f.feature_id
-      LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
-      LEFT OUTER JOIN [chado_organism] fco      ON fco.organism_id = fo.organism_id
-      LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
-      LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
-      LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
-    WHERE n.phylotree_id = :phylotree_id
-  ";
-  $args = array(':phylotree_id' => $phylotree_id);
-  $result = chado_query($sql, $args);
-
-  // Fetch all the phylonodes into an assoc array indexed by phylonode_id.
-  // Convert from resultset record to array, fixing datatypes. chado_query
-  // returns numeric as string and fun stuff like that.
-  $phylonodes = array();
-  $root_phylonode_ref = null;
-
-  foreach ($result as $r) {
-    $phylonode_id = (int) $r->phylonode_id;
-
-    // expect all nodes to have these properties
-    $node = array(
-      'phylonode_id' => $phylonode_id,
-      'parent_phylonode_id' => (int) $r->parent_phylonode_id,
-      'length' => (double) $r->length,
-      'cvterm_name' => $r->cvterm_name
-    );
-
-    // If the nodes are taxonomic then set an equal distance
-    if ($phylotree->type_id->name == 'taxonomy') {
-      $node['length'] = 0.001;
-    }
-
-    // Other props may exist only for leaf nodes
-    if ($r->name) {
-      $node['name'] = $r->name;
-    }
-    // If this node is associated with a feature then add in the details
-    if ($r->feature_id) {
-      $node['feature_id'] = (int) $r->feature_id;
-      $node['feature_name'] = $r->feature_name;
-      $node['feature_node_id'] = (int) $r->feature_node_id;
-    }
-    // Add in the organism fields when they are available via the
-    // phylonode_organism table.
-    if ($r->organism_id) {
-      $node['organism_id'] = (int) $r->organism_id;
-      $node['common_name'] = $r->common_name;
-      $node['abbreviation'] = $r->abbreviation;
-      $node['genus'] = $r->genus;
-      $node['species'] = $r->species;
-      $node['organism_node_id'] = (int) $r->organism_node_id;
-      // If the node does not have a name but is linked to an organism
-      // then set the name to be that of the genus and species.
-      if (!$r->name) {
-        $node['name'] = $r->genus . ' ' . $r->species;
-      }
-    }
-    // Add in the organism fields when they are available via the
-    // the phylonode.feature_id FK relationship.
-    if ($r->fo_organism_id) {
-      $node['fo_organism_id'] = (int) $r->fo_organism_id;
-      $node['fo_common_name'] = $r->fo_common_name;
-      $node['fo_abbreviation'] = $r->fo_abbreviation;
-      $node['fo_genus'] = $r->fo_genus;
-      $node['fo_species'] = $r->fo_species;
-      $node['fo_organism_node_id'] = (int) $r->fo_organism_node_id;
-    }
-
-    // Add this node to the list, organized by ID.
-    $phylonodes[$phylonode_id] = $node;
-  }
-
-  // Populate the children[] arrays for each node.
-  foreach ($phylonodes as $key => &$node) {
-    if ($node['parent_phylonode_id'] !== 0) {
-      $parent_ref = &$phylonodes[ $node['parent_phylonode_id']];
-      // Append node refernce to children.
-      $parent_ref['children'][] = &$node;
-    }
-    else {
-      $root_phylonode_ref = &$node;
-    }
-  }
-
-  // dump datastructure as json to browser. drupal sets the mime-type correctly.
-  drupal_json_output($root_phylonode_ref);
-}
-
-
-
+}

+ 0 - 45
legacy/tripal_pub/includes/tripal_pub.admin.inc

@@ -66,42 +66,6 @@ function tripal_pub_admin() {
   // This sub-form handles it's own validation & submit
   chado_add_admin_form_set_title($form, $form_state, $details);
 
-  // -----------------------------------------
-  // add in the fields for selecting which fields are used when search for pubs
-  $form['searching'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Searching Options'),
-    '#description' => t("The list of checkboxes below indicate which fields a user
-      can search with when using the publication search tool.  Check the fields that you want
-      to allow users to search with.  Click the 'Save configuration' button below to save changes."),
-  );
-
-  // get publication properties list
-  $properties = array();
-  $properties[] = 'Any Field';
-  $sql = "
-    SELECT DISTINCT CVTS.cvterm_id, CVTS.name, CVTS.definition
-    FROM {cvtermpath} CVTP
-      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
-      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
-      INNER JOIN {cv} ON CVTO.cv_id = CV.cv_id
-    WHERE CV.name = 'tripal_pub' and
-      (CVTO.name = 'Publication Details' or CVTS.name = 'Publication Type') and
-      NOT CVTS.is_obsolete = 1
-    ORDER BY CVTS.name ASC
-  ";
-  $prop_types = chado_query($sql);
-  while ($prop = $prop_types->fetchObject()) {
-    $properties[$prop->cvterm_id] = $prop->name;
-  }
-  $form['searching']['allowed_search_fields'] = array(
-    '#type'    => 'checkboxes',
-    '#options' => $properties,
-    '#prefix'  => '<div style="scroll: auto; border:1px solid #CCCCCC;">',
-    '#suffix'  => '</div>',
-    '#default_value' => variable_get('tripal_pub_allowed_search_fields', array()),
-  );
-
   // -----------------------------------------
   // add the field set for syncing publications
   $form['import'] = array(
@@ -179,15 +143,6 @@ function tripal_pub_admin_validate($form, &$form_state) {
   global $user;  // we need access to the user info
   $job_args = array();
 
-  // set the allowed search fields
-  $allowed_fields = $form_state['values']['allowed_search_fields'];
-  foreach ($allowed_fields as $cvterm_id => $selected) {
-    if (!$selected) {
-      unset($allowed_fields[$cvterm_id]);
-    }
-  }
-  variable_set('tripal_pub_allowed_search_fields', $allowed_fields);
-
   $import_duplicate_check = $form_state['values']['import_duplicate_check'];
   variable_set('tripal_pub_import_duplicate_check', $import_duplicate_check);
 

+ 1 - 1
legacy/tripal_pub/includes/tripal_pub.chado_node.inc

@@ -53,7 +53,7 @@ function chado_pub_form($node, $form_state) {
     drupal_set_message(t('The Tripal Pub vocabulary is currently not loaded. ' .
         'This vocabulary is required to be loaded before adding ' .
         'publications.  <br>Please !import',
-        array('!import' => l('load the Tripal Publication vocabulary', 'admin/tripal/loaders/obo_loader'))), 'warning');
+        array('!import' => l('load the Tripal Publication vocabulary', 'admin/tripal/loaders/chado_vocabs/obo_loader'))), 'warning');
   }
 
   // Default values can come in the following ways:

+ 2 - 2
legacy/tripal_pub/theme/templates/tripal_pub_help.tpl.php

@@ -29,7 +29,7 @@ have been added to Chado database.</p>
   </li>
   <li>
   <p><b>Configure the Search Behavior</b>: Before allowing site visitors
-  to search for publications visit the <?php print l('configuration page', 'admin/tripal/tripal_pub/configuration') ?>
+  to search for publications visit the <?php print l('configuration page', 'admin/tripal/storage/chado/pub-search-config') ?>
   to disable or enable fields for searching. Tripal uses its own ontology
   for storing publication information in Chado, and all child terms of
   the "Publication Details" are made available for searching. However,
@@ -48,7 +48,7 @@ have been added to Chado database.</p>
     <li>Install the PHP YAZ extension: sudo pecl install yaz</li>
     <li>Add the text 'extension=yaz.so' to the appropriate php.ini file
     (e.g. /etc/php5/apache2filter/php.ini). On Ubuntu you may need to
-    add it to the php.ini file specfic for the Apache webserver and 
+    add it to the php.ini file specfic for the Apache webserver and
     also to the php.ini specific for the command-line.</li>
     <li>Restart the webserver</li>
   </ol>

+ 0 - 14
legacy/tripal_pub/tripal_pub.module

@@ -10,7 +10,6 @@ require_once 'theme/tripal_pub.theme.inc';
 
 require_once 'includes/tripal_pub.admin.inc';
 require_once 'includes/tripal_pub.chado_node.inc';
-require_once 'includes/tripal_pub.pub_search.inc';
 
 /**
  * @defgroup tripal_pub Publication Module
@@ -53,20 +52,7 @@ function tripal_pub_menu() {
 
   // for backwards compatibility the same chado/publications is also found
   // at find/publications
-  $items['find/publications' ]= array(
-    'title' => 'Publication Search',
-    'description' => ('Search for publications'),
-    'page callback' => 'tripal_pub_search_page',
-    'access arguments' => array('access chado_pub content'),
-    'type' => MENU_CALLBACK
-  );
 
-  $items['find/publications/criteria/%/%'] = array(
-    'page callback' => 'tripal_pub_search_page_update_criteria',
-    'page arguments' => array(5, 6),
-    'access arguments' => array('access chado_pub content'),
-    'type ' => MENU_CALLBACK,
-  );
 
   $items['admin/tripal/legacy/tripal_pub']= array(
     'title' => 'Publications',

+ 1 - 1
legacy/tripal_stock/includes/tripal_stock.chado_node.inc

@@ -202,7 +202,7 @@ function chado_stock_form($node, $form_state) {
   $st_message = tripal_set_message("To add additional items to the stock type drop down list,
      add a term to the " .
     l($st_cv->name . " controlled vocabulary",
-      "admin/tripal/loaders/chado_cv/" . $st_cv->cv_id . "/cvterm/add",
+      "admin/tripal/loaders/chado_vocabs/chado_cv/" . $st_cv->cv_id . "/cvterm/add",
       array('attributes' => array('target' => '_blank'))
     ),
     TRIPAL_INFO, array('return_html' => TRUE)

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

@@ -218,7 +218,7 @@ function hook_tripal_cron_notification() {
  * @param $submitter_id
  *   A unique ID provided by the submitter for checking to make sure that the notification is not added more than once.
  */
-function tripal_add_notifcation($title, $details, $type, $actions, $submitter_id) {
+function tripal_add_notification($title, $details, $type, $actions, $submitter_id) {
   $transaction = db_transaction();
 
   // Check the notification isn't already in the admin notification table.
@@ -242,9 +242,6 @@ function tripal_add_notifcation($title, $details, $type, $actions, $submitter_id
       $transaction->rollback();
       watchdog('tripal_cron', 'Could not write notification to database.');
     }
-    if ($success) {
-      watchdog('tripal_cron', 'New field notification created.');
-    }
   }
 }
 /**
@@ -444,7 +441,7 @@ function tripal_tripal_cron_notification() {
         $type = 'Field';
         $submitter_id = $details['field_name'] . '-' . $bundle_name->name . '-' . $module;
 
-        tripal_add_notifcation($title, $detail_info, $type, $actions, $submitter_id);
+        tripal_add_notification($title, $detail_info, $type, $actions, $submitter_id);
         $num_created++;
       }
     }
@@ -471,13 +468,10 @@ function tripal_tripal_cron_notification() {
         $type = 'Field';
         $submitter_id = $details['field_name'] . '-' . $bundle_name->name . '-' . $module;
 
-        tripal_add_notifcation($title, $detail_info, $type, $actions, $submitter_id);
+        tripal_add_notification($title, $detail_info, $type, $actions, $submitter_id);
         $num_created++;
       }
     }
-    if ($num_created == 0) {
-      watchdog('tripal_cron', '<pre>No new fields for '. print_r($bundle_name->name, TRUE) .'</pre>');
-    }
   }
 }
 
@@ -1113,4 +1107,17 @@ function theme_token_list($tokens) {
   return theme('table', array('header' => $header, 'rows' => $rows));
 }
 
+/**
+ * Define the entity label callback.  This will return the title.
+ *
+ * @param $entity
+ *
+ * @return mixed
+ */
 
+function tripal_entity_label($entity) {
+  if (property_exists($entity, 'title')) {
+    return $entity->title;
+  }
+  return NULL;
+}

+ 68 - 24
tripal/api/tripal.importer.api.inc

@@ -91,44 +91,34 @@ function tripal_load_include_importer_class($class) {
 function tripal_run_importer($import_id, TripalJob $job = NULL) {
 
   $loader = NULL;
-  try {
-    // begin the transaction
-    $transaction = db_transaction();
-    print "\nNOTE: Loading of this file is performed using a database transaction. \n" .
-        "If the load fails or is terminated prematurely then the entire set of \n" .
-        "insertions/updates is rolled back and will not be found in the database\n\n";
+  $loader = TripalImporter::byID($import_id);
+  $loader->setJob($job);
+  $loader->prepareFiles();
 
-    $loader = TripalImporter::byID($import_id);
-    $loader->setJob($job);
-    $loader->prepareFiles();
-    $loader->run();
-    $loader->cleanFile();
+  print "\nRunning '".$loader::$name."' importer";
 
-    if ($job) {
-      $job->logMessage("Done");
-    }
+  print "\nNOTE: Loading of file is performed using a database transaction. \n" .
+        "If it fails or is terminated prematurely then all insertions and \n" .
+        "updates are rolled back and will not be found in the database\n\n";
 
-    // Remove the temp file
-    if (!empty($details->arguments['file_url'])) {
-      $loader->logMessage('Removing downloaded file...');
-      unlink($temp);
-    }
-
-    print "\nDone\n";
+  try {
+    // Run the loader
+    tripal_run_importer_run($loader, $job);
 
+    // Handle the post run.
+    tripal_run_importer_post_run($loader, $job);
 
     // Check for tables with new cvterms
+    print "Remapping Chado Controlled vocabularies to Tripal Terms...";
     tripal_chado_map_cvterms();
 
     // Check for new fields and notify the user.
     tripal_tripal_cron_notification();
 
-    // Clear the Drpual chace
+    // Clear the Drupal cache
     cache_clear_all();
-
   }
   catch (Exception $e) {
-    $transaction->rollback();
     if ($job) {
       $job->logMessage($e->getMessage(), array(), TRIPAL_ERROR);
     }
@@ -138,3 +128,57 @@ function tripal_run_importer($import_id, TripalJob $job = NULL) {
   }
 }
 
+/**
+ * First step of the tripal_run_importer.
+ *
+ * @param $loader
+ *   The TripalImporter object.
+ * @param $job
+ *   The TripalJob object.$this
+ * @throws Exception
+ */
+function tripal_run_importer_run($loader, $job) {
+
+  try {
+    // begin the transaction
+    $transaction = db_transaction();
+
+    $loader->run();
+
+    if ($job) {
+      $job->logMessage("\nDone.\n");
+    }
+
+    // Remove the temp file
+    if (!empty($details->arguments['file_url'])) {
+      $loader->logMessage('Removing downloaded file...');
+      unlink($temp);
+    }
+  }
+  catch (Exception $e) {
+    // Rollback and re-throw the error.
+    $transaction->rollback();
+    throw $e;
+  }
+}
+/**
+ * Second step of the tripal_run_importer.
+ *
+ * @param $loader
+ *   The TripalImporter object.
+ * @param $job
+ *   The TripalJob object.
+ * @throws Exception
+ */
+function tripal_run_importer_post_run($loader, $job) {
+  try {
+    // the transaction
+    $transaction = db_transaction();
+    $loader->postRun();
+  }
+  catch (Exception $e) {
+    // Rollback and re-throw the error.
+    $transaction->rollback();
+    throw $e;
+  }
+}

+ 30 - 4
tripal/api/tripal.terms.api.inc

@@ -105,6 +105,11 @@ function hook_vocab_import_form_submit($form, &$form_state) {
  *       -name:  The short name for the vocabulary (e.g. SO, PATO, etc).
  *       -description: The description of this vocabulary.
  *       -url: The URL for the vocabulary.
+ *       -urlprefix : (optional) A URL to which the short_name and term
+ *        accession can be appended to form a complete URL for a term.  If the
+ *        prefix does not support appending then the exact location for the
+ *        position of the short_name and the term accession will be
+ *        specified with the {db} and {accession} tags respectively.
  *     -accession : The name unique ID of the term.
  *     -url : The URL for the term.
  *     -name : The name of the term.
@@ -132,6 +137,11 @@ function hook_vocab_get_term($vocabulary, $accession) {
  *     - short_name : The short name abbreviation for the vocabulary.
  *     - description : A brief description of the vocabulary.
  *     - url : (optional) A URL for the online resources for the vocabulary.
+ *     - urlprefix : (optional) A URL to which the short_name and term
+ *       accession can be appended to form a complete URL for a term.  If the
+ *       prefix does not support appending then the exact location for the
+ *       position of the short_name and the term accession will be
+ *       specified with the {db} and {accession} tags respectively.
  */
 function hook_vocab_get_vocabulary($vocabulary) {
   // See the tripal_chado_vocab_get_vocabulary() function for an example.
@@ -145,6 +155,11 @@ function hook_vocab_get_vocabulary($vocabulary) {
  *       -name:  The short name for the vocabulary (e.g. SO, PATO, etc).
  *       -description: The description of this vocabulary.
  *       -url: The URL for the vocabulary.
+ *       -urlprefix: (optional) A URL to which the short_name and term
+ *         accession can be appended to form a complete URL for a term.  If the
+ *         prefix does not support appending then the exact location for the
+ *         position of the short_name and the term accession will be
+ *         specified with the {db} and {accession} tags respectively.
  *     -accession : The name unique ID of the term.
  *     -url : The URL for the term.
  *     -name : The name of the term.
@@ -210,6 +225,11 @@ function tripal_add_term($details) {
  *       - short_name : The short name abbreviation for the vocabulary.
  *       - description : A brief description of the vocabulary.
  *       - url : (optional) A URL for the online resources for the vocabulary.
+ *       - urlprefix : (optional) A URL to which the short_name and term
+ *         accession can be appended to form a complete URL for a term.  If the
+ *         prefix does not support appending then the exact location for the
+ *         position of the short_name and the term accession will be
+ *         specified with the {db} and {accession} tags respectively.
  *     - accession : The name unique ID of the term.
  *     - url : The URL for the term.
  *     - name : The name of the term.
@@ -250,10 +270,16 @@ function tripal_get_term_details($vocabulary, $accession) {
  *
  * @return
  *   An array with at least the following keys:
- *     - name : The full name of the vocabulary.
- *     - short_name : The short name abbreviation for the vocabulary.
- *     - description : A brief description of the vocabulary.
- *     - url : (optional) A URL for the online resources for the vocabulary.
+ *     - name: The full name of the vocabulary.
+ *     - short_name: The short name abbreviation for the vocabulary.
+ *     - description: A brief description of the vocabulary.
+ *     - url:  A URL for the online resources for the vocabulary.
+ *     - urlprefix: A URL to which the short_name and term
+ *       accession can be appended to form a complete URL for a term.  If the
+ *       prefix does not support appending then the exact location for the
+ *       position of the short_name and the term accession will be
+ *       specified with the {db} and {accession} tags respectively.
+ *     - sw_url: The URL for mapping terms via the semantic web.
  */
 function tripal_get_vocabulary_details($vocabulary) {
   // TODO: we need some sort of administrative interface that lets the user

+ 11 - 2
tripal/includes/TripalEntityUIController.inc

@@ -308,6 +308,14 @@ function tripal_view_entity($entity, $view_mode = 'full') {
    if ($etype) {
      $query->entityCondition('bundle', $etype);
    }
+   if ($status) {
+     if ($status == 'status-1') {
+       $query->propertyCondition('status', 1);
+     }
+     if ($status == 'status-0') {
+       $query->propertyCondition('status', 0);
+     }
+   }
    //$query->propertyOrderBy('created', 'DESC');
 
    // Find out the total number of records and determine what page we're on, and
@@ -324,8 +332,9 @@ function tripal_view_entity($entity, $view_mode = 'full') {
 
 
    // For each entity retrieved add a row to the data listing.
-   //while ($entity = $entities->fetchObject()) {
-   if (!isset($results['TripalEntity'])) $results['TripalEntity'] = array();
+   if (!isset($results['TripalEntity'])) {
+     $results['TripalEntity'] = array();
+   }
    foreach ($results['TripalEntity'] as $entity_id => $stub) {
      $vocabulary = '';
      $term_name = '';

+ 1 - 1
tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc

@@ -163,7 +163,7 @@ abstract class TripalFieldDownloader {
   }
 
   /**
-   * Formats the entity and the specified fields for utput.
+   * Formats the entity and the specified fields for output.
    *
    * This function should be implemented by a child class. It should iterate
    * over the fields for the entity and return the appropriate format. It may

+ 4 - 1
tripal/includes/TripalFieldDownloaders/TripalNucFASTADownloader.inc

@@ -73,7 +73,10 @@ class TripalNucFASTADownloader extends TripalFieldDownloader {
                 // Skip the identifier fields and the residues fields.
                 if (!in_array($fname, array('data__identifier',
                     'schema__name', 'data__protein_sequence', $field_name))) {
-                  $defline .= $instance['label'] . ': ' . $entity->{$fname}['und'][0]['value'] . '; ';
+                  $fvalue = $entity->{$fname}['und'][0]['value'];
+                  if ($fvalue) {
+                    $defline .= $instance['label'] . ': ' . $fvalue . '; ';
+                  }
                 }
               }
               else {

+ 4 - 1
tripal/includes/TripalFieldDownloaders/TripalProteinFASTADownloader.inc

@@ -72,7 +72,10 @@ class TripalProteinFASTADownloader extends TripalFieldDownloader {
                 // Skip the identifier fields and the residues fields.
                 if (!in_array($fname, array('data__identifier',
                   'schema__name', 'data__sequence', $field_name))) {
-                  $defline .= $instance['label'] . ': ' . $entity->{$fname}['und'][0]['value'] . '; ';
+                  $fvalue = $entity->{$fname}['und'][0]['value'];
+                  if ($fvalue) {
+                    $defline .= $instance['label'] . ': ' . $fvalue . '; ';
+                  }
                 }
               }
               else {

+ 4 - 2
tripal/includes/TripalFields/TripalField.inc

@@ -109,7 +109,9 @@ class TripalField {
    *   An array containing the instance data as returned by field_instance_info().
    */
   public function __construct($field, $instance) {
-    $vocabulary = $term = NULL;
+    $term = NULL;
+    $vocabulary = NULL;
+    $accession = NULL;
     $this->field = $field;
     $this->instance = $instance;
 
@@ -495,7 +497,7 @@ class TripalField {
   }
 
   /**
-   * Provides the list of elements returned by the 'value' of the field. \
+   * Provides the list of elements returned by the 'value' of the field.
    *
    * The elements provided by this function are used to integrate with
    * Drupal Views and Web services.  The return value is an associative array

+ 26 - 17
tripal/includes/TripalFields/TripalFieldFormatter.inc

@@ -53,6 +53,7 @@ class TripalFieldFormatter {
       'label' => $class::$default_label,
       'field types' => $class::$field_types,
       'settings' => $class::$default_settings,
+      'TripalFieldFormatter' => TRUE,
     );
   }
 
@@ -98,23 +99,31 @@ class TripalFieldFormatter {
    * This function corresponds to the hook_field_formatter_view()
    * function of the Drupal Field API.
    *
-   *  This function provides the display for a field when it is viewed on
-   *  the web page.  The content returned by the formatter should only include
-   *  what is present in the $items[$delta]['values] array. This way, the
-   *  contents that are displayed on the page, via webservices and downloaded
-   *  into a CSV file will always be identical.  The view need not show all
-   *  of the data in the 'values' array.
-   *
-   *  @param $element
-   *  @param $entity_type
-   *  @param $entity
-   *  @param $langcode
-   *  @param $items
-   *  @param $display
-   *
-   *  @return
-   *    An element array compatible with that returned by the
-   *    hook_field_formatter_view() function.
+   * This function provides the display for a field when it is viewed on
+   * as a full page, teaser, indexing for searching, etc.  The content
+   * returned by the formatter should only include what is present in the
+   * $items[$delta]['values] array. This way, the contents that are displayed
+   * on the page, via web services and downloaded into a CSV file will
+   * always be identical.  The view need not show all of the data in the
+   * 'values' array.
+   *
+   * @param $element
+   *   A renderable array for the $items, as an array of child elements keyed
+   *   by numeric indexes starting from 0.  When implemented as a child
+   *   class, this argument is set for the display.
+   * @param $entity_type
+   *   The type of $entity.
+   * @param $entity
+   *   The entity object.
+   * @param $langcode
+   *   The language associated with $items.
+   * @param $items
+   *   Array of values for this field.
+   * @param $display
+   *   The display settings to use, as found in the 'display' entry of instance
+   *   definitions. The array notably contains the following keys and values;
+   *     - type: The name of the formatter to use.
+   *     - settings: The array of formatter settings.
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
 

+ 34 - 17
tripal/includes/TripalImporter.inc

@@ -105,6 +105,15 @@ class TripalImporter {
    */
   public static $cardinality = 1;
 
+
+  /**
+   * Be default, all loaders are automaticlly added to the Admin >
+   * Tripal > Data Laders menu.  However, if this loader should be
+   * made available via a different menu path, then set it here.  If the
+   * value is empty then the path will be the default.
+   */
+  public static $menu_path = '';
+
   // --------------------------------------------------------------------------
   //                  PRIVATE MEMBERS -- DO NOT EDIT or OVERRIDE
   // --------------------------------------------------------------------------
@@ -415,15 +424,15 @@ class TripalImporter {
     }
 
     try {
-      for($i = 0; $i < count($this->arguments['file']); $i++) {
-        if (!empty($this->arguments['file'][$i]['file_remote'])) {
-          $file_remote = $this->arguments['file'][$i]['file_remote'];
+      for($i = 0; $i < count($this->arguments['files']); $i++) {
+        if (!empty($this->arguments['files'][$i]['file_remote'])) {
+          $file_remote = $this->arguments['files'][$i]['file_remote'];
           $this->logMessage('Download file: !file_remote...', array('!file_remote' => $file_remote));
 
           // If this file is compressed then keepthe .gz extension so we can
           // uncompress it.
           $ext = '';
-          if (preg_match('/^(.*?)\.gz$/', $this->arguments['file']['file_remote'])) {
+          if (preg_match('/^(.*?)\.gz$/', $file_remote)) {
             $ext = '.gz';
           }
           // Create a temporary file.
@@ -442,17 +451,17 @@ class TripalImporter {
             fwrite($tmp_fh, fread($url_fh, 255), 255);
           }
           // Set the path to the file for the importer to use.
-          $this->arguments['file']['file_path'] = $temp;
+          $this->arguments['files'][$i]['file_path'] = $temp;
           $this->is_prepared = TRUE;
         }
 
         // Is this file compressed?  If so, then uncompress it
         $matches = array();
-        if (preg_match('/^(.*?)\.gz$/', $this->arguments['file']['file_path'], $matches)) {
-          $this->logMessage("Uncompressing: !file", array('!file' => $this->arguments['file']['file_path']));
+        if (preg_match('/^(.*?)\.gz$/', $this->arguments['files'][$i]['file_path'], $matches)) {
+          $this->logMessage("Uncompressing: !file", array('!file' => $this->arguments['files'][$i]['file_path']));
           $buffer_size = 4096;
           $new_file_path = $matches[1];
-          $gzfile = gzopen($this->arguments['file']['file_path'], 'rb');
+          $gzfile = gzopen($this->arguments['files'][$i]['file_path'], 'rb');
           $out_file = fopen($new_file_path, 'wb');
           if (!$out_file) {
             throw new Exception("Cannot uncompress file: new temporary file, '$new_file_path', cannot be created.");
@@ -471,8 +480,8 @@ class TripalImporter {
 
           // Now remove the .gz file and reset the file_path to the new
           // uncompressed version.
-          unlink($this->arguments['file'][$i]['file_path']);
-          $this->arguments['file'][$i]['file_path'] = $new_file_path;
+          unlink($this->arguments['files'][$i]['file_path']);
+          $this->arguments['files'][$i]['file_path'] = $new_file_path;
         }
       }
     }
@@ -490,11 +499,13 @@ class TripalImporter {
   public function cleanFile() {
     try {
       // If a remote file was downloaded then remove it.
-      if (!empty($this->arguments['file']['file_remote']) and
-          file_exists($this->arguments['file']['file_path'])) {
-        $this->logMessage('Removing downloaded file...');
-        unlink($this->arguments['file']['file_path']);
-        $this->is_prepared = FALSE;
+      for($i = 0; $i < count($this->arguments['files']); $i++) {
+          if (!empty($this->arguments['files'][$i]['file_remote']) and
+              file_exists($this->arguments['files'][$i]['file_path'])) {
+            $this->logMessage('Removing downloaded file...');
+            unlink($this->arguments['files'][$i]['file_path']);
+            $this->is_prepared = FALSE;
+          }
       }
     }
     catch (Exception $e){
@@ -566,7 +577,8 @@ class TripalImporter {
   }
   /**
    * Adds to the count of the total number of items that have been handled.
-   * @param unknown $num_handled
+   *
+   * @param $num_handled
    */
   protected function addItemsHandled($num_handled) {
     $items_handled = $this->num_handled = $this->num_handled + $num_handled;
@@ -672,5 +684,10 @@ class TripalImporter {
   public function run() {
   }
 
+  /**
+   * Performs the import.
+   */
+  public function postRun() {
+  }
 
-}
+}

+ 7 - 17
tripal/includes/TripalTerm.inc

@@ -15,23 +15,13 @@ class TripalTerm extends Entity {
     $this->definition = NULL;
     $this->url = NULL;
 
-    // TODO: we need some sort of administrative interface that lets the user
-    // switch to the desired vocabulary type. For now, we'll just use the
-    // first one in the list.
-    $stores = module_invoke_all('vocab_storage_info');
-    if (is_array($stores) and count($stores) > 0) {
-      $keys = array_keys($stores);
-      $module = $stores[$keys[0]]['module'];
-      $function = $module . '_vocab_get_term';
-      if (function_exists($function)) {
-        $term_details = $function($vocab->vocabulary, $this->accession);
-        if ($term_details) {
-//          $this->details = $term_details;
-          if ($term_details and $term_details['definition']) {
-            $this->definition = $term_details['definition'];
-            $this->url = $term_details['url'];
-          }
-        }
+    $term_details = tripal_get_term_details($vocab->vocabulary, $this->accession);
+    if ($term_details) {
+      if ($term_details and $term_details['definition']) {
+        $this->definition = $term_details['definition'];
+      }
+      if ($term_details and $term_details['url']) {
+        $this->url = $term_details['url'];
       }
     }
   }

+ 25 - 2
tripal/includes/tripal.entity.inc

@@ -139,7 +139,7 @@ function tripal_entity_info() {
     'base table' => 'tripal_entity',
 
     // Returns the uri elements of an entity.
-    'uri callback' => 'tripal_vocbulary_term_uri',
+    'uri callback' => 'tripal_entity_uri',
 
     // IF fieldable == FALSE, we can't attach fields.
     'fieldable' => TRUE,
@@ -191,7 +191,20 @@ function tripal_entity_info() {
     )
   );
 
-  //
+  // Search integration
+  if (module_exists('search')) {
+    $entities['TripalEntity']['view modes'] += array(
+      'search_index' => array(
+        'label' => t('Search index'),
+        'custom settings' => FALSE,
+      ),
+      'search_result' => array(
+        'label' => t('Search result highlighting input'),
+        'custom settings' => FALSE,
+      ),
+    );
+  }
+
   // The TripalBundle entity is used manage the bundle types.  The 'bundle of'
   // attribute links this to the TripalEntity and allows the UI provided
   // by the entity module to work for each TripalEntity bundle.
@@ -224,6 +237,16 @@ function tripal_entity_info() {
   return $entities;
 }
 
+/**
+ * Implements the Entity URI callback function.
+ */
+function tripal_entity_uri($entity) {
+  return array(
+    'path' => 'bio-data/' . $entity->id,
+    'options' => array(),
+  );
+}
+
 /**
  * Implements hook_entities_info_alter().
  *

+ 27 - 25
tripal/includes/tripal.fields.inc

@@ -220,11 +220,6 @@ function tripal_field_formatter_view($entity_type, $entity, $field,
     return;
   }
 
-  // TODO: (Shawna) 1) Check the entity type (bundle) settings to see if empty fieleds
-  // should be ignored by default. 2)  Check the field itself to see if it
-  // overides the bundle default.  The idea being do not show fields when
-  // people don't want them shown.
-
   $element = array();
   $formatter_class = $display['type'];
   $is_loaded = tripal_load_include_field_class($formatter_class);
@@ -430,7 +425,7 @@ function tripal_field_instance_settings_form_validate($element, &$form_state, $f
   $field_class = $field['type'];
   if (tripal_load_include_field_class($field_class)) {
     $field = new $field_class($field, $instance);
-    return $field->instanceSettingsForm();
+    return $field->instanceSettingsFormValidate($form, $form_state);
   }
 }
 
@@ -475,7 +470,8 @@ function tripal_field_instance_settings_form_alter_process($element, &$form_stat
   $accession = '';
   $term_name = '';
   $term = NULL;
-  if (array_key_exists('settings', $instance) and array_key_exists('term_vocabulary', $instance['settings'])) {
+  if (array_key_exists('settings', $instance) and
+      array_key_exists('term_vocabulary', $instance['settings'])) {
     $vocabulary = $instance['settings']['term_vocabulary'];
     $accession = $instance['settings']['term_accession'];
     $term_name = $instance['settings']['term_name'];
@@ -536,25 +532,25 @@ function tripal_field_instance_settings_form_alter_process($element, &$form_stat
         be associated with a controlled vocabulary term. This field mapping is
         required and cannot be changed');
   }
-  $element['field_term'] = array(
-    '#type' => 'fieldset',
-    '#title' => 'Controlled Vocabulary Term',
-    '#description' => $description,
-    '#prefix' => '<div id = "tripal-field-term-fieldset">',
-    '#suffix' => '</div>',
-  );
-  $element['field_term']['term_vocabulary'] = array(
+  $element['term_vocabulary'] = array(
     '#type' => 'value',
     '#value' => $vocabulary,
   );
-  $element['field_term']['term_name'] = array(
+  $element['term_name'] = array(
     '#type' => 'value',
     '#value' => $term_name,
   );
-  $element['field_term']['term_accession'] = array(
+  $element['term_accession'] = array(
     '#type' => 'value',
     '#value' => $accession,
   );
+  $element['field_term'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Controlled Vocabulary Term',
+    '#description' => $description,
+    '#prefix' => '<div id = "tripal-field-term-fieldset">',
+    '#suffix' => '</div>',
+  );
   $element['field_term']['details'] = array(
     '#type' => 'item',
     '#title' => 'Current Term',
@@ -606,7 +602,7 @@ function tripal_field_instance_settings_form_alter_process($element, &$form_stat
     $terms = chado_expand_var($terms, 'field', 'cvterm.definition');
     $num_terms = 0;
     foreach ($terms as $term) {
-      // Save the user a click by setting the default value as 1 if there's
+      // Save the user a click, by setting the default value as 1 if there's
       // only one matching term.
       $default = FALSE;
       $attrs = array();
@@ -653,6 +649,9 @@ function tripal_field_instance_settings_form_alter_validate($form, &$form_state)
   if (array_key_exists('clicked_button', $form_state) and $form_state['clicked_button']['#executes_submit_callback'] == TRUE) {
     $has_default = FALSE;
     if ($form_state['values']['term_vocabulary']) {
+      $form_state['values']['instance']['settings']['term_vocabulary'] = $form_state['values']['term_vocabulary'];
+      $form_state['values']['instance']['settings']['term_accession'] = $form_state['values']['term_accession'];
+      $form_state['values']['instance']['settings']['term_name'] = $form_state['values']['term_name'];
       $has_default = TRUE;
     }
 
@@ -670,14 +669,17 @@ function tripal_field_instance_settings_form_alter_validate($form, &$form_state)
       if (preg_match("/^term-(\d+)$/", $key, $matches) and
           $form_state['input']['term-' . $matches[1]]) {
         $cvterm_id = $matches[1];
-        // TODO: this should not call a Chado function.
+        // TODO: this should not call a Chado function, but the autocomplete
+        // currently uses chado cvterm IDs.
         $term = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
-        $form_state['values']['instance']['settings']['term_vocabulary'] = $term->dbxref_id->db_id->name;
-        $form_state['values']['instance']['settings']['term_accession'] = $term->dbxref_id->accession;
-        $form_state['values']['instance']['settings']['term_name'] = $term->name;
-        $selected_term = TRUE;
-        $num_selected++;
-        $has_default =  TRUE;
+        if ($term) {
+          $form_state['values']['instance']['settings']['term_vocabulary'] = $term->dbxref_id->db_id->name;
+          $form_state['values']['instance']['settings']['term_accession'] = $term->dbxref_id->accession;
+          $form_state['values']['instance']['settings']['term_name'] = $term->name;
+          $selected_term = TRUE;
+          $num_selected++;
+          $has_default =  TRUE;
+        }
       }
     }
 

+ 55 - 4
tripal/includes/tripal.jobs.inc

@@ -239,6 +239,14 @@ function tripal_jobs_report() {
  */
 function tripal_jobs_view($job_id) {
 
+  // set the breadcrumb
+  $breadcrumb = array();
+  $breadcrumb[] = l('Home', '<front>');
+  $breadcrumb[] = l('Administration', 'admin');
+  $breadcrumb[] = l('Tripal', 'admin/tripal');
+  $breadcrumb[] = l('Jobs', 'admin/tripal/tripal_jobs');
+  drupal_set_breadcrumb($breadcrumb);
+
   // get the job record
   $sql =
     "SELECT TJ.job_id,TJ.uid,TJ.job_name,TJ.modulename,TJ.progress,
@@ -313,7 +321,6 @@ function tripal_jobs_view($job_id) {
   $rows[] = array('Submit Date', $job->submit_date);
   $rows[] = array('Start time', $job->start_time);
   $rows[] = array('End time', $job->end_time);
-  $rows[] = array('Error Message', $job->error_msg);
   $rows[] = array('Priority', $job->priority);
   $rows[] = array('Submitting User', $job->username);
 
@@ -327,9 +334,53 @@ function tripal_jobs_view($job_id) {
     'empty' => '',
   );
 
-  $output = '<p>' . substr($links, 0, -2) . '</p>'; // remove trailing |
-  $output .= theme_table($table);
-  return $output;
+  $content['links'] = array(
+    '#type' => 'markup',
+    '#markup' => '<p>' . substr($links, 0, -2) . '</p>',
+  );
+  $content['job_title'] = array(
+    '#type' => 'item',
+    '#title' => t('Job Title'),
+    '#markup' => $job->job_name,
+  );
+  $content['job_status'] = array(
+    '#type' => 'item',
+    '#title' => t('Status'),
+    '#markup' => $job->job_status,
+  );
+  $content['details_fset'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Job Details'),
+    '#collapsed' => TRUE,
+    '#collapsible' => TRUE,
+    '#attributes' => array(
+      'class' => array('collapsible', 'collapsed'),
+    ),
+    '#attached' => array(
+      'js' => array('misc/collapse.js', 'misc/form.js')
+    ),
+  );
+  $content['details_fset']['job_details'] = array(
+    '#type' => 'markup',
+    '#markup' => theme_table($table),
+  );
+  $content['log_fset'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Job Logs'),
+    '#collapsed' => TRUE,
+    '#collapsible' => TRUE,
+    '#attributes' => array(
+      'class' => array('collapsible', 'collapsed'),
+    ),
+    '#attached' => array(
+      'js' => array('misc/collapse.js', 'misc/form.js')
+    ),
+  );
+  $content['log_fset']['job_logs'] = array(
+    '#type' => 'markup',
+    '#markup' => '<pre>' . $job->error_msg . '</pre>',
+  );
+  return $content;
 }
 
 /**

+ 2 - 2
tripal/theme/css/tripal.css

@@ -8,7 +8,7 @@
   * site administrator
   */
 
-div.messages.tripal-site-admin-only{
+div.messages.tripal-site-admin-only {
   background-image: url("../images/TripalLogo-sm.png");
   background-repeat: no-repeat;
   background-color: #cce3ff;
@@ -80,7 +80,7 @@ div.messages.tripal-site-admin-only{
 
 
  /******************************************************************************
-  * Format for tripal dashbaord chart
+  * Format for tripal dashboard chart
   *****************************************************************************/
  #tripal-entity-type-chart .bar text.value {
    fill: white;

+ 3 - 2
tripal/theme/js/TripalUploader.js

@@ -127,8 +127,9 @@
       var url = options['url'];
       var self = this;
       
-      // Make sure the file type is allowed
-      if (this.tables[tname]['allowed_types']) {
+      // Make sure the file type is allowed.  If there are no file types
+      // then anything is allowed.
+      if (this.tables[tname]['allowed_types'] && this.tables[tname]['allowed_types'].length > 0) {
         var allowed_types = this.tables[tname]['allowed_types'];
         var matches = file.name.match(/^.*\.(.+)$/);
         if (!matches) {

+ 175 - 30
tripal/tripal.drush.inc

@@ -19,6 +19,35 @@
  *
  * @ingroup tripal_drush
  */
+function tripal_drush_help($command) {
+  switch ($command) {
+
+    // TRIPAL JOBS
+    case 'trp-run-jobs':
+      return dt('Launches pending jobs waiting in the queue.');
+      break;
+    case 'trp-rerun-job':
+      return dt('Rerun a job in the queue.');
+      break;
+    case 'trp-get-currjob':
+      return dt('Returns details about the currently running tripal job including percent complete.');
+      break;
+    // Placeholders for unimplmeneted jobs
+    case 'trp-show-job':
+      break;
+    case 'trp-revert-jobs':
+      break;
+    case 'trp-cancel-job':
+      break;
+    case 'trp-list-jobs':
+      break;
+    case 'trp-prepare-chado':
+      break;
+    case 'trp-set-permissions':
+      break;
+  }
+}
+
 /**
  * Registers a drush command and constructs the full help for that command.
  *
@@ -38,7 +67,7 @@ function tripal_drush_command() {
     ),
   );
   $items['trp-run-jobs'] = array(
-    'description' => dt('Launches jobs waiting in the queue. Only one job can execute at a time unless the --parllel=1 option is provided.'),
+    'description' => dt('Launches jobs waiting in the queue. Only one job can execute at a time unless the --parallel=1 option is provided.'),
     'examples' => array(
       'Single Job' => 'drush trp-run-jobs --username=administrator',
       'Parallel Job' => 'drush trp-run-jobs --username=administrator --parallel=1',
@@ -82,7 +111,20 @@ function tripal_drush_command() {
       'single' => dt('Execute only one queued job'),
     ),
   );
-
+  $items['trp-prepare-chado'] = array(
+    'description' => dt('Prepares a new Tripal installation with content types, requires the username of an administrator to run.'),
+    'arguments'   => array(),
+    'examples' => array(
+      'Standard example' => 'drush trp-prepare-chado --username=administrator',
+    ),
+  );
+  $items['trp-set-permissions'] = array(
+    'description' => dt('Gives view, edit, delete, create priveleges to administrators for all tripal content types.'),
+    'arguments'   => array(),
+    'examples' => array(
+      'Standard example' => 'drush trp-set-permissions --username=administrator',
+    ),
+  );
   return $items;
 }
 
@@ -119,29 +161,12 @@ function drush_tripal_set_user($username) {
  *
  * @ingroup tripal_drush
  */
-function drush_tripal_trp_run_jobs() {
+function drush_tripal_trp_run_jobs_install($username) {
   $parallel = drush_get_option('parallel');
   $job_id   = drush_get_option('job_id');
   $max_jobs = drush_get_option('max_jobs', -1);
   $single   = drush_get_option('single', 0);
 
-  // Unfortunately later versions of Drush use the '--user' argument which
-  // makes it incompatible with how Tripal was using it.  For backwards
-  // compatabiliy we will accept --user with a non numeric value only. The
-  // numeric value should be for Drush. Tripal will instead use the
-  // --username argument for the fture.
-  $user = drush_get_option('user');
-  $uname = drush_get_option('username');
-  if ($user and is_numeric($user)) {
-  }
-  elseif ($user) {
-    print "\nNOTE: Use of the --user argument is deprecated as it conflicts with the --user argument of Drush 7.x. Please now use --username instead.\n\n";
-    $username = $user;
-  }
-  if ($uname) {
-    $username = $uname;
-  }
-
   drush_tripal_set_user($username);
 
   drush_print("\n" . date('Y-m-d H:i:s'));
@@ -160,7 +185,6 @@ function drush_tripal_trp_run_jobs() {
   }
 }
 
-
 /**
  * Executes jobs in the Tripal Jobs Queue.
  *
@@ -168,12 +192,29 @@ function drush_tripal_trp_run_jobs() {
  *
  * @ingroup tripal_drush
  */
-function drush_tripal_trp_run_jobs_install($username) {
+function drush_tripal_trp_run_jobs() {
   $parallel = drush_get_option('parallel');
   $job_id   = drush_get_option('job_id');
   $max_jobs = drush_get_option('max_jobs', -1);
   $single   = drush_get_option('single', 0);
 
+  // Unfortunately later versions of Drush use the '--user' argument which
+  // makes it incompatible with how Tripal was using it.  For backwards
+  // compatibility we will accept --user with a non numeric value only. The
+  // numeric value should be for Drush. Tripal will instead use the
+  // --username argument for the fture.
+  $user = drush_get_option('user');
+  $uname = drush_get_option('username');
+  if ($user and is_numeric($user)) {
+  }
+  elseif ($user) {
+    print "\nNOTE: Use of the --user argument is deprecated as it conflicts with the --user argument of Drush 7.x. Please now use --username instead.\n\n";
+    $username = $user;
+  }
+  if ($uname) {
+    $username = $uname;
+  }
+
   drush_tripal_set_user($username);
 
   drush_print("\n" . date('Y-m-d H:i:s'));
@@ -192,7 +233,6 @@ function drush_tripal_trp_run_jobs_install($username) {
   }
 }
 
-
 /**
  * Executes jobs in the Tripal Jobs Queue.
  *
@@ -261,13 +301,13 @@ function drush_tripal_trp_get_currjob() {
   foreach ($jobs as $job) {
     $job_pid = $job->pid;
     $output = "Name: " . $job->job_name . "\n" .
-      "Submitted: " . date(DATE_RFC822, $job->submit_date) . "\n" .
-      "Started: " . date(DATE_RFC822, $job->start_time) . "\n" .
-      "Module: " . $job->modulename . "\n" .
-      "Callback: " . $job->callback . "\n" .
-      "Process ID: " . $job->pid . "\n" .
-      "Progress: " . $job->progress . "%\n".
-      "Current Date: " . date('Y-m-d H:i:s') . "\n";
+        "Submitted: " . date(DATE_RFC822, $job->submit_date) . "\n" .
+        "Started: " . date(DATE_RFC822, $job->start_time) . "\n" .
+        "Module: " . $job->modulename . "\n" .
+        "Callback: " . $job->callback . "\n" .
+        "Process ID: " . $job->pid . "\n" .
+        "Progress: " . $job->progress . "%\n".
+        "Current Date: " . date('Y-m-d H:i:s') . "\n";
     drush_print(date('Y-m-d H:i:s'));
     drush_print($output);
   }
@@ -278,3 +318,108 @@ function drush_tripal_trp_get_currjob() {
   //log to the command line with an OK status
   drush_log('Running tripal-current-job', 'ok');
 }
+
+/**
+ * Prepares content types on the site after chado installation.
+ *
+ * Executed when 'drush trp-prepare-chado' is called.
+ *
+ * @ingroup tripal_drush
+ */
+function drush_tripal_trp_prepare_chado() {
+  $user = drush_get_option('user');
+  $uname = drush_get_option('username');
+  if ($user and is_numeric($user)) {
+  }
+  elseif ($user) {
+    print "\nNOTE: Use of the --user argument is deprecated as it conflicts with the --user argument of Drush 7.x. Please now use --username instead.\n\n";
+    $username = $user;
+  }
+  if ($uname) {
+    $username = $uname;
+  }
+
+  drush_tripal_set_user($username);
+
+  print_r("Now preparing the site by creating content types.\n");
+  $prepare = drush_invoke_process('@self', 'php-eval', array("module_load_include('inc', 'tripal_chado', 'includes/setup/tripal_chado.setup'); tripal_chado_prepare_drush_submit();"), array());
+  drush_invoke_process('@self', 'php-eval', array("module_load_include('inc', 'tripal', 'tripal.drush'); drush_tripal_trp_run_jobs_install(" . $username . ");"), array());
+  if (!$prepare) {
+    echo "An error occurred when attempting to install Chado. Please navigate to your new site and finish the installation process from the 'Install Tripal' section as described in the online help, found here http://tripal.info/tutorials/v3.x/installation/tripal \n";
+    exit;
+  }
+}
+
+/**
+ * Sets permissions for the content types on the site.
+ *
+ * Executed when 'drush trp-set-permissions' is called.
+ *
+ * @ingroup tripal_drush
+ */
+function drush_tripal_trp_set_permissions() {
+  $user = drush_get_option('user');
+  $uname = drush_get_option('username');
+  if ($user and is_numeric($user)) {
+  }
+  elseif ($user) {
+    print "\nNOTE: Use of the --user argument is deprecated as it conflicts with the --user argument of Drush 7.x. Please now use --username instead.\n\n";
+    $username = $user;
+  }
+  if ($uname) {
+    $username = $uname;
+  }
+
+  drush_tripal_set_user($username);
+
+  $database_info = FALSE;
+  while (!$database_info) {
+    drush_print(dt(""));
+    drush_print(dt(
+      "To add permissions from the command line the database information is required, please have the database host (127.0.0.1 or localhost), database name, postgres username and postgres user password ready.\n"
+    ));
+    print_r("");
+    $host = drush_prompt(dt('host, like localhost or 127.0.0.1'));
+    $database = drush_prompt(dt('database name'));
+    $postgres_username = drush_prompt(dt('postgres username'));
+    $postgres_password = drush_prompt(dt('postgres password'));
+    drush_print(dt(""));
+    drush_print(dt(
+    "This is the information provided, please review and confirm it is correct:
+     Database host: $host
+     Database name: $database
+     Database username: $postgres_username
+     Database user password: $postgres_password
+     "
+    ));
+    $database_info = drush_confirm(dt('Is this information correct?'));
+  }
+  print_r("Adding permissions for the administrator to view, edit, create, and delete all the newly created content types.\n");
+  $permissions = array();
+  $bundles = array();
+  $conn = pg_pconnect("host=$host dbname=$database user=$postgres_username password=$postgres_password");
+  if (!$conn) {
+    echo "An error occurred when attempting to perimssion the administrator. Please navigate to your new site and add permissions to your new content types here admin/people/permissions.\n";
+    exit;
+  }
+  $result = pg_query($conn, "SELECT name FROM tripal_bundle");
+  if (!$result) {
+    echo "An error occurred.\n";
+    exit;
+  }
+
+  while ($row = pg_fetch_row($result)) {
+    array_push($bundles, $row);
+  }
+  foreach ($bundles as $bundles => $bundle) {
+    array_push($permissions, ' view ' . $bundle[0], ' create ' . $bundle[0],
+      ' edit ' . $bundle[0], ' delete ' . $bundle[0]);
+  }
+  $string_permissions = implode(",", $permissions);
+  $args4 = array('administrator', $string_permissions);
+  $options4 = array();
+  drush_invoke_process('@self', 'role-add-perm', $args4, $options4);
+
+  drush_print(dt(""));
+  drush_print(dt("Permissions is now complete."));
+}

+ 23 - 1
tripal/tripal.install

@@ -19,6 +19,13 @@ function tripal_install() {
       'not null' => FALSE,
     ));
   }
+
+  $menu = array(
+    'menu_name' => 'data_search',
+    'title' => t('Data Search'),
+    'description' => 'The Data Search menu contains links to search tools for finding biological data.',
+  );
+  menu_save($menu);
 }
 
 /**
@@ -1033,11 +1040,26 @@ function tripal_update_7306() {
   }
 }
 
+/**
+ * Adds a new Data search menu for the Tripal created search tools.  If you
+ * have customized any of the Tripal content search forms then those forms will
+ * remain unchanged. All other default search pages will be moved to the
+ * new Data Search menu item available at Admin > Stucture > Menu > Data Search.
+ */
+function tripal_update_7307() {
+  $menu = array(
+    'menu_name' => 'data_search',
+    'title' => t('Data Search'),
+    'description' => 'The Data Search menu contains links to search tools for finding biological data.',
+  );
+  menu_save($menu);
+}
+
 /**
  * Remove the bundle_name, ids, fields from the tripal collections table.
  * And add the new tripal_tripal_collection_bundle_schema
  */
-function tripal_update_7307() {
+function tripal_update_7308() {
   $transaction = db_transaction();
   try {
     if (db_field_exists('tripal_collection', 'bundle_name')) {

+ 30 - 7
tripal/tripal.module

@@ -318,17 +318,29 @@ function tripal_menu() {
   );
 
 
+  $items['admin/tripal/loaders'] = array(
+    'title' => 'Data Loaders',
+    'description' => t('Tools facilitating data import.'),
+    'access arguments' => array('load tripal data'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 6
+  );
+
   // Add in the loaders
   $importers = tripal_get_importers();
   foreach ($importers as $class_name) {
     tripal_load_include_importer_class($class_name);
     if (class_exists($class_name)) {
-      $items['admin/tripal/loaders/' . $class_name::$machine_name] = array(
+      $menu_path = 'admin/tripal/loaders/' . $class_name::$machine_name;
+      if ($class_name::$menu_path) {
+        $menu_path = $class_name::$menu_path;
+      }
+      $items[$menu_path] = array(
         'title' => $class_name::$name,
         'description' =>  $class_name::$description,
         'page callback' => 'drupal_get_form',
         'page arguments' => array('tripal_get_importer_form', $class_name),
-        'access arguments' => array('administer tripal'),
+        'access arguments' => array('load tripal data'),
         'type' => MENU_NORMAL_ITEM,
         'file' => 'includes/tripal.importer.inc',
         'file path' => drupal_get_path('module', 'tripal'),
@@ -413,7 +425,6 @@ function tripal_accesss_user_collections($uid) {
 function tripal_access_user_data($uid) {
   global $user;
 
-  drupal_debug($uid);
   if ($uid == $user->uid) {
     return TRUE;
   }
@@ -440,6 +451,15 @@ function tripal_permission() {
       'description' => t('Allows the user to create, update and delete Tripal content types.'),
       'restrict access' => TRUE,
     ),
+    'publish tripal content' => array(
+      'title' => t('Publish Tripal content types'),
+      'description' => t('Allows the user to publish Tripal content for online access.'),
+      'restrict access' => TRUE,
+    ),
+    'load tripal data' => array(
+      'title' => t('Load data into the Tripal site'),
+      'description' => t('Allows the user to load data into the Tripal site using data loaders. Some data loaders may have their own specific permission as well.'),
+    ),
     'upload files' => array(
       'title' => t('Upload Files'),
       'description' => t('Allows the user to upload files using Tripal\'s HTML5 loader.'),
@@ -1017,16 +1037,20 @@ function tripal_block_configure($delta = '') {
  * Implements hook_cron().
  */
 function tripal_cron() {
+
+  // Add jobs to the Tripal queue for commong tasks.
+  $args = array();
+
   if (variable_get('tripal_admin_notification_creation_during_cron', TRUE)) {
     $modules = module_implements('tripal_cron_notification');
     foreach ($modules as $module) {
       $function = $module . '_tripal_cron_notification';
-      $function();
+      tripal_add_job("Cron: Checking for '$module' notifications.", 'tripal', $function, $args, 1);
     }
-    watchdog('tripal_cron', 'tripal_cron ran');
   }
 
-  tripal_expire_collections();
+  // Check for expired collections.
+  tripal_add_job('Cron: Checking expired collections', 'tripal', 'tripal_expire_collections', $args, 1);
 }
 
 /**
@@ -1302,4 +1326,3 @@ function tripal_field_group_table_rows_alter(&$element, &$children) {
     }
   }
 }
-

+ 3 - 0
tripal/tripal.views_default.inc

@@ -228,6 +228,9 @@ function tripal_bundle_default_views(&$views) {
     $handler->display->display_options['menu']['title'] = $bundle->label . ' Search';
     $handler->display->display_options['menu']['description'] = 'A search form for finding ' . $bundle->label . ' records';
     $handler->display->display_options['menu']['weight'] = '-10';
+    $handler->display->display_options['menu']['name'] = 'data_search';
+    $handler->display->display_options['menu']['context'] = 0;
+    $handler->display->display_options['menu']['context_only_inline'] = 0;
 
     $views[$view_name] = $view;
   }

+ 63 - 19
tripal/views_handlers/tripal_views_handler_area_collections.inc

@@ -69,7 +69,7 @@ function tripal_views_handler_area_collections_form($form, $form_state, $view, $
   $form['save_collection']['summary'] = array(
    '#type' => 'item',
    '#title' => 'Results Summary',
-   '#markup' => t('There are @total_rows record(s) that can be added to a container.', array('@total_rows' => $view->total_rows)),
+   '#markup' => t('There are @total_rows record(s) that can be added to a data collection.', array('@total_rows' => $view->total_rows)),
   );
   $form['save_collection']['collection_name'] = array(
    '#type' => 'textfield',
@@ -85,23 +85,77 @@ function tripal_views_handler_area_collections_form($form, $form_state, $view, $
    '#default_value' => $collection_name,
   );
 
+  // Get the list of fields used in the view.
+  $current_display = $view->current_display;
+  if (array_key_exists('fields', $view->display[$current_display]->display_options)) {
+    $view_fields = $view->display[$current_display]->display_options['fields'];
+  }
+  else {
+    $view_fields = $view->display['default']->display_options['fields'];
+  }
   // Get the list of fields in this view.
+  $field_ids = array();
+  $defaults = array();
   $fields = field_info_instances('TripalEntity', $bundle->name);
   foreach ($fields as $field_name => $instance) {
+    $field = field_info_field($field_name);
+    $field_type = $field['type'];
     if ($instance['field_name'] == 'entity_id') {
       continue;
     }
-    $field_ids[$instance['field_id']] = $instance['label'];
+    // Skip hidden fields.
+    if ($instance['display']['default']['type'] == 'hidden') {
+      continue;
+    }
+    $field_label = $instance['label'];
+
+    // Add in in any non CSV or Tab formatters to the label.
+    $formatters = array();
+    if (tripal_load_include_field_class($field_type)) {
+      $instance_settings = $field_type::$default_instance_settings;
+      if (array_key_exists('download_formatters', $instance_settings)) {
+        foreach ($instance_settings['download_formatters'] as $class_name) {
+          if ($class_name != 'TripalTabDownloader' and $class_name != 'TripalCSVDownloader') {
+            tripal_load_include_downloader_class($class_name);
+              $formatters[] = $class_name::$label;
+          }
+        }
+      }
+    }
+    if (count($formatters) > 0) {
+      $field_label .= ' (' . implode(',' , $formatters) . ')';
+    }
+
+    // Add the field to those supported.
+    $field_ids[$instance['field_id']] =  $field_label;
+
+    // Automatically check fields that are in the view and not excluded.
+    if (array_key_exists($field_name, $view_fields)) {
+      if (array_key_exists('exclude', $view_fields[$field_name]) and
+          $view_fields[$field_name]['exclude'] == TRUE) {
+        continue;
+      }
+      $defaults[] = $instance['field_id'];
+    }
   }
-  $form['save_collection']['field_ids'] = array(
+  $form['save_collection']['fields'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Advanced field selection'),
+    '#description' => t('Please select the fields to include in this data
+      collection. Not all of these fields appear in the search results
+      above but they are available for this content type. By default,
+      tab-delimeted and comma-separated files are genearted for the
+      collection using only the fields selected. However, some fields when
+      selected will generate other downloadable file formats.  Fields that
+      generate other file formats are indicated. '),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $form['save_collection']['fields']['field_ids'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Field Selection'),
-    '#description' => t('Please select the fields to include in this data
-      collection. Not all of these fields will appear in the search results
-      above but they are available for this content type. Also, not all will
-      be compatible with every download file format. If you do not select any
-      fields then all fields will be included.'),
     '#options' => $field_ids,
+    '#default_value' => $defaults,
   );
 
   $form['save_collection']['button'] = array(
@@ -136,7 +190,7 @@ function tripal_views_handler_area_collections_form_submit($form, $form_state) {
   $bundle = $form_state['values']['bundle'];
   $view = $form_state['values']['view'];
   $query = $form_state['values']['query'];
-  $collection_name = $form_state['values']['collection_name'];
+  $collection_name = trim($form_state['values']['collection_name']);
   $description = $form_state['values']['collection_desc'];
   $field_ids = array_key_exists('field_ids', $form_state['values']) ? $form_state['values']['field_ids'] : array();
   $uid = $user->uid;
@@ -149,16 +203,6 @@ function tripal_views_handler_area_collections_form_submit($form, $form_state) {
     }
   }
 
-  if (count($selected_fids) == 0) {
-    $fields = field_info_instances('TripalEntity', $bundle->name);
-    foreach ($fields as $field_name => $instance) {
-      if ($instance['field_name'] == 'entity_id') {
-        continue;
-      }
-      $selected_fids[] = $instance['field_id'];
-    }
-  }
-
   // Get the entity Ids that match results
   $query->range['length'] = $view->total_rows;
   $results = $query->execute();

+ 0 - 31
tripal_chado/api/modules/tripal_chado.analysis.api.inc

@@ -14,36 +14,6 @@
  * @}
  */
 
-/**
- * Register tripal_analysis_api sub-modules
- *
- * @param $modulename
- *  The name of the module to be registered as a tripal analysis submodule
- *
- * @ingroup tripal_analysis_api
- */
-function tripal_register_analysis_child($modulename) {
-  $sql = "SELECT * FROM {tripal_analysis} WHERE modulename = :modname";
-  if (!db_query($sql, array(':modname' => $modulename))->fetchField()) {
-    $sql = "INSERT INTO {tripal_analysis} (modulename) VALUES (:modname)";
-    db_query($sql, array(':modname' => $modulename));
-  }
-}
-
-/**
- * Un-register a tripal analysis sub-module
- *
- * @param $modulename
- *  The name of the module to un-register
- *
- * @ingroup tripal_analysis_api
- */
-function tripal_unregister_analysis_child($modulename) {
-  if (db_table_exists('tripal_analysis')) {
-      $sql = "DELETE FROM {tripal_analysis} WHERE modulename = :modname";
-      db_query($sql, array(':modname' => $modulename));
-  }
-}
 
 /**
  * Retrieves an chado analysis variable
@@ -165,7 +135,6 @@ function tripal_get_analysis($identifier, $options) {
  *   An array of analyses sync'd with Drupal where each value is the analysis scientific
  *   name and the keys are analysis_id's
  *
- * @ingroup tripal_analysis_api
  */
 function tripal_get_analysis_select_options($syncd_only = TRUE) {
   $analysis_list = array();

+ 277 - 332
tripal_chado/api/modules/tripal_chado.cv.api.inc

@@ -418,8 +418,8 @@ function tripal_update_cvtermpath($cv_id, $job_id = NULL){
  * @param $cvid
  *   The controlled vocabulary ID from the cv table of Chado (i.e. cv.cv_id).
  */
-function tripal_update_cvtermpath_root_loop($rootid, $cvid, &$roots){
-
+function tripal_update_cvtermpath_root_loop($rootid, $cvid, &$roots) {
+  
   // Get's the cvterm record for this "root".
   $ttype = db_select('cvterm', 'cv')
           ->fields('cv', array('cvterm_id'));
@@ -437,23 +437,26 @@ function tripal_update_cvtermpath_root_loop($rootid, $cvid, &$roots){
     }
   }
   // Then add that new entry to the $tree_path.
-  $roots[] =  $term_id;
+  $roots[] = $term_id;
 
   // Descends through the branch starting at this "root" term.
-  $visited = array();
+  $tree_path = array();
+  $matched_rows = array();
+  $possible_start_of_loop = array();
   $depth = 0;
-  tripal_update_cvtermpath_loop($rootid, $rootid, $cvid, $result->cvterm_id, $depth, 0, $visited);
+  tripal_update_cvtermpath_loop($rootid, $rootid, $cvid, $result->cvterm_id, $depth,
+                                0, $tree_path, FALSE, $matched_rows, $possible_start_of_loop, FALSE);
 
   // Get's the children terms of this "root" term and then recursively calls
   // this function making each child root.
   $cterm = db_query(
     'SELECT *
-     FROM cvterm_relationship
-     WHERE object_id = :rootid
+      FROM cvterm_relationship
+      WHERE object_id = :rootid
     ',
     array(':rootid' => $rootid)
   );
-  while($cterm_result = $cterm->fetchAssoc()) {
+  while ($cterm_result = $cterm->fetchAssoc()) {
     tripal_update_cvtermpath_root_loop($cterm_result['subject_id'], $cvid, $roots);
   }
 }
@@ -479,370 +482,313 @@ function tripal_update_cvtermpath_root_loop($rootid, $cvid, &$roots){
  *     -build_id: an string identifier for the child that combines the origin,
  *      child cvterm_id,cv_id, and the type_id.
  *     -depth: the depth that a child was inserted into the cvtermpath table.
+ * @param $possible_loop
+ *    A boolean flag.
+ * @param $matched_row
+ *    An array of rows that are currently in the cvtermpath table that match the 
+ *    build_id of the current term trying to be written to the table
+ * @param $possible_start_of_ loop
+ *    The array of the possible loop item between the current child and the origin. 
+ *    Each element in the array is an associative array with the keys:
+ *     - cvid : $cv_id
+ *     - subject_id: 
+ *     - child_id : $child_id,
+ *     - type_id : $type_id,
+ *     - depth : $depth,
+ * @param $no_loop_skip_test
+ *     A boolean used when the possible loop has been ruled out as a loop.
  * @return multitype:
  */
-$loop_data;
+function tripal_update_cvtermpath_loop(
+  $origin,
+  $child_id,
+  $cv_id,
+  $type_id,
+  $depth,
+  $increment_of_depth,
+  $tree_path,
+  $possible_loop,
+  $matched_rows,
+  $possible_start_of_loop,
+  $no_loop_skip_test) {
 
-function tripal_update_cvtermpath_loop($origin, $child_id, $cv_id, $type_id, $depth,
-                                       $increment_of_depth, $tree_path){
-  // An array of
-  global $loop_data;
-  // Check to see if a row with these values already exists.
-  chado_set_active('chado');
-  $count =  db_query(
-    'SELECT *
-     FROM cvtermpath
-     WHERE cv_id = :cvid
-      AND object_id = :origin
-      AND subject_id = :child_id
-      AND pathdistance = :depth
-    ',
-    array(':cvid' => $cv_id, ':origin' => $origin, ':child_id' => $child_id, ':depth' => $depth)
-  );
-  $count_total = $count->rowCount();
-  // If we've already seen this term then just return, we don't want
-  // to insert it again.
-  if ($count_total > 0) {
-    return $loop_data;
-  }
-  // Build the ID.
-  $term_id = $origin . '|' . $child_id . '|' . $cv_id . '|' . $type_id;
-  // Now check if the most recent entry already exists in the array.
-  if ($increment_of_depth != 0 && empty($loop_data)) {
-    // Search the $tree_path for the new $child_id in the build_id column.
-    foreach ($tree_path as $parent) {
-      // If this child is the same as a parent term that has already been
-      // processed then we have a potential loop.
-      if ($parent['build_id'] == $term_id) {
-        // The loop checker function below.
-        $result_of_loop_checker = tripal_update_cvtermpath_loop_checker($origin,
-          $child_id, $cv_id, $type_id, $depth, $increment_of_depth, 0,
-          $parent, array(), $depth);
-        if (!empty($result_of_loop_checker)) {
-          $loop_data = $result_of_loop_checker;
-          //Find the depth of the loop start by finding it in the array_of_children
-          foreach($tree_path as $children => $child){
-            if($child['build_id'] == $loop_data['build_id']){
-              $loop_location = $child['depth'];
-            }
-          }
-          $array_loop_data = (array)$loop_data;
-          $array_loop_data['depth'] = $loop_location;
-          $loop_data = $array_loop_data;
-          break;
-        }
-      }
-      if (!empty($loop_data)) {
-        return $loop_data;
-      }
-    }
-  }
   // We have not detected a loop, so it's safe to insert the term.
-  $query = db_insert('cvtermpath')
-    ->fields([
-      'object_id' => $origin,
-      'subject_id' => $child_id,
-      'cv_id' => $cv_id,
-      'type_id' => $type_id,
-      'pathdistance' => $depth,
-    ]);
-  $rows = $query->execute();
-  // Then add that new entry to the $tree_path.
-  $tree_path[$increment_of_depth] = [
-    'build_id' => $term_id,
-    'depth' => $depth
-  ];
-  // Get all of the relationships of this child term, and recursively
-  // call the tripal_update_cvtermpath_loop() function to continue
-  // descending down the tree.
-  $query = db_select('cvterm_relationship', 'cvtr')
-    ->fields('cvtr')
-    ->condition('cvtr.object_id', $child_id, '=')
-    ->execute();
-  $cterm_relationships = $query->fetchAll();
-  foreach ($cterm_relationships as $item) {
-    if (!empty($loop_data)) {
-      if ($loop_data['depth'] < $depth) {
-        break;
+  $new_match_rows = array();
+  if (!empty($possible_start_of_loop)) {
+    // Go through each matched_row.
+    if (count($matched_rows) === 1) {
+      // Get the cvtermpath_id and then increment down one row.
+      $cvtermpath_id = (int) $matched_rows[0]->cvtermpath_id;
+      $cvtermpath_id = $cvtermpath_id + 1;
+      chado_set_active('chado');
+      $next_row = db_query(
+        'SELECT *
+          FROM cvtermpath
+          WHERE cvtermpath_id = :cvtermpath_id
+        ',
+        array(':cvtermpath_id' => $cvtermpath_id)
+      );
+      $next_row = $next_row->fetchObject();
+      
+      // If the next row matches the values passed we can't rule out a loop.
+      if (($next_row->type_id === $type_id) &&
+          ($next_row->subject_id === $child_id) &&
+          ($next_row->object_id === $origin) &&
+          ($next_row->cv_id === $cv_id)) {
+        $new_match_rows[] = $next_row;
       }
-      elseif ($loop_data['depth'] > $depth) {
-        $loop_data = NULL;
-        break;
+      elseif (($next_row->type_id === $possible_start_of_loop['type_id']) &&
+              ($next_row->subject_id === $possible_start_of_loop['subject_id']) &&
+              ($next_row->object_id === $possible_start_of_loop['object_id']) &&
+              ($next_row->cv_id === $possible_start_of_loop['cv_id'])) {
+        // The next_row is equal to start of loop, so we've reached the end
+        // and confirmed that this is a loop.
+        $possible_loop == FALSE;
+        $matched_rows = array();
+        tripal_update_cvtermpath_loop_increment($origin, $child_id, $cv_id,
+        $type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+        $new_match_rows, $possible_start_of_loop, $no_loop_skip_test);
+
       }
     }
     else {
-      $increment_of_depth++;
-      tripal_update_cvtermpath_loop($origin, $item->subject_id, $cv_id,
-        $item->type_id, $depth + 1, $increment_of_depth, $tree_path);
-    }
-  }
-}
+      foreach ($matched_rows as $matched_row) {
+        // Get the cvtermpath_id and then increment down one row.
+        $cvtermpath_id = (int) $match_row->cvtermpath_id;
+        // Get the cvtermpath_id and then increment down one row.
+        $cvtermpath_id = $cvtermpath_id + 1;
+        chado_set_active('chado');
+        $next_row = db_query(
+          'SELECT *
+            FROM cvtermpath
+            WHERE cvtermpath_id = :cvtermpath_id
+          ',
+          array(':cvtermpath_id' => $cvtermpath_id)
+        );
+        $next_row = $next_row->fetchObject();
+
+        // If the next row matches the values passed we can't rule out a loop.
+        if (($next_row->type_id === $type_id) &&
+            ($next_row->subject_id === $child_id) &&
+            ($next_row->object_id === $origin) &&
+            ($next_row->cv_id === $cv_id)) {
+          $new_match_rows[] = $next_row;
+        }
+        elseif (($next_row->type_id === $possible_start_of_loop['type_id']) &&
+                ($next_row->subject_id === $possible_start_of_loop['subject_id']) &&
+                ($next_row->object_id === $possible_start_of_loop['object_id']) &&
+                ($next_row->cv_id === $possible_start_of_loop['cv_id'])) {
+          // The next_row is equal to start of loop, so we've reached the end
+          // and confirmed that this is a loop.
+          $possible_loop == FALSE;
+          $matched_rows = array();
+          tripal_update_cvtermpath_loop_increment($origin, $child_id, $cv_id,
+          $type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+          $new_match_rows, $possible_start_of_loop, $no_loop_skip_test);
 
-/**
- * @param $origin
- * @param $child_id
- * @param $cv_id
- * @param $type_id
- * @param $depth
- * @param $increment_of_depth
- * @param $distance_between_parent_child
- * @param $possible_start_of_loop
- * @param $array_of_possible_loop
- * @param $depth_at_start_of_loop
- *
- * @return bool
- */
-function tripal_update_cvtermpath_loop_checker($origin, $child_id, $cv_id, $type_id,
-    $depth, $increment_of_depth, $distance_between_parent_child, $possible_start_of_loop,
-    $array_of_possible_loop, $depth_at_start_of_loop){
-
-  // Find the child terms of the current term via the relationship taboe.
-  chado_set_active('chado');
-  $query = db_select('cvterm_relationship', 'cvtr')
-    ->fields('cvtr')
-    ->condition('cvtr.object_id', $child_id, '=')
-    ->execute();
-  $cterm_relationships = $query->fetchAll();
-
-  // Iterate through the child terms via the relationships.
-  foreach ($cterm_relationships as $item){
-    // Search the $tree_path for the new $child_id in the build_id column.
-    foreach ($array_of_possible_loop as $parent) {
-      if ($parent['build_id'] === $possible_start_of_loop['build_id']) {
-        // If the search returns something check for a possible loop.
-        if (!empty($parent)) {
-          $result = tripal_update_cvtermpath_loop_checker_traverse($origin, $child_id,
-              $cv_id, $type_id, $depth, $increment_of_depth, $possible_start_of_loop,
-              $array_of_possible_loop, array(), 0);
-          if(!empty($result)){
-            break 2;
-          }
         }
       }
     }
-
-    $increment_of_depth++;
-    $distance_between_parent_child++;
-    $child_id = $origin . '|' . $item->subject_id . '|' . $cv_id . '|' . $item->type_id;
-    $array_of_possible_loop[$distance_between_parent_child] = ['build_id' => $child_id];
-    $result = tripal_update_cvtermpath_loop_checker($origin, $item->subject_id, $cv_id, $item->type_id, $depth + 1, $increment_of_depth, $distance_between_parent_child, $possible_start_of_loop, $array_of_possible_loop, $depth_at_start_of_loop);
-    if($result !== FALSE){
-      return $result;
-    }
-
-  }
-  if (!empty($result)) {
-    return $result;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-function tripal_update_cvtermpath_loop_checker_traverse($origin, $child_id, $cv_id,
-    $type_id, $depth, $increment_of_depth, $possible_start_of_loop, $array_of_possible_loop,
-    $traverse_of_loop, $increment) {
-
-  //watchdog('debug', '<pre>$increment: '. print_r($increment, TRUE) .'</pre>');
-  /*if ($increment > 10) {
-    die();
-  }*/
-  chado_set_active('chado');
-  $query = db_select('cvterm_relationship', 'cvtr')
-    ->fields('cvtr')
-    ->condition('cvtr.object_id', $child_id, '=')
-    ->execute();
-  $cterm = $query->fetchAll();
-
-  foreach ($cterm as $item) {
-    if ($array_of_possible_loop === $traverse_of_loop) {
-      watchdog('debug', 'LOOP');
-      //Report the loop.
-      $loop_found = end($array_of_possible_loop);
-      break;
+    // If $match_rows is empty there is no loop.
+    if (empty($new_match_rows)) {
+      $possible_loop == FALSE;
+      $matched_rows = array();
+      unset($new_match_rows);
+      $no_loop_skip_test = TRUE;
+      // There is not loop so pass it back the possible_start_of_loop info
+      // and a flag telling it to skip the loop check.
+      tripal_update_cvtermpath_loop_increment($possible_start_of_loop->subject_id,
+      $possible_start_of_loop->child_id, $possible_start_of_loop->cvid,
+      $possible_start_of_loop->type_id, $possible_start_of_loop->depth,
+      $increment_of_depth, $tree_path, $possible_loop, $matched_rows,
+      $possible_start_of_loop, $no_loop_skip_test);
     }
-    elseif ($array_of_possible_loop != $traverse_of_loop) {
-      $increment_of_depth++;
-      $increment++;
-      $child_id = $origin . '|' . $item->subject_id . '|' . $cv_id . '|' . $item->type_id;
-      $traverse_of_loop[$increment] = ['build_id' => $child_id];
-      $result = tripal_update_cvtermpath_loop_checker_traverse($origin,
-          $item->subject_id, $cv_id, $item->type_id, $depth + 1,
-          $increment_of_depth, $possible_start_of_loop, $array_of_possible_loop,
-          $traverse_of_loop, $increment);
-      if ($result !== FALSE) {
-        return $result;
-      }
+    // If $match_rows is not empty we need to keep trying rows.
+    else {
+      tripal_update_cvtermpath_loop_increment($origin, $child_id, $cv_id,
+      $type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+      $match_rows, $possible_start_of_loop, $no_loop_skip_test);
     }
   }
-  if ($loop_found) {
-    return $loop_found;
-  }
-  else {
-    return FALSE;
-
+  elseif ($possible_loop === FALSE) {
+    tripal_update_cvtermpath_loop_increment($origin, $child_id, $cv_id,
+    $type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+    $matched_rows, $possible_start_of_loop, $no_loop_skip_test);
   }
 }
-/*
+
+/**
  *
  * @param $origin
- * @param $subject_id
- * @param $cv_idxkcd
+ *   The root terms cvterm_id.
+ * @param $child_id
+ *   The cvterm_id of the current child term.  The child term is a descendent
+ *   of the origin.
+ * @param $cv_id
+ *   The controlled vocabulary ID from the cv table of Chado (i.e. cv.cv_id).
  * @param $type_id
+ *   The relationship type between the origin term and the child.
  * @param $depth
+ *   The depth of the recursion.
+ * @param $increment_of_depth.
+ *   An integer ??
+ * @param $tree_path.
+ *   The array of every term between the current child and the origin. Each
+ *   element in the array is an associative array with the keys:
+ *     -build_id: an string identifier for the child that combines the origin,
+ *      child cvterm_id,cv_id, and the type_id.
+ *     -depth: the depth that a child was inserted into the cvtermpath table.
+ * @param $possible_loop
+ *    A boolean flag.
+ * @param $matched_row
+ *    An array of rows that are currently in the cvtermpath table that match the 
+ *    build_id of the current term trying to be written to the table
+ * @param $possible_start_of_ loop
+ *    The array of the possible loop item between the current child and the origin. 
+ *    Each element in the array is an associative array with the keys:
+ *     - cvid : $cv_id
+ *     - subject_id: 
+ *     - child_id : $child_id,
+ *     - type_id : $type_id,
+ *     - depth : $depth,
+ * @param $no_loop_skip_test
+ *     A boolean used when the possible loop has been ruled out as a loop.
  * @return multitype:
+ */
+function tripal_update_cvtermpath_loop_increment(
+  $origin,
+  $child_id,
+  $cv_id,
+  $type_id,
+  $depth,
+  $increment_of_depth,
+  $tree_path,
+  $possible_loop,
+  $matched_rows,
+  &$possible_start_of_loop,
+  $no_loop_skip_test) {
 
-function tripal_update_cvtermpath_loop($origin, $child_id, $cv_id, $type_id, $depth){
-  // Variables and arrays needed for loop checking.
-  $tree_path;
-  $array_of_possible_loop;
-  $possible_start_of_loop;
-  $distance_between_parent_child;
-  $increment_of_depth;
-
-
-  chado_set_active('chado');
-  $count =  db_query(
-    'SELECT *
-     FROM cvtermpath
-     WHERE cv_id = :cvid
-      AND object_id = :origin
-      AND subject_id = :child_id
-      AND pathdistance = :depth
-    ',
-    array(':cvid' => $cv_id, ':origin' => $origin, ':child_id' => $child_id, ':depth' => $depth)
-  );
-  $count_total = $count->rowCount();
-
-  //Loop check
-  chado_set_active('chado');
-  $loop = db_query(
-    'SELECT *
-     FROM cvtermpath
-     WHERE cv_id = :cvid
-      AND object_id = :origin
-      AND subject_id = :child_id
-      AND type_id = :type_id
-    ',
-    array(':cvid' => $cv_id, ':origin' => $origin, ':child_id' => $child_id, ':type_id' => $type_id,)
-  );
-  $loop_check = $loop->rowCount();
-
-  //watchdog('debug', '<pre>tripal_ds_preprocess_TripalEntity $rows ' . print_r($rows, TRUE) . '</pre>');
-  /*if(!empty($rows)){
-    foreach($rows as $row){
-      tripal_update_cvtermpath_loop_check($origin, $child_id, $cv_id, $type_id, $depth, $row->cvtermpath_id, 0);
+  // Check to see if a row with these values already exists.
+  if ($possible_loop === FALSE && empty($possible_start_of_loop)) {
+    chado_set_active('chado');
+    $count = db_query(
+      'SELECT *
+        FROM cvtermpath
+        WHERE cv_id = :cvid
+        AND object_id = :origin
+        AND subject_id = :child_id
+        AND pathdistance = :depth
+      ',
+      array(
+        ':cvid' => $cv_id,
+        ':origin' => $origin,
+        ':child_id' => $child_id,
+        ':depth' => $depth
+      )
+    );
+    $count_total = $count->rowCount();
+    if ($count_total > 0) {
+      return $count;
     }
-  }
-  else {*/
-  //If no loop proceed.
-  /*try{
-    if($count_total == 0) {
-      chado_set_active('chado');
-
+    // Build the ID.
+    $term_id = $origin . '|' . $child_id . '|' . $cv_id . '|' . $type_id;
+
+    if ($no_loop_skip_test === FALSE) {
+      // Now check if the most recent entry already exists in the array.
+      if ($increment_of_depth != 0) {
+        // Search the $tree_path for the new $child_id in the build_id column.
+        foreach ($tree_path as $parent) {
+          // If this child is the same as a parent term that has already been
+          // processed then we have a potential loop.
+          if ($parent['build_id'] == $term_id) {
+            // Tell the function this is a possible loop and to stop writing to the table.
+            $possible_loop = TRUE;
+            // Find all the results in the table that might be the start of the loop.
+            $matching_rows = db_query(
+              'SELECT *
+              FROM cvtermpath
+              WHERE cv_id = :cvid
+              AND object_id = :origin
+              AND subject_id = :child_id
+              AND type_id = :type_id
+              ',
+              array(
+                ':cvid' => $cv_id,
+                ':origin' => $origin,
+                ':child_id' => $child_id,
+                ':type_id' => $type_id
+              )
+            );
+            $matched_rows = $matching_rows->fetchAll();
+            $possible_start_of_loop = array(
+              'cvid' => $cv_id,
+              'subject_id' => $origin,
+              'child_id' => $child_id,
+              'type_id' => $type_id,
+              'depth' => $depth,
+            );
+          }
+        }
+      }
+    
       $query = db_insert('cvtermpath')
-        ->fields(array(
+        ->fields([
           'object_id' => $origin,
           'subject_id' => $child_id,
           'cv_id' => $cv_id,
           'type_id' => $type_id,
           'pathdistance' => $depth,
-        ));
+        ]);
       $rows = $query->execute();
-    }
-    if ($loop_check == 0) {
-      chado_set_active('chado');
 
-      $query = db_select('cvterm_relationship', 'cvtr')
-        ->fields('cvtr')
-        ->condition('cvtr.object_id', $child_id, '=')
-        ->execute();
-      $cterm = $query->fetchAll();
-
-      foreach ($cterm as $item) {
-        //watchdog('debug', '<pre>tripal_ds_preprocess_TripalEntity $item ' . print_r($item, TRUE) . '</pre>');
-        tripal_update_cvtermpath_loop($origin, $item->subject_id, $cv_id, $item->type_id, $depth + 1);
-      };
-      //}
+      // Then add that new entry to the $tree_path.
+      $tree_path[$increment_of_depth] = [
+        'build_id' => $term_id,
+        'depth' => $depth
+      ];
     }
-  }
-  catch(Exception $e){
-    watchdog_exception('tripal_ds', $e);
-    return FALSE;
-  }
-
-  return 1;
+    // Reset to FALSE  and empty variable if passed in as TRUE.
+    $no_loop_skip_test == FALSE;
+    $possible_start_of_loop = array();
 
-}
-*/
-/**
- *
- * @param $origin
- * @param $subject_id
- * @param $cv_id
- * @param $type_id
- * @param $depth
- * @return multitype:
+    // Get all of the relationships of this child term, and recursively
+    // call the tripal_update_cvtermpath_loop() function to continue
+    // descending down the tree.
+    $query = db_select('cvterm_relationship', 'cvtr')
+      ->fields('cvtr')
+      ->condition('cvtr.object_id', $child_id, '=')
+      ->execute();
+    $cterm_relationships = $query->fetchAll();
 
-function tripal_update_cvtermpath_loop_check($origin, $child_id, $cv_id, $type_id, $depth, $cvtermpath_id, $loop_count, $loop_check, $object_id){
-  //Store the
-
-  //Check if the passed parameters match any of the items in the loop_check array.
-  if(!empty($loop_check)){
-    foreach($loop_check as $item){
-        if ($item['type_id'] = $type_id){
-          if($item['subject_id'] = $child_id){
-            if($item['object_id'] = $object_id){
-              //Loop found, roll back all rows until $cvtermpath_id-1 (last correct entry)
-              // and step into the next loop
-            }
-          }
-        }
+    foreach ($cterm_relationships as $item) {
+      $increment_of_depth++;
+      tripal_update_cvtermpath_loop_increment($origin, $item->subject_id, $cv_id,
+        $item->type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+        $matched_rows, $possible_start_of_loop, $no_loop_skip_test);
     }
   }
-  $loop_count + 1;
-
-  chado_set_active('chado');
-  $count =  db_query(
-    'SELECT *
-     FROM cvtermpath
-     WHERE cv_id = :cvid AND object_id = :origin
-       AND subject_id = :child_id
-       AND pathdistance = :depth
-    ',
-    array(':cvid' => $cv_id, ':origin' => $origin, ':child_id' => $child_id, ':depth' => $depth)
-  );
-  $count_total = $count->rowCount();
-
-  if ($count_total == 0) {
-    chado_set_active('chado');
-    $query = db_insert('cvtermpath')
-      ->fields(array(
-        'object_id' => $origin,
-        'subject_id' => $child_id,
-        'cv_id' => $cv_id,
-        'type_id' => $type_id,
-        'pathdistance' => $depth,
-      ));
-    $rows = $query->execute();
-    $cterm = array();
+  elseif ($possible_loop === FALSE && !empty($possible_start_of_loop)) {
+    // This means a loop has been identified and the recursive call can move
+    // on to the next item and skip the rest of this run.
+    return $possible_start_of_loop;
+  }
+  elseif ($possible_loop === TRUE) {
+    // Get all of the relationships of this child term, and recursively
+    // call the tripal_update_cvtermpath_loop() function to continue
+    // descending down the tree.
     $query = db_select('cvterm_relationship', 'cvtr')
       ->fields('cvtr')
-      ->condition('cvtr.object_id', $child_id, '=' )
+      ->condition('cvtr.object_id', $child_id, '=')
       ->execute();
-    $cterm = $query->fetchAll();
-
-    foreach ($cterm as $item) {
-      $loop_check[$loop_count]= $item;
-      tripal_update_cvtermpath_loop_check($origin, $item->subject_id, $cv_id, $item->type_id, $depth + 1, $loop_count, $loop_check, $item->object_id);
-    };
+    $cterm_relationships = $query->fetchAll();
+    foreach ($cterm_relationships as $item) {
+      $increment_of_depth++;
+      tripal_update_cvtermpath_loop($origin, $item->subject_id, $cv_id,
+        $item->type_id, $depth + 1, $increment_of_depth, $tree_path, $possible_loop,
+        $matched_rows, $possible_start_of_loop, $no_loop_skip_test);
+    }
   }
-
-
-  return 1;
-
 }
- */
 
 /**
  * Adds a controlled vocabular to the CV table of Chado.
@@ -1251,7 +1197,7 @@ function tripal_insert_cvterm($term, $options = array()) {
         INNER JOIN {cv} CV on CV.cv_id = CVT.cv_id
       WHERE DBX.accession = :accession and DB.name = :dbname and CVT.name = :term and CV.name = :cvname
     ";
-    $check = chado_query($checksql, array(':accession' => $dbxref->accession, ':dbname' => $dbname, ':term' => $name, ':cvname' => $cvname))->fetchObject();
+    $check = chado_query($checksql, array(':accession' => $accession, ':dbname' => $dbname, ':term' => $name, ':cvname' => $cvname))->fetchObject();
     if (!$check) {
 
       // check to see if the dbxref exists if not, add it.
@@ -1654,4 +1600,3 @@ function tripal_set_default_cv($table, $field, $cv_name, $cv_id = FALSE) {
     return FALSE;
   }
 }
-

+ 81 - 2
tripal_chado/api/modules/tripal_chado.organism.api.inc

@@ -126,6 +126,44 @@ function tripal_get_organism($identifiers, $options = array()) {
   }
 }
 
+/**
+ * Returns the full scientific name of an organism.
+ *
+ * @param $organism
+ *   An organism object.
+ * @return
+ *   The full scientific name of the organism.
+ */
+function tripal_get_organism_scientific_name($organism) {
+  $name = $organism->genus . ' ' . $organism->species;
+
+  // For Chado v1.3 we have a type_id and infraspecific name.
+  if (property_exists($organism, 'type_id')) {
+    $rank = '';
+    // For organism objects crated using chado_generate_var
+    if (is_object($organism->type_id)) {
+      if ($organism->type_id) {
+        $rank = $orgasnism->type_id->name;
+      }
+    }
+    else {
+      $rank_term = tripal_get_cvterm(array('cvterm_id' => $organism->type_id));
+      if ($rank_term) {
+        $rank = $rank_term->name;
+      }
+    }
+
+    if ($rank) {
+      $rank = tripal_abbreviate_infraspeicifc_rank($rank);
+      $name .= ' ' . $rank . ' ' . $organism->infraspecific_name;
+    }
+    else if ($organism->infraspecific_name) {
+      $name .= ' ' . $organism->infraspecific_name;
+    }
+  }
+  return $name;
+}
+
 /**
  * Returns a list of organisms that are currently synced with Drupal to use in select lists
  *
@@ -245,7 +283,7 @@ function tripal_autocomplete_organism($text) {
   $matches = array();
   $genus = $text;
   $species = '';
-  if (preg_match('/^(.*?) (.*)$/', $text, $matches)) {
+  if (preg_match('/^(.*?)\s+(.*)$/', $text, $matches)) {
     $genus = $matches[1];
     $species = $matches[2];
   }
@@ -261,8 +299,49 @@ function tripal_autocomplete_organism($text) {
   $results = chado_query($sql, $args);
   $items = array();
   foreach ($results as $organism) {
-    $name = $organism->genus . ' ' .$organism->species;
+    $name = tripal_get_organism_scientific_name($organism);
     $items["$name [id: $organism->organism_id]"] = $name;
   }
   drupal_json_output($items);
+}
+
+/**
+ * A handy function to abbreviate the infraspecific rank.
+ *
+ * @param $rank
+ *   The rank below species.
+ * @return
+ *   The proper abbreviation for the rank.
+ */
+function tripal_abbreviate_infraspeicifc_rank($rank) {
+  $abb = '';
+  switch($rank) {
+    case 'no_rank':
+      $abb = '';
+      break;
+    case 'subspecies':
+      $abb = 'subsp.';
+      break;
+    case 'varietas':
+      $abb = 'var.';
+      break;
+    case 'variety':
+      $abb = 'var.';
+      break;
+    case 'subvarietas':
+      $abb = 'subvar.';
+      break;
+    case 'subvariety':
+      $abb = 'subvar.';
+      break;
+    case 'forma':
+      $abb = 'f.';
+      break;
+    case 'subforma':
+      $abb = 'subf.';
+      break;
+    default:
+      $abb = $rank;
+  }
+  return $abb;
 }

+ 5 - 5
tripal_chado/api/modules/tripal_chado.phylotree.api.inc

@@ -146,9 +146,9 @@ function tripal_validate_phylotree($val_type, &$options, &$errors, &$warnings) {
     if ($options['leaf_type'] == 'taxonomy') {
       $values = array(
         'cv_id' => array(
-           'name' => 'tripal_phylogeny'
+           'name' => 'EDAM'
         ),
-        'name' => 'taxonomy'
+        'name' => 'Species tree'
       );
       $type = chado_select_record('cvterm', array('cvterm_id'), $values);
     }
@@ -238,7 +238,7 @@ function tripal_validate_phylotree($val_type, &$options, &$errors, &$warnings) {
  *                   term 'polypeptide'.
  *     'tree_file':  The path of the file containing the phylogenetic tree to
  *                   import or a Drupal managed_file numeric ID.
- *     'format':     The file format. Currently only 'newick is supported'
+ *     'format':     The file format. Currently only 'newick is supported'.
  *  Optional keys:
  *     'dbxref':     A database cross-reference of the form DB:ACCESSION.
  *                   Where DB is the database name, which is already present
@@ -582,6 +582,8 @@ function tripal_assign_phylogeny_tree_indices(&$tree, &$index = 1) {
  *
  * @param $tree
  *   The tree array.
+ * @param $phylotree.
+ *   The phylotree object (from Chado).
  * @param $options
  *   The options provide some direction for how the tree is imported.  The
  *   following keys can be used:
@@ -847,7 +849,5 @@ function tripal_phylogeny_import_tree_file($file_name, $format, $options = array
   catch (Exception $e) {
     $transaction->rollback();
     watchdog_exception('tripal_phylogeny', $e);
-    print "\nFAILED: Rolling back database changes...\n";
   }
-  print "\nDone Importing Tree.\n";
 }

+ 1 - 1
tripal_chado/api/tripal_chado.api.inc

@@ -199,7 +199,7 @@ function tripal_chado_publish_records($values, $job_id = NULL) {
       $entity = $ec->create(array(
         'bundle' => $bundle_name,
         'term_id' => $bundle->term_id,
-        // Add in the Chaod details for when the hook_entity_create()
+        // Add in the Chado details for when the hook_entity_create()
         // is called and our tripal_cahdo_entity_create() implementation
         // can deal with it.
         'chado_record' => chado_generate_var($table, array($pkey_field => $record_id)),

+ 4 - 6
tripal_chado/api/tripal_chado.query.api.inc

@@ -1619,6 +1619,8 @@ function chado_select_record_check_value_type(&$op, &$value, $type) {
  * @ingroup tripal_chado_query_api
  */
 function chado_query($sql, $args = array()) {
+  $results = NULL;
+
   $is_local = isset($GLOBALS["chado_is_local"]) && $GLOBALS["chado_is_local"];
 
   // Args should be an array
@@ -1666,12 +1668,8 @@ function chado_query($sql, $args = array()) {
         chado_set_active($previous_db);
       }
       catch (Exception $e) {
-        try {
-          chado_set_active($previous_db);
-        }
-        catch (Exception $e2) {
-          throw new Exception($e->getMessage() . $e2->getMessage());
-        }
+        chado_set_active($previous_db);
+        throw $e;
       }
     }
     // for all other tables we should have everything in scope so just run the query

+ 8 - 5
tripal_chado/api/tripal_chado.schema.api.inc

@@ -87,12 +87,15 @@ function chado_column_exists($table, $column) {
 
   $default_db = $databases['default']['default']['database'];
   $cached_obj = cache_get('chado_table_columns', 'cache');
-  $cached_cols = $cached_obj->data;
-  if (is_array($cached_cols) and
-      array_key_exists($table, $cached_cols) and
-      array_key_Exists($column, $cached_cols[$table])) {
-    return $cached_cols[$table][$column]['exists'];
+  if ($cached_obj) {
+      $cached_cols = $cached_obj->data;
+      if (is_array($cached_cols) and
+          array_key_exists($table, $cached_cols) and
+          array_key_Exists($column, $cached_cols[$table])) {
+        return $cached_cols[$table][$column]['exists'];
+      }
   }
+
   $sql = "
     SELECT 1
     FROM information_schema.columns

+ 9 - 6
tripal_chado/includes/TripalFields/data__accession/data__accession.inc

@@ -68,12 +68,15 @@ class data__accession extends ChadoField {
     if ($record->$field_column) {
       $dbxref = $record->$field_column;
       $value = $dbxref->db_id->name . ':' . $dbxref->accession;
-      $entity->{$field_name}['und'][0] = array(
-        'value' => $dbxref->accession,
-        'chado-' . $field_table . '__' . $field_column => $record->$field_column->$field_column,
-        'db_id' => $dbxref->db_id->db_id,
-        'accession' => $dbxref->accession,
-      );
+      // Skip the local:null accession as it is just a placeholder.
+      if ($value != 'local:null') {
+        $entity->{$field_name}['und'][0] = array(
+          'value' => $dbxref->accession,
+          'chado-' . $field_table . '__' . $field_column => $record->$field_column->$field_column,
+          'db_id' => $dbxref->db_id->db_id,
+          'accession' => $dbxref->accession,
+        );
+      }
     }
   }
 

+ 1 - 1
tripal_chado/includes/TripalFields/local__source_data/local__source_data_widget.inc

@@ -70,7 +70,7 @@ class local__source_data_widget extends ChadoFieldWidget {
       '#title' => 'Data Source Name',
       '#description' => 'The name of the source where data was obtained for this analysis.',
       '#default_value' => $sourcename,
-      '#required' => TRUE,
+      '#required' => $element['#required'],
     );
     $widget['source_data']['sourceversion'] = array(
       '#type' => 'textfield',

+ 105 - 0
tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis.inc

@@ -0,0 +1,105 @@
+<?php
+
+class operation__phylotree_vis extends ChadoField {
+
+  // The default lable for this field.
+  public static $default_label = 'Phylogenetic tree visualisation';
+
+  // The default description for this field.
+  public static $description = 'Rendering of a phylogenetic tree.';
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instnace.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are
+  // required for all TripalFields.
+  public static $default_instance_settings  = array(
+    // The short name for the vocabulary (e.g. shcema, SO, GO, PATO, etc.).
+    'term_vocabulary' => 'operation',
+    // The name of the term.
+    'term_name' => 'Phylogenetic tree visualisation',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => '0567',
+    // Set to TRUE if the site admin is allowed to change the term
+    // type. This will create form elements when editing the field instance
+    // to allow the site admin to change the term settings above.
+    'term_fixed' => FALSE,
+  );
+
+  // The default widget for this field.
+  public static $default_widget = 'operation__phylotree_vis_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'operation__phylotree_vis_formatter';
+
+
+  /**
+   * @see TripalField::validate()
+   */
+  public function validate($entity_type, $entity, $langcode, $items, &$errors) {
+
+    // If we don't have an entity then we don't want to validate.  The case
+    // where this could happen is when a user is editing the field settings
+    // and trying to set a default value. In that case there's no entity and
+    // we don't want to validate.  There will always be an entity for creation
+    // and update operations of a content type.
+    if (!$entity) {
+      return;
+    }
+    $settings = $this->field['settings'];
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+  }
+
+  /**
+   * @see TripalField::load()
+   */
+  public function load($entity) {
+
+    $record = $entity->chado_record;
+    $settings = $this->instance['settings'];
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $field_column = $this->instance['settings']['chado_column'];
+
+    // Get the terms for each of the keys for the 'values' property.
+    $label_term = 'operation:0567';
+
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0]['value'] = array();
+
+    if ($record) {
+      $entity->{$field_name}['und'][0]['value'] = array(
+        'schema:url' => url('bio_data/' . $entity->id, array('absolute' => TRUE)),
+      );
+    }
+  }
+
+  /**
+   * @see TripalField::elementInfo()
+   */
+  public function elementInfo() {
+    $field_term = $this->getFieldTermID();
+
+    return array(
+      $field_term => array(
+        'operations' => array(),
+        'sortable' => FALSE,
+        'searchable' => FALSE,
+        'type' => 'string',
+        'elements' => array(
+          'schema:url' => array(
+            'searchabel' => FALSE,
+          ),
+        ),
+      )
+    );
+  }
+}

+ 54 - 0
tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis_formatter.inc

@@ -0,0 +1,54 @@
+<?php
+
+class operation__phylotree_vis_formatter extends ChadoFieldFormatter {
+
+  // The default lable for this field.
+  public static $default_label = 'Phylogenetic tree visualisation';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('operation__phylotree_vis');
+
+  /**
+   * @see TripalFieldFormatter::view()
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    global $user;
+    if (count($items) > 0) {
+
+      if (user_access('administrator')) {
+        $message = t('Site administrators:  You can customize the look of phylogenetic trees at the !menu page.',
+          array('!menu' => l('Phylogeny and Taxonomy', 'admin/tripal/storage/chado/phylogeny')));
+        $admin_message = tripal_set_message($message, TRIPAL_INFO, array('return_html' => TRUE));
+        $element[0]['admin_message'] = array(
+          '#type' => 'markup',
+          '#markup' => $admin_message,
+        );
+      }
+
+      $phylotree = $entity->chado_record;
+
+      $node = new stdClass();
+      $node->phylotree = $phylotree;
+
+      $phylotree = chado_expand_var($phylotree,'field','phylotree.comment');
+      module_load_include('inc', 'tripal_chado', 'includes/tripal_chado.phylotree');
+      tripal_phylogeny_prepare_tree_viewer($phylotree);
+
+      if ($phylotree->has_nodes) {
+        $element[0]['comment'] = array(
+          '#type' => 'markup',
+          '#markup' => 'Click a species to view its species page.',
+        );
+        $ajax_loader = url(drupal_get_path('module', 'tripal') . '/theme/images/ajax-loader.gif');
+        $element[0]['phylogram'] = array(
+          '#type' => 'markup',
+          '#markup' => '
+            <div id="phylogram">
+              <img src="' . $ajax_loader . '" class="phylogram-ajax-loader"/>
+            </div>
+          '
+        );
+      }
+    }
+  }
+}

+ 11 - 0
tripal_chado/includes/TripalFields/operation__phylotree_vis/operation__phylotree_vis_widget.inc

@@ -0,0 +1,11 @@
+<?php
+
+class operation__phylotree_vis_widget extends ChadoFieldWidget {
+
+  // The default lable for this field.
+  public static $default_label = 'Phylogenetic tree visualisation';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('operation__phylotree_vis');
+
+}

+ 5 - 4
tripal_chado/includes/TripalImporter/FASTAImporter.inc

@@ -21,7 +21,7 @@ class FASTAImporter extends TripalImporter {
   /**
    * An array containing the extensions of allowed file types.
    */
-  public static $file_types = array('fasta');
+  public static $file_types = array('fasta', 'txt', 'fa', 'aa', 'pep', 'nuc', 'faa', 'fna');
 
 
   /**
@@ -581,10 +581,10 @@ class FASTAImporter extends TripalImporter {
           $analysis_id, $organism_id, $cvterm, $source, $method, $re_name,
           $match_type, $parentcvterm, $relcvterm, $seq['seq_start'],
           $seq['seq_end']);
-      $this->setItemsHandled($i);
+      $this->setItemsHandled($j);
     }
     fclose($fh);
-    $this->setItemsHandled($i);
+    $this->setItemsHandled($num_seqs);
   }
 
   /**
@@ -886,6 +886,7 @@ class FASTAImporter extends TripalImporter {
     // get a specific bytes read then append the sequence to the one in the
     // database.
     $partial_seq_size = 0;
+    $chunk_intv_read = 0;
     while ($line = fgets($fh)) {
       $num_read += strlen($line) + 1;
       $chunk_intv_read += strlen($line) + 1;
@@ -936,4 +937,4 @@ class FASTAImporter extends TripalImporter {
     chado_query($sql, array(':feature_id' => $feature_id));
 
   }
-}
+}

+ 42 - 56
tripal_chado/includes/TripalImporter/OBOImporter.inc

@@ -14,7 +14,7 @@ class OBOImporter extends TripalImporter {
    * The name of this loader.  This name will be presented to the site
    * user.
    */
-  public static $name = 'Chado OBO Loader';
+  public static $name = 'OBO Vocabulary Loader';
 
   /**
    * The machine name for this loader. This name will be used to construct
@@ -81,9 +81,25 @@ class OBOImporter extends TripalImporter {
     'file_remote' => FALSE,
   );
 
+  /**
+   * Be default, all loaders are automaticlly added to the Admin >
+   * Tripal > Data Laders menu.  However, if this loader should be
+   * made available via a different menu path, then set it here.  If the
+   * value is empty then the path will be the default.
+   */
+  public static $menu_path = 'admin/tripal/loaders/chado_vocabs/obo_loader';
+
   public static $file_required = FALSE;
 
 
+  /**
+   * Keep track of vocabularies that have been added.
+   *
+   * @var array
+   */
+  private $newcvs = array();
+
+
   /**
    * @see TripalImporter::form()
    */
@@ -384,7 +400,19 @@ class OBOImporter extends TripalImporter {
 
     $this->loadOBO_v1_2_id($obo);
   }
+  /**
+   * @see TripalImporter::postRun()
+   *
+   */
+  public function postRun() {
 
+    // Upate the cvtermpath table for each newly added CV.
+    $this->logMessage("Updating cvtermpath table.  This may take a while...");
+    foreach ($this->newcvs as $namespace => $cvid) {
+      $this->logMessage("- Loading paths for @vocab", array('@vocab' => $namespace));
+      tripal_update_cvtermpath($cvid);
+    }
+  }
   /**
    * A wrapper function for importing the user specified OBO file into Chado by
    * specifying the obo_id of the OBO. It requires that the file be in OBO v1.2
@@ -447,18 +475,11 @@ class OBOImporter extends TripalImporter {
    * @ingroup tripal_obo_loader
    */
   private function loadOBO_v1_2_file($obo_name, $file, $is_new = TRUE) {
-    $newcvs = array();
-
     if ($is_new) {
       tripal_insert_obo($obo_name, $file);
     }
 
-    $success = $this->loadOBO_v1_2($file, $newcvs);
-    if ($success) {
-      // update the cvtermpath table
-      $this->load_cvtermpath($newcvs);
-      print "\nDone\n";
-    }
+    $success = $this->loadOBO_v1_2($file);
   }
 
   /**
@@ -479,8 +500,6 @@ class OBOImporter extends TripalImporter {
    */
   private function loadOBO_v1_2_url($obo_name, $url, $is_new = TRUE) {
 
-    $newcvs = array();
-
     // first download the OBO
     $temp = tempnam(sys_get_temp_dir(), 'obo_');
     print "Downloading URL $url, saving to $temp\n";
@@ -503,34 +522,12 @@ class OBOImporter extends TripalImporter {
     }
 
     // second, parse the OBO
-    $this->loadOBO_v1_2($temp, $newcvs);
-
-    // update the cvtermpath table
-    $this->load_cvtermpath($newcvs);
+    $this->loadOBO_v1_2($temp);
 
     // now remove the temp file
     unlink($temp);
   }
 
-  /**
-   * A function for executing the cvtermpath function of Chado.  This function
-   * populates the cvtermpath table of Chado for quick lookup of term
-   * relationships
-   *
-   * @param $newcvs
-   *   An associative array of controlled vocabularies to update.  The key must be
-   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
-   *
-   * @ingroup tripal_obo_loader
-   */
-  private function load_cvtermpath($newcvs) {
-
-    print "\nUpdating cvtermpath table.  This may take a while...\n";
-    foreach ($newcvs as $namespace => $cvid) {
-      tripal_update_cvtermpath($cvid);
-    }
-  }
-
   /**
    * Imports a given OBO file into Chado.  This function is usually called by
    * one of three wrapper functions:  loadOBO_v1_2_id,
@@ -540,15 +537,10 @@ class OBOImporter extends TripalImporter {
    *
    * @param $flie
    *   The full path to the OBO file on the file system
-   * @param $newcvs
-   *   An empty array passed by reference that upon return will contain the list
-   *   of newly added vocabularies.  The key will contain the CV name and the
-   *   value the new cv_id
-   *
    *
    * @ingroup tripal_obo_loader
    */
-  private function loadOBO_v1_2($file, &$newcvs) {
+  private function loadOBO_v1_2($file) {
 
     $header = array();
     $ret = array();
@@ -571,7 +563,7 @@ class OBOImporter extends TripalImporter {
       if (!$defaultcv) {
         throw new Exception('Cannot add namespace ' . $header['default-namespace'][0]);
       }
-      $newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
+      $this->newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
     }
     // If the 'default-namespace' is missing.
     else {
@@ -583,7 +575,7 @@ class OBOImporter extends TripalImporter {
         if (!$defaultcv) {
           throw new Exception('Cannot add namespace ' . strtoupper($header['ontology'][0]));
         }
-        $newcvs[strtoupper(strtoupper($header['ontology'][0]))] = $defaultcv->cv_id;
+        $this->newcvs[strtoupper(strtoupper($header['ontology'][0]))] = $defaultcv->cv_id;
       }
       else {
         throw new Exception("Could not find a namespace for this OBO file: $file");
@@ -595,11 +587,11 @@ class OBOImporter extends TripalImporter {
     }!
     // Add any typedefs to the vocabulary first.
     $this->logMessage("Step 2: Loading type defs...");
-    $this->loadTypeDefs($defaultcv, $newcvs, $default_db);
+    $this->loadTypeDefs($defaultcv, $default_db);
 
     // Next add terms to the vocabulary.
     $this->logMessage("Step 3: Loading terms...");
-    if (!$this->processTerms($defaultcv, $newcvs, $default_db)) {
+    if (!$this->processTerms($defaultcv, $default_db)) {
       throw new Exception('Cannot add terms from this ontology');
     }
 
@@ -612,15 +604,12 @@ class OBOImporter extends TripalImporter {
    * @param $defaultcv
    *   A database object containing a record from the cv table for the
    *   default controlled vocabulary
-   * @param $newcvs
-   *   An associative array of controlled vocabularies for this OBO.  The key must be
-   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
    * @param $default_db
    *   The name of the default database.
    *
    * @ingroup tripal_obo_loader
    */
-  private function loadTypeDefs($defaultcv, $newcvs, $default_db) {
+  private function loadTypeDefs($defaultcv, $default_db) {
     $sql = "SELECT * FROM {tripal_obo_temp} WHERE type = 'Typedef' ";
     $typedefs = chado_query($sql);
 
@@ -638,7 +627,7 @@ class OBOImporter extends TripalImporter {
     foreach ($typedefs as $typedef) {
       $this->setItemsHandled($i);
       $term = unserialize(base64_decode($typedef->stanza));
-      $this->processTerm($term, $defaultcv->name, 1, $newcvs, $default_db);
+      $this->processTerm($term, $defaultcv->name, 1, $default_db);
       $i++;
     }
 
@@ -654,13 +643,10 @@ class OBOImporter extends TripalImporter {
    * @param $defaultcv
    *   A database object containing a record from the cv table for the
    *   default controlled vocabulary
-   * @param $newcvs
-   *   An associative array of controlled vocabularies for this OBO.  The key must be
-   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
    * @param $default_db
    *   The name of the default database.
    */
-  private function processTerms($defaultcv, &$newcvs, $default_db) {
+  private function processTerms($defaultcv, $default_db) {
     $i = 0;
 
     // Iterate through each term from the OBO file and add it.
@@ -687,7 +673,7 @@ class OBOImporter extends TripalImporter {
       $this->setItemsHandled($i);
 
       // Add/update this term.
-      if (!$this->processTerm($term, $defaultcv->name, 0, $newcvs, $default_db)) {
+      if (!$this->processTerm($term, $defaultcv->name, 0, $default_db)) {
         throw new Exception("Failed to process terms from the ontology");
       }
 
@@ -712,7 +698,7 @@ class OBOImporter extends TripalImporter {
    *
    * @ingroup tripal_obo_loader
    */
-  private function processTerm($term, $defaultcv, $is_relationship = 0, &$newcvs, $default_db) {
+  private function processTerm($term, $defaultcv, $is_relationship = 0, $default_db) {
 
     // make sure we have a namespace for this term
     if (!array_key_exists('namespace', $term) and !($defaultcv or $defaultcv == '')) {
@@ -756,7 +742,7 @@ class OBOImporter extends TripalImporter {
     chado_query($sql, array(':cvterm_id' => $cvterm->cvterm_id));
 
     if (array_key_exists('namespace', $term)) {
-      $newcvs[$term['namespace'][0]] = $cvterm->cv_id;
+      $this->newcvs[$term['namespace'][0]] = $cvterm->cv_id;
     }
 
     // now handle other properites

+ 917 - 0
tripal_chado/includes/TripalImporter/TaxonomyImporter.inc

@@ -0,0 +1,917 @@
+<?php
+
+class TaxonomyImporter extends TripalImporter {
+  /**
+   * The name of this loader.  This name will be presented to the site
+   * user.
+   */
+  public static $name = 'Chado NCBI Taxonomy Loader';
+
+  /**
+   * The machine name for this loader. This name will be used to construct
+   * the URL for the loader.
+   */
+  public static $machine_name = 'chado_taxonomy';
+
+  /**
+   * A brief description for this loader.  This description will be
+   * presented to the site user.
+   */
+  public static $description = 'Imports new organisms from NCBI using taxonomy IDs, or loads taxonomic details about existing organisms.';
+
+  /**
+   * An array containing the extensions of allowed file types.
+   */
+  public static $file_types = array();
+
+
+  /**
+   * Provides information to the user about the file upload.  Typically this
+   * may include a description of the file types allowed.
+   */
+  public static $upload_description = '';
+
+  /**
+   * The title that should appear above the upload button.
+   */
+  public static $upload_title = 'File Upload';
+
+  /**
+   * If the loader should require an analysis record.  To maintain provenance
+   * we should always indiate where the data we are uploading comes from.
+   * The method that Tripal attempts to use for this by associating upload files
+   * with an analysis record.  The analysis record provides the details for
+   * how the file was created or obtained. Set this to FALSE if the loader
+   * should not require an analysis when loading. if $use_analysis is set to
+   * true then the form values will have an 'analysis_id' key in the $form_state
+   * array on submitted forms.
+   */
+  public static $use_analysis = FALSE;
+
+  /**
+   * If the $use_analysis value is set above then this value indicates if the
+   * analysis should be required.
+   */
+  public static $require_analysis = FALSE;
+
+  /**
+   * Text that should appear on the button at the bottom of the importer
+   * form.
+   */
+  public static $button_text = 'Import from NCBI Taxonomy';
+
+  /**
+   * Indicates the methods that the file uploader will support.
+   */
+  public static $methods = array(
+    // Allow the user to upload a file to the server.
+    'file_upload' => FALSE,
+    // Allow the user to provide the path on the Tripal server for the file.
+    'file_local' => FALSE,
+    // Allow the user to provide a remote URL for the file.
+    'file_remote' => FALSE,
+  );
+
+  /**
+   * Indicates if the file must be provided.  An example when it may not be
+   * necessary to require that the user provide a file for uploading if the
+   * loader keeps track of previous files and makes those available for
+   * selection.
+   */
+  public static $file_required = FALSE;
+
+
+  /**
+   * The array of arguments used for this loader.  Each argument should
+   * be a separate array containing a machine_name, name, and description
+   * keys.  This information is used to build the help text for the loader.
+   */
+  public static $argument_list = array();
+
+
+  /**
+   * Indicates how many files are allowed to be uploaded.  By default this is
+   * set to allow only one file.  Change to any positive number. A value of
+   * zero indicates an unlimited number of uploaded files are allowed.
+   */
+  public static $cardinality = 0;
+
+  /**
+   * Holds the list of all orgainsms currently in Chado. This list
+   * is needed when checking to see if an organism has already been
+   * loaded.
+   */
+  private  $all_orgs = array();
+
+  /**
+   * The record from the Chado phylotree table that refers to this
+   * Taxonomic tree.
+   */
+  private $phylotree = NULL;
+
+  /**
+   * The temporary tree array used by the Tripal Phylotree API for
+   * importing a new tree.
+   */
+  private $tree = NULL;
+
+  /**
+   * @see TripalImporter::form()
+   */
+  public function form($form, &$form_state) {
+
+    $form['instructions'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'instructions',
+      '#description' => t('This form is used to import species from the NCBI
+        Taxonomy database into this site. Alternatively, it can import details
+        about organisms from the NCBI Taxonomy database for organisms that
+        already exist on this site.  This loader will also construct
+        the taxonomic tree for the species loaded.'),
+    );
+    $form['taxonomy_ids'] = array(
+      '#type' => 'textarea',
+      '#title' => 'Taxonomy IDa',
+      '#description' => t('Please provide a list of NCBI taxonomy IDs separated
+        by spaces, tabs or new lines.
+        The information about these organisms will be downloaded and new organism
+        records will be added to this site.')
+    );
+
+    $form['import_existing'] = array(
+      '#type' => 'checkbox',
+      '#title' => 'Import details for existing species.',
+      '#description' =>  t('The NCBI Taxonmic Importer examines the organisms
+        currently present in the database and queries NCBI for the
+        taxonomic details.  If the importer is able to match the
+        genus and species with NCBI the species details will be imported,
+        and a page containing the taxonomic tree will be created.'),
+      '#default value' => 1,
+    );
+    return $form;
+  }
+
+  /**
+   * @see TripalImporter::formValidate()
+   */
+  public function formValidate($form, &$form_state) {
+    global $user;
+
+    $import_existing = $form_state['values']['import_existing'];
+    $taxonomy_ids = $form_state['values']['taxonomy_ids'];
+
+    // make sure that we have numeric values, one per line.
+    if ($taxonomy_ids) {
+      $tax_ids = preg_split("/[\s\n\t\r]+/", $taxonomy_ids);
+      $bad_ids = array();
+      foreach ($tax_ids as $tax_id) {
+        $tax_id = trim($tax_id);
+        if (!preg_match('/^\d+$/', $tax_id)) {
+          $bad_ids[] = $tax_id;
+        }
+      }
+      if (count($bad_ids) > 0) {
+        form_set_error('taxonomy_ids',
+            t('Taxonomy IDs must be numeric. The following are not valid: "@ids".',
+                array('@ids' => implode('", "', $bad_ids))));
+      }
+    }
+  }
+
+  /**
+   * Performs the import.
+   */
+  public function run() {
+    global $site_name;
+
+
+    $arguments = $this->arguments['run_args'];
+    $taxonomy_ids = $arguments['taxonomy_ids'];
+    $import_existing = $arguments['import_existing'];
+
+    // Get the list of all organisms as we'll need this to lookup existing
+    // organisms.
+    if (chado_get_version() > 1.2) {
+      $sql = "
+        SELECT O.*, CVT.name as type
+        FROM {organism} O
+         LEFT JOIN {cvterm} CVT ON CVT.cvterm_id = O.type_id
+        ORDER BY O.genus, O.species
+      ";
+    }
+    else {
+      $sql = "
+        SELECT O.*, '' as type
+        FROM {organism} O
+        ORDER BY O.genus, O.species
+      ";
+    }
+    $results = chado_query($sql);
+    while ($item = $results->fetchObject()) {
+      $this->all_orgs[] = $item;
+    }
+
+    // Get the phylotree object.
+    $this->logMessage('Initializing Tree...');
+    $this->phylotree = $this->initTree();
+    $this->logMessage('Rebuilding Tree...');
+    $this->tree = $this->rebuildTree();
+
+    // Clean out the phnylondes for this tree in the event this is a reload
+    chado_delete_record('phylonode', array('phylotree_id' => $this->phylotree->phylotree_id));
+
+    // Get the taxonomy IDs provided by the user (if any).
+    $tax_ids = array();
+    if ($taxonomy_ids) {
+      $tax_ids = preg_split("/[\s\n\t\r]+/", $taxonomy_ids);
+    }
+
+    // Set the number of items to handle.
+    if ($taxonomy_ids and $import_existing) {
+      $this->setTotalItems(count($this->all_orgs) + count($tax_ids));
+    }
+    if ($taxonomy_ids and !$import_existing) {
+      $this->setTotalItems(count($tax_ids));
+    }
+    if (!$taxonomy_ids and $import_existing) {
+      $this->setTotalItems(count($this->all_orgs));
+    }
+    $this->setItemsHandled($num_handled);
+
+    // If the user wants to import new taxonomy IDs then do that.
+    if ($taxonomy_ids){
+      $this->logMessage('Importing Taxonomy IDs...');
+      foreach ($tax_ids as $tax_id) {
+        $tax_id = trim($tax_id);
+        $this->importRecord($tax_id);
+        $this->addItemsHandled(1);
+      }
+    }
+
+    // If the user wants to update existing records then do that.
+    if ($import_existing) {
+      $this->logMessage('Updating Existing...');
+      $this->updateExisting();
+    }
+
+    // Now import the tree.
+    $options = array('taxonomy' => 1);
+    tripal_phylogeny_import_tree($this->tree, $this->phylotree, $options);
+  }
+
+
+  /**
+   * Create the taxonomic tree in Chado.
+   *
+   * If the tree already exists it will not be recreated.
+   *
+   * @throws Exception
+   * @return
+   *   Returns the phylotree object.
+   */
+  private function initTree() {
+    // Add the taxonomy tree record into the phylotree table. If the tree
+    // already exists then don't insert it again.
+    $tree_name = $site_name . 'Taxonomy Tree';
+    $phylotree = chado_select_record('phylotree', array('*'), array('name' => $tree_name));
+    if (count($phylotree) == 0) {
+      // Add the taxonomic tree.
+      $phylotree = array(
+        'name' =>  $site_name . 'Taxonomy Tree',
+        'description' => 'A phylogenetic tree based on taxonomic rank.',
+        'leaf_type' => 'taxonomy',
+        'tree_file' => '/dev/null',
+        'format' => 'taxonomy',
+        'no_load' => TRUE,
+      );
+      $errors = array();
+      $warnings = array();
+      $success = tripal_insert_phylotree($phylotree, $errors, $warnings);
+      if (!$success) {
+        throw new Exception("Cannot add the Taxonomy Tree record.");
+      }
+      $phylotree = (object) $phylotree;
+    }
+    else {
+      $phylotree = $phylotree[0];
+    }
+    return $phylotree;
+  }
+
+
+
+  /**
+   * Iterates through all existing organisms and rebuilds the taxonomy tree.
+   *
+   * The phloytree API doesn't support adding nodes to existing trees only
+   * importing whole trees. So, we must rebuild the tree using the current
+   * organisms and then we can add to it.
+   *
+   */
+  private function rebuildTree() {
+    $lineage_nodes[] = array();
+
+    // Get the "rank" cvterm. It requires that the TAXRANK vocabulary is loaded.
+    $rank_cvterm = tripal_get_cvterm(array(
+      'name' => 'rank',
+      'cv_id' => array('name' => 'local')
+    ));
+
+    // The taxonomic tree must have a root, so create that first.
+    $tree = array(
+      'name' => 'root',
+      'depth' => 0,
+      'is_root' => 1,
+      'is_leaf' => 0,
+      'is_internal' => 0,
+      'left_index' => 0,
+      'right_index' => 0,
+      'branch_set' => array(),
+    );
+
+    $total = count($this->all_orgs);
+    $j = 1;
+    foreach ($this->all_orgs as $organism) {
+      $sci_name =  tripal_get_organism_scientific_name($organism);
+      //$this->logMessage("- " . ($j++) . " of $total. Adding @organism", array('@organism' => $sci_name));
+
+      // First get the phylonode record for this organism.
+      $sql = "
+        SELECT P.*
+        FROM {phylonode} P
+         INNER JOIN {phylonode_organism} PO on PO.phylonode_id = P.phylonode_id
+        WHERE P.phylotree_id = :phylotree_id AND PO.organism_id = :organism_id
+      ";
+      $args = array(
+        ':phylotree_id' => $this->phylotree->phylotree_id,
+        ':organism_id' => $organism->organism_id,
+      );
+      $result = chado_query($sql, $args);
+      if (!$result) {
+        continue;
+      }
+      $phylonode = $result->fetchObject();
+
+      // Next get the lineage for this organism.
+      $lineage = $this->getProperty($organism->organism_id, 'lineage');
+      if (!$lineage) {
+        continue;
+      }
+      $lineage_depth = preg_split('/;\s*/', $lineage->value);
+
+      // Now rebuild the tree by first creating the nodes for the full
+      // lineage and then adding the organism as a leaf node.
+      $parent = $tree;
+      $i = 1;
+      $lineage_good = TRUE;
+      foreach ($lineage_depth as $child) {
+        // We need to find the node in the phylotree for this level of the
+        // lineage, but there's a lot of repeats and we don't want to keep
+        // doing the same queries over and over, so we store the nodes
+        // we've already seen in the $lineage_nodes array for fast lookup.
+        if (array_key_exists($child, $lineage_nodes)) {
+          $phylonode = $lineage_nodes[$child];
+          if (!$phylonode) {
+            $lineage_good = FALSE;
+            continue;
+          }
+        }
+        else {
+          $values = array(
+            'phylotree_id' => $this->phylotree->phylotree_id,
+            'label' => $child,
+          );
+          $columns = array('*');
+          $phylonode = chado_select_record('phylonode', $columns, $values);
+          if (count($phylonode) == 0) {
+            $lineage_nodes[$child] = NULL;
+            $lineage_good = FALSE;
+            continue;
+          }
+          $phylonode = $phylonode[0];
+          $lineage_nodes[$child] = $phylonode;
+
+
+          $values = array(
+            'phylonode_id' => $phylonode->phylonode_id,
+            'type_id' => $rank_cvterm->cvterm_id,
+          );
+          $columns = array('*');
+          $phylonodeprop = chado_select_record('phylonodeprop', $columns, $values);
+        }
+        $name = $child;
+        $node_rank = (string) $child->Rank;
+        $node = array(
+          'name' => $name,
+          'depth' => $i,
+          'is_root' => 0,
+          'is_leaf' => 0,
+          'is_internal' => 1,
+          'left_index' => 0,
+          'right_index' => 0,
+          'parent' => $parent,
+          'branch_set' => array(),
+          'parent' => $parent['name'],
+          'properties' => array(
+            $rank_cvterm->cvterm_id => $phylonodeprop[0]->value,
+          ),
+        );
+        $parent = $node;
+        $this->addTaxonomyNode($tree, $node, $lineage_depth);
+        $i++;
+      } // end foreach ($lineage_depth as $child) { ...
+
+      // If $stop is set then we had problems setting the lineage so
+      // skip adding the leaf node below.
+      if (!$lineage_good) {
+        continue;
+      }
+
+      $rank_type = 'species';
+      if (property_exists($organism, 'type_id') and $organism->type_id) {
+        $rank_type = $organism->type;
+      }
+
+      // Now add in the leaf node
+      $sci_name = tripal_get_organism_scientific_name($organism);
+      $node = array(
+        'name' => $sci_name,
+        'depth' => $i,
+        'is_root' => 0,
+        'is_leaf' => 1,
+        'is_internal' => 0,
+        'left_index' => 0,
+        'right_index' => 0,
+        'parent' => $parent['name'],
+        'organism_id' => $organism->organism_id,
+        'properties' => array(
+          $rank_cvterm->cvterm_id => $rank_type,
+        ),
+      );
+      $this->addTaxonomyNode($tree, $node, $lineage_depth);
+
+      // Set the indecies for the tree.
+      tripal_assign_phylogeny_tree_indices($tree);
+    }
+
+    return $tree;
+  }
+
+  /**
+   * Imports details from NCBI Taxonomy for organisms that alrady exist.
+   */
+  private function updateExisting() {
+
+    foreach ($this->all_orgs as $organism) {
+      // If the organism record is marked as new then let's skip it because
+      // it was newly added and should have the updated information already.
+      if ($organism->is_new) {
+        continue;
+      }
+
+      // TODO: we should check if the organism already has a taxonomy ID.
+      // if so we should use that instead of the scientific name.
+
+      // Build the query string to get the information about this species.
+      $sci_name = tripal_get_organism_scientific_name($organism);
+      $sci_name = urlencode($sci_name);
+      $search_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?".
+          "db=taxonomy" .
+          "&term=$sci_name";
+
+      // Get the search response from NCBI.
+      $rfh = fopen($search_url, "r");
+      $xml_text = '';
+      while (!feof($rfh)) {
+        $xml_text .= fread($rfh, 255);
+      }
+      fclose($rfh);
+
+      // Parse the XML to get the taxonomy ID
+      $xml = new SimpleXMLElement($xml_text);
+      if ($xml) {
+        $taxid = (string) $xml->IdList->Id;
+        if ($taxid) {
+          $this->importRecord($taxid, $organism);
+        }
+      }
+      $this->addItemsHandled(1);
+    }
+  }
+
+  /**
+   * Checks the Chado database to see if the organism already exists.
+   *
+   * @param $taxid
+   *   The taxonomic ID for the organism.
+   * @param $sci_name
+   *   The scientific name for the organism as returned by NCBI
+   */
+  private function findOrganism($taxid, $sci_name) {
+    $organism = NULL;
+
+    // First check the taxid to see if it's present and assocaited with an
+    // organism already.
+    $values = array(
+      'db_id' => array(
+        'name' => NCBITaxon
+      ),
+      'accession' => $taxid,
+    );
+    $columns = array('dbxref_id');
+    $dbxref = chado_select_record('dbxref', $columns, $values);
+    if (count($dbxref) > 0) {
+      $columns = array('organism_id');
+      $values = array('dbxref_id' => $dbxref[0]->dbxref_id);
+      $organism_dbxref = chado_select_record('organism_dbxref', $columns, $values);
+      if (count($organism_dbxref) >0) {
+        $organism_id = $organism_dbxref[0]->organism_id;
+        $columns = array('*');
+        $values = array('organism_id' => $organism_id);
+        $organism = chado_select_record('organism', $columns, $values);
+        if (count($organism) > 0) {
+          $organism = $organism[0];
+        }
+      }
+    }
+
+    // If the caller did not provide an organism then we want to try and
+    // add one. But, it only makes sense to add one if this record
+    // is of rank species.
+    // First check if the full name (including the infrasepcific name)
+    // are all present in the genus and species name.  This would have
+    // been the Chado v1.2 (or less) of storing species.
+    if (!$organism) {
+      $sql = "
+          SELECT organism_id
+          FROM {organism}
+          WHERE concat(genus, ' ', species) = :sci_name
+        ";
+      $results = chado_query($sql, array(':sci_name' => $sci_name));
+      if ($results) {
+        $item = $results->fetchObject();
+        $columns = array('*');
+        $values = array('organism_id' => $item->organism_id);
+        $organism = chado_select_record('organism', $columns, $values);
+        if (count($organism) > 0) {
+          $organism = $organism[0];
+        }
+      }
+    }
+    // Second, check if the full name includes the infraspecific name.
+    if (!$organism) {
+      foreach ($this->all_orgs as $item) {
+        $internal_sci_name = tripal_get_organism_scientific_name($item);
+        if ($sci_name == $internal_sci_name) {
+          $organism = $item;
+        }
+      }
+    }
+    return $organism;
+  }
+  /**
+   * Adds a new organism record to Chado.
+   *
+   * @param sci_name
+   *   The scientific name as provied by NCBI Taxonomy.
+   * @param $rank
+   *   The rank of the organism as provied by NCBI Taxonomy.
+   */
+  private function addOrganism($sci_name, $rank) {
+
+    $organism = NULL;
+    $matches = array();
+    $genus = '';
+    $species = '';
+    $infra = '';
+    $values = array();
+
+    // Check if the scientific name has an infraspecific part or is just
+    // a species name.
+    if (preg_match('/^(.+?)\s+(.+?)\s+(.+)$/', $sci_name, $matches)) {
+      $genus = $matches[1];
+      $species = $matches[2];
+      $infra = $matches[3];
+
+      // Get the CV term for the rank.
+      $type = tripal_get_cvterm(array(
+        'name' => preg_replace('/ /','_', $rank),
+        'cv_id' => array('name' => 'taxonomic_rank')
+      ));
+
+      // Remove the rank from the infraspecific name.
+      $abbrev = tripal_abbreviate_infraspeicifc_rank($rank);
+      $infra = preg_replace("/$abbrev/", "", $infra);
+      $infra = trim($infra);
+
+      $values = array(
+        'genus' => $genus,
+        'species' => $species,
+        'abbreviation' => $genus[0] . '. ' . $species,
+        'type_id' => $type->cvterm_id,
+        'infraspecific_name' => $infra,
+      );
+      $organism = chado_insert_record('organism', $values);
+      $organism = (object) $organism;
+      $organism->type = $rank;
+    }
+    else if (preg_match('/^(.+?)\s+(.+?)$/', $sci_name, $matches)) {
+      $genus = $matches[1];
+      $species = $matches[2];
+      $infra = '';
+      $values = array(
+        'genus' => $genus,
+        'species' => $species,
+        'abbreviation' => $genus[0] . '. ' . $species,
+      );
+      $organism = chado_insert_record('organism', $values);
+      $organism = (object) $organism;
+    }
+    if ($organism) {
+      $organism->is_new = TRUE;
+      $this->all_orgs[] = $organism;
+    }
+
+    return $organism;
+  }
+
+  /**
+   * Imports an organism from the NCBI taxonomy DB by its taxonomy ID
+   *
+   * @param $taxid
+   *   The NCBI Taxonomy ID.
+   * @param $organism
+   *   The organism object to which this taxonomy belongs.  If the organism
+   *   is NULL then it will be created.
+   */
+  private function importRecord($taxid, $organism = NULL) {
+    $adds_organism = $organism ? FALSE : TRUE;
+
+    // Get the "rank" cvterm. It requires that the TAXRANK vocabulary is loaded.
+    $rank_cvterm = tripal_get_cvterm(array(
+      'name' => 'rank',
+      'cv_id' => array('name' => 'local')
+    ));
+
+    // Get the details for this taxonomy.
+    $fetch_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?".
+        "db=taxonomy" .
+        "&id=$taxid";
+
+    // Get the search response from NCBI.
+    $rfh = fopen($fetch_url, "r");
+    $xml_text = '';
+    while (!feof($rfh)) {
+      $xml_text .= fread($rfh, 255);
+    }
+    fclose($rfh);
+
+    $xml = new SimpleXMLElement($xml_text);
+    if ($xml) {
+      $taxon = $xml->Taxon;
+
+      // Get the genus and species from the xml.
+      $parent = (string) $taxon->ParentTaxId;
+      $rank = (string) $taxon->Rank;
+      $sci_name = (string) $taxon->ScientificName;
+      //$this->logMessage(' - Importing @sci_name', array('@sci_name' => $sci_name));
+
+      // If we don't have an organism record provided then see if there
+      // is one provided by Chado, if not, the try to add one.
+      if (!$organism) {
+        $organism = $this->findOrganism($taxid, $sci_name);
+        if (!$organism) {
+          $organism = $this->addOrganism($sci_name, $rank);
+          if (!$organism) {
+            throw new Exception(t('Cannot add organism: @sci_name', array('@sci_name' => $sci_name)));
+          }
+        }
+      }
+
+      // Associate the Dbxref with the organism.
+      $this->addDbxref($organism->organism_id, $taxid);
+
+      // Get properties for this organism.
+      $lineage = (string) $taxon->Lineage;
+      $genetic_code = (string) $taxon->GeneticCode->GCId;
+      $genetic_code_name = (string) $taxon->GeneticCode->GCName;
+      $mito_genetic_code = (string) $taxon->MitoGeneticCode->MGCId;
+      $mito_genetic_code_name = (string) $taxon->MitoGeneticCode->MGCName;
+      $division = (string) $taxon->Division;
+
+      // Add in the organism properties.
+      $this->addProperty($organism->organism_id, 'division', $division);
+      $this->addProperty($organism->organism_id, 'mitochondrial_genetic_code_name', $mito_genetic_code_name);
+      $this->addProperty($organism->organism_id, 'mitochondrial_genetic_code', $mito_genetic_code);
+      $this->addProperty($organism->organism_id, 'genetic_code_name', $genetic_code_name);
+      $this->addProperty($organism->organism_id, 'lineage', $lineage);
+      $this->addProperty($organism->organism_id, 'genetic_code', $genetic_code);
+
+      $name_ranks = array();
+      if ($taxon->OtherNames->children) {
+        foreach ($taxon->OtherNames->children() as $child) {
+          $type = $child->getName();
+          $name = (string) $child;
+          if (!array_key_exists($type, $name_ranks)) {
+            $name_ranks[$type] = 0;
+          }
+          switch ($type) {
+            case 'GenbankCommonName':
+              $this->addProperty($organism->organism_id, 'genbank_common_name', $name, $name_ranks[$type]);
+              break;
+            case 'Synonym':
+            case 'GenbankSynonym':
+              $this->addProperty($organism->organism_id, 'synonym', $name, $name_ranks[$type]);
+              break;
+            case 'CommonName':
+              // If we had to add the organism then include the commone name too.
+              if ($adds_organism) {
+                $organism->common_name = $name;
+                $values = array('organism_id' => $organism->id);
+                chado_update_record('organism', $values, $organism);
+              }
+            case 'Includes':
+              $this->addProperty($organism->organism_id, 'other_name', $name, $name_ranks[$type]);
+              break;
+            case 'EquivalentName':
+              $this->addProperty($organism->organism_id, 'equivalent_name', $name, $name_ranks[$type]);
+              break;
+            case 'Anamorph':
+              $this->addProperty($organism->organism_id, 'anamorph', $name, $name_ranks[$type]);
+              break;
+            case 'Name':
+              // skip the Name stanza
+              break;
+            default:
+              print "NOTICE: Skipping unrecognzed name type: $type\n";
+              // do nothing for unrecognized types
+          }
+          $name_ranks[$type]++;
+        }
+      }
+
+      // Generate a nested array structure that can be used for importing the tree.
+      $lineage_depth = preg_split('/;\s*/', $lineage);
+      $parent = $this->tree;
+      $i = 1;
+      foreach ($taxon->LineageEx->children() as $child) {
+        $tid = (string) $child->TaxID;
+        $name = (string) $child->ScientificName;
+        $node_rank = (string) $child->Rank;
+        $node = array(
+          'name' => $name,
+          'depth' => $i,
+          'is_root' => 0,
+          'is_leaf' => 0,
+          'is_internal' => 1,
+          'left_index' => 0,
+          'right_index' => 0,
+          'parent' => $parent,
+          'branch_set' => array(),
+          'parent' => $parent['name'],
+          'properties' => array(
+            $rank_cvterm->cvterm_id => $node_rank,
+          ),
+        );
+        $parent = $node;
+        $this->addTaxonomyNode($this->tree, $node, $lineage_depth);
+        $i++;
+      }
+      // Now add in the leaf node
+      $node = array(
+        'name' => $sci_name,
+        'depth' => $i,
+        'is_root' => 0,
+        'is_leaf' => 1,
+        'is_internal' => 0,
+        'left_index' => 0,
+        'right_index' => 0,
+        'parent' => $parent['name'],
+        'organism_id' => $organism->organism_id,
+        'properties' => array(
+          $rank_cvterm->cvterm_id => $rank,
+        ),
+      );
+      $this->addTaxonomyNode($this->tree, $node, $lineage_depth);
+
+      // Set the indecies for the tree.
+      tripal_assign_phylogeny_tree_indices($this->tree);
+    }
+  }
+  /**
+   *
+   */
+  private function addTaxonomyNode(&$tree, $node, $lineage_depth) {
+
+    // Get the branch set for the tree root.
+    $branch_set = &$tree['branch_set'];
+
+    // Iterate through the tree up until the depth where this node will
+    // be placed.
+    $node_depth = $node['depth'];
+    for ($i = 1; $i <= $node_depth; $i++) {
+      // Iterate through any existing nodes in the branch set to see if
+      // the node name matches the correct name for the lineage at this
+      // depth. If it matches then it is inside of this branch set that
+      // we will place the node.
+      for ($j = 0; $j < count($branch_set); $j++) {
+        // If this node already exists in the tree then return.
+        if ($branch_set[$j]['name'] == $node['name'] and
+            $branch_set[$j]['depth'] = $node['depth']) {
+          return;
+        }
+        // Otherwise, set the branch to be the current branch and continue.
+        if ($branch_set[$j]['name'] == $lineage_depth[$i-1]) {
+          $branch_set = &$branch_set[$j]['branch_set'];
+          break;
+        }
+      }
+    }
+    // Add the node to the last branch set.  This should be where this node goes.
+    $branch_set[] = $node;
+  }
+
+  /**
+   * Retrieves a property for a given organism.
+   *
+   * @param $organism_id
+   *   The organism ID to which the property is added.
+   * @param $term_name
+   *   The name of the organism property term.  This term must be
+   *   present in the 'organism_property' cv.
+   * @param $rank
+   *   The order for this property. The first instance of this term for
+   *   this organism should be zero. Defaults to zero.
+   * @return
+   *   The property object.
+   */
+  private function getProperty($organism_id, $term_name, $rank = 0) {
+    $record = array(
+      'table' => 'organism',
+      'id' => $organism_id
+    );
+    $property = array(
+      'type_name' => $term_name,
+      'cv_name' => 'organism_property',
+      'value' => $value,
+      'rank' => $rank
+    );
+
+    return chado_get_property($record, $property);
+  }
+
+  /**
+   * Adds a property to an organism node.
+   *
+   * @param $organism_id
+   *   The organism ID to which the property is added.
+   * @param $term_name
+   *   The name of the organism property term.  This term must be
+   *   present in the 'organism_property' cv.
+   * @param $value
+   *   The value of the property.
+   * @param $rank
+   *   The order for this property. The first instance of this term for
+   *   this organism should be zero. Defaults to zero.
+   */
+  private function addProperty($organism_id, $term_name, $value, $rank = 0) {
+    if (!$value) {
+      return;
+    }
+
+    $record = array(
+      'table' => 'organism',
+      'id' => $organism_id
+    );
+    $property = array(
+      'type_name' => $term_name,
+      'cv_name' => 'organism_property',
+      'value' => $value
+    );
+    // Delete all properties of this type if the rank is zero.
+    if ($rank == 0) {
+      chado_delete_property($record, $property);
+    }
+    chado_insert_property($record, $property);
+  }
+
+  /**
+   *
+   * @param unknown $organism_id
+   * @param unknown $taxId
+   */
+  private function addDbxref($organism_id, $taxId) {
+    $db = tripal_get_db(array('name' => 'NCBITaxon'));
+    $values = array(
+      'db_id' => $db->db_id,
+      'accession' => $taxId
+    );
+    $dbxref = tripal_insert_dbxref($values);
+
+    $values = array(
+      'dbxref_id' => $dbxref->dbxref_id,
+      'organism_id' => $organism_id,
+    );
+    chado_insert_record('organism_dbxref', $values);
+  }
+}

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

@@ -78,10 +78,12 @@ function tripal_pub_remote_validate_form_PMID($form, $form_state) {
  * @ingroup tripal_pub
  */
 function tripal_pub_remote_search_PMID($search_array, $num_to_retrieve, $page) {
-  // convert the terms list provicded by the caller into a string with words
+  // convert the terms list provided by the caller into a string with words
   // separated by a '+' symbol.
   $num_criteria = $search_array['num_criteria'];
-  $days = $search_array['days'];
+  $days = NULL;
+  if (isset($search_array['days']))
+    $days = $search_array['days'];
 
   $search_str = '';
 

+ 13 - 11
tripal_chado/includes/loaders/tripal_chado.pub_importers.inc

@@ -22,7 +22,7 @@ function tripal_pub_importers_list() {
     drupal_set_message(t('The Tripal Pub vocabulary is currently not loaded. ' .
         'This vocabulary is required to be loaded before importing of ' .
         'publications.  <br>Please !import',
-        array('!import' => l('load the Tripal Publication vocabulary', 'admin/tripal/loaders/obo_loader'))), 'warning');
+        array('!import' => l('load the Tripal Publication vocabulary', 'aadmin/tripal/loaders/chado_vocabs/obo_loader'))), 'warning');
   }
 
   // clear out the session variable when we view the list.
@@ -117,12 +117,12 @@ function tripal_pub_importer_setup_page($action = 'new', $pub_import_id = NULL)
   $values = array('name' => 'tripal_pub');
   $tpub_cv = chado_select_record('cv', array('cv_id'), $values);
   if (count($tpub_cv) == 0) {
-    drupal_set_message(t('Before importing publications you must first ') . l(t('load the Tripal Pub Ontology'), 'admin/tripal/tripal_cv/obo_loader'), 'error');
+    drupal_set_message(t('Before importing publications you must first ') . l(t('load the Tripal Pub Ontology'), 'admin/tripal/loaders/chado_vocabs/obo_loader'), 'error');
   }
   $values = array('name' => 'tripal_contact');
   $tpub_cv = chado_select_record('cv', array('cv_id'), $values);
   if (count($tpub_cv) == 0) {
-    drupal_set_message(t('If you want to create contact pages for authors, you must first ') . l(t('load the Tripal Contact Ontology'), 'admin/tripal/tripal_cv/obo_loader'), 'error');
+    drupal_set_message(t('If you want to create contact pages for authors, you must first ') . l(t('load the Tripal Contact Ontology'), 'admin/tripal/loaders/chado_vocabs/obo_loader'), 'error');
   }
 
   if(!extension_loaded ('yaz')){
@@ -1222,32 +1222,35 @@ function tripal_pub_add_authors($pub_id, $authors, $do_contact) {
     // remove the 'valid' property as we don't have a CV term for it
     unset($author['valid']);
 
+    $values = array(
+      'pub_id' => $pub_id,
+      'rank' => $rank,
+    );
+
     // construct the contact.name field using the author information
     $name = '';
     $type = 'Person';
     if (isset($author['Given Name'])) {
       $name .= $author['Given Name'];
+      $values['givennames'] = $author['Given Name'];
     }
     if (isset($author['Surname'])) {
       $name .= ' ' . $author['Surname'];
+      $values['surname'] = substr($author['Surname'], 0, 100);
     }
     if (isset($author['Suffix'])) {
       $name .= ' ' . $author['Suffix'];
+      $values['suffix'] = $author['Suffix'];
     }
     if (isset($author['Collective'])) {
       $name = $author['Collective'];
       $type = 'Collective';
+      if (!isset($author['Surname']))
+        $values['surname'] = substr($author['Collective'], 0, 100);
     }
     $name = trim($name);
 
     // add an entry to the pubauthors table
-    $values = array(
-      'pub_id' => $pub_id,
-      'rank' => $rank,
-      'surname' => $author['Surname'] ? substr($author['Surname'], 0, 100) : substr($author['Collective'], 0, 100),
-      'givennames' => $author['Given Name'],
-      'suffix' => $author['Suffix'],
-    );
     $options = array('statement_name' => 'ins_pubauthor_idrasugisu');
     $pubauthor = chado_insert_record('pubauthor', $values, $options);
 
@@ -1694,4 +1697,3 @@ function tripal_pub_citation_form_submit(&$form_state) {
   $options [0] = $form_state['options']['#value'];
   tripal_add_job("Create citations ($options[0])", 'tripal_pub', 'tripal_pub_create_citations', $options, $user->uid);
 }
-

+ 0 - 368
tripal_chado/includes/loaders/tripal_chado.taxonomy_importer.inc

@@ -1,368 +0,0 @@
-<?php
-
-/**
- *
- */
-function tripal_chado_taxonomy_load_form($form, &$form_state) {
-  $form['instructions'] = array(
-    '#type' => 'item',
-    '#markup' => '',
-  );
-
-  $form['import_existing'] = array(
-    '#type' => 'checkbox',
-    '#title' => 'Import taxonomy for existing species.',
-    '#description' =>  t('The NCBI Taxonmic Importer examines the organisms
-      currently present in the database and queries NCBI for the
-      taxonomic details.  If the importer is able to match the
-      genus and species with NCBI the species details will be imported,
-      and a page containing the taxonomic tree will be created.'),
-  );
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#name' => 'import',
-    '#value' => 'Submit',
-  );
-  return $form;
-}
-
-/**
- *
- * @param unknown $form
- * @param unknown $form_state
- */
-function tripal_chado_taxonomy_load_form_validate($form, &$form_state) {
-  global $user;
-
-  if (!$form_state['values']['import_existing']) {
-    form_set_error('import_exists', 'Please confirm the import by clicking the checkbox.');
-  }
-}
-
-/**
- *
- * @param unknown $form
- * @param unknown $form_state
- */
-function tripal_chado_taxonomy_load_form_submit($form, &$form_state) {
-  global $user;
-
-  if ($form_state['values']['import_existing']) {
-    $args = array();
-    $includes = array();
-    $includes[] = module_load_include('inc', 'tripal_chado', 'includes/loaders/tripal_chado.taxonomy_importer');
-    tripal_add_job("Import NCBI Taxonomy", 'tripal_chado',
-      'tripal_chado_ncbi_taxonomy_import', $args, $user->uid, 10, $includes);
-  }
-}
-
-/**
- *
- * @param unknown $job_id
- */
-function tripal_chado_ncbi_taxonomy_import($job_id) {
-
-  print "\nNOTE: Importing of NCBI taxonomy data is performed using a database transaction. \n" .
-    "If the load fails or is terminated prematurely then the entire set of \n" .
-    "insertions/updates is rolled back and will not be found in the database\n\n";
-
-  $transaction = db_transaction();
-  try {
-    // TDDO: there should be an API function named tripal_insert_analysis().
-    // But until then we have to insert the analysis manually.
-    // Get the version of this module for the analysis record:
-    $info = system_get_info('module', 'tripal_chado');
-    $version = $info['version'];
-    $analysis_name = 'NCBI Taxonomy Tree Import';
-
-    // If the analysis record already exists then don't add it again.
-    $analysis = chado_select_record('analysis', array('*'), array('name' => $analysis_name));
-    if (count($analysis) == 0) {
-      $values = array(
-        'name' => 'NCBI Taxonomy Tree Import',
-        'description' => 'Used to import NCBI taxonomy details for organisms in this database.',
-        'program' => 'Tripal Phylogeny Module NCBI Taxonomy Importer',
-        'programversion' => $version,
-        'sourcename' => 'NCBI Taxonomy',
-        'sourceuri' => 'http://www.ncbi.nlm.nih.gov/taxonomy',
-      );
-      $analysis = chado_insert_record('analysis', $values);
-      if (!$analysis) {
-        throw new Exception("Cannot add NCBI Taxonomy Tree Import Analysis.");
-      }
-    }
-    else {
-      $analysis = $analysis[0];
-    }
-
-    // If the tree already exists then don't insert it again.
-    global $site_name;
-    $tree_name = $site_name . 'Taxonomy Tree';
-    $phylotree = chado_select_record('phylotree', array('*'), array('name' => $tree_name));
-    if (count($phylotree) == 0) {
-      // Add the taxonomic tree.
-      $options = array(
-        'name' =>  $site_name . 'Taxonomy Tree',
-        'description' => 'The taxonomic tree of species present on this site. Click a species name for more details.',
-        'leaf_type' => 'taxonomy',
-        'analysis_id' => $analysis->analysis_id,
-        'tree_file' => '/dev/null',
-        'format' => 'taxonomy',
-        'no_load' => TRUE,
-      );
-      $errors = array();
-      $warnings = array();
-      $success = tripal_insert_phylotree($options, $errors, $warnings);
-      if (!$success) {
-        throw new Exception("Cannot add the Taxonomy Tree record.");
-      }
-      $phylotree = (object) $options;
-    }
-    else {
-      $phylotree = $phylotree[0];
-    }
-
-    // Clean out the phylotree in the event this is a reload
-    chado_delete_record('phylonode', array('phylotree_id' => $phylotree->phylotree_id));
-
-    // The taxonomic tree must have a root, so create that first.
-    $tree = array(
-      'name' => 'root',
-      'depth' => 0,
-      'is_root' => 1,
-      'is_leaf' => 0,
-      'is_internal' => 0,
-      'left_index' => 0,
-      'right_index' => 0,
-      'branch_set' => array(),
-    );
-
-    // Get the "rank" cvterm. It requires that the TAXRANK vocabulary is loaded.
-    $rank_cvterm = tripal_get_cvterm(array(
-      'name' => 'rank',
-      'cv_id' => array('name' => 'local')
-    ));
-
-    // Get the list of organisms
-    $sql = "SELECT O.* FROM {organism} O";
-    $organisms = chado_query($sql);
-    while ($organism = $organisms->fetchObject()) {
-      // Build the query string to get the information about this species.
-      $term = $organism->genus . ' ' . $organism->species;
-      $term = urlencode($term);
-      $search_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?".
-        "db=taxonomy" .
-        "&term=$term";
-
-      // Get the search response from NCBI.
-      $rfh = fopen($search_url, "r");
-      $xml_text = '';
-      while (!feof($rfh)) {
-        $xml_text .= fread($rfh, 255);
-      }
-      fclose($rfh);
-
-      // Parse the XML to get the taxonomy ID
-      $xml = new SimpleXMLElement($xml_text);
-      if ($xml) {
-        $taxid = (string) $xml->IdList->Id;
-        if ($taxid) {
-          print "$taxid\t$organism->genus $organism->species\n";
-          // If we have a taxonomy ID we can now get the details.
-          $fetch_url = "http://www.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?".
-            "db=taxonomy" .
-            "&id=$taxid";
-          // Get the search response from NCBI.
-          $rfh = fopen($fetch_url, "r");
-          $xml_text = '';
-          while (!feof($rfh)) {
-            $xml_text .= fread($rfh, 255);
-          }
-          fclose($rfh);
-
-          $xml = new SimpleXMLElement($xml_text);
-          if ($xml) {
-            $taxon = $xml->Taxon;
-
-            // Add in the organism properties
-            $lineage = (string) $taxon->Lineage;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'lineage', $lineage);
-
-            $genetic_code = (string) $taxon->GeneticCode->GCId;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'genetic_code', $genetic_code);
-
-            $genetic_code_name = (string) $taxon->GeneticCode->GCName;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'genetic_code_name', $genetic_code_name);
-
-            $mito_genetic_code = (string) $taxon->MitoGeneticCode->MGCId;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'mitochondrial_genetic_code', $mito_genetic_code);
-
-            $mito_genetic_code_name = (string) $taxon->MitoGeneticCode->MGCName;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'mitochondrial_genetic_code_name', $mito_genetic_code_name);
-
-            $division = (string) $taxon->Division;
-            tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'division', $division);
-
-            $name_ranks = array();
-            foreach ($taxon->OtherNames->children() as $child) {
-              $type = $child->getName();
-              $name = (string) $child;
-              if (!array_key_exists($type, $name_ranks)) {
-                $name_ranks[$type] = 0;
-              }
-              switch ($type) {
-                case 'GenbankCommonName':
-                  tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'genbank_common_name', $name, $name_ranks[$type]);
-                  break;
-                case 'Synonym':
-                case 'GenbankSynonym':
-                  tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'synonym', $name, $name_ranks[$type]);
-                  break;
-                case 'CommonName':
-                case 'Includes':
-                  tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'other_name', $name, $name_ranks[$type]);
-                  break;
-                case 'EquivalentName':
-                  tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'equivalent_name', $name, $name_ranks[$type]);
-                  break;
-                case 'Anamorph':
-                  tripal_chado_taxonomy_add_organism_property($organism->organism_id, 'anamorph', $name, $name_ranks[$type]);
-                  break;
-                case 'Name':
-                  // skip the Name stanza
-                  break;
-                default:
-                  print "NOTICE: Skipping unrecognzed name type: $type\n";
-                  // do nothing for unrecognized types
-              }
-              $name_ranks[$type]++;
-            }
-
-            // Generate a nested array structure that can be used for importing the tree.
-            $parent = (string) $taxon->ParentTaxId;
-            $rank = (string) $taxon->Rank;
-            $sci_name = (string) $taxon->ScientificName;
-            $lineage_depth = preg_split('/;\s*/', $lineage);
-            $parent = $tree;
-            $i = 1;
-            foreach ($taxon->LineageEx->children() as $child) {
-              $tid = (string) $child->TaxID;
-              $name = (string) $child->ScientificName;
-              $node_rank = (string) $child->Rank;
-              $node = array(
-                'name' => $name,
-                'depth' => $i,
-                'is_root' => 0,
-                'is_leaf' => 0,
-                'is_internal' => 1,
-                'left_index' => 0,
-                'right_index' => 0,
-                'parent' => $parent,
-                'branch_set' => array(),
-                'parent' => $parent['name'],
-                'properties' => array(
-                  $rank_cvterm->cvterm_id => $node_rank,
-                ),
-              );
-              $parent = $node;
-              tripal_chado_taxonomy_import_add_node($tree, $node, $lineage_depth);
-              $i++;
-            }
-            // Now add in the leaf node
-            $node = array(
-              'name' => $sci_name,
-              'depth' => $i,
-              'is_root' => 0,
-              'is_leaf' => 1,
-              'is_internal' => 0,
-              'left_index' => 0,
-              'right_index' => 0,
-              'parent' => $parent['name'],
-              'organism_id' => $organism->organism_id,
-              'properties' => array(
-                $rank_cvterm->cvterm_id => $rank,
-              ),
-            );
-            tripal_chado_taxonomy_import_add_node($tree, $node, $lineage_depth);
-
-            // Set the indecies for the tree.
-            tripal_assign_phylogeny_tree_indices($tree);
-          } // end: if ($xml) { ...
-        } // end: if ($taxid) { ...
-      } // end: if ($xml) { ...
-    } // end: while ($organism = $organisms->fetchObject()) { ...
-    // print json_encode(($tree));
-
-    // Now add the tree
-    $options = array('taxonomy' => 1);
-    tripal_phylogeny_import_tree($tree, $phylotree, $options);
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    print "\n"; // make sure we start errors on new line
-    watchdog_exception('tripal_chado', $e);
-    print "FAILED: Rolling back database changes...\n";
-  }
-}
-
-/**
- *
- * @param unknown $node
- */
-function tripal_chado_taxonomy_import_add_node(&$tree, $node, $lineage_depth) {
-
-   // Get the branch set for the tree root.
-   $branch_set = &$tree['branch_set'];
-
-   // Iterate through the tree up until the depth where this node will
-   // be placed.
-   $node_depth = $node['depth'];
-   for ($i = 1; $i <= $node_depth; $i++) {
-     // Iterate through any existing nodes in the branch set to see if
-     // the node name matches the correct name for the lineage at this
-     // depth. If it matches then it is inside of this branch set that
-     // we will place the node.
-     for ($j = 0; $j < count($branch_set); $j++) {
-       // If this node already exists in the tree then return.
-       if ($branch_set[$j]['name'] == $node['name'] and
-           $branch_set[$j]['depth'] = $node['depth']) {
-         return;
-       }
-       // Otherwise, set the branch to be the current branch and continue.
-       if ($branch_set[$j]['name'] == $lineage_depth[$i-1]) {
-         $branch_set = &$branch_set[$j]['branch_set'];
-         break;
-       }
-     }
-   }
-   // Add the node to the last branch set.  This should be where this node goes.
-   $branch_set[] = $node;
-}
-
-/**
- *
- * @param unknown $organism_id
- * @param unknown $term_name
- * @param unknown $value
- */
-function tripal_chado_taxonomy_add_organism_property($organism_id, $term_name, $value, $rank = 0) {
-  if (!$value) {
-    return;
-  }
-
-  $record = array(
-    'table' => 'organism',
-    'id' => $organism_id
-  );
-  $property = array(
-    'type_name' => $term_name,
-    'cv_name' => organism_property,
-    'value' => $value
-  );
-  // Delete all properties of this type if the rank is zero.
-  if ($rank == 0) {
-    chado_delete_property($record, $property);
-  }
-  chado_insert_property($record, $property);
-}

+ 0 - 1
tripal_chado/includes/setup/tripal_chado.chado_v1_1.inc

@@ -3,7 +3,6 @@
  * Create a legacy custom chado table (analysisfeatureprop) to store properties of
  * analysisfeature links.
  *
- * @ingroup tripal_analysis
  */
 function tripal_chado_add_analysisfeatureprop_table() {
 

+ 0 - 1
tripal_chado/includes/setup/tripal_chado.chado_vx_x.inc

@@ -352,7 +352,6 @@ function tripal_chado_add_organism_feature_count_mview() {
 /**
  * Creates a view showing the link between an organism & it's analysis through associated features.
  *
- * @ingroup tripal_analysis
  */
 function tripal_chado_add_analysis_organism_mview() {
   $view_name = 'analysis_organism';

+ 47 - 8
tripal_chado/includes/setup/tripal_chado.setup.inc

@@ -240,7 +240,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'OBI', 'accession' => '0100026'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -261,7 +263,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'operation', 'accession' => '2945'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -282,7 +286,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'local', 'accession' => 'project'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -303,7 +309,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'data', 'accession' => '1274'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -335,7 +343,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'TPUB', 'accession' => '0000002'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+        $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -376,7 +386,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'SO', 'accession' => '0000704'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -398,7 +410,9 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'SO', 'accession' => '0000234'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);
@@ -419,7 +433,32 @@ function tripal_chado_prepare_chado($job = NULL) {
       )
     );
     $term = tripal_load_term_entity(array('vocabulary' => 'sep', 'accession' => '00195'));
-    $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    if ($term) {
+        $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
+    if (!$term or !$bundle) {
+      if (!tripal_create_bundle($args, $error)) {
+        throw new Exception($error['!message']);
+      }
+    }
+    if ($report_progress) {
+      $job->setProgress(99);
+    }
+
+    // Create the 'Phylogenetic tree' entity type.
+    $error = '';
+    $args = array(
+      'vocabulary' => 'data',
+      'accession' => '0872',
+      'term_name' => 'Phylogenetic tree',
+      'storage_args' => array(
+        'data_table' => 'phylotree',
+      )
+    );
+    $term = tripal_load_term_entity(array('vocabulary' => 'data', 'accession' => '0872'));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
     if (!$term or !$bundle) {
       if (!tripal_create_bundle($args, $error)) {
         throw new Exception($error['!message']);

+ 13 - 1
tripal_chado/includes/tripal_chado.entity.inc

@@ -152,7 +152,7 @@ function tripal_chado_tripal_default_title_format($bundle, $available_tokens) {
     }
     else {
       $format[] = array(
-        'format' => '[taxrank__genus] [taxrank__species] [taxrank__infraspecies]',
+        'format' => '[taxrank__genus] [taxrank__species] [taxrank__infraspecific_taxon]',
         'weight' => -5
       );
     }
@@ -211,6 +211,18 @@ function tripal_chado_tripal_default_title_format($bundle, $available_tokens) {
       'weight' => -5,
     );
   }
+  if ($table == 'phylotree') {
+    $format[] = array(
+      'format' => '[schema__name]',
+      'weight' => -5,
+    );
+  }
+  if ($table == 'library') {
+    $format[] = array(
+      'format' => '[schema__name]',
+      'weight' => -5,
+    );
+  }
   return $format;
 }
 

+ 40 - 2
tripal_chado/includes/tripal_chado.field_storage.inc

@@ -519,7 +519,7 @@ function tripal_chado_field_storage_query($query) {
         $field_obj = new $field_type($field, $instance);
         $field_obj->query($cquery, $condition);
       }
-      // If there is no ChadoField class for this field then add the
+      // If there is not a ChadoField class for this field then add the
       // condition as is.
       else {
         $alias = $field['field_name'];
@@ -532,7 +532,45 @@ function tripal_chado_field_storage_query($query) {
           // Get the base table column that is associated with the term
           // passed as $condition['column'].
           $base_field = tripal_get_chado_semweb_column($chado_table, $condition['column']);
-          $cquery->condition('base.' . $base_field , $condition['value'], $condition['operator']);
+
+          $matches = array();
+          $key = $condition['value'];
+          $op = '=';
+          if (preg_match('/^(.+);(.+)$/', $key, $matches)) {
+            $key = $matches[1];
+            $op = $matches[2];
+          }
+          switch ($op) {
+            case 'eq':
+              $op = '=';
+              break;
+            case 'gt':
+              $op = '>';
+              break;
+            case 'gte':
+              $op = '>=';
+              break;
+            case 'lt':
+              $op = '<';
+              break;
+            case 'lte':
+              $op = '<=';
+              break;
+            case 'ne':
+              $op = '<>';
+              break;
+            case 'contains':
+              $op = 'LIKE';
+              $key = '%' . $key . '%';
+              break;
+            case 'starts':
+              $op = 'LIKE';
+              $key = $key . '%';
+              break;
+            default:
+              $op = '=';
+          }
+          $cquery->condition('base.' . $base_field , $key, $op);
         }
         if ($chado_table != $base_table) {
           // TODO: I don't think we'll get here because linker fields will

+ 50 - 0
tripal_chado/includes/tripal_chado.fields.inc

@@ -433,6 +433,21 @@ function tripal_chado_bundle_fields_info_custom(&$info, $details, $entity_type,
       ),
     );
   }
+
+  // Add field for viewing the phylogenetic tree.
+  if ($table_name == 'phylotree') {
+    $field_name = 'operation__phylotree_vis';
+    $field_type = 'operation__phylotree_vis';
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'type' => $field_type,
+      'cardinality' => 1,
+      'locked' => FALSE,
+      'storage' => array(
+        'type' => 'field_chado_storage',
+      ),
+    );
+  }
 }
 
 /**
@@ -1526,6 +1541,40 @@ function tripal_chado_bundle_instances_info_custom(&$info, $entity_type, $bundle
       ),
     );
   }
+
+  // Phylotree Viewer
+  if ($table_name == 'phylotree') {
+    $field_name = 'operation__phylotree_vis';
+    $schema = chado_get_schema('phylotree');
+    $pkey = $schema['primary key'][0];
+    $info[$field_name] = array(
+      'field_name' => $field_name,
+      'entity_type' => $entity_type,
+      'bundle' => $bundle->name,
+      'label' => 'Tree View',
+      'description' => 'Rendering of a phylogenetic tree.',
+      'required' => FALSE,
+      'settings' => array(
+        'auto_attach' => FALSE,
+        'chado_table' => 'phylotree',
+        'chado_column' => 'type_id',
+        'base_table' => 'phylotree',
+      ),
+      'widget' => array(
+        'type' => 'operation__phylotree_vis_widget',
+        'settings' => array(
+          'display_label' => 0,
+        ),
+      ),
+      'display' => array(
+        'default' => array(
+          'label' => 'hidden',
+          'type' => 'operation__phylotree_vis_formatter',
+          'settings' => array(),
+        ),
+      ),
+    );
+  }
 }
 
 /**
@@ -2105,6 +2154,7 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
       ),
     );
   }
+
 }
 
 /**

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

@@ -14,7 +14,6 @@ function tripal_chado_map_cvterms() {
 
     // Iterate through the referring tables to see what records are there.
     foreach ($base_tables as $tablename) {
-      print "Examining $tablename...\n";
       $ref_schema = chado_get_schema($tablename);
       $fkeys = $ref_schema['foreign keys'];
       if (!isset($fkeys['cvterm']['columns'])) {
@@ -126,4 +125,4 @@ function tripal_chado_add_cvterm_mapping($cvterm_id, $tablename, $chado_field) {
     ->execute()
     ;
   }
-}
+}

+ 5 - 5
tripal_chado/includes/tripal_chado.migrate.inc

@@ -151,7 +151,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
          ";
         $ana_count = chado_query($sql)->fetchField();
         if ($ana_count > 0) {
-          $key = urlencode('tv3_content_type--local--analysis--analysis');
+          $key  = urlencode('tv3_content_type--operation--2945--analysis');
           $form['step2']['step2_container']['tv3_content_type'][$key] = array(
             '#type' => 'checkbox',
             '#title' => 'Analysis (' . $ana_count . ')',
@@ -215,7 +215,7 @@ function tripal_chado_migrate_form($form, &$form_state) {
           ";
         $bm_count = chado_query($sql)->fetchField();
         if ($bm_count > 0) {
-          $key = urlencode('tv3_content_type--local--biomaterial--biomaterial');
+          $key = urlencode('tv3_content_type--sep--1095--biological sample');
           $form['step2']['step2_container']['tv3_content_type'][$key] = array(
             '#type' => 'checkbox',
             '#title' => 'Biomaterial (' . $bm_count . ')',
@@ -684,9 +684,9 @@ function tripal_chado_migrate_map_types($tv2_content_types) {
   }
   else if ($table == 'biomaterial') {
     array_push($types, array(
-      'vocabulary' => 'local',
-      'accession' => 'biomaterial',
-      'term_name' => 'biomaterial',
+      'vocabulary' => 'sep',
+      'accession' => '1095',
+      'term_name' => 'biological sample',
       'storage_args' => array (
         'data_table' => $table
       )

+ 514 - 0
tripal_chado/includes/tripal_chado.phylotree.inc

@@ -0,0 +1,514 @@
+<?php
+/**
+ * Prepares a phylogenetic tree for viewing.
+ *
+ * @param $phylotree
+ */
+function tripal_phylogeny_prepare_tree_viewer($phylotree) {
+
+  // Don't prepare for viewing more than once.
+  if (property_exists($phylotree, 'prepared_to_view') and
+      $phylotree->prepared_to_view == TRUE) {
+    return;
+  }
+
+  $module_path = drupal_get_path('module', 'tripal_chado');
+
+  drupal_add_js('https://d3js.org/d3.v3.min.js', 'external');
+
+  drupal_add_js("$module_path/theme/js/d3.phylogram.js");
+  drupal_add_js("$module_path/theme/js/tripal_phylogeny.js");
+  drupal_add_css("$module_path/theme/css/tripal_phylogeny.css");
+
+  drupal_add_library('system', 'ui.dialog');
+
+  // Don't show tick marks for the taxonomy tree.
+  $skip_ticks = 0;
+  if ($phylotree->type_id->name == 'taxonomy' or $phylotree->type_id->name == 'Species tree') {
+    $skip_ticks = 1;
+  }
+
+  // Get the node colors as set by the administrator.
+  $colors = array();
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+  foreach ($color_defaults as $i => $details) {
+    if ($details['organism']) {
+      // Strip the [id:xxx] from the name
+      $organism_id = preg_replace('/^.+\[id: (\d+)\].*$/', '\1', $details['organism']);
+      $colors[$organism_id] =  $details['color'];
+    }
+  }
+
+  drupal_add_js(array(
+    'tripal_chado' => array(
+      'phylotree_url' => url('phylotree/' . $phylotree->phylotree_id),
+      'phylotree_theme_url' => url($module_path . '/theme'),
+      'tree_options' => array(
+        'phylogram_width' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+        'root_node_size' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+        'interior_node_size' => variable_get('tripal_phylogeny_default_interior_node_size', 1),
+        'leaf_node_size' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+        'skipTicks' => $skip_ticks,
+      ),
+      'org_colors' => $colors,
+    ),
+  ), 'setting');
+
+  if (!property_exists($phylotree, 'has_nodes')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the phylogram.
+    $values  = array('phylotree_id' => $phylotree->phylotree_id);
+    $options = array('limit' => 1, 'offset' => 0, 'has_record' => 1);
+    $phylotree->has_nodes = chado_select_record('phylonode', array('phylonode_id'), $values, $options);
+  }
+  if (!property_exists($phylotree, 'has_features')) {
+    // If the nodes haven't loaded then set a value so the template can
+    // choose not to show the circular dendrogram. The chado_select_record()
+    // API call can't do this query so we have to do it manually.
+    $sql = "
+      SELECT count(*) as num_features
+      FROM {phylonode}
+      WHERE NOT feature_id IS NULL and phylotree_id = :phylotree_id
+      LIMIT 1 OFFSET 0
+    ";
+    $phylotree->has_features = chado_query($sql, array(':phylotree_id' => $phylotree->phylotree_id))->fetchField();
+  }
+
+  $phylotree->prepared_to_view = TRUE;
+}
+
+/**
+ * Get json representation of a phylotree id.
+ *
+ * This function is meant to be called via AJAX.
+ *
+ * @param int $phylotree_id
+ *   the ID of the phylotree node.
+ *
+ * @return string json
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_ajax_get_tree_json($phylotree_id) {
+
+  $phylotree = chado_generate_var('phylotree', array('phylotree_id' => $phylotree_id));
+
+  // For backwards compatibility with Tripal v2 and the legacy modules of
+  // Tripal v3 we have two different SQL statements.
+  if (module_exists('tripal_phylogeny')) {
+    // This SQL gets all of the phylonodes for a given tree as well as the
+    // features and organisms with which it is associated.  Each phylonode
+    // can be associated with an organism in one of two ways: 1) via a
+    // feature linked by the phylonode.feature_id field or 2) via a
+    // a record in the phylonde_organsim table.  Therefore both types of
+    // organism records are returned in the query below, but those
+    // retrieved via a FK link on features are prefixed with 'fo_'.
+    $sql = "
+      SELECT
+        n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
+        f.feature_id, f.name AS feature_name,
+        cvt.name AS cvterm_name,
+        o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
+        fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
+        fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species,
+        cf.nid AS feature_nid,
+        fco.nid AS fo_organism_nid,
+        co.nid AS organism_nid
+      FROM {phylonode} n
+        LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
+        LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
+        LEFT OUTER JOIN [chado_feature] cf        ON cf.feature_id = f.feature_id
+        LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
+        LEFT OUTER JOIN [chado_organism] fco      ON fco.organism_id = fo.organism_id
+        LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
+        LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
+        LEFT OUTER JOIN [chado_organism] co       ON co.organism_id = o.organism_id
+      WHERE n.phylotree_id = :phylotree_id
+    ";
+  }
+  else {
+    $sql = "
+      SELECT
+        n.phylonode_id, n.parent_phylonode_id, n.label AS name, n.distance AS length,
+        f.feature_id, f.name AS feature_name,
+        cvt.name AS cvterm_name,
+        o.organism_id, o.common_name, o.abbreviation, o.genus, o.species,
+        fo.organism_id AS fo_organism_id, fo.common_name AS fo_common_name,
+        fo.abbreviation AS fo_abbreviation, fo.genus as fo_genus, fo.species AS fo_species
+      FROM {phylonode} n
+        LEFT OUTER JOIN {cvterm} cvt              ON n.type_id = cvt.cvterm_id
+        LEFT OUTER JOIN {feature} f               ON n.feature_id = f.feature_id
+        LEFT OUTER JOIN {organism} fo             ON f.organism_id = fo.organism_id
+        LEFT OUTER JOIN {phylonode_organism} po   ON po.phylonode_id = n.phylonode_id
+        LEFT OUTER JOIN {organism} o              ON PO.organism_id = o.organism_id
+      WHERE n.phylotree_id = :phylotree_id
+    ";
+  }
+  $args = array(':phylotree_id' => $phylotree_id);
+  $results = chado_query($sql, $args);
+
+  // Fetch all the phylonodes into an assoc array indexed by phylonode_id.
+  // Convert from resultset record to array, fixing datatypes. chado_query
+  // returns numeric as string and fun stuff like that.
+  $phylonodes = array();
+  $root_phylonode_ref = null;
+
+  if ($results) {
+    while($r = $results->fetchObject()) {
+      $phylonode_id = (int) $r->phylonode_id;
+
+      // expect all nodes to have these properties
+      $node = array(
+        'phylonode_id' => $phylonode_id,
+        'parent_phylonode_id' => (int) $r->parent_phylonode_id,
+        'length' => (double) $r->length,
+        'cvterm_name' => $r->cvterm_name
+      );
+
+      // If the nodes are taxonomic then set an equal distance
+      if ($phylotree->type_id->name == 'taxonomy' or $phylotree->type_id->name == 'Speces tree') {
+        $node['length'] = 0.001;
+      }
+
+      // Other props may exist only for leaf nodes
+      if ($r->name) {
+        $node['name'] = $r->name;
+      }
+      // If this node is associated with a feature then add in the details
+      if ($r->feature_id) {
+        $node['feature_id'] = (int) $r->feature_id;
+        $node['feature_name'] = $r->feature_name;
+        if (module_exists('tripal_phylogeny')) {
+          $node['feature_nid'] = (int) $r->feature_nid;
+        }
+        else {
+          $entity_id = chado_get_record_entity_by_table('feature', $r->feature_id);
+          $node['feature_eid'] = (int) $entity_id;
+        }
+      }
+      // Add in the organism fields when they are available via the
+      // phylonode_organism table.
+      if ($r->organism_id) {
+        $node['organism_id'] = (int) $r->organism_id;
+        $node['common_name'] = $r->common_name;
+        $node['abbreviation'] = $r->abbreviation;
+        $node['genus'] = $r->genus;
+        $node['species'] = $r->species;
+        if (module_exists('tripal_phylogeny')) {
+          $node['organism_nid'] = (int) $r->organism_nid;
+        }
+        else {
+          $entity_id = chado_get_record_entity_by_table('organism', $r->organism_id);
+          $node['organism_eid'] = (int) $entity_id;
+        }
+        // If the node does not have a name but is linked to an organism
+        // then set the name to be that of the genus and species.
+        if (!$r->name) {
+          $node['name'] = $r->genus . ' ' . $r->species;
+        }
+      }
+      // Add in the organism fields when they are available via the
+      // the phylonode.feature_id FK relationship.
+      if ($r->fo_organism_id) {
+        $node['fo_organism_id'] = (int) $r->fo_organism_id;
+        $node['fo_common_name'] = $r->fo_common_name;
+        $node['fo_abbreviation'] = $r->fo_abbreviation;
+        $node['fo_genus'] = $r->fo_genus;
+        $node['fo_species'] = $r->fo_species;
+        if (module_exists('tripal_phylogeny')) {
+          $node['fo_organism_nid'] = (int) $r->fo_organism_nid;
+        }
+        else {
+          $entity_id = chado_get_record_entity_by_table('organism', $r->fo_organism_id);
+          $node['fo_organism_eid'] = (int) $entity_id;
+        }
+      }
+
+      // Add this node to the list, organized by ID.
+      $phylonodes[$phylonode_id] = $node;
+    }
+
+    // Populate the children[] arrays for each node.
+    foreach ($phylonodes as $key => &$node) {
+      if ($node['parent_phylonode_id'] !== 0) {
+        $parent_ref = &$phylonodes[ $node['parent_phylonode_id']];
+        // Append node refernce to children.
+        $parent_ref['children'][] = &$node;
+      }
+      else {
+        $root_phylonode_ref = &$node;
+      }
+    }
+  }
+
+  // dump datastructure as json to browser. drupal sets the mime-type correctly.
+  drupal_json_output($root_phylonode_ref);
+}
+
+
+/**
+ * @file
+ * This file contains the functions used for administration of the module
+ *
+ */
+
+function tripal_phylogeny_admin_phylotrees_listing() {
+  $output = '';
+
+  // set the breadcrumb
+  $breadcrumb = array();
+  $breadcrumb[] = l('Home', '<front>');
+  $breadcrumb[] = l('Administration', 'admin');
+  $breadcrumb[] = l('Tripal', 'admin/tripal');
+  $breadcrumb[] = l('Data Storage', 'admin/tripal/storage');
+  $breadcrumb[] = l('Chado', 'admin/tripal/storage/chado');
+  drupal_set_breadcrumb($breadcrumb);
+
+  // Add the view
+  $view = views_embed_view('tripal_phylogeny_admin_phylotree','default');
+  if (isset($view)) {
+    $output .= $view;
+  }
+  else {
+    $output .= '<p>The Phylotree module uses primarily views to provide an '
+        . 'administrative interface. Currently one or more views needed for this '
+            . 'administrative interface are disabled. <strong>Click each of the following links to '
+                . 'enable the pertinent views</strong>:</p>';
+                $output .= '<ul>';
+                $output .= '<li>'.l('Phylotree View', 'admin/tripal/extension/tripal_phylogeny/views/phylotree/enable').'</li>';
+                $output .= '</ul>';
+  }
+  return $output;
+}
+
+
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_default_plots_form($form, &$form_state) {
+  $form = array();
+
+  $form['plot_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Plot Settings'),
+    '#description' => t('You can customize settings for each plot'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+
+  $form['plot_settings']['phylogram_width'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Tree Width',
+    '#description' => 'Please specify the width in pixels for the phylogram',
+    '#default_value' => variable_get('tripal_phylogeny_default_phylogram_width', 350),
+    '#element_validate' => array(
+      'element_validate_integer_positive'
+    ),
+    '#size' => 5,
+  );
+
+  $form['node_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Node Settings'),
+    '#description' => t('You can customize settings for the nodes on the trees.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+  $form['node_settings']['root_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Root Node Size',
+    '#description' => 'Please specify a size for the root node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_root_node_size', 3),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['interior_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Interor Node Size',
+    '#description' => 'Please specify a size for the interior node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_interior_node_size', 0),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+  $form['node_settings']['leaf_node_size'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Leaf Node Size',
+    '#description' => 'Please specify a size for the leaf node size. If set to zero, the node will not appear.',
+    '#default_value' => variable_get('tripal_phylogeny_default_leaf_node_size', 6),
+    '#element_validate' => array(
+      'element_validate_integer'
+    ),
+    '#size' => 3,
+  );
+
+  // Get the number of organism colors that already exist. If the site admin
+  // has set colors then those settings will be in a Drupal variable which we
+  // will retrieve.  Otherwise the num_orgs defaults to 1 and a single
+  // set of fields is provided.
+  $num_orgs = variable_get("tripal_phylogeny_num_orgs", 1);
+  if (array_key_exists('values', $form_state) and array_key_exists('num_orgs', $form_state['values'])) {
+    $num_orgs = $form_state['values']['num_orgs'];
+  }
+  // The default values for each organism color are provided in a d
+  // Drupal variable that gets set when the form is set.
+  $color_defaults = variable_get("tripal_phylogeny_org_colors", array('1' => array('organism' => '', 'color' => '')));
+
+  $form['node_settings']['desc'] = array(
+    '#type' => 'item',
+    '#title' => t('Node Colors by Organism'),
+    '#markup' => t('If the trees are associated with features (e.g. proteins)
+      then the nodes can be color-coded by their organism.  This helps the user
+      visualize which nodes belong to each organism.  Please enter the
+      name of the organism and it\'s corresponding color in HEX code (e.g. #FF0000 == red).
+      Organisms that are not given a color will be gray.'),
+  );
+  $form['node_settings']['org_table']['num_orgs'] = array(
+    '#type' => 'value',
+    '#value' => $num_orgs,
+  );
+
+  // Iterate through the number of organism colors and add a field for each one.
+  for ($i = 0; $i < $num_orgs; $i++) {
+    $form['node_settings']['org_table']['organism_' . $i] = array(
+      '#type' => 'textfield',
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['organism'] : '',
+      '#autocomplete_path' => "admin/tripal/storage/chado/auto_name/organism",
+      '#description' => t('Please enter the name of the organism.'),
+      '#size' => 30,
+    );
+    $form['node_settings']['org_table']['color_' . $i] = array(
+      '#type' => 'textfield',
+      '#description' => t('Please provide a color in Hex format (e.g. #FF0000).'),
+      '#default_value' => array_key_exists($i, $color_defaults) ? $color_defaults[$i]['color'] : '',
+      '#suffix' => "<div id=\"color-box-$i\" style=\"width: 30px;\"></div>",
+      '#size' => 10,
+    );
+  }
+  $form['node_settings']['org_table']['add'] = array(
+    '#type' => 'submit',
+    '#name' => 'add',
+    '#value' => 'Add',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['remove'] = array(
+    '#type' => 'submit',
+    '#name' => 'remove',
+    '#value' => 'Remove',
+    '#ajax' => array(
+      'callback' => "tripal_phylogeny_default_plots_form_ajax_callback",
+      'wrapper'  => 'tripal_phylogeny_default_plots_form',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+  );
+  $form['node_settings']['org_table']['#theme'] = 'tripal_phylogeny_admin_org_color_tables';
+  $form['node_settings']['org_table']['#prefix'] = '<div id="tripal_phylogeny_default_plots_form">';
+  $form['node_settings']['org_table']['#suffix'] = '</div>';
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#name' => 'submit',
+    '#value' => 'Save Configuration',
+  );
+
+  $form['#submit'][] = 'tripal_phylogeny_default_plots_form_submit';
+
+  return $form;
+}
+
+/**
+ * Validate the phylotree settings forms
+ *
+ * @ingroup tripal_phylogeny
+ */
+function tripal_phylogeny_default_plots_form_validate($form, &$form_state) {
+
+}
+/**
+ *
+ * @param unknown $form
+ * @param unknown $form_state
+ */
+function tripal_phylogeny_default_plots_form_submit($form, &$form_state) {
+  // Rebuild this form after submission so that any changes are reflected in
+  // the flat tables.
+  $form_state['rebuild'] = TRUE;
+
+  if ($form_state['clicked_button']['#name'] == 'submit') {
+    variable_set('tripal_phylogeny_default_phylogram_width', $form_state['values']['phylogram_width']);
+
+    variable_set('tripal_phylogeny_default_root_node_size', $form_state['values']['root_node_size']);
+    variable_set('tripal_phylogeny_default_interior_node_size', $form_state['values']['interior_node_size']);
+    variable_set('tripal_phylogeny_default_leaf_node_size', $form_state['values']['leaf_node_size']);
+
+    $num_orgs = $form_state['values']['num_orgs'];
+    variable_set("tripal_phylogeny_num_orgs", $num_orgs);
+    $colors = array();
+    for ($i = 0; $i < $num_orgs ;$i++) {
+      $colors[$i] = array(
+        'organism' => $form_state['values']['organism_' . $i],
+        'color' => $form_state['values']['color_' . $i]
+      );
+    }
+    variable_set("tripal_phylogeny_org_colors", $colors);
+  }
+  if ($form_state['clicked_button']['#name'] == 'add') {
+    $form_state['values']['num_orgs']++;
+  }
+  if ($form_state['clicked_button']['#name'] == 'remove') {
+    $form_state['values']['num_orgs']--;
+  }
+}
+
+/**
+ *
+ * @param unknown $variables
+ */
+function theme_tripal_phylogeny_admin_org_color_tables($variables){
+  $fields = $variables['element'];
+  $num_orgs = $fields['num_orgs']['#value'];
+  $headers = array('Organism', 'Color', '');
+  $rows = array();
+  for ($i = 0; $i < $num_orgs; $i++) {
+    $add_button = ($i == $num_orgs - 1) ? drupal_render($fields['add']) : '';
+    $del_button = ($i == $num_orgs - 1 and $i != 0) ? drupal_render($fields['remove']) : '';
+    $rows[] = array(
+      drupal_render($fields['organism_' . $i]),
+      drupal_render($fields['color_' . $i]),
+      $add_button . $del_button,
+    );
+  }
+  $table_vars = array(
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => array(),
+    'sticky' => FALSE,
+    'colgroups' => array(),
+    'empty' => '',
+  );
+  $form['orgs']['num_orgs'] = $fields['num_orgs'];
+  return theme('table', $table_vars);
+}
+
+
+/**
+ * Ajax callback function for the gensas_job_view_panel_form.
+ *
+ * @param $form
+ * @param $form_state
+ */
+function tripal_phylogeny_default_plots_form_ajax_callback($form, $form_state) {
+
+  return $form['node_settings']['org_table'];
+}

+ 214 - 165
legacy/tripal_pub/includes/tripal_pub.pub_search.inc → tripal_chado/includes/tripal_chado.pub_search.inc

@@ -7,12 +7,68 @@
  * in Chado.
  */
 
+function tripal_chado_pub_search_admin_form($form, &$form_state) {
+  // -----------------------------------------
+  // add in the fields for selecting which fields are used when search for pubs
+  $form['searching'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Searching Options'),
+    '#description' => t("The list of checkboxes below indicate which fields a user
+      can search with when using the publication search tool.  Check the fields that you want
+      to allow users to search with.  Click the 'Save configuration' button below to save changes."),
+  );
+
+  // get publication properties list
+  $properties = array();
+  $properties[] = 'Any Field';
+  $sql = "
+    SELECT DISTINCT CVTS.cvterm_id, CVTS.name, CVTS.definition
+    FROM {cvtermpath} CVTP
+      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
+      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
+      INNER JOIN {cv} ON CVTO.cv_id = CV.cv_id
+    WHERE CV.name = 'tripal_pub' and
+      (CVTO.name = 'Publication Details' or CVTS.name = 'Publication Type') and
+      NOT CVTS.is_obsolete = 1
+    ORDER BY CVTS.name ASC
+  ";
+  $prop_types = chado_query($sql);
+  while ($prop = $prop_types->fetchObject()) {
+    $properties[$prop->cvterm_id] = $prop->name;
+  }
+  $form['allowed_search_fields'] = array(
+    '#type'    => 'checkboxes',
+    '#options' => $properties,
+    '#default_value' => variable_get('tripal_pub_allowed_search_fields', array()),
+  );
+
+  $form['button'] = array(
+    '#type' => 'submit',
+    '#value' => 'Save configuration',
+    '#name' => 'sumbit',
+  );
+  return $form;
+}
+/**
+ *
+ */
+function tripal_chado_pub_search_admin_form_submit($form, &$form_state) {
+  // set the allowed search fields
+  $allowed_fields = $form_state['values']['allowed_search_fields'];
+  foreach ($allowed_fields as $cvterm_id => $selected) {
+    if (!$selected) {
+      unset($allowed_fields[$cvterm_id]);
+    }
+  }
+  variable_set('tripal_pub_allowed_search_fields', $allowed_fields);
+  drupal_set_message('Changes saved.');
+}
 /**
  * The page that contains the publication search form and the results for the search
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_page() {
+function tripal_chado_pub_search_page() {
 
   // This line may not be required, but on some sites the $_SESSION
   // variable wasn't being set for anonymous users. This line solves that
@@ -22,117 +78,111 @@ function tripal_pub_search_page() {
   $limit = 25;
 
   // generate the search form
-  $form = drupal_get_form('tripal_pub_search_form');
+  $form = drupal_get_form('tripal_chado_pub_search_form');
   $output = drupal_render($form);
 
   // retrieve any results
-  if (array_key_exists('tripal_pub_search_form', $_SESSION) and
-      $_SESSION['tripal_pub_search_form']['perform_search']) {
-    $num_criteria = $_SESSION['tripal_pub_search_form']['num_criteria'];
-    $from_year    = $_SESSION['tripal_pub_search_form']['from_year'];
-    $to_year      = $_SESSION['tripal_pub_search_form']['to_year'];
-
-    $search_array = array();
-    $search_array['num_criteria'] = $num_criteria;
-    $search_array['from_year']    = $from_year;
-    $search_array['to_year']      = $to_year;
-    for ($i = 0; $i <= $num_criteria; $i++) {
-      $search_array['criteria'][$i]['search_terms'] = $_SESSION['tripal_pub_search_form']['criteria'][$i]['search_terms'];
-      $search_array['criteria'][$i]['scope']        = $_SESSION['tripal_pub_search_form']['criteria'][$i]['scope'];
-      $search_array['criteria'][$i]['mode']         = $_SESSION['tripal_pub_search_form']['criteria'][$i]['mode'];
-      $search_array['criteria'][$i]['operation']    = $_SESSION['tripal_pub_search_form']['criteria'][$i]['operation'];
-    }
+  if (array_key_exists('tripal_chado_pub_search_form', $_SESSION) and
+      $_SESSION['tripal_chado_pub_search_form']['perform_search']) {
+        $num_criteria = $_SESSION['tripal_chado_pub_search_form']['num_criteria'];
+        $from_year    = $_SESSION['tripal_chado_pub_search_form']['from_year'];
+        $to_year      = $_SESSION['tripal_chado_pub_search_form']['to_year'];
+
+        $search_array = array();
+        $search_array['num_criteria'] = $num_criteria;
+        $search_array['from_year']    = $from_year;
+        $search_array['to_year']      = $to_year;
+        for ($i = 0; $i <= $num_criteria; $i++) {
+          $search_array['criteria'][$i]['search_terms'] = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['search_terms'];
+          $search_array['criteria'][$i]['scope']        = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['scope'];
+          $search_array['criteria'][$i]['mode']         = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['mode'];
+          $search_array['criteria'][$i]['operation']    = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['operation'];
+        }
+
+        // get the list of publications from the remote database using the search criteria.
+        $page = isset($_GET['page']) ? $_GET['page'] : '0';
+        $offset = $page * $limit;
+        $total_records = 0;
+        $pubs = tripal_search_publications($search_array, $offset, $limit, $total_records);
+        pager_default_initialize($total_records, $limit, 0);
+
+        // iterate through the results and construct the table displaying the publications
+        $rows = array();
+        $i = $page * $limit + 1;
+        foreach ($pubs as $pub) {
+          // get the citation for this publication
+          $values = array(
+            'pub_id' => $pub->pub_id,
+            'type_id' => array(
+              'name' => 'Citation',
+            ),
+          );
+          $citation_rec = chado_generate_var('pubprop', $values);
+          $citation_rec = chado_expand_var($citation_rec, 'field', 'pubprop.value');
+
+          // if we have the citation then use it, otherwise, just use the title
+          $title = htmlspecialchars($pub->title);
+          $result = $title;
+          $link = NULL;
+
+          // tripal v2 link (node)
+          if (module_exists('tripal_pub')) {
+            $nid = chado_get_nid_from_id ('pub', $pub->pub_id);
+            if ($nid) {
+              $link = "/node/$nid";
+            }
+          }
+          // try tripal v3 link (entity), if it exists, update the link to entity
+          $entity_id = tripal_get_chado_entity_id ('pub', $pub->pub_id);
+          if ($entity_id) {
+            $link = "/bio_data/$entity_id";
+          }
+
+          if ($link) {
+            $result = l($title , $link, array('attributes' => array('target' => '_blank')));
+          }
+          if ($citation_rec->value) {
+            $citation = htmlspecialchars($citation_rec->value);
+            $result .= '<br>' . $citation;
+          }
+          $rows[] = array(
+            number_format($i) . ".",
+            $pub->pyear,
+            $result
+          );
+          $i++;
+        }
+
+        $headers = array('', 'Year', 'Reference');
+        $table = array(
+          'header' => $headers,
+          'rows' => $rows,
+          'attributes' => array(
+            'id' => 'tripal-pub-search-results-table',
+            'border' => '0',
+            'class' => array('tripal-data-table')
+          ),
+          'sticky' => TRUE,
+          'caption' => '',
+          'colgroups' => array(),
+          'empty' => 'No publications found',
+        );
+        $results = theme_table($table);
+
+        // generate the pager
+        $pager = array(
+          'tags' => array(),
+          'element' => 0,
+          'parameters' => array(),
+          'quantity' => $limit,
+        );
+        $pager = theme_pager($pager);
 
-    // get the list of publications from the remote database using the search criteria.
-    $page = isset($_GET['page']) ? $_GET['page'] : '0';
-    $offset = $page * $limit;
-    $total_records = 0;
-    $pubs = tripal_search_publications($search_array, $offset, $limit, $total_records);
-    pager_default_initialize($total_records, $limit, 0);
-
-    // iterate through the results and construct the table displaying the publications
-    $rows = array();
-    $i = $page * $limit + 1;
-    foreach ($pubs as $pub) {
-      // get the citation for this publication
-      $values = array(
-        'pub_id' => $pub->pub_id,
-        'type_id' => array(
-          'name' => 'Citation',
-        ),
-      );
-      $citation_rec = chado_generate_var('pubprop', $values);
-      $citation_rec = chado_expand_var($citation_rec, 'field', 'pubprop.value');
-
-      // if we have the citation then use it, otherwise, just use the title
-      $title = htmlspecialchars($pub->title);
-      $result = $title;
-      $link = NULL;
-      // tripal v2 link (node)
-      $nid = chado_get_nid_from_id ('pub', $pub->pub_id);
-      if ($nid) {
-        $link = "/node/$nid";
-      }
-      // try tripal v3 link (entity), if it exists, update the link to entity
-      $entity_id = tripal_get_chado_entity_id ('pub', $pub->pub_id);
-      if ($entity_id) {
-        $link = "/bio_data/$entity_id";
+        // join all to form the results
+        $output .= "<p><b>Found " . number_format($total_records) .
+        " Results</b></br>" . $results . $pager;
       }
-      
-      if ($link) {
-        $result = l($title , $link, array('attributes' => array('target' => '_blank')));
-      }
-      if ($citation_rec->value) {
-        $citation = htmlspecialchars($citation_rec->value);
-        $result .= '<br>' . $citation;
-      }
-      $rows[] = array(
-      number_format($i) . ".",
-      $pub->pyear,
-      $result
-      );
-      $i++;
-    }
-
-    if (count($rows) == 0) {
-      $rows[] = array(
-        array(
-          'data' => 'No results found',
-          'colspan' => 3
-        )
-      );
-    }
-
-    $headers = array('', 'Year', 'Publication');
-    $table = array(
-      'header' => $headers,
-      'rows' => $rows,
-      'attributes' => array(
-        'id' => 'tripal-pub-search-results-table',
-        'border' => '0',
-        'class' => array('tripal-data-table')
-      ),
-      'sticky' => TRUE,
-      'caption' => '',
-      'colgroups' => array(),
-      'empty' => '',
-    );
-    $results = theme_table($table);
-
-    // generate the pager
-    $pager = array(
-      'tags' => array(),
-      'element' => 0,
-      'parameters' => array(),
-      'quantity' => $limit,
-    );
-    $pager = theme_pager($pager);
-
-    // join all to form the results
-    $output .= "<p><b>Found " . number_format($total_records) .
-      " Results</b></br>" . $results . $pager;
-  }
-  return $output;
+      return $output;
 }
 
 /**
@@ -140,7 +190,7 @@ function tripal_pub_search_page() {
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_form($form, &$form_state) {
+function tripal_chado_pub_search_form($form, &$form_state) {
   // Default values can come in the following ways:
   //
   // 1) as elements of the $pub_importer object.  This occurs when editing an existing importer
@@ -162,10 +212,10 @@ function tripal_pub_search_form($form, &$form_state) {
   if (array_key_exists('storage', $form_state)) {
     $num_criteria = $form_state['storage']['num_criteria'];
   }
-  if (array_key_exists('tripal_pub_search_form', $_SESSION)) {
-    $num_criteria = $_SESSION['tripal_pub_search_form']['num_criteria'] ? $_SESSION['tripal_pub_search_form']['num_criteria'] : $num_criteria;
-    $from_year    = $_SESSION['tripal_pub_search_form']['from_year']    ? $_SESSION['tripal_pub_search_form']['from_year']    : '';
-    $to_year      = $_SESSION['tripal_pub_search_form']['to_year']      ? $_SESSION['tripal_pub_search_form']['to_year']      : '';
+  if (array_key_exists('tripal_chado_pub_search_form', $_SESSION)) {
+    $num_criteria = $_SESSION['tripal_chado_pub_search_form']['num_criteria'] ? $_SESSION['tripal_chado_pub_search_form']['num_criteria'] : $num_criteria;
+    $from_year    = $_SESSION['tripal_chado_pub_search_form']['from_year']    ? $_SESSION['tripal_chado_pub_search_form']['from_year']    : '';
+    $to_year      = $_SESSION['tripal_chado_pub_search_form']['to_year']      ? $_SESSION['tripal_chado_pub_search_form']['to_year']      : '';
   }
   if (array_key_exists('values', $form_state)) {
     $from_year    = $form_state['values']['from_year']    ? $form_state['values']['from_year']    : $from_year;
@@ -178,27 +228,27 @@ function tripal_pub_search_form($form, &$form_state) {
 
   if (array_key_exists('triggering_element', $form_state) and
       $form_state['triggering_element']['#name'] == 'add') {
-    $num_criteria++;
+        $num_criteria++;
   }
   if (array_key_exists('triggering_element', $form_state) and
       $form_state['triggering_element']['#name'] == 'remove') {
-    $num_criteria--;
+        $num_criteria--;
   }
 
   $form_state['storage']['num_criteria'] = $num_criteria;
 
   $form['admin-instructions'] = array(
     '#markup'  =>  tripal_set_message(
-      t('Administrators, you can select the fields with which a user can use to search, by checking the desired fields on the ' .
-        l('Publication Module Settings Page', 'admin/tripal/legacy/tripal_pub/configuration', array('attributes' => array('target' => '_blank'))) . '
-        in the section titled "Search Options".  The selected fields will appear in the dropdowns below.'),
+        t('Administrators, you can select the fields with which a user can use to search, by checking the desired fields on the ' .
+        l('Publication Search Settings Page', 'admin/tripal/storage/chado/pub-search-config', array('attributes' => array('target' => '_blank'))) . '
+      in the section titled "Search Options".  The selected fields will appear in the dropdowns below.'),
       TRIPAL_INFO,
       array('return_html' => 1)),
   );
   $form['instructions'] = array(
     '#markup'  =>  t('To search for publications enter keywords in the text boxes below.
-        You can limit your search by selecting the field in the dropdown box. Click the
-        add and remove buttons to add additional fields for searching. '),
+    You can limit your search by selecting the field in the dropdown box. Click the
+    add and remove buttons to add additional fields for searching. '),
   );
 
   // get publication properties list
@@ -230,11 +280,11 @@ function tripal_pub_search_form($form, &$form_state) {
     $mode = '';
 
     // first populate defaults using any values in the SESSION variable
-    if (array_key_exists('tripal_pub_search_form', $_SESSION)) {
-      $search_terms = $_SESSION['tripal_pub_search_form']['criteria'][$i]['search_terms'] ? $_SESSION['tripal_pub_search_form']['criteria'][$i]['search_terms'] : $search_terms;
-      $scope        = $_SESSION['tripal_pub_search_form']['criteria'][$i]['scope']        ? $_SESSION['tripal_pub_search_form']['criteria'][$i]['scope']        : $scope;
-      $mode         = $_SESSION['tripal_pub_search_form']['criteria'][$i]['mode']         ? $_SESSION['tripal_pub_search_form']['criteria'][$i]['mode']         : $mode;
-      $operation    = $_SESSION['tripal_pub_search_form']['criteria'][$i]['operation']    ? $_SESSION['tripal_pub_search_form']['criteria'][$i]['operation']    : $operation;
+    if (array_key_exists('tripal_chado_pub_search_form', $_SESSION)) {
+      $search_terms = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['search_terms'] ? $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['search_terms'] : $search_terms;
+      $scope        = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['scope']        ? $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['scope']        : $scope;
+      $mode         = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['mode']         ? $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['mode']         : $mode;
+      $operation    = $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['operation']    ? $_SESSION['tripal_chado_pub_search_form']['criteria'][$i]['operation']    : $operation;
     }
     if (array_key_exists('values', $form_state)) {
       $search_terms = array_key_exists("search_terms-$i", $form_state['values']) ? $form_state['values']["search_terms-$i"] : $search_terms;
@@ -306,8 +356,8 @@ function tripal_pub_search_form($form, &$form_state) {
           // but we need it or Drupal will run the default validate anyway.
           // we also set #limit_validation_errors to empty so fields that
           // are required that don't have values won't generate warnings.
-          '#submit'   => array('tripal_pub_search_form_ajax_button_submit'),
-          '#validate' => array('tripal_pub_search_form_ajax_button_validate'),
+          '#submit'   => array('tripal_chado_pub_search_form_ajax_button_submit'),
+          '#validate' => array('tripal_chado_pub_search_form_ajax_button_validate'),
           '#limit_validation_errors' => array(),
         );
       }
@@ -329,16 +379,16 @@ function tripal_pub_search_form($form, &$form_state) {
         // but we need it or Drupal will run the default validate anyway.
         // we also set #limit_validation_errors to empty so fields that
         // are required that don't have values won't generate warnings.
-        '#submit'   => array('tripal_pub_search_form_ajax_button_submit'),
-        '#validate' => array('tripal_pub_search_form_ajax_button_validate'),
+        '#submit'   => array('tripal_chado_pub_search_form_ajax_button_submit'),
+        '#validate' => array('tripal_chado_pub_search_form_ajax_button_validate'),
         '#limit_validation_errors' => array(),
       );
     }
   }
   $form['criteria']["date"] = array(
-      '#type'          => 'select',
-      '#options'       => array('Years' => 'Years'),
-      '#attributes'    => array('class' => array('tripal-pub-search-form-scope-select')),
+    '#type'          => 'select',
+    '#options'       => array('Years' => 'Years'),
+    '#attributes'    => array('class' => array('tripal-pub-search-form-scope-select')),
   );
   $form['criteria']["from_year"] = array(
     '#type'          => 'textfield',
@@ -366,7 +416,7 @@ function tripal_pub_search_form($form, &$form_state) {
     '#value'        => t('Reset'),
   );
 
-  $form['criteria']['#theme'] = 'tripal_pub_search_setup_form_elements';
+  $form['criteria']['#theme'] = 'tripal_chado_pub_search_setup_form_elements';
 
   return $form;
 }
@@ -379,7 +429,7 @@ function tripal_pub_search_form($form, &$form_state) {
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_form_ajax_button_submit() {
+function tripal_chado_pub_search_form_ajax_button_submit() {
   $form_state['rebuild'] = TRUE;
 }
 
@@ -388,16 +438,16 @@ function tripal_pub_search_form_ajax_button_submit() {
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_form_ajax_button_validate() {
+function tripal_chado_pub_search_form_ajax_button_validate() {
   // do nothing
 }
 
 /**
- * Validate the tripal_pub_search_form form
+ * Validate the tripal_chado_pub_search_form form
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_form_validate($form, &$form_state) {
+function tripal_chado_pub_search_form_validate($form, &$form_state) {
   $num_criteria = $form_state['storage']['num_criteria'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
@@ -423,11 +473,11 @@ function tripal_pub_search_form_validate($form, &$form_state) {
 }
 
 /**
- * Submit the tripal_pub_search_form form
+ * Submit the tripal_chado_pub_search_form form
  *
  * @ingroup tripal_pub
  */
-function tripal_pub_search_form_submit($form, &$form_state) {
+function tripal_chado_pub_search_form_submit($form, &$form_state) {
   $num_criteria = $form_state['storage']['num_criteria'];
   $from_year    = $form_state['values']['from_year'];
   $to_year      = $form_state['values']['to_year'];
@@ -435,8 +485,8 @@ function tripal_pub_search_form_submit($form, &$form_state) {
 
   // set the session variables
   if($op == 'Search') {
-    $_SESSION['tripal_pub_search_form']['num_criteria'] = $num_criteria;
-    unset($_SESSION['tripal_pub_search_form']['criteria']);
+    $_SESSION['tripal_chado_pub_search_form']['num_criteria'] = $num_criteria;
+    unset($_SESSION['tripal_chado_pub_search_form']['criteria']);
     for ($i = 0; $i <= $num_criteria; $i++) {
       $search_terms = '';
       $scope = '';
@@ -453,7 +503,7 @@ function tripal_pub_search_form_submit($form, &$form_state) {
       }
       //$mode =  $form_state['values']["mode-$i"];
 
-      $_SESSION['tripal_pub_search_form']['criteria'][$i] = array(
+      $_SESSION['tripal_chado_pub_search_form']['criteria'][$i] = array(
         'search_terms' => $search_terms,
         'scope' => $scope,
         'mode' => $mode,
@@ -461,12 +511,12 @@ function tripal_pub_search_form_submit($form, &$form_state) {
       );
 
     }
-    $_SESSION['tripal_pub_search_form']['from_year'] = $from_year;
-    $_SESSION['tripal_pub_search_form']['to_year'] = $to_year;
-    $_SESSION['tripal_pub_search_form']['perform_search'] = 1;
+    $_SESSION['tripal_chado_pub_search_form']['from_year'] = $from_year;
+    $_SESSION['tripal_chado_pub_search_form']['to_year'] = $to_year;
+    $_SESSION['tripal_chado_pub_search_form']['perform_search'] = 1;
   }
   if($op == 'Reset') {
-    unset($_SESSION['tripal_pub_search_form']);
+    unset($_SESSION['tripal_chado_pub_search_form']);
   }
 }
 
@@ -486,11 +536,11 @@ function tripal_pubs_search_form_ajax_update($form, $form_state) {
 }
 
 /**
- * Theme the tripal_pub_search_setup_form form
+ * Theme the tripal_chado_pub_search_setup_form form
  *
  * @ingroup tripal_pub
  */
-function theme_tripal_pub_search_setup_form_elements($variables) {
+function theme_tripal_chado_pub_search_setup_form_elements($variables) {
   $form = $variables['form'];
 
   $rows = array();
@@ -516,13 +566,13 @@ function theme_tripal_pub_search_setup_form_elements($variables) {
     drupal_render($form['date']),
     array(
       'data' =>
-        "<div id=\"pub-search-form-dates-row\">
-           <div id=\"pub-search-form-dates\"> ".
-             drupal_render($form['from_year']) .
-             drupal_render($form['to_year']) . "
-           </div>
+      "<div id=\"pub-search-form-dates-row\">
+         <div id=\"pub-search-form-dates\"> ".
+           drupal_render($form['from_year']) .
+           drupal_render($form['to_year']) . "
          </div>
-        ",
+       </div>
+      ",
     ),
     ''
   );
@@ -551,19 +601,21 @@ function theme_tripal_pub_search_setup_form_elements($variables) {
 /**
  * Builds the SQL statement need to search Chado for the publications
  * that match the user supplied criteria.  Tpyically, this function is
- * called by the search form generated by the tripal_pub_search_form() function
- * but this function is included in the API for calling by anyone.
+ * called by the search form generated by the tripal_chado_pub_search_form()
+ * function but this function is included in the API for calling by anyone.
  *
  * @param $search_array
  *   An array of search criteria provided by the user. The search array is
  *   an associative array with the following keys:
- *     'num_criteria': an integer indicating the number of search criteria supplied
+ *     'num_criteria': an integer indicating the number of search criteria
+ *        supplied
  *     'from_year':    filters records by a start year
  *     'to_year':      filters records by an end year
  *     'criteria':     an array of criteria. Each criteria is an associative
  *                     array with the following keys:
  *                     'search_terms':   The text used for searching
- *                     'scope':          The cvterm_id of the property used for filtering
+ *                     'scope':          The cvterm_id of the property used for
+ *                        filtering
  *                     'mode':           The operation (e.g. AND, OR or NOT)
  * @param $offset
  *   The offset for paging records.  The first record returned will be
@@ -584,15 +636,12 @@ function theme_tripal_pub_search_setup_form_elements($variables) {
  */
 function tripal_search_publications($search_array, $offset, $limit, &$total_records) {
 
-  // build the SQL based on the criteria provided by the user
-  $select = "SELECT DISTINCT P.*, CP.nid ";
-  $from   = "FROM {pub} P
-               LEFT JOIN [chado_pub] CP on P.pub_id = CP.pub_id
-               INNER JOIN {cvterm} CVT on CVT.cvterm_id = P.type_id
-            ";
-  $where  = "WHERE (NOT P.title = 'null') "; // always exclude the dummy pub
+  // Build the SQL based on the criteria provided by the user
+  $select = "SELECT DISTINCT P.* ";
+  $from   = "FROM {pub} P INNER JOIN {cvterm} CVT on CVT.cvterm_id = P.type_id ";
+  $where  = "WHERE (NOT P.title = 'null') ";
   $order  = "ORDER BY P.pyear DESC, P.title ASC";
-  $args = array();  // arguments for where clause
+  $args = array();
   $join = 0;
 
   $num_criteria = $search_array['num_criteria'];

+ 62 - 23
tripal_chado/includes/tripal_chado.semweb.inc

@@ -65,11 +65,13 @@ function tripal_chado_populate_vocab_FOAF() {
  */
 function tripal_chado_populate_vocab_HYDRA() {
 
+  // For the HydraConsole to work with webservices the URL must be set as
+  // http://www.w3.org/ns/hydra/core
   tripal_insert_db(array(
     'name' => 'hydra',
     'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
-    'url' => 'https://www.hydra-cg.com/spec/latest/core/',
-    'urlprefix' => 'https://www.hydra-cg.com/spec/latest/core/#{db}:{accession}',
+    'url' => 'http://www.w3.org/ns/hydra/core',
+    'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
   ));
   tripal_insert_cv(
     'hydra',
@@ -117,11 +119,23 @@ function tripal_chado_populate_vocab_HYDRA() {
  * Adds the RDFS database and terms.
  */
 function tripal_chado_populate_vocab_RDFS() {
+
+  tripal_insert_db(array(
+    'name' => 'rdf',
+    'description' => 'Resource Description Framework',
+    'url' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
+    'urlprefix' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+  ));
+  tripal_insert_cv(
+    'rdf',
+    'Resource Description Framework'
+  );
+
   tripal_insert_db(array(
     'name' => 'rdfs',
     'description' => 'Resource Description Framework Schema',
     'url' => 'https://www.w3.org/TR/rdf-schema/',
-    'urlprefix' => 'https://www.w3.org/TR/rdf-schema/#ch_{accession}',
+    'urlprefix' => 'http://www.w3.org/2000/01/rdf-schema#{accession}',
   ));
   tripal_insert_cv(
     'rdfs',
@@ -550,6 +564,33 @@ function tripal_chado_populate_vocab_EDAM() {
     'cv_name' => 'EDAM',
     'definition' => 'Apply analytical methods to existing data of a specific type.',
   ));
+  tripal_associate_chado_semweb_term('phylotree', 'analysis_id', $term);
+
+  $term = tripal_insert_cvterm(array(
+    'id' => 'data:0872',
+    'name' => 'Phylogenetic tree',
+    'cv_name' => 'EDAM',
+    'definition' => 'The raw data (not just an image) from which a phylogenetic tree is directly generated or plotted, such as topology, lengths (in time or in expected amounts of variance) and a confidence interval for each length.',
+  ));
+  $term = tripal_insert_cvterm(array(
+    'id' => 'data:3272',
+    'name' => 'Species tree',
+    'cv_name' => 'EDAM',
+    'definition' => 'A phylogenetic tree that reflects phylogeny of the taxa from which the characters (used in calculating the tree) were sampled.',
+  ));
+  $term = tripal_insert_cvterm(array(
+    'id' => 'data:3271',
+    'name' => 'Gene tree',
+    'cv_name' => 'EDAM',
+    'definition' => 'A phylogenetic tree that is an estimate of the character\'s phylogeny.',
+  ));
+  $term = tripal_insert_cvterm(array(
+    'id' => 'operation:0567',
+    'name' => 'Phylogenetic tree visualisation',
+    'cv_name' => 'EDAM',
+    'definition' => 'A phylogenetic tree that is an estimate of the character\'s phylogeny.',
+  ));
+
 }
 
 /**
@@ -1043,6 +1084,7 @@ function tripal_chado_populate_vocab_LOCAL() {
   ));
 
   // Add the terms used to identify nodes in the tree.
+  // DEPRECATED: use EDAM's data 'Species tree' term instead.
   tripal_insert_cvterm(array(
     'name' => 'taxonomy',
     'definition' => 'A term used to indicate if a phylotree is a taxonomic tree',
@@ -1051,7 +1093,6 @@ function tripal_chado_populate_vocab_LOCAL() {
     'db_name' => 'local'
   ));
 
-
   //--------------
   // Project Terms
   //--------------
@@ -1447,11 +1488,10 @@ function tripal_chado_populate_vocab_SWO() {
   tripal_insert_db(array(
     'name' => 'SWO',
     'description' => 'Software Ontology',
-    'url' => 'http://theswo.sourceforge.net/',
-    'urlprefix' => '',
+    'url' => 'http://purl.obolibrary.org/obo/swo',
+    'urlprefix' => 'http://www.ebi.ac.uk/swo/',
   ));
-  tripal_insert_cv('swo','Software Ontology. An ontology representation of the NCBI organismal taxonomy.');
-
+  tripal_insert_cv('swo','Software Ontology.');
 
   $term = tripal_insert_cvterm(array(
     'id' => 'SWO:0000001',
@@ -1465,26 +1505,19 @@ function tripal_chado_populate_vocab_SWO() {
       'specific operations.',
   ));
   tripal_associate_chado_semweb_term('analysis', 'program', $term);
-
-  $term = tripal_insert_cvterm(array(
-    'id' => 'SWO:0000001',
-    'name' => 'software',
-    'cv_name' => 'swo',
-    'definition' => 'Computer software, or generally just software, is any ' .
-    'set of machine-readable instructions (most often in the form of a ' .
-    'computer program) that conform to a given syntax (sometimes ' .
-    'referred to as a language) that is interpretable by a given ' .
-    'processor and that directs a computer\'s processor to perform ' .
-    'specific operations.',
-  ));
-  tripal_associate_chado_semweb_term('analysis', 'program', $term);
-
 }
 
 /**
  * Adds the contact table mapping.
  */
 function tripal_chado_populate_vocab_TCONTACT() {
+  tripal_insert_db(array(
+    'name' => 'TContact',
+    'description' => 'Tripal Contact Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+    'url' => 'cv/lookup/TContact',
+    'urlprefix' => 'cv/lookup/TContact/{accession}',
+  ));
+  tripal_insert_cv('tripal_contact', 'Tripal Contact Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
 }
 
 /**
@@ -1492,7 +1525,13 @@ function tripal_chado_populate_vocab_TCONTACT() {
  */
 function tripal_chado_populate_vocab_TPUB() {
 
-  // No need to insert the TPUB cv/db those should already be added.
+  tripal_insert_db(array(
+    'name' => 'TPUB',
+    'description' => 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+    'url' => 'cv/lookup/TPUB',
+    'urlprefix' => 'cv/lookup/TPUB/{accession}',
+  ));
+  tripal_insert_cv('tripal_pub', 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
 
   // make sure we have our supported databases
   tripal_insert_db(

+ 31 - 5
tripal_chado/includes/tripal_chado.vocab_storage.inc

@@ -29,17 +29,29 @@ function tripal_chado_vocab_get_vocabulary($vocabulary) {
     return FALSE;
   }
   $sql = "
-     SELECT DB.name as name, CV.name as short_name, DB.description, DB.url
+     SELECT DB.name as short_name, CV.name as name, DB.description, DB.url, DB.urlprefix
      FROM {db} DB
-      INNER JOIN {dbxref} DBX on DBX.db_id = DB.db_id
-      INNER JOIN {cvterm} CVT on CVT.dbxref_id = DBX.dbxref_id
-      INNER JOIN {cv} CV on CV.cv_id = CVT.cv_id
+      LEFT JOIN {dbxref} DBX on DBX.db_id = DB.db_id
+      LEFT JOIN {cvterm} CVT on CVT.dbxref_id = DBX.dbxref_id
+      LEFT JOIN {cv} CV on CV.cv_id = CVT.cv_id
      WHERE
       DB.name = :name
      LIMIT 1 OFFSET 0
   ";
   $result = chado_query($sql, array(':name' => $vocabulary));
   $result = $result->fetchAssoc();
+
+  if (!$result['name']) {
+    $result['name'] = $result['short_name'];
+  }
+  $sw_url = $result['urlprefix'];
+  if ($sw_url) {
+    $sw_url = preg_replace('/\{db\}/', $result['short_name'], $sw_url);
+    $sw_url = preg_replace('/\{accession\}/', '', $sw_url);
+    $sw_url = url($sw_url, array('absolute' => TRUE));
+  }
+  $result['sw_url'] = $sw_url;
+  unset($result['short_name']);
   return $result;
 }
 
@@ -70,12 +82,26 @@ function tripal_chado_vocab_get_term($vocabulary, $accession) {
     return FALSE;
   }
   $cvterm = chado_expand_var($cvterm, 'field', 'cvterm.definition');
+
+  $url = $cvterm->dbxref_id->db_id->url;
+  $urlprefix = $cvterm->dbxref_id->db_id->urlprefix;
+
+  // Generate the URL that can be used for semantic web applications.
+  $sw_url = $urlprefix;
+  if ($sw_url) {
+    $sw_url = preg_replace('/{db}/', $cvterm->dbxref_id->db_id->name, $sw_url);
+    $sw_url = preg_replace('/{accession}/', '', $sw_url);
+    $sw_url = url($sw_url, array('absolute' => TRUE));
+  }
+
   $term = array(
     'vocabulary' => array(
       'name' => $cvterm->cv_id->name,
       'short_name' => $cvterm->dbxref_id->db_id->name,
       'description' =>  $cvterm->dbxref_id->db_id->description,
-      'url' => $cvterm->dbxref_id->db_id->url
+      'url' => $url,
+      'urlprefix' => $urlprefix,
+      'sw_url' => $sw_url,
     ),
     'accession'  => $cvterm->dbxref_id->accession,
     'name'       => $cvterm->name,

+ 12 - 0
tripal_chado/theme/css/tripal_phylogeny.css

@@ -0,0 +1,12 @@
+.pylotree-tooltip { 
+    position: absolute;
+    text-align: center;
+    width: 60px;
+    height: 28px;
+    padding: 2px;
+    font: 12px sans-serif;
+    background: lightsteelblue;
+    border: 0px;
+    border-radius: 8px;
+    pointer-events: none;
+}

+ 402 - 0
tripal_chado/theme/js/d3.phylogram.js

@@ -0,0 +1,402 @@
+/*
+  (agr@ncgr.org : this is a significantly modified version of
+  d3.phylogram.js....  retaining attribution/copyright per below)
+
+  d3.phylogram.js http://bl.ocks.org/kueda/1036776
+
+  Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
+  Also includes a radial dendrogram visualization (branch lengths not scaled)
+  along with some helper methods for building angled-branch trees.
+
+  Copyright (c) 2013, Ken-ichi Ueda
+
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer. Redistributions in binary
+  form must reproduce the above copyright notice, this list of conditions and
+  the following disclaimer in the documentation and/or other materials
+  provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+  POSSIBILITY OF SUCH DAMAGE.
+
+  DOCUEMENTATION
+
+  d3.phylogram.build(selector, nodes, options)
+    Creates a phylogram.
+    Arguments:
+      selector: selector of an element that will contain the SVG
+      nodes: JS object of nodes
+    Options:
+      width       
+        Width of the vis, will attempt to set a default based on the width of
+        the container.
+      height
+        Height of the vis, will attempt to set a default based on the height
+        of the container.
+      fill
+        Function for generating fill color for leaf nodes.
+      vis
+        Pre-constructed d3 vis.
+      tree
+        Pre-constructed d3 tree layout.
+      children
+        Function for retrieving an array of children given a node. Default is
+        to assume each node has an attribute called "children"
+      diagonal
+        Function that creates the d attribute for an svg:path. Defaults to a
+        right-angle diagonal.
+      skipTicks
+        Skip the tick rule.
+      skipBranchLengthScaling
+        Make a dendrogram instead of a phylogram.
+  
+  d3.phylogram.buildRadial(selector, nodes, options)
+    Creates a radial dendrogram.
+    Options: same as build, but without diagonal, skipTicks, and 
+      skipBranchLengthScaling
+  
+  d3.phylogram.rightAngleDiagonal()
+    Similar to d3.diagonal except it create an orthogonal crook instead of a
+    smooth Bezier curve.
+    
+  d3.phylogram.radialRightAngleDiagonal()
+    d3.phylogram.rightAngleDiagonal for radial layouts.
+*/
+
+if (!d3) { throw "d3 wasn't included!"};
+(function() {
+  d3.phylogram = {}
+  d3.phylogram.rightAngleDiagonal = function() {
+    var projection = function(d) { return [d.y, d.x]; }
+    
+    var path = function(pathData) {
+      return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
+    }
+    
+    function diagonal(diagonalPath, i) {
+      var source = diagonalPath.source,
+          target = diagonalPath.target,
+          midpointX = (source.x + target.x) / 2,
+          midpointY = (source.y + target.y) / 2,
+          pathData = [source, {x: target.x, y: source.y}, target];
+      pathData = pathData.map(projection);
+      return path(pathData)
+    }
+    
+    diagonal.projection = function(x) {
+      if (!arguments.length) return projection;
+      projection = x;
+      return diagonal;
+    };
+    
+    diagonal.path = function(x) {
+      if (!arguments.length) return path;
+      path = x;
+      return diagonal;
+    };
+    
+    return diagonal;
+  }
+  
+  d3.phylogram.radialRightAngleDiagonal = function() {
+    return d3.phylogram.rightAngleDiagonal()
+      .path(function(pathData) {
+        var src = pathData[0],
+            mid = pathData[1],
+            dst = pathData[2],
+            radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
+            srcAngle = d3.phylogram.coordinateToAngle(src, radius),
+            midAngle = d3.phylogram.coordinateToAngle(mid, radius),
+            clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
+            rotation = 0,
+            largeArc = 0,
+            sweep = clockwise ? 0 : 1;
+        return 'M' + src + ' ' +
+          "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
+          'L' + dst;
+      })
+      .projection(function(d) {
+        var r = d.y, a = (d.x - 90) / 180 * Math.PI;
+        return [r * Math.cos(a), r * Math.sin(a)];
+      })
+  }
+  
+  // Convert XY and radius to angle of a circle centered at 0,0
+  d3.phylogram.coordinateToAngle = function(coord, radius) {
+    var wholeAngle = 2 * Math.PI,
+        quarterAngle = wholeAngle / 4
+    
+    var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
+        coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
+    
+    // Since this is just based on the angle of the right triangle formed
+    // by the coordinate and the origin, each quad will have different 
+    // offsets
+    switch (coordQuad) {
+      case 1:
+        coordAngle = quarterAngle - coordBaseAngle
+        break
+      case 2:
+        coordAngle = quarterAngle + coordBaseAngle
+        break
+      case 3:
+        coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
+        break
+      case 4:
+        coordAngle = 3*quarterAngle + coordBaseAngle
+    }
+    return coordAngle
+  }
+
+  function scaleBranchLengths(nodes, w) {
+    // Visit all nodes and adjust y pos width distance metric
+    var visitPreOrder = function(root, callback) {
+      callback(root)
+      if (root.children) {
+        for (var i = root.children.length - 1; i >= 0; i--){
+          visitPreOrder(root.children[i], callback);
+        };
+      }
+    }
+    visitPreOrder(nodes[0], function(node) {
+      node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.length || 0)
+    })
+    var rootDists = nodes.map(function(n) { return n.rootDist; });
+    var yscale = d3.scale.linear()
+      .domain([0, d3.max(rootDists)])
+      .range([0, w]);
+    visitPreOrder(nodes[0], function(node) {
+      node.y = yscale(node.rootDist)
+    })
+    return yscale
+  }
+  
+  d3.phylogram.build = function(selector, nodes, options) {
+    options = options || {}
+    var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
+        h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
+        w = parseInt(w),
+        h = parseInt(h);
+    var fill = options.fill || function(d) {
+      return 'cyan';
+    };
+    var size = options.size || function(d) {
+      return 6;
+    }
+    var nodeMouseOver = options.nodeMouseOver || function(d) {};
+    var nodeMouseOut  = options.nodeMouseOut  || function(d) {};
+    var nodeMouseDown = options.nodeMouseDown || function(d) {};
+    
+    var tree = options.tree || d3.layout.cluster()
+      .size([h, w])
+      .sort(function(node) { return node.children ? node.children.length : -1; })
+      .children(options.children || function(node) {
+        return node.children;
+      });
+    var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
+    var vis = options.vis || d3.select(selector).append("svg:svg")
+        .attr("width", w + 300)
+        .attr("height", h + 30)
+        .append("svg:g")
+        .attr("transform", "translate(20, 20)");
+    var nodes = tree(nodes);
+    
+    if (options.skipBranchLengthScaling) {
+      var yscale = d3.scale.linear()
+        .domain([0, w])
+        .range([0, w]);
+    } 
+    else {
+      var yscale = scaleBranchLengths(nodes, w)
+    }
+    
+    if (!options.skipTicks) {
+      vis.selectAll('line')
+          .data(yscale.ticks(10))
+          .enter().append('svg:line')
+          .attr('y1', 0)
+          .attr('y2', h)
+          .attr('x1', yscale)
+          .attr('x2', yscale)
+          .attr("stroke", "#ddd");
+
+      vis.selectAll("text.rule")
+          .data(yscale.ticks(10))
+          .enter().append("svg:text")
+          .attr("class", "rule")
+          .attr("x", yscale)
+          .attr("y", 0)
+          .attr("dy", -3)
+          .attr("text-anchor", "middle")
+          .attr('font-size', '9px')
+          .attr('fill', 'grey')
+          .text(function(d) { return Math.round(d*100) / 100; });
+    }
+        
+    var link = vis.selectAll("path.link")
+        .data(tree.links(nodes))
+        .enter().append("svg:path")
+        .attr("class", "link")
+        .attr("d", diagonal)
+        .attr("fill", "none")
+        .attr("stroke", "#aaa")
+        .attr("stroke-width", "4px");
+        
+    var node = vis.selectAll("g.node")
+        .data(nodes)
+        .enter().append("svg:g")
+        .attr("class", function(n) {
+          if (n.children) {
+            if (n.depth == 0) {
+              return "root node"
+            } 
+            else {
+              return "inner node"
+            }
+          } 
+          else {
+            return "leaf node"
+          }
+        })
+        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
+
+     // style the root node
+     vis.selectAll('g.root.node')
+      .append('svg:circle')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .attr("r", size)
+      .attr('fill', 'dimgrey')
+      .attr('stroke', 'black')
+      .attr('stroke-width', '2px');
+
+    // style the leaf nodes and add js event handlers
+    vis.selectAll('g.leaf.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .attr('stroke', 'dimgrey')
+      .attr('fill', fill)
+      .attr('stroke-width', '2px');
+
+    vis.selectAll('g.inner.node')
+      .on('click', nodeMouseDown)
+      .on('mouseover', nodeMouseOver)
+      .on('mouseout', nodeMouseOut)
+      .append("svg:circle")
+      .attr("r", size)
+      .attr('stroke', 'dimgrey')
+      .attr('stroke-width', '2px')
+      .attr('fill', 'white');
+    
+    if (!options.skipLabels) {
+      vis.selectAll('g.inner.node')
+        .append("svg:text")
+          .attr("dx", -6)
+          .attr("dy", -6)
+          .attr("text-anchor", 'end')
+          .attr('font-size', '9px')
+          .attr('fill', 'black')
+        //.text(function(d) { return d.length.toFixed(4); }); // hide length
+
+      vis.selectAll('g.leaf.node').append("svg:text")
+        .attr("dx", 8)
+        .attr("dy", 3)
+        .attr("text-anchor", "start")
+        .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .text(function(d) {
+          // return d.name + ' (' + d.length.toFixed(4) + ')'; // hide length
+          return d.name;
+         });
+    }
+    return {tree: tree, vis: vis}
+  }
+  
+  d3.phylogram.buildRadial = function(selector, nodes, options) {
+    options = options || {};
+    
+    var fill = options.fill || function(d) {
+      return 'cyan';
+    };
+    var size = options.size || function(d) {
+      return 6;
+    }
+    var nodeMouseOver = options.nodeMouseOver || function(d) {};
+    var nodeMouseOut = options.nodeMouseOut || function(d) {};
+    var nodeMouseDown = options.nodeMouseDown || function(d) {};
+    
+    var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
+        r = w / 2,
+        labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
+    
+    var vis = d3.select(selector).append("svg:svg")
+        .attr("width", r * 2)
+        .attr("height", r * 2)
+        .append("svg:g")
+        .attr("transform", "translate(" + r + "," + r + ")");
+        
+    var tree = d3.layout.tree()
+      .size([360, r - labelWidth])
+      .sort(function(node) { return node.children ? node.children.length : -1; })
+      .children(options.children || function(node) {
+        return node.children;
+      })
+      .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
+
+    var phylogram = d3.phylogram.build(selector, nodes, {
+      vis: vis,
+      tree: tree,
+      fill : fill,
+      size: size,
+      nodeMouseOver : nodeMouseOver,
+      nodeMouseOut : nodeMouseOut,
+      nodeMouseDown : nodeMouseDown,
+      skipBranchLengthScaling: true,
+      skipTicks: true,
+      skipLabels: options.skipLabels,
+      diagonal: d3.phylogram.radialRightAngleDiagonal()
+    })
+    vis.selectAll('g.node')
+      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
+    
+    if (!options.skipLabels) {
+      vis.selectAll('g.leaf.node text')
+        .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
+        .attr("dy", ".31em")
+        .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
+        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
+        .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
+        .attr('font-size', '10px')
+        .attr('fill', 'black')
+        .text(function(d) {
+          return d.name;
+        });
+
+      vis.selectAll('g.inner.node text')
+        .attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
+        .attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
+        .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
+    }
+    
+    return {tree: tree, vis: vis}
+  }
+}());

+ 143 - 0
tripal_chado/theme/js/tripal_phylogeny.js

@@ -0,0 +1,143 @@
+/* phylotree d3js graphs */
+
+(function ($) {
+    
+  "use strict";
+
+  // Will be dynamically sized.
+  var height = 0; 
+
+  // Store our function as a property of Drupal.behaviors.
+  Drupal.behaviors.TripalPhylotree = {
+    attach: function (context, settings) {
+      
+      // Retrieve the data for this tree.
+      var data_url = Drupal.settings.tripal_chado.phylotree_url;
+      $.getJSON(data_url, function(treeData) {
+        phylogeny_display_data(treeData);
+        $('.phylogram-ajax-loader').hide();
+      });
+    }
+  }
+
+  // Callback function to determine node size.
+  var phylogeny_node_size = function(d) {
+    var size;
+    var tree_options = Drupal.settings.tripal_chado.tree_options;
+    if (d.cvterm_name == "phylo_root") {
+      size = tree_options['root_node_size']; 
+    }
+    if (d.cvterm_name == "phylo_interior") {
+      size = tree_options['interior_node_size']; 
+    }
+    if (d.cvterm_name == "phylo_leaf") {
+      size = tree_options['leaf_node_size']; 
+    }
+    return size;
+  }
+
+  // Callback function to determine the node color.
+  var phylogeny_organism_color = function(d) {
+    var organism_color = Drupal.settings.tripal_chado.org_colors;
+    var color = null;
+    if (d.genus) {
+      color = organism_color[d.organism_id];
+    }
+    if (color) { 
+      return color; 
+    }
+    else { 
+      return 'grey'; 
+    }
+  };
+
+  // Callback for mouseover event on graph node d.
+  var phylogeny_node_mouse_over = function(d) {
+    var el = $(this);
+    el.attr('cursor', 'pointer');
+    var circle = el.find('circle');
+    // highlight in yellow no matter if leaf or interior node
+    circle.attr('fill', 'yellow');
+    if(!d.children) {
+      // only leaf nodes have descriptive text
+      var txt = el.find('text');
+      txt.attr('font-weight', 'bold');
+    }
+  };
+  
+  // Callback for mouseout event on graph node d.
+  var phylogeny_node_mouse_out = function(d) {
+    var el = $(this);
+    el.attr('cursor', 'default');
+    var circle = el.find('circle');
+    if(!d.children) {
+      // restore the color based on organism id for leaf nodes
+      circle.attr('fill', phylogeny_organism_color(d));
+      var txt = el.find('text');
+      txt.attr('font-weight', 'normal');
+    }
+    else {
+      // restore interior nodes to white
+      circle.attr('fill', 'white');
+    }
+  };
+  
+  // Callback for mousedown/click event on graph node d.
+  var phylogeny_node_mouse_down = function(d) {
+    var el = $(this);
+    var title = (! d.children ) ? d.name : 'interior node ' + d.phylonode_id;
+
+    if(d.children) {
+      // interior node
+      if(d.phylonode_id) {
+      }
+      else {
+        // this shouldn't happen but ok
+      }
+    }
+    else {
+      // If this node is not associated with a feature but it has an 
+      // organism node then this is a taxonomic node and we want to
+      // link it to the organism page.
+      if (!d.feature_id && d.organism_nid) {
+        window.location.replace(baseurl + '/node/' + d.organism_nid);
+      }
+      if (!d.feature_id && d.organism_eid) {
+        window.location.replace(baseurl + '/bio_data/' + d.organism_eid);
+      }
+      // leaf node
+    }
+  };
+
+  // Creates the tree using the d3.phylogram.js library.
+  function phylogeny_display_data(treeData) {
+    var height = phylogeny_graph_height(treeData);
+    var tree_options = Drupal.settings.tripal_chado.tree_options;
+    d3.phylogram.build('#phylogram', treeData, {
+      'width' : tree_options['phylogram_width'],
+      'height' : height,
+      'fill' : phylogeny_organism_color,
+      'size' : phylogeny_node_size,
+      'nodeMouseOver' : phylogeny_node_mouse_over,
+      'nodeMouseOut' : phylogeny_node_mouse_out,
+      'nodeMouseDown' : phylogeny_node_mouse_down,
+      'skipTicks' : tree_options['skipTicks']
+    });
+  }
+
+  /* graphHeight() generate graph height based on leaf nodes */
+  function phylogeny_graph_height(data) {
+    function count_leaf_nodes(node) {
+      if(! node.children) {
+        return 1;
+      }
+      var ct = 0;
+      node.children.forEach( function(child) {
+        ct+= count_leaf_nodes(child);
+      });
+      return ct;
+    }
+    var leafNodeCt = count_leaf_nodes(data);
+    return 22 * leafNodeCt;
+  }
+})(jQuery);

+ 140 - 2
tripal_chado/tripal_chado.install

@@ -900,8 +900,8 @@ function tripal_chado_update_7305() {
     tripal_insert_db(array(
       'name' => 'hydra',
       'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
-      'url' => 'https://www.hydra-cg.com/spec/latest/core/',
-      'urlprefix' => 'https://www.hydra-cg.com/spec/latest/core/#{db}:{accession}',
+      'url' => 'http://www.w3.org/ns/hydra/core',
+      'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
     ));
     tripal_insert_cv(
       'hydra',
@@ -1331,3 +1331,141 @@ function tripal_chado_update_7319() {
   }
 }
 
+/**
+ * Adding Phylogenetic Tree content type.
+ */
+function tripal_chado_update_7320() {
+    try {
+    // Associate the Analysis term with the analysis_id of the phylotree table.
+    $term = tripal_get_cvterm(array('id' => 'operation:2945'));
+    tripal_associate_chado_semweb_term('phylotree', 'analysis_id', $term);
+
+    $term = tripal_insert_cvterm(array(
+      'id' => 'data:0872',
+      'name' => 'Phylogenetic tree',
+      'cv_name' => 'EDAM',
+      'definition' => 'The raw data (not just an image) from which a phylogenetic tree is directly generated or plotted, such as topology, lengths (in time or in expected amounts of variance) and a confidence interval for each length.',
+    ));
+    $term = tripal_insert_cvterm(array(
+      'id' => 'data:3272',
+      'name' => 'Species tree',
+      'cv_name' => 'EDAM',
+      'definition' => 'A phylogenetic tree that reflects phylogeny of the taxa from which the characters (used in calculating the tree) were sampled.',
+    ));
+    $term = tripal_insert_cvterm(array(
+      'id' => 'data:3271',
+      'name' => 'Gene tree',
+      'cv_name' => 'EDAM',
+      'definition' => 'A phylogenetic tree that is an estimate of the character\'s phylogeny.',
+    ));
+    $term = tripal_insert_cvterm(array(
+      'id' => 'operation:0567',
+      'name' => 'Phylogenetic tree visualisation',
+      'cv_name' => 'EDAM',
+      'definition' => 'A phylogenetic tree that is an estimate of the character\'s phylogeny.',
+    ));
+
+    // Create the 'Phylogenetic tree' content type.
+    $error = '';
+    $args = array(
+      'vocabulary' => 'data',
+      'accession' => '0872',
+      'term_name' => 'Phylogenetic tree',
+      'storage_args' => array(
+        'data_table' => 'phylotree',
+      )
+    );
+    $term = tripal_load_term_entity(array('vocabulary' => 'data', 'accession' => '0872'));
+    if ($term) {
+      $bundle = tripal_load_bundle_entity(array('term_id' => $term->id));
+    }
+    if (!$term or !$bundle) {
+      if (!tripal_create_bundle($args, $error)) {
+        throw new Exception($error['!message']);
+      }
+    }
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+
+/**
+ * Updating details for local ontologies.
+ */
+function tripal_chado_update_7321() {
+  try {
+    tripal_insert_db(array(
+      'name' => 'TPUB',
+      'description' => 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+      'url' => '/cv/lookup/TPUB',
+      'urlprefix' => '/cv/lookup/TPUB/{accession}',
+    ));
+    tripal_insert_cv('tripal_pub', 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
+
+    tripal_insert_db(array(
+      'name' => 'rdf',
+      'description' => 'Resource Description Framework',
+      'url' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
+      'urlprefix' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+    ));
+    tripal_insert_cv(
+      'rdf',
+      'Resource Description Framework'
+    );
+    tripal_insert_db(array(
+      'name' => 'rdfs',
+      'description' => 'Resource Description Framework Schema',
+      'url' => 'https://www.w3.org/TR/rdf-schema/',
+      'urlprefix' => 'http://www.w3.org/2000/01/rdf-schema#{accession}',
+    ));
+    tripal_insert_db(array(
+      'name' => 'hydra',
+      'description' => 'A Vocabulary for Hypermedia-Driven Web APIs',
+      'url' => 'http://www.w3.org/ns/hydra/core',
+      'urlprefix' => 'http://www.w3.org/ns/hydra/core#{accession}',
+    ));
+    tripal_insert_cv(
+      'hydra',
+      'A Vocabulary for Hypermedia-Driven Web APIs.'
+    );
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+/**
+ * Fixing the SWO, TPUB and TContact vocabulary URLs
+ */
+function tripal_chado_update_7322() {
+  try {
+    tripal_insert_db(array(
+      'name' => 'TPUB',
+      'description' => 'Tripal Publiation Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+      'url' => 'cv/lookup/TPUB',
+      'urlprefix' => 'cv/lookup/TPUB/{accession}',
+    ));
+    tripal_insert_db(array(
+      'name' => 'TContact',
+      'description' => 'Tripal Contact Ontology. A temporary ontology until a more formal appropriate ontology an be identified.',
+      'url' => 'cv/lookup/TContact',
+      'urlprefix' => 'cv/lookup/TContact/{accession}',
+    ));
+    tripal_insert_cv('tripal_contact', 'Tripal Contact Ontology. A temporary ontology until a more formal appropriate ontology an be identified.');
+
+    tripal_insert_db(array(
+      'name' => 'SWO',
+      'description' => 'Software Ontology',
+      'url' => 'http://purl.obolibrary.org/obo/swo',
+      'urlprefix' => 'http://www.ebi.ac.uk/swo/',
+    ));
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}
+
+

+ 150 - 66
tripal_chado/tripal_chado.module

@@ -156,7 +156,7 @@ function tripal_chado_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_publish_form'),
     'type' => MENU_NORMAL_ITEM,
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('publish tripal content'),
     'file' => 'includes/tripal_chado.publish.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'weight' => -99
@@ -170,7 +170,7 @@ function tripal_chado_menu() {
         earlier this was refered to as "syncing".'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_publish_form'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('publish tripal content'),
     'file' => 'includes/tripal_chado.publish.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_LOCAL_ACTION,
@@ -183,7 +183,7 @@ function tripal_chado_menu() {
         earlier this was refered to as "syncing".'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_publish_form'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('publish tripal content'),
     'file' => 'includes/tripal_chado.publish.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_LOCAL_ACTION,
@@ -198,7 +198,7 @@ function tripal_chado_menu() {
     'title' => 'Materialized Views',
     'description' => t('Materialized views are used to improve speed of large or complex queries.'),
     'page callback' => 'tripal_mview_admin_view',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_NORMAL_ITEM,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -209,7 +209,7 @@ function tripal_chado_menu() {
     'description' => t('Help for the materialized views management system'),
     'page callback' => 'theme',
     'page arguments' => array('tripal_mviews_help'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_LOCAL_TASK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -220,7 +220,7 @@ function tripal_chado_menu() {
     'description' => t('Materialized views are used to improve speed of large or complex queries. These are database views as compared to Drupal views.'),
     'page callback' => 'tripal_mview_report',
     'page arguments' => array(6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -230,7 +230,7 @@ function tripal_chado_menu() {
     'description' => t('Create a new materialized view.'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_mviews_form'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -239,7 +239,7 @@ function tripal_chado_menu() {
     'title' => 'Edit Materialized View',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_mviews_form', 6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -249,7 +249,7 @@ function tripal_chado_menu() {
     'description' => t('Materialized views are used to improve speed of large or complex queries.'),
     'page callback' => 'tripal_mviews_add_populate_job',
     'page arguments' => array(6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -259,7 +259,7 @@ function tripal_chado_menu() {
     'description' => t('Materialized views are used to improve speed of large or complex queries.'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_mviews_delete_form', 5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -271,7 +271,7 @@ function tripal_chado_menu() {
     'description' => 'Import a materialized view from another Tripal instance.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_mviews_import_form'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -281,7 +281,7 @@ function tripal_chado_menu() {
     'description' => 'Export a materialized view for use by another Tripal instance.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_mviews_export_form', 5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado mviews'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal_chado.mviews.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -294,7 +294,7 @@ function tripal_chado_menu() {
     'title' => 'Custom Tables',
     'description' => t('Creation of custom tables that are added to Chado database.'),
     'page callback' => 'tripal_custom_table_admin_view',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'type' => MENU_NORMAL_ITEM,
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -305,7 +305,7 @@ function tripal_chado_menu() {
     'description' => t('Help for the tripal job management system'),
     'page callback' => 'theme',
     'page arguments' => array('tripal_job_help'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'type' => MENU_LOCAL_TASK,
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -316,7 +316,7 @@ function tripal_chado_menu() {
     'description' => t('Custom tables are added to Chado.'),
     'page callback' => 'tripal_custom_table_view',
     'page arguments' => array(6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -325,7 +325,7 @@ function tripal_chado_menu() {
     'title' => 'Create Custom Table',
     'description' => t('An interface for creating your own custom tables.'),
     'page callback' => 'tripal_custom_table_new_page',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -334,7 +334,7 @@ function tripal_chado_menu() {
     'title' => 'Edit Custom Table',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_custom_tables_form', 6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -344,7 +344,7 @@ function tripal_chado_menu() {
     'description' => t('Custom tables are added to Chado.'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_custom_tables_delete_form', 6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -353,7 +353,7 @@ function tripal_chado_menu() {
     'title' => 'Enable Custom Tables Administrative View',
     'page callback' => 'tripal_enable_view',
     'page arguments' => array('tripal_admin_custom_table', 'admin/tripal/storage/chado/custom_tables'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado custom tables'),
     'file' => 'includes/tripal_chado.custom_tables.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -363,23 +363,6 @@ function tripal_chado_menu() {
   //////////////////////////////////////////////////////////////////////////////
   //                           Data Loaders
   //////////////////////////////////////////////////////////////////////////////
-  $items['admin/tripal/loaders'] = array(
-    'title' => 'Data Loaders',
-    'description' => t('Tools facilitating data import.'),
-    'access arguments' => array('administer tripal'),
-    'type' => MENU_NORMAL_ITEM,
-    'weight' => 6
-  );
-  $items['admin/tripal/loaders/ncbi_taxonomy_loader'] = array(
-    'title' => 'Chado NCBI Taxonomy Loader',
-    'description' => 'Loads taxonomic details about installed organisms.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_chado_taxonomy_load_form'),
-    'access arguments' => array('administer tripal'),
-    'file' => 'includes/loaders/tripal_chado.taxonomy_importer.inc',
-    'file path' => drupal_get_path('module', 'tripal_chado'),
-    'type' => MENU_NORMAL_ITEM,
-  );
 
   // Data Loaders
   $items['admin/tripal/loaders/newic_phylotree_loader'] = array(
@@ -387,7 +370,7 @@ function tripal_chado_menu() {
     'description' => 'Loads phylogenetic trees in Newic format.',
     'page callback' => 'drupal_goto',
     'page arguments' => array('node/add/chado-phylotree'),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type' => MENU_NORMAL_ITEM,
   );
 
@@ -395,7 +378,7 @@ function tripal_chado_menu() {
     'title' => t('Chado Publication Importers'),
     'description' => t('Create and modify importers that can connect to and retreive publications from remote databases.'),
     'page callback' => 'tripal_pub_importers_list',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_NORMAL_ITEM,
@@ -406,7 +389,7 @@ function tripal_chado_menu() {
     'title' => t('Add an Importer'),
     'description' => t('Add a new publication importer.'),
     'page callback' => 'tripal_pub_importer_setup_page',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -414,7 +397,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/edit/%'] = array(
     'page callback' => 'tripal_pub_importer_setup_page',
     'page arguments' => array(5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -422,7 +405,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/raw/%'] = array(
     'page callback' => 'tripal_get_remote_pub_raw_page',
     'page arguments' => array(5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -431,7 +414,7 @@ function tripal_chado_menu() {
   // add a second link for the importer on the data loaders page
   $items['admin/tripal/loaders/pub/import'] = array(
     'page callback' => 'tripal_pub_importers_list',
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -440,7 +423,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/submit/%'] = array(
     'page callback' => 'tripal_pub_importer_submit_job',
     'page arguments' => array(5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -449,7 +432,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/delete/%'] = array(
     'page callback' => 'tripal_pub_importer_delete',
     'page arguments' => array(5),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -457,7 +440,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/changedb'] = array(
     'page callback' => 'tripal_pub_importer_setup_page_update_remotedb',
     'page arguments' => array(),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -466,7 +449,7 @@ function tripal_chado_menu() {
   $items['admin/tripal/loaders/pub/criteria/%/%'] = array(
     'page callback' => 'tripal_pub_importer_setup_page_update_criteria',
     'page arguments' => array(5, 6),
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('load tripal data'),
     'type ' => MENU_CALLBACK,
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
@@ -497,7 +480,7 @@ function tripal_chado_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_semweb_form'),
     'type' => MENU_NORMAL_ITEM,
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado semantic web'),
     'file' => 'includes/tripal_chado.semweb.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'weight' => 10
@@ -508,7 +491,7 @@ function tripal_chado_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_semweb_edit_form', 6, 7),
     'type' => MENU_CALLBACK,
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado semantic web'),
     'file' => 'includes/tripal_chado.semweb.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
   );
@@ -518,7 +501,7 @@ function tripal_chado_menu() {
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_semweb_reset_form', 6, 7),
     'type' => MENU_CALLBACK,
-    'access arguments' => array('administer tripal'),
+    'access arguments' => array('administer chado semantic web'),
     'file' => 'includes/tripal_chado.semweb.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
   );
@@ -579,16 +562,24 @@ function tripal_chado_menu() {
   //////////////////////////////////////////////////////////////////////////////
   //                          Controlled Vocabularies
   //////////////////////////////////////////////////////////////////////////////
-  $items['admin/tripal/loaders/chado_cv'] = array(
-    'title' => 'Chado Controlled Vocabularies',
-    'description' => 'Controlled Vocabularies control the terms available for various chado fields.',
+  $items['admin/tripal/loaders/chado_vocabs'] = array(
+    'title' => 'Chado Vocabularies',
+    'description' => t('Tools facilitating management (including import) of controlled
+      vocabularies and their terms into Chado.'),
+    'access arguments' => array('administer controlled vocabularies'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 6
+  );
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv'] = array(
+    'title' => 'Manage Controlled Vocabularies',
+    'description' => 'View, edit and add controlled vocabularies used by this site.',
     'page callback' => 'tripal_cv_admin_cv_listing',
     'access arguments' => array('administer controlled vocabularies'),
     'file' => 'includes/tripal_chado.cv.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_NORMAL_ITEM,
   );
-  $items['admin/tripal/loaders/chado_cv/edit/%'] = array(
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv/edit/%'] = array(
     'title' => 'Edit a Controlled Vocabulary',
     'description' => 'Edit the details such as name and description for an existing controlled vocabulary.',
     'page callback' => 'drupal_get_form',
@@ -599,7 +590,7 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/tripal/loaders/chado_cv/add'] = array(
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv/add'] = array(
     'title' => 'Add a Controlled Vocabulary',
     'description' => 'Manually a new controlled vocabulary.',
     'page callback' => 'drupal_get_form',
@@ -610,7 +601,7 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/tripal/loaders/chado_cv/%/cvterm/add'] = array(
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv/%/cvterm/add'] = array(
     'title' => 'Add a Controlled Vocabulary Term',
     'description' => 'Add a new controlled vocabulary term.',
     'page callback' => 'drupal_get_form',
@@ -620,7 +611,7 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/tripal/loaders/chado_cv/cvterm/add'] = array(
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv/cvterm/add'] = array(
     'title' => 'Add a Controlled Vocabulary Term',
     'description' => 'Add a new controlled vocabulary term.',
     'page callback' => 'drupal_get_form',
@@ -630,7 +621,7 @@ function tripal_chado_menu() {
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
   );
-  $items['admin/tripal/loaders/chado_cv/%/cvterm/edit/%'] = array(
+  $items['admin/tripal/loaders/chado_vocabs/chado_cv/%/cvterm/edit/%'] = array(
     'title' => 'Edit a Controlled Vocabulary Term',
     'description' => 'Edit an existing controlled vocabulary term.',
     'page callback' => 'drupal_get_form',
@@ -641,9 +632,14 @@ function tripal_chado_menu() {
     'type' => MENU_CALLBACK,
   );
 
-  $items['admin/tripal/storage/chado/cvtermpath'] = array(
-    'title' => 'Update Chado cvtermpath table',
-    'description' => 'The Chado cvtermpath table provides lineage for terms and is useful for quickly finding any ancestor parent of a term.  However, this table must be populated.  This page allows for populating of this table one vocabulary at a time',
+  $items['admin/tripal/loaders/chado_vocabs/cvtermpath'] = array(
+    'title' => 'Update CV Term Paths',
+    'description' => 'The Chado cvtermpath table provides lineage for
+      controlled vocabulary terms and is useful for quickly finding any
+      ancestor parent of a term.  If controlled vocabularies are loaded
+      using the OBO Importer then the vocabulary is automatically added to
+      the cvtermpath table.  However, if it is needed, the addition of terms
+      to the cvtermpath table can be manually executed here.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_cv_cvtermpath_form'),
     'access arguments' => array('administer controlled vocabularies'),
@@ -656,7 +652,10 @@ function tripal_chado_menu() {
   //////////////////////////////////////////////////////////////////////////////
   $items['admin/tripal/loaders/chado_db'] = array(
     'title' => 'Chado Databases',
-    'description' => 'References to External Database sites such as NCBI',
+    'description' => 'View, edit and add external databases.  When data originates
+       from a remote online resource (or database) then that database must
+       be added in order to cross link records in this site to records in
+       the remote site.',
     'page callback' => 'tripal_chado_admin_db_listing',
     'access arguments' => array('administer db cross-references'),
     'file' => 'includes/tripal_chado.db.inc',
@@ -704,7 +703,7 @@ function tripal_chado_menu() {
     'description' => 'Download a file of sequences',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('tripal_chado_feature_seq_extract_form'),
-    'access arguments' => array('access chado_feature content'),
+    'access arguments' => array('access content'),
     'file' =>  'includes/tripal_chado.seq_extract.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
@@ -712,12 +711,72 @@ function tripal_chado_menu() {
 
   $items['find/sequences/download'] = array(
     'page callback' => 'tripal_chado_feature_seq_extract_download',
-    'access arguments' => array('access chado_feature content'),
+    'access arguments' => array('access content'),
     'file' =>  'includes/tripal_chado.seq_extract.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
     'type' => MENU_CALLBACK,
   );
 
+  //////////////////////////////////////////////////////////////////////////////
+  //                           Publications
+  //////////////////////////////////////////////////////////////////////////////
+  $items['find/publications' ]= array(
+    'title' => 'Publication Search',
+    'description' => ('Search for publications'),
+    'page callback' => 'tripal_chado_pub_search_page',
+    'access arguments' => array('access content'),
+    'file' =>  'includes/tripal_chado.pub_search.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+    'type' => MENU_CALLBACK
+  );
+
+  $items['find/publications/criteria/%/%'] = array(
+    'page callback' => 'tripal_chado_pub_search_page_update_criteria',
+    'page arguments' => array(5, 6),
+    'access arguments' => array('access content'),
+    'file' =>  'includes/tripal_chado.pub_search.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+    'type ' => MENU_CALLBACK,
+  );
+
+  $items['admin/tripal/storage/chado/pub-search-config'] = array(
+    'title' => 'Publication Search Settings',
+    'description' => 'Configure the settings for the publication search.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_chado_pub_search_admin_form'),
+    'file' =>  'includes/tripal_chado.pub_search.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+    'type' => MENU_NORMAL_ITEM,
+    'access arguments' => array('administer tripal'),
+  );
+
+  //////////////////////////////////////////////////////////////////////////////
+  //                           Phylogeny
+  //////////////////////////////////////////////////////////////////////////////
+
+  // create a route for viewing json of all phylonodes having this phylotree_id
+  $items['phylotree/%'] = array(
+    'page callback' => 'tripal_phylogeny_ajax_get_tree_json',
+    'page arguments' => array(1),
+    // allow all anonymous http clients
+    'access callback' => TRUE,
+    'file' =>  'includes/tripal_chado.phylotree.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+  );
+
+  $items['admin/tripal/storage/chado/phylogeny'] = array(
+    'title' => 'Phylogeny and Taxonomy',
+    'description' => 'Settings for display of phylogenetic and taxonomic trees.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('tripal_phylogeny_default_plots_form'),
+    'access arguments' => array('administer tripal'),
+    'type' => MENU_NORMAL_ITEM,
+    'weight' => 2,
+    'file' =>  'includes/tripal_chado.phylotree.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+  );
+
+
   return $items;
 }
 
@@ -746,7 +805,28 @@ function tripal_chado_permission() {
           be useful for site developers who might want these IDs when working
           with the underlying database.'),
       'restrict access' => TRUE,
-    )
+    ),
+    'administer chado mviews' => array(
+      'title' => t('Administer Chado Materialized Views'),
+      'description' => t('Allows the user to create, edit and populate materialized views in the Chado databse.'),
+    ),
+    'administer chado custom tables' => array(
+      'title' => t('Administer Chado Custom Tables'),
+      'description' => t('Allows the user to create, edit and delete custom tables in the Chado database.'),
+    ),
+    'administer db cross-references' => array(
+      'title' => t('Administer Chado Databases'),
+      'description' => t('Allows the user to create, edit and delete database records in the Chado database.')
+    ),
+    'administer controlled vocabularies' => array(
+      'title' => t('Administer Chado Controlled Vocabularies'),
+      'description' => t('Allows the user to create, edit and delete controlled vocabularies in the Chado database.')
+    ),
+    'administer chado semantic web' => array(
+      'title' => t('Administer Semantic Web and Chado Integration'),
+      'description' => t('Allows the user to assign controlled vocabulary terms to tables and table columns in Chado.')
+    ),
+
   );
 }
 
@@ -770,10 +850,14 @@ function tripal_chado_theme($existing, $type, $theme, $path) {
       'render element' => 'form',
       'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     ),
-    'tripal_pub_search_setup_form_elements' => array(
+    'tripal_chado_pub_search_setup_form_elements' => array(
       'render element' => 'form',
-      'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
+      'file' => 'includes/tripal_chado.pub_search.inc',
     ),
+    'tripal_phylogeny_admin_org_color_tables' => array(
+      'render element' => 'element',
+    )
+
   );
 
   // Override the theme for each entity to use the legacy modules

+ 11 - 11
tripal_chado/tripal_chado.views_default.inc

@@ -504,7 +504,7 @@ function tripal_chado_defaultview_admin_cvs_listing() {
   $handler->display->display_options['header']['action_links_area']['empty'] = TRUE;
   $handler->display->display_options['header']['action_links_area']['link-1'] = array(
     'label-1' => 'Add Vocabulary',
-    'path-1' => 'admin/tripal/loaders/chado_cv/add',
+    'path-1' => 'admin/tripal/loaders/chado_vocabs/chado_cv/add',
   );
   /* No results behavior: Global: Text area */
   $handler->display->display_options['empty']['text']['id'] = 'area';
@@ -534,7 +534,7 @@ function tripal_chado_defaultview_admin_cvs_listing() {
   $handler->display->display_options['fields']['nothing_1']['exclude'] = TRUE;
   $handler->display->display_options['fields']['nothing_1']['alter']['text'] = 'edit';
   $handler->display->display_options['fields']['nothing_1']['alter']['make_link'] = TRUE;
-  $handler->display->display_options['fields']['nothing_1']['alter']['path'] = 'admin/tripal/loaders/chado_cv/edit/[cv_id]';
+  $handler->display->display_options['fields']['nothing_1']['alter']['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cv/edit/[cv_id]';
   /* Field: Global: Custom text */
   $handler->display->display_options['fields']['nothing']['id'] = 'nothing';
   $handler->display->display_options['fields']['nothing']['table'] = 'views';
@@ -543,7 +543,7 @@ function tripal_chado_defaultview_admin_cvs_listing() {
   $handler->display->display_options['fields']['nothing']['exclude'] = TRUE;
   $handler->display->display_options['fields']['nothing']['alter']['text'] = 'View Terms';
   $handler->display->display_options['fields']['nothing']['alter']['make_link'] = TRUE;
-  $handler->display->display_options['fields']['nothing']['alter']['path'] = 'admin/tripal/loaders/chado_cvterms?cv=[name]';
+  $handler->display->display_options['fields']['nothing']['alter']['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cvterms?cv=[name]';
   $handler->display->display_options['fields']['nothing']['hide_alter_empty'] = TRUE;
   /* Field: Global: Custom text */
   $handler->display->display_options['fields']['nothing_3']['id'] = 'nothing_3';
@@ -553,7 +553,7 @@ function tripal_chado_defaultview_admin_cvs_listing() {
   $handler->display->display_options['fields']['nothing_3']['exclude'] = TRUE;
   $handler->display->display_options['fields']['nothing_3']['alter']['text'] = 'Add Term';
   $handler->display->display_options['fields']['nothing_3']['alter']['make_link'] = TRUE;
-  $handler->display->display_options['fields']['nothing_3']['alter']['path'] = 'admin/tripal/loaders/chado_cv/[cv_id]/cvterm/add';
+  $handler->display->display_options['fields']['nothing_3']['alter']['path'] = 'aadmin/tripal/loaders/chado_vocabs/chado_cv/[cv_id]/cvterm/add';
   /* Field: Global: Custom text */
   $handler->display->display_options['fields']['nothing_2']['id'] = 'nothing_2';
   $handler->display->display_options['fields']['nothing_2']['table'] = 'views';
@@ -609,7 +609,7 @@ function tripal_chado_defaultview_admin_cvs_listing() {
 
   /* Display: Page */
   $handler = $view->new_display('page', 'Page', 'page_1');
-  $handler->display->display_options['path'] = 'admin/tripal/loaders/chado_cvs';
+  $handler->display->display_options['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cvs';
   $handler->display->display_options['menu']['type'] = 'default tab';
   $handler->display->display_options['menu']['title'] = 'Chado Controlled Vocabularies';
   $handler->display->display_options['menu']['description'] = 'A listing of all controlled vocabularies';
@@ -704,7 +704,7 @@ $handler->display->display_options['header']['action_links_area']['label'] = 'Ac
 $handler->display->display_options['header']['action_links_area']['empty'] = TRUE;
 $handler->display->display_options['header']['action_links_area']['link-1'] = array(
   'label-1' => 'Add Term',
-  'path-1' => 'admin/tripal/loaders/chado_cv/cvterm/add',
+  'path-1' => 'admin/tripal/loaders/chado_vocabs/chado_cv/cvterm/add',
 );
 /* No results behavior: Global: Text area */
 $handler->display->display_options['empty']['text']['id'] = 'area';
@@ -724,7 +724,7 @@ $handler->display->display_options['fields']['name_1']['table'] = 'cv';
 $handler->display->display_options['fields']['name_1']['field'] = 'name';
 $handler->display->display_options['fields']['name_1']['label'] = 'Vocabulary';
 $handler->display->display_options['fields']['name_1']['alter']['make_link'] = TRUE;
-$handler->display->display_options['fields']['name_1']['alter']['path'] = 'admin/tripal/loaders/chado_cvs?name=[name_1]';
+$handler->display->display_options['fields']['name_1']['alter']['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cvs?name=[name_1]';
 /* Field: Chado Cvterm: Cvterm Id */
 $handler->display->display_options['fields']['cvterm_id']['id'] = 'cvterm_id';
 $handler->display->display_options['fields']['cvterm_id']['table'] = 'cvterm';
@@ -773,7 +773,7 @@ $handler->display->display_options['fields']['nothing']['label'] = 'Edit Link';
 $handler->display->display_options['fields']['nothing']['exclude'] = TRUE;
 $handler->display->display_options['fields']['nothing']['alter']['text'] = 'edit';
 $handler->display->display_options['fields']['nothing']['alter']['make_link'] = TRUE;
-$handler->display->display_options['fields']['nothing']['alter']['path'] = 'admin/tripal/loaders/chado_cv/[cv_id]/cvterm/edit/[cvterm_id]';
+$handler->display->display_options['fields']['nothing']['alter']['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cv/[cv_id]/cvterm/edit/[cvterm_id]';
 /* Field: Global: Custom text */
 $handler->display->display_options['fields']['nothing_1']['id'] = 'nothing_1';
 $handler->display->display_options['fields']['nothing_1']['table'] = 'views';
@@ -843,10 +843,10 @@ $handler->display->display_options['filters']['definition']['expose']['remember_
 
 /* Display: Page */
 $handler = $view->new_display('page', 'Page', 'page_1');
-$handler->display->display_options['path'] = 'admin/tripal/loaders/chado_cvterms';
+$handler->display->display_options['path'] = 'admin/tripal/loaders/chado_vocabs/chado_cvterms';
 $handler->display->display_options['menu']['type'] = 'normal';
-$handler->display->display_options['menu']['title'] = 'Chado CV Terms';
-$handler->display->display_options['menu']['description'] = 'A listing of a controlled vocabulary terms for a given vocabulary';
+$handler->display->display_options['menu']['title'] = 'Manage Chado CV Terms';
+$handler->display->display_options['menu']['description'] = 'View, edit and add controlled vocabulary terms.';
 $handler->display->display_options['menu']['weight'] = '-8';
 $handler->display->display_options['menu']['name'] = 'management';
 $handler->display->display_options['menu']['context'] = 0;

+ 5 - 179
tripal_chado_views/api/tripal_chado_views.api.inc

@@ -4,181 +4,6 @@
  * API functions for Chado Views Integration
  */
 
- /**
- * @defgroup tripal_chado_views_api Tripal Views Module API
- * @ingroup tripal_api
- * @{
- * Provides functions to help extension modules add their own tripal views integrations
- * as well as functions for managing default views.
- *
- * Managing Default Views:
- *
- * When you create administrative default views (or really any default view) you really
- * have no way to ensure that the site administrator keeps these views enabled. In some
- * cases this is a good thing and provides the site administrator the ability to disable
- * views that don't apply to their particular installation or to replace your default view
- * with a custom view of their own. But in other cases, particularily for administration
- * views, you want to gaurd against accidental disabling of your views.
- *
- * One way to do this is to add a landing page using a heook_menu() definition to a custom
- * callback returning HTML. You can use this page to render the view if it is enabled but
- * provide logic to display a message if the view is disabled. Furthermore, it's often
- * helpful to provide a link allowing site administrators to enable the view from your
- * page rather than expecting them to go to the views administration UI. This is done for
- * all the administration views provided by Tripal. The following is an example of how to
- * achomplish this functionality for your own module:
- * @code
-function mymodule_menu() {
-  $items = array();
-
-  // Create the landing page
-  $items['admin/tripal/<PATH-TO-YOUR-LANDING-PAGE>'] = array(
-    'title' => 'MyModule Administration',
-    'description' => 'Administration of my module.',
-    'page callback' => 'mymodule_admin_landing_page',
-    'access arguments' => array('<YOUR-PERMISSION-KEY>'),
-    'type' => MENU_NORMAL_ITEM,
-  );
-
-  // Create one of these for each of your default views
-  $items['admin/tripal/<PATH-TO-YOUR-ADMIN-SECTION>/views/<VIEW-MACHINE-NAME>/enable'] = array(
-    'title' => 'Enable <VIEW-HUMAN-READABLE-NAME>',
-    'page callback' => 'tripal_enable_view',
-    'page arguments' => array('<VIEW-MACHINE-NAME>', '<PATH-TO-REDIRECT-TO-AFTERWARDS>'),
-    'access arguments' => array('<YOUR-PERMISSION-KEY>'),
-    'type' => MENU_CALLBACK,
-  );
-
-  return $items;
-}
-
-function mymodule_admin_landing_page() {
-
-  // Get the View Embed Code
-  // This function will return FALSE if your view is not enabled
-  $view_code = views_embed_view('<VIEW-MACHINE-NAME>', '<DISPLAY-MACHINE-NAME');
-  // If your view is enabled then embed it in this page by returning the embed code
-  if (isset($view_code)) {
-    $output .= $view_code;
-  }
-  else {
-    // Provide the landing page with links to the menu item created in hook_menu to
-    // to enable your view
-    $output .= '<p>The My Module module uses primarily views to provide an '
-      . 'administrative interface. Currently one or more views needed for this '
-      . 'administrative interface are disabled. <strong>Click each of the following links to '
-      . 'enable the pertinent views</strong>:</p>';
-    $output .= '<ul>';
-    // NOTE: <URL-FROM-MENU-TO-ENABLE-VIEW> is
-    // admin/tripal/<PATH-TO-YOUR-ADMIN-SECTION>/views/<VIEW-MACHINE-NAME>/enable
-    // from above hook_menu().
-    $output .= '<li>' . l('<VIEW-HUMAN-RADABLE-NAME>', '<URL-FROM-MENU-TO-ENABLE-VIEW>') . '</li>';
-    $output .= '</ul>';
-  }
-
-  return $output;
-}
-
- * @endcode
- *
- * Adding your own Chado Views Integrations:
- *
- * One of the main ways the Tripal View API is likely to be used is if your module needs
- * to create it's own tripal views integration. You might need to do this for any number of
- * reasons but the following examples are thought to be the most common:
- *
- *   1) Your module wants to add handlers with better functionality to fields it has more
- *       knowledge of than the general integration for all tables
- * @code
-   mymodule_views_data() {
-
-    // First check that your integration has not already been added
-    // When selecting a priority, do not choose -10 or -9 and make sure it is below 0
-    // so that individual sites still have the ability to override it, if needed
-    $table_name = 'my_chado_table';
-    $priority = -5;
-    if (!tripal_is_table_integrated($table_name, $priority)) {
-
-      // If you really only need to tweak an existing integration you can clone it
-      // If you want to clone the integration that is currently taking priority
-      // then use tripal_get_lightest_views_integration_priority() as below
-      $lightest_priority = tripal_get_lightest_views_integration_priority($table_name);
-      $setup_id = tripal_clone_views_integration($table_name, $priority, $lightest_priority);
-
-      // And then make a few changes
-      // First get the definition array created via the clone above
-      $defn_array = tripal_export_views_integration($setup_id);
-
-      // Then make some changes to the array here
-
-      // And finally save the changes to the integration
-      tripal_update_views_integration($setup_id, $defn_array);
-
-    }
-
-  }
- * @endcode
- *   2) Your module creates a chado table that is not already integrated.
- * @code
-  mymodule_views_data() {
-
-    // First check that your integration has not already been added
-    // When selecting a priority, do not choose 10 or 9 and make sure it is below 0
-    // so that individual sites still have the ability to override it, if needed
-    $table_name = 'my_chado_table';
-    $priority = 5;
-    if (!tripal_is_table_integrated($table_name, $priority)) {
-
-      // Describe your table using a large array as specified by tripal_add_views_integration().
-      $defn_array = array(
-        'table' => $table_name, //tablename or materialized view name
-        'name' => 'My Chado Table', // Human readable name
-        'type' => 'chado', //either chado or mview depending on tablename
-        'description' => 'Create a listing from my chado table.', //description seen when creating a view of this type
-        'priority' => $priority, //For Base tripal modules: 10; custom modules: 9 to 0;
-        'base_table' => TRUE //either TRUE or FALSE depending on whether the current table should show up in the add view list
-        'fields' => array(
-          'feature_id' => array(
-            'name' => 'feature_id', //field name in database
-            'title' => 'Feature ID', //human-readable name -seen in Views UI
-            'description' => 'This is the unique identifier for features', //help/description seen in Views UI
-            'type' => 'int', // the type of field
-            'handlers' => array(  //possible keys are field, filter, sort, argument, relationship
-              'field' => array(
-                'name' => 'chado_views_handler_numeric' //name of handler
-              ),
-              'filter' => array( ... ),
-              ...
-            ),
-            // Describe any joins involving this field.
-            // Note: you can include both foreign keys (feature.type_id => cvterm.cvterm_id)
-            // and referring tables (ie: feature.feature_id <= feature_relationship.subject_id)
-            'joins' => array(
-              'feature_relationship' => array( //table to join to.
-                'subject_id' => array( //field in above table (feature_relationship)
-                  'table' => 'featureprop', //table to join to
-                  'field' => 'feature_id', //field in above table (feature_relationship)
-                  'handler' => 'views_join', //handler to use for joining
-                  'relationship_handler' => 'views_handler_relationship', //handler to use when a relationship is added.
-                  'relationship_only' => FALSE, //whether to join automatically (FALSE) or not (TRUE)
-                ),
-                ...
-              ),
-              ...
-            ),
-          )
-        ),
-      );
-      // Actually create the entry
-      tripal_add_views_integration($defn_array);
-
-    }
-
-  }
- * @endcode
- * @}
- */
-
 /**
  * Programatically enable view
  *
@@ -839,10 +664,11 @@ function tripal_add_views_integration($defn_array, $setup_id = FALSE) {
 /**
  * Export Views integration records.
  *
- * This is a great way to create your own integration since it returns an already defined
- * integration in array form that you can modify. After modifications simply set the
- * priority to something lighter (but still below 0) than any existing integrations
- * and use tripal_add_views_integration() to add it to the list of integrations.
+ * This is a great way to create your own integration since it returns an
+ * already defined integration in array form that you can modify. After
+ * modifications simply set the priority to something lighter (but still
+ * below 0) than any existing integrations and use
+ * tripal_add_views_integration() to add it to the list of integrations.
  *
  * @param $setup_id
  *   The unique setup id of the tripal views integration

+ 0 - 3
tripal_ds/includes/tripal_ds.ds.inc

@@ -46,9 +46,6 @@ function _ds_layout_settings_info($bundle_name, $instances) {
   $fields_with_regions = array();
   $i = 0;
   $all_fields = array();
-  watchdog('debug', '<pre>$bundle_name: '. print_r($bundle_name, TRUE) .'</pre>');
-
-  watchdog('debug', '<pre>$instances: '. print_r($instances, TRUE) .'</pre>');
 
   try {
     // Get the bundle and term objects.

+ 2 - 2
tripal_ds/theme/css/tripal_ds.css

@@ -1,11 +1,11 @@
 
 .ds-right {
-  width: 75%;
+  width: 85%;
   float: right;
 }
 
 .ds-left {
-  width: 23%;
+  width: 12%;
   float: left;
 }
 

BIN
tripal_ds/theme/fonts/font-awesome-4.7.0/.DS_Store


+ 26 - 20
tripal_ds/theme/js/tripal_ds.js

@@ -36,28 +36,34 @@
       });
       // Move the tripal pane to the first position when its TOC item is clicked.
       $('.tripal_pane-toc-list-item-link').each(function (i) {
+        var id = '.tripal_pane-fieldset-' + $(this).attr('id');
+        if ($(id).length === 0) {
+            $(this).parents('.views-row').first().remove();
+            return;
+        }
         $(this).click(function() {
-          var id = '.tripal_pane-fieldset-' + $(this).attr('id');
           var prevObj = $(id).prev().attr('class');
-
-            // Highlight the fieldset instead of moving if it's already at the top
-            if (prevObj.indexOf('group-tripal-pane-content-top') == 0) {
-              $(id).fadeTo(10, 0.3, function() {});
-              $(id).fadeTo(200, 1, function() {});
-            }
-            if ($(id).hasClass('hideTripalPane')) {
-              $(id).removeClass('hideTripalPane');
-              $(id).addClass('showTripalPane');
-            }
-            $(id).hide();
-            var obj = $(id).detach();
-            $('.group-tripal-pane-content-top').after(obj);
-            $(id).show(300, function () {
-              // Trigger expansion event to allow the pane content
-              // to react to the size change
-              $(id).trigger($.Event('tripal_ds_pane_expanded', {id: id}));
-            });
-            return false;
+          if(prevObj.length === 0) {
+            return;
+          }
+          // Highlight the fieldset instead of moving if it's already at the top
+          if (prevObj.indexOf('group-tripal-pane-content-top') == 0) {
+            $(id).fadeTo(10, 0.3, function() {});
+            $(id).fadeTo(200, 1, function() {});
+          }
+          if ($(id).hasClass('hideTripalPane')) {
+            $(id).removeClass('hideTripalPane');
+            $(id).addClass('showTripalPane');
+          }
+          $(id).hide();
+          var obj = $(id).detach();
+          $('.group-tripal-pane-content-top').after(obj);
+          $(id).show(300, function () {
+            // Trigger expansion event to allow the pane content
+            // to react to the size change
+            $(id).trigger($.Event('tripal_ds_pane_expanded', {id: id}));
+          });
+          return false;
         });
       });
     }

+ 118 - 0
tripal_ws/includes/TripalFields/WebServicesField.inc

@@ -0,0 +1,118 @@
+<?php
+/**
+ * @class
+ * Purpose:
+ *
+ * Data:
+ * Assumptions:
+ */
+class WebServicesField extends TripalField {
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendant class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------
+
+  // The default label for this field.
+  public static $default_label = 'Remote Data';
+
+  // The default description for this field.
+  public static $default_description = '';
+/*
+  // The default widget for this field.
+  public static $default_widget = '';
+
+  // The default formatter for this field.
+  public static $default_formatter = '';
+*/
+  // The module that manages this field.
+  public static $module = 'tripal_ws';
+
+  // A list of global settings. These can be accessed within the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  // Once instances exist for a field type then these settings cannot be
+  // changed.
+  public static $default_settings = array(
+    'storage' => 'field_tripal_ws_storage',
+    // It is expected that all fields set a 'value' in the load() function.
+    // In many cases, the value may be an associative array of key/value pairs.
+    // In order for Tripal to provide context for all data, the keys should
+    // be a controlled vocabulary term (e.g. rdfs:type). Keys in the load()
+    // function that are supported by the query() function should be
+    // listed here.
+    'searchable_keys' => array(),
+  );
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instance.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are
+  // required for all TripalFields.
+  public static $default_instance_settings = array(
+    // The short name for the vocabulary (e.g. schema, SO, GO, PATO, etc.).
+    'term_vocabulary' => 'schema',
+    // The name of the term.
+    'term_name' => 'Thing',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => 'Thing',
+    // Set to TRUE if the site admin is not allowed to change the term
+    // type, otherwise the admin can change the term mapped to a field.
+    'term_fixed' => FALSE,
+    // Indicates if this field should be automatically attached to display
+    // or web services or if this field should be loaded separately. This
+    // is convenient for speed.  Fields that are slow should for loading
+    // should have auto_attach set to FALSE so tha their values can be
+    // attached asynchronously.
+    'auto_attach' => FALSE,
+  );
+
+  // A boolean specifying that users should not be allowed to create
+  // fields and instances of this field type through the UI. Such
+  // fields can only be created programmatically with field_create_field()
+  // and field_create_instance().
+ // public static $no_ui = FALSE;
+
+  // A boolean specifying that the field will not contain any data. This
+  // should exclude the field from web services or downloads.  An example
+  // could be a quick search field that appears on the page that redirects
+  // the user but otherwise provides no data.
+  //public static $no_data = FALSE;
+
+
+ /**
+   * Loads the field values from the underlying data store.
+   *
+   * @param $entity
+   *
+   * @return
+   *   An array of the following format:
+   *     $entity->{$field_name}['und'][0]['value'] = $value;
+   *   where:
+   *     - $entity is the entity object to which this field is attached.
+   *     - $field_name is the name of this field
+   *     - 'und' is the language code (in this case 'und' == undefined)
+   *     - 0 is the cardinality.  Increment by 1 when more than one item is
+   *       available.
+   *     - 'value' is the key indicating the value of this field. It should
+   *       always be set.  The value of the 'value' key will be the contents
+   *       used for web services and for downloadable content.  The value
+   *       should be of the follow format types: 1) A single value (text,
+   *       numeric, etc.) 2) An array of key value pair. 3) If multiple entries
+   *       then cardinality should incremented and format types 1 and 2 should
+   *       be used for each item.
+   *   The array may contain as many other keys at the same level as 'value'
+   *   but those keys are for internal field use and are not considered the
+   *   value of the field.
+   *
+   *
+   */
+  public function load($entity) {
+  }
+
+}

+ 118 - 0
tripal_ws/includes/TripalFields/WebServicesFieldFormatter.inc

@@ -0,0 +1,118 @@
+<?php
+/**
+ * @class
+ * Purpose:
+ *
+ * Display:
+ * Configuration:
+ */
+class WebServicesFieldFormatter extends TripalFieldFormatter {
+/*
+  // The default label for this field.
+  public static $default_label = '';
+
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array();
+
+  // The list of default settings for this formatter.
+  public static $default_settings = array(
+    'key' => 'default_value',
+  );
+
+  /**
+   * Provides the field's setting form.
+   *
+   * This function corresponds to the hook_field_formatter_settings_form()
+   * function of the Drupal Field API.
+   *
+   * The settings form appears on the 'Manage Display' page of the content
+   * type administration page. This function provides the form that will
+   * appear on that page.
+   *
+   * To add a validate function, please create a static function in the
+   * implementing class, and indicate that this function should be used
+   * in the form array that is returned by this function.
+   *
+   * This form will not be displayed if the formatter_settings_summary()
+   * function does not return anything.
+   *
+   * param $field
+   *   The field structure being configured.
+   * param $instance
+   *   The instance structure being configured.
+   * param $view_mode
+   *   The view mode being configured.
+   * param $form
+   *   The (entire) configuration form array, which will usually have no use
+   *   here.  Typically for reference only.
+   * param $form_state
+   *   The form state of the (entire) configuration form.
+   *
+   * @return
+   *   A Drupal Form array containing the settings form for this field.
+
+  public function settingsForm($view_mode, $form, &$form_state) {
+  }
+
+  /**
+   *  Provides the display for a field
+   *
+   * This function corresponds to the hook_field_formatter_view()
+   * function of the Drupal Field API.
+   *
+   *  This function provides the display for a field when it is viewed on
+   *  the web page.  The content returned by the formatter should only include
+   *  what is present in the $items[$delta]['values] array. This way, the
+   *  contents that are displayed on the page, via webservices and downloaded
+   *  into a CSV file will always be identical.  The view need not show all
+   *  of the data in the 'values' array.
+   *
+   *  @param $element
+   *  @param $entity_type
+   *  @param $entity
+   *  @param $langcode
+   *  @param $items
+   *  @param $display
+   *
+   *  @return
+   *    An element array compatible with that returned by the
+   *    hook_field_formatter_view() function.
+
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+
+    // Get the settings
+    $settings = $display['settings'];
+
+  }
+
+  /**
+   * Provides a summary of the formatter settings.
+   *
+   * This function corresponds to the hook_field_formatter_settings_summary()
+   * function of the Drupal Field API.
+   *
+   * On the 'Manage Display' page of the content type administration page,
+   * fields are allowed to provide a settings form.  This settings form can
+   * be used to allow the site admin to define how the field should be
+   * formatted.  The settings are then available for the formatter()
+   * function of this class.  This function provides a text-based description
+   * of the settings for the site developer to see.  It appears on the manage
+   * display page inline with the field.  A field must always return a
+   * value in this function if the settings form gear button is to appear.
+   *
+   * See the hook_field_formatter_settings_summary() function for more
+   * information.
+   *
+   * @param $field
+   * @param $instance
+   * @param $view_mode
+   *
+   * @return string
+   *   A string that provides a very brief summary of the field settings
+   *   to the user.
+   *
+
+  public function settingsSummary($view_mode) {
+  }
+*/
+}

+ 5 - 0
tripal_ws/includes/TripalFields/WebServicesFieldWidget.inc

@@ -0,0 +1,5 @@
+<?php
+
+class WebServicesFieldWidget extends TripalFieldWidget {
+
+}

+ 391 - 0
tripal_ws/includes/TripalFields/remote__data/remote__data.inc

@@ -0,0 +1,391 @@
+<?php
+/**
+ * @class
+ * Purpose:
+ *
+ * Data:
+ * Assumptions:
+ */
+class remote__data extends WebServicesField {
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendant class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------
+  // The default label for this field.
+  public static $default_label = 'Remote Tripal Site';
+
+  // The default description for this field.
+  public static $default_description = 'Allows for inclusion of remote data from another Tripal site.';
+
+  // The default widget for this field.
+  public static $default_widget = 'remote__data_widget';
+
+  // The default formatter for this field.
+  public static $default_formatter = 'remote__data_formatter';
+
+  // The module that manages this field.
+  public static $module = 'tripal_ws';
+
+  // A list of global settings. These can be accessed within the
+  // globalSettingsForm.  When the globalSettingsForm is submitted then
+  // Drupal will automatically change these settings for all fields.
+  // Once instances exist for a field type then these settings cannot be
+  // changed.
+  public static $default_settings = array(
+    'storage' => 'field_tripal_ws_storage',
+    // It is expected that all fields set a 'value' in the load() function.
+    // In many cases, the value may be an associative array of key/value pairs.
+    // In order for Tripal to provide context for all data, the keys should
+    // be a controlled vocabulary term (e.g. rdfs:type). Keys in the load()
+    // function that are supported by the query() function should be
+    // listed here.
+    'searchable_keys' => array(),
+  );
+
+  // Provide a list of instance specific settings. These can be access within
+  // the instanceSettingsForm.  When the instanceSettingsForm is submitted
+  // then Drupal with automatically change these settings for the instance.
+  // It is recommended to put settings at the instance level whenever possible.
+  // If you override this variable in a child class be sure to replicate the
+  // term_name, term_vocab, term_accession and term_fixed keys as these are
+  // required for all TripalFields.
+  public static $default_instance_settings = array(
+    // The short name for the vocabulary (e.g. schema, SO, GO, PATO, etc.).
+    'term_vocabulary' => 'schema',
+    // The name of the term.
+    'term_name' => 'Thing',
+    // The unique ID (i.e. accession) of the term.
+    'term_accession' => 'property',
+    // Set to TRUE if the site admin is not allowed to change the term
+    // type, otherwise the admin can change the term mapped to a field.
+    'term_fixed' => FALSE,
+    // Indicates if this field should be automatically attached to display
+    // or web services or if this field should be loaded separately. This
+    // is convenient for speed.  Fields that are slow should for loading
+    // should have auto_attach set to FALSE so tha their values can be
+    // attached asynchronously.
+    'auto_attach' => FALSE,
+    // Settings to allow the site admin to set the remote data source info.
+    'data_info' => array(
+      'query' => '',
+      'remote_site' => '',
+      'description' => '',
+      'rd_field_name' => '',
+      'site_logo' => '',
+    ),
+  );
+
+  // A boolean specifying that users should not be allowed to create
+  // fields and instances of this field type through the UI. Such
+  // fields can only be created programmatically with field_create_field()
+  // and field_create_instance().
+  public static $no_ui = FALSE;
+
+  // A boolean specifying that the field will not contain any data. This
+  // should exclude the field from web services or downloads.  An example
+  // could be a quick search field that appears on the page that redirects
+  // the user but otherwise provides no data.
+  public static $no_data = FALSE;
+
+  // Holds an object describing the remote site that tihs field connects to.
+  private $remote_site = NULL;
+
+  // Set to TRUE if this field is being loaded via web services. WE don't
+  // want remote fields loaded when a web-service call is made.
+  private $loaded_via_ws = FALSE;
+
+  public function __construct($field, $instance) {
+    parent::__construct($field, $instance);
+
+    // This field should not do anything if it is loaded via web-services.
+    // We don't want remote content to be available in web services.  There
+    // is an if statement to not show this field in the web services but the
+    // entity_load function doesn't know this field shouldn't be loaded so
+    // we need to short-circuit that.
+    $_SERVER['REQUEST_URI'];
+    if (preg_match('/^web-services/', $_SERVER['REQUEST_URI'])) {
+      $this->loaded_via_ws = TRUE;
+      return;
+    }
+
+    // Get the site url from the tripal_sites table.
+    if (array_key_exists('data_info', $instance['settings'])) {
+      $site_id_ws = $instance['settings']['data_info']['remote_site'];
+      if ($site_id_ws) {
+        $this->remote_site = db_select('tripal_sites', 'ts')
+          ->fields('ts')
+          ->condition('ts.id', $site_id_ws)
+          ->execute()
+          ->fetchObject();
+      }
+    }
+  }
+  /**
+   * @see WebServicesField::load()
+   */
+  public function load($entity) {
+
+    $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+
+    // Set some defaults for the empty record.
+    $entity->{$field_name}['und'][0] = array(
+      'value' => array(),
+      'remote_entity' => array(),
+    );
+
+    // If this field is being loaded via web services then just return.
+    if ($this->loaded_via_ws == TRUE) {
+      return;
+    }
+
+    // Get the query set by the admin for this field and replace any tokesn
+    $query_str = $this->instance['settings']['data_info']['query'];
+    $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+    $query_str = tripal_replace_entity_tokens($query_str, $entity, $bundle);
+
+    // Make the request.
+    $data = $this->makeRemoteRequest($query_str);
+    if(!$data){
+      return;
+    }
+
+    $total_items = $data['totalItems'];
+
+    // Iterate through the members returned and save those for the field.
+    for ($i = 0; $i < count($data['members']); $i++) {
+      $member = $data['members'][$i];
+
+      // Get the cotent type and remote entity id
+      $content_type = $member['@type'];
+      $remote_entity_id = $member['@id'];
+      $remote_entity_id = preg_replace('/^.*\/(\d+)/', '$1', $remote_entity_id);
+
+      // Save the member information for use later.
+      $entity->{$field_name}['und'][$i]['remote_entity'] = $member;
+
+      // Separate the query_field if it has subfields.
+      $rd_field_name = $this->instance['settings']['data_info']['rd_field_name'];
+      $subfields = explode(',', $rd_field_name);
+      $query_field = $subfields[0];
+
+      // Next get the the details about this member.
+      $query_field_url =  $content_type . '/' . $remote_entity_id . '/' . $query_field;
+      $field_data = $this->makeRemoteRequest($query_field_url);
+      if(!$field_data){
+        // If we encounter any type of error, we'll reset the field and return.
+        $entity->{$field_name}['und'] = array();
+        $entity->{$field_name}['und'][0] = array(
+          'value' => array(),
+          'remote_entity' => array(),
+        );
+        return;
+      }
+
+      // Set the field data as the value.
+      $field_data_type = $field_data['@type'];
+      $entity->{$field_name}['und'][$i]['value'] = $field_data;
+    }
+   }
+   /**
+    * Makes a request to a remote Tripal web services site.
+    *
+    * @param $query
+    *   The query string. This string is added to the URL for the remote
+    *   website.
+    */
+   private function makeRemoteRequest($query) {
+     $ctype = $query;
+     $qdata = '';
+     if (preg_match('/\?/', $query)) {
+       list($ctype, $qdata) = explode('?', $query);
+     }
+
+     // Build the URL to the remote web services.
+     $ws_version = $this->remote_site->version;
+     $ws_url = $this->remote_site->url;
+     $ws_url = trim($ws_url, '/');
+     $ws_url .= '/web-services/content/' . $ws_version . '/' . $ctype;
+
+     // Build the Query and make and substitions needed.
+     //dpm($ws_url . '?' . $query);
+     $options = array(
+       'data' => $qdata,
+     );
+     $data = drupal_http_request($ws_url, $options);
+
+     if (!$data) {
+       tripal_report_error('tripal_ws', TRIPAL_ERROR,
+           t('Could not connect to the remote web service.'));
+       return FALSE;
+     }
+
+     // If the data object has an error then this is some sort of
+     // connection error (not a Tripal web servcies error).
+     if (property_exists($data, 'error')) {
+       tripal_report_error('tripal_ws', TRIPAL_ERROR,
+           'Web Services error on remote site: %error.',
+           array('%error' => $data->error));
+       return FALSE;
+     }
+
+     // We got a response, so convert it to a PHP array.
+     $data = drupal_json_decode($data->data);
+
+     // Check if there was a Tripal Web Services error.
+     if (array_key_exists('error', $data)) {
+       $error = '</pre>' . print_r($data['error'], TRUE) . '</pre>';
+       tripal_report_error('tripal_ws', TRIPAL_ERROR,
+           'Web Services error on remote site: %error.',
+           array('%error' => $error));
+       return FALSE;
+     }
+
+     return $data;
+   }
+   /**
+   *
+   * @see TripalField::settingsForm()
+   */
+  public function instanceSettingsForm() {
+    $element = parent::instanceSettingsForm();
+
+    // Get the setting for the option for how this widget.
+    $instance = $this->instance;
+    $settings = '';
+    $site_list = '';
+
+    $tokens = array();
+    // Get the form info from the bundle about to be saved.
+    $bundle = tripal_load_bundle_entity(array('name' => $instance['bundle']));
+    // Retrieve all available tokens.
+    $tokens = tripal_get_entity_tokens($bundle);
+
+    $element['data_info'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Remote Data Settings',
+      '#description' => 'These settings allow you to provide a Tripal web
+        services query to identify content on another Tripal site and display
+        that here within this field.  You must specify the query to execute and
+        the field to display.',
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#prefix' => "<div id='set_titles-fieldset'>",
+      '#suffix' => '</div>',
+    );
+
+    // Get the site info from the tripal_sites table.
+    $sites = db_select('tripal_sites', 's')
+      ->fields('s')
+      ->execute()->fetchAll();
+
+    foreach ($sites as $site) {
+      $rows[$site->id] =$site->name;
+    }
+
+    $element['data_info']['remote_site'] = array(
+      '#type' => 'select',
+      '#title' => t('Remote Tripal Site'),
+      '#options' => $rows,
+      '#default_value' => $this->instance['settings']['data_info']['remote_site'],
+    );
+
+    $element['data_info']['query'] = array(
+      '#type' => 'textarea',
+      '#title' => 'Query to Execute',
+      '#description' => 'Build the query string that should be appended after the url. The tokens
+      listed below may be used in your query build.',
+      '#default_value' => $this->instance['settings']['data_info']['query'],
+      '#rows' => 5,
+      '#required' => TRUE
+    );
+    $element['data_info']['rd_field_name'] = array(
+      '#type' => 'textfield',
+      '#title' => 'Field to Display',
+      '#description' => 'The results returned by the query should match
+        entities (or records) from the selected remote site.  That entity
+        will have multiple fields. Only one remote field can be shown by
+        this field. Please enter the name of the field you would like
+        to display.  Some fields have "subfields".  You can display a subfield
+        rather than the entire field by entering a comma-separated sequence
+        of subfields.  For example, for relationships, you may only want to
+        show the "clause", therefore, the entry here would be: realtionship,clause.',
+      '#default_value' => $this->instance['settings']['data_info']['rd_field_name'],
+      '#required' => TRUE
+    );
+    $element['data_info']['token_display']['tokens'] = array(
+      '#type' => 'hidden',
+      '#value' => serialize($tokens)
+    );
+
+    $element['data_info']['token_display'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Available Tokens',
+      '#description' => 'Copy the token and paste it into the "Query" text field above.',
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE
+    );
+
+    $element['data_info']['token_display']['content'] = array(
+      '#type' => 'item',
+      '#markup' => theme_token_list($tokens),
+    );
+
+    $element['data_info']['description'] = array(
+      '#type' => 'textarea',
+      '#title' => 'Description',
+      '#description' => 'Describe the data being pulled in.',
+      '#default_value' =>  $this->instance['settings']['data_info']['description'],
+      '#rows' => 1
+    );
+
+    $fid = $this->instance['settings']['data_info']['site_logo'] ? $this->instance['settings']['data_info']['site_logo'] : NULL;
+    $file = NULL;
+    if ($fid) {
+      $file = file_load($fid);
+    }
+    $element['data_info']['site_logo'] = array(
+      '#title' => 'Remote Site Logo',
+      '#description' => t('When data is taken from a remote site and shown to the user,
+         the site from which the data was retrieved is indicated.  If you would like to
+         include the logo for the remote site, please upload an image here.'),
+      '#type' => 'managed_file',
+      '#default_value' => $file ? $file->fid : NULL,
+      '#theme' => 'image_widget',
+      '#attached' => array(
+        'css' => array(
+          'image-preview' => drupal_get_path('module', 'image') . '/image.css',
+        ),
+      ),
+      'preview' => array(
+        '#markup' => theme('image_style', array('style_name' => 'thumbnail', 'path' => $file ? $file->uri : '')),
+      ),
+    );
+
+    return $element;
+  }
+
+ /**
+   *
+   * @param unknown $form
+   * @param unknown $form_state
+   */
+  public function instanceSettingsFormValidate($form, &$form_state) {
+    $site_logo = $form_state['values']['instance']['settings']['data_info']['site_logo'];
+
+    // If we have a site logo then add usage information.
+    if ($site_logo) {
+      $file = file_load($site_logo);
+      $file_usage  = file_usage_list($file);
+      if (!array_key_exists('tripal_ws', $file_usage)) {
+        file_usage_add($file, 'tripal_ws', 'site-logo', 1);
+      }
+    }
+  }
+
+
+}

+ 368 - 0
tripal_ws/includes/TripalFields/remote__data/remote__data_formatter.inc

@@ -0,0 +1,368 @@
+<?php
+
+class remote__data_formatter extends WebServicesFieldFormatter {
+
+  // The default label for this field.
+  public static $default_label = 'Remote Data';
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('remote__data');
+  // The list of default settings for this formatter.
+  public static $default_settings = array(
+    'setting1' => 'default_value',
+  );
+  /**
+   * Provides the field's setting form.
+   *
+   * This function corresponds to the hook_field_formatter_settings_form()
+   * function of the Drupal Field API.
+   *
+   * The settings form appears on the 'Manage Display' page of the content
+   * type administration page. This function provides the form that will
+   * appear on that page.
+   *
+   * To add a validate function, please create a static function in the
+   * implementing class, and indicate that this function should be used
+   * in the form array that is returned by this function.
+   *
+   * This form will not be displayed if the formatter_settings_summary()
+   * function does not return anything.
+   *
+   * param $field
+   *   The field structure being configured.
+   * param $instance
+   *   The instance structure being configured.
+   * param $view_mode
+   *   The view mode being configured.
+   * param $form
+   *   The (entire) configuration form array, which will usually have no use
+   *   here.  Typically for reference only.
+   * param $form_state
+   *   The form state of the (entire) configuration form.
+   *
+   * @return
+   *   A Drupal Form array containing the settings form for this field.
+   */
+  public function settingsForm($view_mode, $form, &$form_state) {
+  }
+  /**
+   *  Provides the display for a field
+   *
+   * This function corresponds to the hook_field_formatter_view()
+   * function of the Drupal Field API.
+   *
+   *  This function provides the display for a field when it is viewed on
+   *  the web page.  The content returned by the formatter should only include
+   *  what is present in the $items[$delta]['values] array. This way, the
+   *  contents that are displayed on the page, via webservices and downloaded
+   *  into a CSV file will always be identical.  The view need not show all
+   *  of the data in the 'values' array.
+   *
+   *  @param $element
+   *  @param $entity_type
+   *  @param $entity
+   *  @param $langcode
+   *  @param $items
+   *  @param $display
+   *
+   *  @return
+   *    An element array compatible with that returned by the
+   *    hook_field_formatter_view() function.
+   */
+  public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
+    // Get the settings
+    $settings = $display['settings'];
+    $field_name = $this->field['field_name'];
+
+    // Get any subfields and the header label.  Shift the array because the
+    // results should already be the value of the fisrt entry.
+    $rd_field_name = $this->instance['settings']['data_info']['rd_field_name'];
+    $subfields = explode(',', $rd_field_name);
+    $header_label = $this->getHeaderLabel($subfields);
+    $flabel = array_shift($subfields);
+
+    // Get the site logo if one is provided
+    $site_logo = $this->instance['settings']['data_info']['site_logo'];
+    if ($site_logo) {
+      $site_logo = file_load($site_logo);
+    }
+
+    // Get the site name where the data came from.
+    $site_id_ws = $this->instance['settings']['data_info']['remote_site'];
+    $site = db_select('tripal_sites', 'ts')
+      ->fields('ts', array('name', 'url'))
+      ->condition('ts.id', $site_id_ws)
+      ->execute()
+      ->fetchObject();
+
+      $content = '<p>';
+    if (is_object($site_logo)) {
+      $content .= '<img class="tripal-remote--data-field-logo" src="' . file_create_url($site_logo->uri) . '"><br/>';
+    }
+    $content .=  t('This content provided by !site.',
+        array('!site' => l($site->name, $site->url, array('attributes' => array("target" => '_blank')))));
+    $content .= '</p>';
+    $rows = array();
+    foreach ($items as $index => $item) {
+      $remote_entity_label = $item['remote_entity']['label'];
+      $remote_entity_page = $item['remote_entity']['ItemPage'];
+      $link = t('View !data on %site',
+          array('!data' => l('this content', $remote_entity_page, array('attributes' => array('target' => '_blank'))),
+            '%site' => $site->name));
+      $value = $item['value'];
+      if (!$value) {
+        continue;
+      }
+      $headers = array('');
+
+      // If this is a collection then handle it as a list of members.
+      if (array_key_exists('members', $value)) {
+        foreach ($value['members'] as $subvalue) {
+          $subvalue = $this->refineSubValue($subvalue, $subfields, $header_label);
+          $rows[] = array($subvalue);
+        }
+      }
+      else {
+        if (count($subfields) > 0) {
+          $subvalue = $this->refineSubValue($value, $subfields, $header_label);
+          $rows[] = array($subvalue);
+        }
+        else {
+          if (array_key_exists($flabel, $value)) {
+            $rows[] = array(l($value[$flabel], $remote_entity_page, array('attributes' => array('target' => '_blank'))));
+          }
+          else {
+            $value['Link'] = l('View content on ' . $site->name, $remote_entity_page, array('attributes' => array('target' => '_blank')));
+            $rows[] = array($value);
+          }
+        }
+      }
+
+    }
+
+    $has_sub_tables = FALSE;
+    for ($i = 0; $i < count($rows); $i++) {
+      if (is_array($rows[$i][0])) {
+        $rows[$i][0] = $this->createTable($rows[$i]);
+        $has_sub_tables = TRUE;
+      }
+    }
+
+    // If we don't have  tables for each row then we'll put everything into
+    // a table.
+    if (!$has_sub_tables) {
+      $headers = array($header_label . '(s)');
+      $content .= theme_table(array(
+        'header' => $headers,
+        'rows' => $rows,
+        'attributes' => array(
+          'class' => 'tripal-remote--data-field-table',
+        ),
+        'sticky' => FALSE,
+        'caption' => "",
+        'colgroups' => array(),
+        'empty' => 'There are no results.',
+      ));
+    }
+    else {
+      for ($i = 0; $i < count($rows); $i++) {
+        if (count($rows) > 1) {
+          $content .= '<span class="tripal-remote--data-field-table-label">' . $header_label . ' ' . ($i + 1) . '</span>';
+        }
+        $content .= $rows[$i][0];
+      }
+    }
+    // Return the content for this field.
+    $element[0] = array(
+      '#type' => 'markup',
+      '#markup' => '<div class="tripal-remote--data-field">' . $content . '</div>',
+    );
+  }
+
+
+  /**
+   * Retrieves the header label given the subfields criteria.
+   *
+   * @param $subfields
+   *   An array of the sequence of subfields.
+   */
+  private function getHeaderLabel($subfields) {
+     $subfield = array_shift($subfields);
+     $header_label = ucwords(preg_replace('/_/', ' ', $subfield));
+     if (count($subfields) > 0) {
+       $header_label .= ' ' . $this->getHeaderLabel($subfields);
+     }
+     return $header_label;
+  }
+  /**
+   * Adjusts the items array to contain only the section/subsection desired.
+   *
+   * The field settings can indicate a field with sub fields that should
+   * be displayed (e.g. organism,genus or relationship,clause).  We want
+   * to adjust the item to only include what the user requested.
+   *
+   * @param $values
+   * @param $subfields
+   */
+  private function refineSubValue($values, $subfields) {
+
+    // Remove unwanted elements.
+    unset($values['@id']);
+    unset($values['@context']);
+    unset($values['@type']);
+    unset($values['remote_entity']);
+
+    $subfield = array_shift($subfields);
+    if (array_key_exists($subfield, $values)) {
+      if (is_array($values[$subfield]) and count($subfields) > 0) {
+        return  $this->refineSubvalue($values[$subfield], $subfields);
+      }
+      else {
+        return $values[$subfield];
+      }
+    }
+    else {
+      return $values;
+    }
+  }
+  /**
+   * A recursive function for displaying an item in a table.
+   *
+   * @param $item
+   *   An item from the $items array passed to the view() function.
+   * @return
+   *   An HTML formatted Table.
+   */
+  private function createTable($item, &$pkey = '', &$rows = array(), $depth = 0) {
+    foreach ($item as $key => $value) {
+      // Skip JSON-LD keys.
+      if (preg_match('/^\@/', $key)) {
+        continue;
+      }
+      $key = preg_replace('/_/', ' ', $key);
+      $key = ucwords($key);
+      if ($pkey) {
+        $key = $pkey . ' ' . $key;
+      }
+      if (is_array($value)) {
+        $this->createTable($value, $key, $rows, $depth + 1);
+      }
+      else {
+        $rows[] = array(
+          'data'=> array(
+            $key,
+            $value
+          ),
+          'no_striping' => TRUE,
+        );
+      }
+    }
+    if ($depth == 0) {
+      $headers = array('Data Type', 'Value');
+      return theme_table(array(
+        'header' => $headers,
+        'rows' => $rows,
+        'attributes' => array(
+          'class' => 'tripal-remote--data-field-table',
+        ),
+        'sticky' => FALSE,
+        'caption' => "",
+        'colgroups' => array(),
+        'empty' => 'There are no results.',
+      ));
+    }
+  }
+
+  /**
+   * A recursive function for creating an HTML dictionary list from
+   * the results for the item provided.
+   *
+   * @param $item
+   *   An item from the $items array passed to the view() function.
+   * @return
+   *   An HTML formatted DL.
+   */
+  private function createDL($item, &$pkey = '', &$content= '', $depth = 0) {
+    if ($depth == 0) {
+      $content = '<dl class="tripal-remote--data-field-dl">';
+    }
+    foreach ($item as $key => $value) {
+      // Skip JSON-LD keys.
+      if (preg_match('/^\@/', $key)) {
+        continue;
+      }
+
+      $key = preg_replace('/_/', ' ', $key);
+      $key = ucwords($key);
+      if ($pkey) {
+        $key = $pkey . ' ' . $key;
+      }
+      if (is_array($value)) {
+        $this->createDL($value, $key, $content, $depth + 1);
+      }
+      else {
+        $content .= '<dt>' . $key . '&nbsp;:&nbsp;</dt><dd>' . $value . '</dd>';
+      }
+    }
+    if ($depth == 0) {
+      $content .= '</dl>';
+      return $content;
+    }
+  }
+
+  /**
+   * A recursive function for creating an HTML dictionary list from
+   * the results for the item provided.
+   *
+   * @param $item
+   *   An item from the $items array passed to the view() function.
+   * @return
+   *   An HTML formatted DL.
+   */
+  private function createNestedDL($item) {
+    $content = '<dl>';
+    foreach ($item as $key => $value) {
+      // Skip JSON-LD keys.
+      if (preg_match('/^\@/', $key)) {
+        continue;
+      }
+
+      $key = preg_replace('/_/', ' ', $key);
+      $key = ucwords($key);
+      if (is_array($value)) {
+        $value = $this->createDL($value);
+      }
+      $content .= '<dt>' . $key . '</dt><dd>' . $value . '</dd>';
+    }
+    $content .= '</dl>';
+    return $content;
+  }
+  /**
+   * Provides a summary of the formatter settings.
+   *
+   * This function corresponds to the hook_field_formatter_settings_summary()
+   * function of the Drupal Field API.
+   *
+   * On the 'Manage Display' page of the content type administration page,
+   * fields are allowed to provide a settings form.  This settings form can
+   * be used to allow the site admin to define how the field should be
+   * formatted.  The settings are then available for the formatter()
+   * function of this class.  This function provides a text-based description
+   * of the settings for the site developer to see.  It appears on the manage
+   * display page inline with the field.  A field must always return a
+   * value in this function if the settings form gear button is to appear.
+   *
+   * See the hook_field_formatter_settings_summary() function for more
+   * information.
+   *
+   * @param $field
+   * @param $instance
+   * @param $view_mode
+   *
+   * @return string
+   *   A string that provides a very brief summary of the field settings
+   *   to the user.
+   *
+   */
+  public function settingsSummary($view_mode) {
+  }
+}

+ 169 - 0
tripal_ws/includes/TripalFields/remote__data/remote__data_widget.inc

@@ -0,0 +1,169 @@
+<?php
+
+class remote__data_widget extends WebServicesFieldWidget {
+
+  // The default label for this field.
+  public static $default_label = 'Remote Data';
+  // The list of field types for which this formatter is appropriate.
+  public static $field_types = array('remote__data');
+  /**
+   * Provides the form for editing of this field.
+   *
+   * This function corresponds to the hook_field_widget_form()
+   * function of the Drupal Field API.
+   *
+   * This form is diplayed when the user creates a new entity or edits an
+   * existing entity.  If the field is attached to the entity then the form
+   * provided by this function will be displayed.
+   *
+   * At a minimum, the form must have a 'value' element.  For Tripal, the
+   * 'value' element of a field always corresponds to the value that is
+   * presented to the end-user either directly on the page (with formatting)
+   * or via web services, or some other mechanism.  However, the 'value' is
+   * sometimes not enough for a field.  For example, the Tripal Chado module
+   * maps fields to table columns and sometimes those columns are foreign keys
+   * therefore, the Tripal Chado modules does not just use the 'value' but adds
+   * additional elements to help link records via FKs.  But even in this case
+   * the 'value' element must always be present in the return form and in such
+   * cases it's value should be set equal to that added in the 'load' function.
+   *
+   * @param $widget
+   * @param $form
+   *   The form structure where widgets are being attached to. This might be a
+   *   full form structure, or a sub-element of a larger form.
+   * @param $form_state
+   *   An associative array containing the current state of the form.
+   * @param $langcode
+   *   The language associated with $items.
+   * @param $items
+   *   Array of default values for this field.
+   * @param $delta
+   *   The order of this item in the array of subelements (0, 1, 2, etc).
+   * @param $element
+   * A form element array containing basic properties for the widget:
+   *  - #entity_type: The name of the entity the field is attached to.
+   *  - #bundle: The name of the field bundle the field is contained in.
+   *  - #field_name: The name of the field.
+   *  - #language: The language the field is being edited in.
+   *  - #field_parents: The 'parents' space for the field in the form. Most
+   *    widgets can simply overlook this property. This identifies the location
+   *    where the field values are placed within $form_state['values'], and is
+   *    used to access processing information for the field through the
+   *    field_form_get_state() and field_form_set_state() functions.
+   *  - #columns: A list of field storage columns of the field.
+   *  - #title: The sanitized element label for the field instance, ready for
+   *    output.
+   *  - #description: The sanitized element description for the field instance,
+   *    ready for output.
+   *  - #required: A Boolean indicating whether the element value is required;
+   *    for required multiple value fields, only the first widget's values are
+   *    required.
+   *  - #delta: The order of this item in the array of subelements; see
+   *    $delta above
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+    parent::form($widget, $form, $form_state, $langcode, $items, $delta, $element);
+    // Get the field settings.
+   /* $field_name = $this->field['field_name'];
+    $field_type = $this->field['type'];
+
+    // Get the setting for the option for how this widget.
+    $instance = $this->instance;
+    $settings = '';
+    $site_list = '';
+
+    $tokens = array();
+    // Get the form info from the bundle about to be saved.
+    $bundle_info = tripal_load_bundle_entity(array('name' => $form_state['build_info']['args']['0']['bundle']));
+    // Retrieve all available tokens.
+    $tokens = tripal_get_entity_tokens($bundle_info);
+    // If the field already has a value then it will come through the $items
+    // array.  This happens when editing an existing record.
+    dpm($items);
+    // FORM PROPER
+    $widget['#prefix'] =  "<span id='$field_name-remote_data-$delta'>";
+    $widget['#suffix'] =  "</span>";
+
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+
+    $widget['data_info'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Remote Data Settings',
+      '#description' => 'Provide the site name, query and description for the remote data source.',
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#prefix' => "<div id='set_titles-fieldset'>",
+      '#suffix' => '</div>',
+    );
+
+    // Get the site info from the tripal_sites table.
+      // Get the field groups associated with this bundle.
+    $sites = db_select('tripal_sites', 's')
+      ->fields('s')
+      ->execute()->fetchAll();
+
+    foreach ($sites as $site) {
+      $rows[] = $site->name;
+    }
+
+    $widget['data_info']['site'] = array(
+      '#type' => 'select',
+      '#title' => t('Site'),
+      '#options' => $rows,
+      '#default_value' => $site_list,
+    );
+
+    $widget['data_info']['query'] = array(
+      '#type' => 'textarea',
+      '#title' => 'Query',
+      '#description' => 'Build the query string that should be appended after the url. The tokens
+       listed below may be used in your query build.',
+      '#default_value' => $this->instance['settings']['data_info']['query'],
+      '#rows' => 5
+    );
+
+    $widget['set_titles']['token_display']['tokens'] = array(
+      '#type' => 'hidden',
+      '#value' => serialize($tokens)
+    );
+
+    $widget['data_info']['token_display'] = array(
+      '#type' => 'fieldset',
+      '#title' => 'Available Tokens',
+      '#description' => 'Copy the token and paste it into the "Query" text field above.',
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE
+    );
+
+    $widget['data_info']['token_display']['content'] = array(
+      '#type' => 'item',
+      '#markup' => theme_token_list($tokens),
+    );
+
+    $widget['data_info']['description'] = array(
+      '#type' => 'textarea',
+      '#title' => 'Description',
+      '#description' => 'Describe the data being pulled in.',
+      '#default_value' =>  $this->instance['settings']['data_info']['description'],
+      '#rows' => 1
+    );
+*/
+    //TODO Add test button to ensure query returns info.
+  }
+  /**
+   * Performs validation of the widgetForm.
+   *
+   * Use this validate to ensure that form values are entered correctly.
+   * The 'value' key of this field must be set in the $form_state['values']
+   * array anytime data is entered by the user.  It may be the case that there
+   * are other fields for helping select a value. In the end those helper
+   * fields must be used to set the 'value' field.
+   */
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+    //TODO validate the tokens, site, and query. Test that query returns data.
+
+  }
+}

+ 473 - 7
tripal_ws/includes/TripalWebService.inc

@@ -54,6 +54,13 @@ class TripalWebService {
    */
   protected $base_path;
 
+  /**
+   * The list of documented classes used by this service. For the web service
+   * to be discoverable all of the entity classes and their collections
+   * must be defined.
+   */
+  protected $documentation;
+
   // --------------------------------------------------------------------------
   //                             CONSTRUCTORS
   // --------------------------------------------------------------------------
@@ -72,6 +79,15 @@ class TripalWebService {
     $this->path = array();
     $this->params = array();
     $this->base_path = $base_path;
+    $this->documentation = array();
+
+    $this->addDocClass(array(
+      "id" => "http://www.w3.org/ns/hydra/core#Resource",
+      "name" => 'resource',
+      "title" => "Resource",
+      'subClassOf' => NULL,
+    ));
+
   }
 
   // --------------------------------------------------------------------------
@@ -135,13 +151,13 @@ class TripalWebService {
    * are separated by an underscore.  This function identifies the version
    * from the class name and returns it here in a human-readable format.
    *
-   * @param $sanatize
+   * @param $sanitize
    *   Set to TRUE to convert the period to underscore.
    *
    * @return
    *   The version number for this web service.
    */
-  public function getVersion($sanatize = FALSE) {
+  public function getVersion($sanitize = FALSE) {
 
     $class = get_class($this);
     $major_version = '';
@@ -150,6 +166,9 @@ class TripalWebService {
     if (preg_match('/v(\d+)_(\d+)$/', $class, $matches)) {
       $major_version = $matches[1];
       $minor_version = $matches[2];
+      if ($sanitize) {
+        return 'v' . $major_version . '_' . $minor_version;
+      }
       return 'v' . $major_version . '.' . $minor_version;
     }
     return '';
@@ -182,11 +201,55 @@ class TripalWebService {
    *   An associative array containing that can be converted to JSON.
    */
   public function getResponse() {
+    $class = get_called_class();
+
+    $context_filename = $class::$type . '.' . $this->getVersion(TRUE);
+    foreach ($this->path as $element) {
+      $context_filename .= '.' . $element;
+    }
+    $context_filename .= '.json';
+    $context_filename = strtolower($context_filename);
+    $context_dir = 'public://tripal/ws/context';
+    $context_file_path = $context_dir . '/' . $context_filename;
+
+    // Make sure the user directory exists
+    if (!file_prepare_directory($context_dir, FILE_CREATE_DIRECTORY)) {
+      throw new Exception('Could not access the tripal/ws/context directory on the server for storing web services context files.');
+    }
 
     $context = $this->resource ? $this->resource->getContext() : array();
+    $context = array(
+      '@context' => $context
+    );
+    $cfh = fopen($context_file_path, "w");
+    if (flock($cfh, LOCK_EX)) {
+      fwrite($cfh, json_encode($context));
+      flock($context_file_path, LOCK_UN);
+      fclose($cfh);
+    }
+    else {
+      throw new Exception(t('Error locking file: @file.', array('@file' => $context_file_path)));
+    }
+
+    $fid = db_select('file_managed', 'fm')
+      ->fields('fm', array('fid'))
+      ->condition('uri', $context_file_path)
+      ->execute()
+      ->fetchField();
+
+    // Save the context file so Drupal can manage it and remove the file later.
+    if (!$fid) {
+      $context_file = new stdClass();
+      $context_file->uri = $context_file_path;
+      $context_file->filename = $context_filename;
+      $context_file->filemime = 'application/ld+json';
+      $context_file->uid = 0;
+      file_save($context_file);
+    }
+
     $type = $this->resource ? $this->resource->getType() : 'unknown';
     $json_ld = array(
-      '@context' => $context,
+      '@context' => file_create_url($context_file_path),
       '@id' => '',
       '@type' => $type,
     );
@@ -249,6 +312,7 @@ class TripalWebService {
     $this->resource = new TripalWebServiceResource($this->base_path);
     $this->resource->setID('error');
     $this->resource->addContextItem('error', 'rdfs:error');
+    $this->resource->setType('error');
     $this->resource->addProperty('error', $message);
   }
 
@@ -262,8 +326,410 @@ class TripalWebService {
    *   An array of TripalWebServiceResource objects that follow the Hydra
    *   documentation for documenting supported classes.
    */
-  public function getSupportedClasses() {
-     $supported_classes = array();
-     return $supported_classes;
+  public function getDocumentation() {
+    return $this->documentation;
+  }
+
+  /**
+   *  Defines an entity class for the web services.
+   *
+   *  A class defines a resource including information about its properties
+   *  and the actions that can be performed.  Each class as a unique @id,
+   *  title, type and description.  The $details parameter is used to provide
+   *  this information.  Additionally, a resource may support Create, Read
+   *  Update and Delete (CRUD) operations.  Those are defined using the
+   *  $ops argument. Finally, resources may have properties (or fields). These
+   *  properties should be defined using the $props arugment.
+   *
+   *  @param $details.
+   *    An array of key/value pairs providing the details for the class. Valid
+   *    keys include:
+   *      - id: The unique IRI for this class.
+   *      - name: a computer-readable name for this class (i.e. no spaces,
+   *        no special characters).  This name is used to construct
+   *        type identifiers for operations.
+   *      - title:  The title for the resource that this Class represents.
+   *      - description: (Optional). A description of the resource.
+   *      - subClassOf: (Optional). If this class is a subclass of another
+   *        class then the value should be the @id of the parent class. This
+   *        defaults to hydra:Resource. If no subClass is desired, set it
+   *        to NULL.
+   *      - type: (Optional). Indicates the type of class. Defaults to
+   *        hydra:Class
+   *  @param $ops
+   *    If the resource supports CRUD functionality then those functions should
+   *    be defined using this argument.  This is an associative array with
+   *    the following keys: GET, POST, PUT, DELETE. These keys, if provided,
+   *    indicate that a resource of this type can be retrieved (GET),
+   *    created (POST), updated (PUT) or deleted (DELETE).  The value for each
+   *    key should be an associative array that supports the following keys:
+   *      = type: the @id that determines the type of operation.  This type
+   *        should be specific to the resource, and it need not be a term
+   *        from a real vocabulary.  Use the '_:' prefix for the term. For
+   *        example, for an 'Event' resource with a GET method, an appropriate
+   *        type would be '_:event_retrieve'.
+   *      - label: A label that describes the operation in the
+   *        context of the resource.
+   *      - description: A description for the operation.  Can be set to NULL
+   *        for no description.
+   *      - expects: The information expected by the Web API.
+   *      - returns: The @id of a Class that will be returned by
+   *        the operation. Set to NULL if there is no return value.
+   *      - statusCodes: An array of status codes that could be returned when
+   *        an error occurs.  Each element of the array is an associative
+   *        array with two key/value pairs: 'code' and 'description'.  Set the
+   *        'code' to be the HTTP code that can be returned on error and the
+   *        'description' to an error message appropriate for the error. For
+   *        example an HTTP 404 error means "Not Found".  You can sepecify this
+   *        code and provide a description appropriate for the method and
+   *        class.
+   *  @param $props.
+   *    Defines the list of properties that the resource provides.  This is an
+   *    array of properties where each property is an associative array
+   *    containing the following keys:
+   *      - type:  The @id of the type of property. Alternatively, this can
+   *        be an instance of a TripalWebServiceResource when the property
+   *        must support operations such as a property of type hydra:Link.
+   *      - title:  (Optional). The human-readable title of this property. If
+   *        this key is absent then the title will be set to the term's title.
+   *      - description:  (Optional). A short description of this property. If
+   *        this key is absent then the description will be set to the term's
+   *        description.
+   *      - required:  Set to TRUE to indicate if this property is a required
+   *        field when creating a new resource.
+   *      - readable:  Set to TRUE to indicate that the client can retrieve the
+   *        property's value?
+   *        altered by an update.
+   *      - writeable: Set to TRUE if the client can alter the value of the
+   *        property.
+   *      - domain: the @id of the rdfs:domain.
+   *      - range:  the @id of the rdfs:range.
+   */
+  protected function addDocClass($details = array(), $ops = array(), $props = array()) {
+    $supported_class = new TripalWebServiceResource($this->getServicePath());
+
+    // Add the context which all classes will need
+    $supported_class->addContextItem('description', 'rdfs:comment');
+    $supported_class->addContextItem('subClassOf', 'hydra:subClassOf');
+    $supported_class->addContextItem('description', 'rdfs:comment');
+    $supported_class->addContextItem('label', 'rdfs:label');
+
+    // Set the Class ID.
+    $class_id = $details['id'];
+    $supported_class->setID($class_id);
+
+    // Set the class Type.
+    if (array_key_exists('type', $details)) {
+      $supported_class->setType($details['type']);
+    }
+    else {
+      $supported_class->setType('hydra:Class');
+    }
+
+    // Set title and description.
+    $supported_class->addProperty('hydra:title', $details['title']);
+    $supported_class->addProperty('hydra:description', array_key_exists('description', $details) ? $details['description'] : NULL);
+
+    // Set the sub class.
+    if (array_key_exists('subClassOf', $details)) {
+      if ($details['subClassOf']) {
+        $supported_class->addProperty('subClassOf', $details['subClassOf']);
+      }
+    }
+    else {
+      $supported_class->addProperty('subClassOf', 'hydra:Resource');
+    }
+
+    // Now add the supported operations.
+    $class_ops = array();
+    foreach ($ops as $op => $op_details) {
+      $class_ops[] = $this->generateDocClassOp($supported_class, $op, $op_details);
+    }
+    $supported_class->addContextItem('supportedOperation', 'hydra:supportedOperation');
+    $supported_class->addProperty('supportedOperation', $class_ops);
+
+    // Now add in the supported proprerties.
+    $class_props = array();
+    foreach ($props as $prop) {
+      $class_props[] = $this->generateDocClassProp($supported_class, $prop);
+    }
+    $supported_class->addContextItem('supportedProperty', 'hydra:supportedProperty');
+    $supported_class->addProperty('supportedProperty', $class_props);
+
+    $this->documentation[] = $supported_class;
+  }
+
+  /**
+   * A helper function for the addClass() function for generating a property.
+   */
+  private function generateDocClassProp($supported_class, $prop) {
+    $supported_class->addContextItem('property', array(
+      "@id" => "hydra:property",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('domain', array(
+      "@id" => "rdfs:domain",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('range', array(
+      "@id" => "rdfs:range",
+      "@type" => "@id"
+    ));
+    $supported_class->addContextItem('readable', 'hydra:readable');
+    $supported_class->addContextItem('writeable', 'hydra:writeable');
+    $supported_class->addContextItem('required', 'hydra:required');
+    $class_prop = array(
+      'property' => $prop['type'],
+      'hydra:title' => $prop['title'],
+      'hydra:description' => array_key_exists('description', $prop) ? $prop['description'] : NULL,
+      'required' => array_key_exists('required', $prop) ? $prop['required'] : NULL,
+      'readable' => array_key_exists('readonly', $prop) ? $prop['readonly'] : NULL,
+      'writeable' => array_key_exists('writeonly', $prop) ? $prop['writeonly'] : NULL,
+    );
+    if (array_key_exists('domain', $prop)) {
+      $class_prop['domain'] = $prop['domain'];
+    }
+    if (array_key_exists('range', $prop)) {
+      $class_prop['range'] = $prop['range'];
+    }
+    if (array_key_exists('operations', $prop)) {
+      $class_prop['supportedOperation'] = array();
+      foreach ($prop['operations'] as $op => $op_details) {
+        $class_prop['supportedOperation'][] = $this->generateOp($supported_class, $op, $op_details);
+      }
+    }
+    return $class_prop;
+  }
+  /**
+   * A helper function for the addClass() function for generating an operation.
+   */
+  private function generateDocClassOp($supported_class, $op, $op_details) {
+
+    if ($op != 'GET' and $op != 'PUT' and $op != 'DELETE' and $op != 'POST') {
+      throw new Exception(t('The method, @method, provided to the TripalWebService::addClass function is not an oppropriate operations.', array('@method' => $op)));
+    }
+
+    if (!array_key_exists('type', $op_details)) {
+      throw new Exception(t('Please provide a type in the operations array passed to the TripalWebService::addClass() function: @details', array('@details' => print_r($op_details, TRUE))));
+    }
+    if (!array_key_exists('label', $op_details)) {
+      throw new Exception(t('Please provide a label in the operations array passed to the TripalWebService::addClass() function: @details', array('@details' => print_r($op_details, TRUE))));
+    }
+
+    $class_op = new TripalWebServiceResource($this->base_path);
+    $class_op->setID($op_details['type']);
+    switch($op) {
+      case 'GET':
+        $class_op->setType('hydra:Operation');
+        break;
+      case 'POST':
+        $class_op->setType('http://schema.org/AddAction');
+        break;
+      case 'DELETE':
+        $class_op->setType('http://schema.org/DeleteAction');
+        break;
+      case 'PUT':
+        $class_op->setType('http://schema.org/UpdateAction');
+        break;
+    }
+
+    $class_op->addContextItem('method', 'hydra:method');
+    $class_op->addContextItem('label', 'rdfs:label');
+    $class_op->addContextItem('description', 'rdfs:comment');
+    $class_op->addContextItem('expects', array(
+      "@id" => "hydra:expects",
+      "@type" => "@id"
+    ));
+    $class_op->addContextItem('returns', array(
+      "@id" => "hydra:returns",
+      "@type" => "@id"
+    ));
+    $class_op->addContextItem('statusCodes', 'hydra:statusCodes');
+    $class_op->addContextItem('code', 'hydra:statusCode');
+
+    $class_op->addProperty('method', $op);
+    $class_op->addProperty('label', array_key_exists('label', $op_details) ? $op_details['label'] : 'Retrieves an instance of this resource');
+    $class_op->addProperty('description', array_key_exists('description', $op_details) ? $op_details['description'] : NULL);
+    $status_codes = array_key_exists('statusCodes', $op_details) ? $op_details['statusCodes'] : array();
+    foreach ($status_codes as $code) {
+      if (array_key_exists('code', $code) and array_key_exists('description', $code)) {
+        $class_op->addProperty('statusCodes', $code);
+      }
+      else {
+        throw new Exception(t('The status code provided to TripalWebService::addClass function is improperly formatted @code.', array('@code' => print_r($code, TRUE))));
+      }
+    }
+    if (count($status_codes) == 0) {
+      $class_op->addProperty('statusCodes', array());
+    }
+    $class_op->addProperty('expects', array_key_exists('expects', $op_details) ? $op_details['expects'] : NULL);
+    $class_op->addProperty('returns', array_key_exists('returns', $op_details) ? $op_details['returns'] : ' "http://www.w3.org/2002/07/owl#Nothing",');
+
+    return $class_op;
+  }
+
+  /**
+   * Converts a term array into an value appropriate for an @id or @type.
+   *
+   * @param $term
+   *   The term array.
+   * @param $santize
+   *   An array of keywords indicating how to santize the key.  By default,
+   *   no sanitizing occurs.  The two valid types are 'lowercase', and 'spacing'
+   *   where 'lowercase' converts the term name to all lowercase and
+   *   'spacing' replaces any spaces with underscores.
+   *
+   * @return
+   *   The id (the term name but with spaces replaced with underscores).
+   */
+  protected function getContextTerm($term, $sanitize = array()) {
+    if (!$term) {
+      $backtrace = debug_backtrace();
+      throw new Exception('getContextTerm: Please provide a non NUll or non empty $term.');
+
+    }
+    if (!$term['name']) {
+      throw new Exception('getContextTerm: The provided term does not have a name: ' . print_r($term, TRUE));
+    }
+
+    $key = $term['name'];
+    $key_adj = $key;
+    if (in_array('spacing', $sanitize)) {
+      $key_adj = preg_replace('/ /', '_', $key_adj);
+    }
+    if (in_array('lowercase', $sanitize)) {
+      $key_adj = strtolower($key_adj);
+    }
+    return $key_adj;
+  }
+  /**
+   * Adds a term to the '@context' section for a given resource.
+   *
+   * @param $resource
+   *   A TripalWebServiceResource instance.
+   * @param $term
+   *   The term array.
+   * @param $santize
+   *   An array of keywords indicating how to santize the key.  By default,
+   *   no sanitizing occurs.  The two valid types are 'lowercase', and 'spacing'
+   *   where 'lowercase' converts the term name to all lowercase and
+   *   'spacing' replaces any spaces with underscores.
+   * @return
+   *   The id (the term name but with spaces replaced with underscores).
+   */
+  protected function addContextTerm($resource, $term, $sanitize = array()) {
+    if (!is_a($resource, 'TripalWebServiceResource')) {
+      throw new Exception('addContextTerm: Please provide a $resource of type TripalWebServiceResource.');
+    }
+
+    if (!$term) {
+      $backtrace = debug_backtrace();
+      throw new Exception('addContextTerm: Please provide a non NUll or non empty $term.');
+
+    }
+    if (!$term['name']) {
+      throw new Exception('addContextTerm: The provided term does not have a name: ' . print_r($term, TRUE));
+    }
+
+    // Make sure the vocab is present
+    $vocab = $term['vocabulary'];
+    $this->addContextVocab($resource, $vocab);
+
+    // Sanitize the term key
+    $key_adj = $this->getContextTerm($term, $sanitize);
+
+    // Create the compact IRI
+    $compact_iri = $vocab['short_name'] . ':' . $term['accession'];
+
+    // If the full naming is indicated in the service parameters then
+    // set the full name of the keys to include the vocab short name.
+    if (array_key_exists('full_keys', $this->params)) {
+      //$key_adj = $vocab['short_name'] . ':' . $key_adj;
+    }
+
+    $iri = $term['url'];
+
+    $resource->addContextItem($key_adj, $compact_iri);
+    $resource->addContextItem($compact_iri, $iri);
+
+    return $key_adj;
+  }
+
+  /**
+   * Adds a vocabulary to the '@context' section for a given resource.
+   *
+   * @param $resource
+   *   A TripalWebServiceResource instance.
+   * @param $vocab
+   *   The vocabulary array.
+   */
+  protected function addContextVocab($resource, $vocab) {
+
+    if (!is_a($resource, 'TripalWebServiceResource')) {
+      throw new Exception('addContextVocab: Please provide a $resource of type TripalWebServiceResource.');
+    }
+
+    $key = $vocab['short_name'];
+    // The URL here should be the URL prefix not the database home
+    // page.  But unfortunately, the URL prefix can't be guaranteed to be
+    // a true prefix. Not all databases place by the rules.  For this reason,
+    // we can never use JSON-LD compact IRIs. :-(
+    $iri = $vocab['sw_url'];
+    $resource->addContextItem($key, $iri);
+  }
+
+  /**
+   * Adds a key/value property to the given resource.
+   *
+   * @param $resource
+   *   A TripalWebServiceResource instance.
+   * @param $term
+   *   The term array for the key.
+   * @param $value
+   *   The value to add.
+   * @param $santize
+   *   An array of keywords indicating how to santize the key.  By default,
+   *   no sanitizing occurs.  The two valid types are 'lowercase', and 'spacing'
+   *   where 'lowercase' converts the term name to all lowercase and
+   *   'spacing' replaces any spaces with underscores.
+   * @return $key
+   *   The key (the term name but with spaces replaced with underscores).
+   */
+  public function addResourceProperty($resource, $term, $value, $sanitize = array()) {
+    if (!is_a($resource, 'TripalWebServiceResource')) {
+      throw new Exception('addProperty: Please provide a $resource of type TripalWebServiceResource.');
+    }
+
+    // First add the term
+    $key = $this->addContextTerm($resource, $term, $sanitize);
+
+    // Then add the property.
+    $resource->addProperty($key, $value);
+  }
+  /**
+   * Sets the type for the given resource.
+   *
+   * The type exist in the context of the web service.
+   *
+   * @param $resource
+   *   A TripalWebServiceResource instance.
+   * @param $type
+   *   The type
+   * @param $santize
+   *   An array of keywords indicating how to santize the key.  By default,
+   *   no sanitizing occurs.  The two valid types are 'lowercase', and 'spacing'
+   *   where 'lowercase' converts the term name to all lowercase and
+   *   'spacing' replaces any spaces with underscores.
+   */
+  public function setResourceType($resource, $term, $sanitize = array('spacing')) {
+
+    if (!is_a($resource, 'TripalWebServiceResource')) {
+      throw new Exception('addProperty: Please provide a $resource of type TripalWebServiceResource.');
+    }
+    // First add the term
+    $key = $this->addContextTerm($resource, $term, $sanitize);
+
+    // Then set the type
+    $resource->setType($key);
   }
-}
+}

+ 317 - 239
tripal_ws/includes/TripalWebService/TripalEntityService_v0_1.inc

@@ -10,10 +10,10 @@ class TripalEntityService_v0_1 extends TripalWebService {
   /**
    * A bit of text to describe what this service provides.
    */
-  public static $description = 'Provides acesss to the biological and
-    ancilliary data available on this site. Each content type represents
-    biological data that is defined in a controlled vocabulary (e.g.
-    Sequence Ontology term: gene (SO:0000704)).';
+  public static $description = 'Provides acesss to the biological and ' .
+    'ancilliary data available on this site. Each content type represents ' .
+    'biological data that is defined in a controlled vocabulary (e.g. ' .
+    'Sequence Ontology term: gene (SO:0000704)).';
 
   /**
    * A machine-readable type for this service. This name must be unique
@@ -30,6 +30,17 @@ class TripalEntityService_v0_1 extends TripalWebService {
     parent::__construct($base_path);
   }
 
+  /**
+   * @see TripalWebService::getDocumentation()
+   */
+  public function getDocumentation() {
+    // Add the classes that this resource supports.
+    $this->addDocBundleClasses();
+    $this->addDocContentCollectionClass();
+
+    return parent::getDocumentation();
+  }
+
   /**
    * @see TripalWebService::handleRequest()
    */
@@ -40,6 +51,14 @@ class TripalEntityService_v0_1 extends TripalWebService {
     $entity_id = (count($this->path) > 1) ? $this->path[1] : '';
     $expfield  = (count($this->path) > 2) ? $this->path[2] : '';
 
+    // is this a valid content type?
+    if ($ctype) {
+      $bundle = tripal_load_bundle_entity(array('label' => $ctype));
+      if (!$bundle) {
+        throw new Exception('Invalid content type: ' . $ctype);
+      }
+    }
+
     // If we have a content type then list all of the entities that belong
     // to it.
     if ($ctype and !$entity_id and !$expfield) {
@@ -67,37 +86,39 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     // Get the TripalBundle, TripalTerm and TripalVocab for this type.
     $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-    $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-    $term = reset($term);
-    $vocab = $term->vocab;
+
+    // Find the field that matches the field name provided by the user.
+    list($field, $instance, $term) = $this->findField($bundle, $expfield);
+
+    if (!$field) {
+      throw new Exception("Could not find a matching field for the name: $expfield");
+    }
 
     // Get the TripalEntity
-    $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
+    $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id), FALSE, array($field['id']));
     $entity = reset($entity);
 
-    // If we couldn't match this field argument to a field and entity then return
+    // If we couldn't find the entity then fail.
     if (!$entity) {
-      throw new Exception("Cannot find this record.");
+      throw new Exception("Cannot find the record with id $entity_id.");
     }
 
     // Check that the user has access to this entity.  If not then the
     // function call will throw an error.
     $this->checkAccess($entity);
 
-    list($field, $instance, $term) = $this->findField($bundle, $expfield);
 
     // Next add in the ID and Type for this resources.
-    $key = $term['name'];
-    $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
-    $this->resource->addContextItem($key_adj, $term['url']);
-    $this->resource->setID(urlencode($key));
-    $this->resource->setType($key_adj);
+    $this->setResourceType($this->resource, $term);
+    $this->resource->setID(urlencode($term['name']));
 
-    // Attach the field and then add it's values to the response.
-    field_attach_load($entity->type, array($entity->id => $entity),
-        FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
+    if (!property_exists($entity, $field['field_name'])) {
+      // Attach the field and then add its values to the response.
+      field_attach_load($entity->type, array($entity->id => $entity),
+          FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
+    }
 
-    $this->addEntityField($key_adj, $term, $entity, $bundle, $field, $instance, $service_path, $expfield);
+    $this->addEntityField($term, $entity, $bundle, $field, $instance, $service_path, $expfield);
   }
 
   /**
@@ -110,6 +131,11 @@ class TripalEntityService_v0_1 extends TripalWebService {
     foreach ($instances as $instance) {
       $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);
@@ -130,10 +156,12 @@ class TripalEntityService_v0_1 extends TripalWebService {
     $bundle = tripal_load_bundle_entity(array('label' => $ctype));
     $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
     $term = reset($term);
-    $vocab = $term->vocab;
+
+    // Convert the $term to a simple array
+    $term = tripal_get_term_details($term->vocab->vocabulary, $term->accession);
 
     // Add the vocabulary to the context.
-    $this->resource->addContextItem($term->name, $term->url);
+    $this->resource->addContextItem($term['name'], $term['url']);
 
     // Get the TripalEntity.
     $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
@@ -151,16 +179,12 @@ class TripalEntityService_v0_1 extends TripalWebService {
     $itemPage = tripal_get_term_details('schema', 'ItemPage');
     $label = tripal_get_term_details('rdfs', 'label');
     $this->resource->setID($entity_id);
-    $this->resource->setType($term->name);
-    $this->resource->addContextItem('label', $label['url']);
-    $this->resource->addContextItem('ItemPage', $itemPage['url']);
-    $this->resource->addProperty('label', $entity->title);
-    $this->resource->addProperty('ItemPage', url('/bio_data/' . $entity->id, array('absolute' => TRUE)));
+    $this->setResourceType($this->resource, $term);
+    $this->addResourceProperty($this->resource, $label, $entity->title);
+    $this->addResourceProperty($this->resource, $itemPage, url('/bio_data/' . $entity->id, array('absolute' => TRUE)));
 
+    // Add in the entitie's fields.
     $this->addEntityFields($entity, $bundle, $term, $service_path);
-
-//    tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, $response, $ws_path, $ctype, $entity_id, $params);
-//    tripal_ws_services_v0_1_write_context($response, $ctype);
   }
 
   /**
@@ -199,6 +223,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
     // in the order they were set for the display.
     $instances = field_info_instances('TripalEntity', $bundle->name);
 
+    // Sort the instances by their weight.
     uasort($instances, function($a, $b) {
       $a_weight = (is_array($a) && isset($a['widget']['weight'])) ? $a['widget']['weight'] : 0;
       $b_weight = (is_array($b) && isset($b['widget']['weight'])) ? $b['widget']['weight'] : 0;
@@ -221,6 +246,12 @@ class TripalEntityService_v0_1 extends TripalWebService {
       // Get the information about this field.
       $field = field_info_field($field_name);
 
+      // Skip the remote__data field that is provided by the tripal_Ws
+      // module.
+      if ($field['type'] == 'remote__data') {
+        continue;
+      }
+
       // By default, the label for the key in the output should be the
       // term from the vocabulary that the field is assigned. But in the
       // case that the field is not assigned a term, we must use the field name.
@@ -231,8 +262,6 @@ class TripalEntityService_v0_1 extends TripalWebService {
       if (!$term) {
         continue;
       }
-      $key = $term['name'];
-      $key_adj = strtolower(preg_replace('/ /', '_', $key));
 
       // If this field should not be attached by default then just add a link
       // so that the caller can get the information separately.
@@ -244,29 +273,28 @@ class TripalEntityService_v0_1 extends TripalWebService {
         // that information.
         $items = field_get_items('TripalEntity', $entity, $field_name);
         if ($items and count($items) > 0 and $items[0]['value']) {
-          $this->resource->addContextItem($key_adj, $term['url']);
-          $this->resource->addProperty($key_adj, $service_path . '/' . $entity->id . '/' . urlencode($term['name']));
+         $this->addResourceProperty($this->resource, $term, $service_path . '/' . $entity->id . '/' . urlencode($term['name']), array('lowercase', 'spacing'));
         }
         else {
           if ($hide_fields == 'show') {
-            $this->resource->addContextItem($key_adj, $term['url']);
-            $this->resource->addProperty($key_adj, NULL);
+            $this->addResourceProperty($this->resource, $term, NULL, array('lowercase', 'spacing'));
           }
         }
         continue;
       }
 
       // Get the details for this field for the JSON-LD response.
-      $this->addEntityField($key_adj, $term, $entity, $bundle, $field, $instance, $service_path);
+      $this->addEntityField($term, $entity, $bundle, $field, $instance, $service_path);
     }
   }
 
   /**
    * Adds the field as a property of the entity resource.
    */
-  private function addEntityField($key, $term, $entity, $bundle, $field, $instance,
+  private function addEntityField($term, $entity, $bundle, $field, $instance,
       $service_path, $expfield = NULL) {
 
+
     // If the entity is set to hide fields that have no values then we
     // want to honor that in the web services too.
     $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
@@ -288,7 +316,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
 
     $values = array();
     for ($i = 0; $i < count($items); $i++) {
-      $values[$i] = $this->sanitizeFieldKeys($items[$i]['value'], $bundle, $service_path);
+      $values[$i] = $this->sanitizeFieldKeys($this->resource, $items[$i]['value'], $bundle, $service_path);
     }
 
     if ($hide_fields == 'hide' and empty($values[0])) {
@@ -307,27 +335,24 @@ class TripalEntityService_v0_1 extends TripalWebService {
           }
         }
         else {
-          $this->resource->addContextItem($key, $term['url']);
-          $this->resource->addProperty($key, $values[0]);
+          $this->addResourceProperty($this->resource, $term, $values[0], array('lowercase', 'spacing'));
         }
       }
       // If the value is not an array it's a scalar so add it as is to the
       // response.
       else {
-        $this->resource->addContextItem($key, $term['url']);
-        $this->resource->addProperty($key, $values[0]);
+        $this->addResourceProperty($this->resource, $term, $values[0], array('lowercase', 'spacing'));
       }
     }
 
-    // If the field cardinality is > 1
+    // If the field cardinality is > 1 or -1 (for unlimited)
     if ($field['cardinality'] != 1) {
 
       // If this is the expanded field page then we need to swap out
       // the resource for a collection.
       $response = new TripalWebServiceCollection($service_path . '/' . urlencode($expfield), $this->params);
       $label = tripal_get_term_details('rdfs', 'label');
-      $response->addContextItem('label', $label['url']);
-      $response->addProperty('label', $instance['label']);
+      $this->addResourceProperty($response, $label, $instance['label']);
       $i = 0;
       foreach ($values as $delta => $element) {
         $member = new TripalWebServiceResource($service_path . '/' . urlencode($expfield));
@@ -335,7 +360,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
         // Add the context of the parent resource because all of the keys
         // were santizied and set to match the proper context.
         $member->setContext($this->resource);
-        $member->setType($key);
+        $this->setResourceType($member, $term);
         foreach ($element as $key => $value) {
           $member->addProperty($key, $value);
         }
@@ -346,7 +371,8 @@ class TripalEntityService_v0_1 extends TripalWebService {
         $this->resource = $response;
       }
       else {
-        $this->resource->addProperty($key, $response);
+        //$this->resource->addProperty($key, $response);
+        $this->addResourceProperty($this->resource, $term, $response, array('lowercase', 'spacing'));
       }
     }
   }
@@ -354,7 +380,8 @@ class TripalEntityService_v0_1 extends TripalWebService {
   /**
    * Rewrites the keys of a field's items array for use with web services.
    */
-  private function sanitizeFieldKeys($value, $bundle, $service_path) {
+  private function sanitizeFieldKeys($resource, $value, $bundle, $service_path) {
+
     // If the entity is set to hide fields that have no values then we
     // want to honor that in the web services too.
     $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
@@ -376,15 +403,15 @@ class TripalEntityService_v0_1 extends TripalWebService {
           $vocabulary = $matches[1];
           $accession = $matches[2];
           $term = tripal_get_term_details($vocabulary, $accession);
+          $key = $this->addContextTerm($resource, $term, array('lowercase', 'spacing'));
 
-          $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
           if (is_array($v)) {
-            $temp[$key_adj] = $this->sanitizeFieldKeys($v, $bundle, $service_path);
+            $temp[$key] = $this->sanitizeFieldKeys($resource, $v, $bundle, $service_path);
           }
           else {
-            $temp[$key_adj] = $v;
+            $temp[$key] = $v;
           }
-          $this->resource->addContextItem($key_adj, $term['url']);
+          $term['name'] = $key;
 
         }
         else {
@@ -491,7 +518,7 @@ class TripalEntityService_v0_1 extends TripalWebService {
       $dir = 'ASC';
 
       // If the user provided more than one order statement then those are
-      // separated by a semicolong.
+      // separated by a semicolon.
       $items = explode(';', $order_params);
       foreach ($items as $key) {
 
@@ -741,17 +768,25 @@ class TripalEntityService_v0_1 extends TripalWebService {
    */
   private function doContentTypeList($ctype) {
     $service_path = $this->getServicePath() . '/' . urlencode($ctype);
-    $label = tripal_get_term_details('rdfs', 'label');
     $this->resource = new TripalWebServiceCollection($service_path, $this->params);
-    $this->resource->addContextItem('label', $label['url']);
 
     // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
     $bundle = tripal_load_bundle_entity(array('label' => $ctype));
     $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
     $term = reset($term);
 
+    // The type of collection is provided by our API vocabulary service.
+    $vocab_service = new TripalVocabService_v0_1($this->base_path);
+    $this->resource->addContextItem('vocab', $vocab_service->getServicePath() . '#');
+    $this->resource->addContextItem(urlencode($bundle->label) . 'Collection', 'vocab:' . urlencode($bundle->label) . 'Collection');
+    $this->resource->setType(urlencode($bundle->label) . 'Collection');
+
+    // Convert term to a simple array
+    $term = tripal_get_term_details($term->vocab->vocabulary, $term->accession);
+
     // Set the label for this collection.
-    $this->resource->addProperty('label', $bundle->label . " collection");
+    $label = tripal_get_term_details('rdfs', 'label');
+    $this->addResourceProperty($this->resource, $label, $bundle->label . " Collection");
 
     // For quick lookup, get the mapping of WS keys to their appropriate fields.
     $field_mapping = $this->getFieldMapping($bundle);
@@ -832,13 +867,10 @@ class TripalEntityService_v0_1 extends TripalWebService {
       $itemPage = tripal_get_term_details('schema', 'ItemPage');
       $label = tripal_get_term_details('rdfs', 'label');
       $member = new TripalWebServiceResource($service_path);
-      $member->addContextItem('label', $label['url']);
-      $member->addContextItem('ItemPage', $itemPage['url']);
-      $member->addContextItem($term->name, $term->url);
       $member->setID($entity->id);
-      $member->setType($term->name);
-      $member->addProperty('label', $entity->title);
-      $member->addProperty('ItemPage', url('/bio_data/' . $entity->id, array('absolute' => TRUE)));
+      $this->setResourceType($member, $term);
+      $this->addResourceProperty($member, $label, $entity->title);
+      $this->addResourceProperty($member, $itemPage, url('/bio_data/' . $entity->id, array('absolute' => TRUE)));
       $this->resource->addMember($member);
     }
   }
@@ -848,10 +880,14 @@ class TripalEntityService_v0_1 extends TripalWebService {
    */
   private function doAllTypesList() {
     $service_path = $this->getServicePath();
-    $label = tripal_get_term_details('rdfs', 'label');
+    $service_vocab = new TripalVocabService_v0_1($this->base_path);
     $this->resource = new TripalWebServiceCollection($service_path, $this->params);
-    $this->resource->addContextItem('label', $label['url']);
-    $this->resource->addProperty('label', 'Content Types');
+    $this->resource->addContextItem('vocab', $service_vocab->getServicePath());
+    $this->resource->addContextItem('ContentCollection', $service_vocab->getServicePath() . '#ContentCollection');
+    $this->resource->setType('ContentCollection');
+
+    $label = tripal_get_term_details('rdfs', 'label');
+    $this->addResourceProperty($this->resource, $label, 'Content Types');
 
     // Get the list of published terms (these are the bundle IDs)
     $bundles = db_select('tripal_bundle', 'tb')
@@ -866,53 +902,98 @@ class TripalEntityService_v0_1 extends TripalWebService {
       $term = reset($entity);
       $vocab = $term->vocab;
 
+      // Convert the term to a simple array
+      $term = tripal_get_term_details($term->vocab->vocabulary, $term->accession);
+
       $member = new TripalWebServiceResource($service_path);
-      // If the term has no URL then we'll default to using thie site's
-      // term lookup service.
-      $url = $term->url;
+      $member->setID(urlencode($bundle->label));
+
+      $vocab_service = new TripalVocabService_v0_1($this->base_path);
+      $member->addContextItem('vocab', $vocab_service->getServicePath() . '#');
+      $member->addContextItem(urlencode($bundle->label) . 'Collection', 'vocab:' . urlencode($bundle->label) . 'Collection');
+      $member->setType(urlencode($bundle->label) . 'Collection');
+
+      // Make sure the term has a URL.
+      $url = $term['url'];
       if (!$url) {
-        $url = url('cv/lookup/' . $term->vocab->vocabulary . '/' . $term->accession , array('absolute' => TRUE));
+        throw new Exception(t('Missing a URL for the term: @term.', array('@term' => $term['vocabulary']['short_name'] . ':' . $term['accession'])));
       }
-      $member->addContextItem($term->name, $url);
-      $member->addContextItem('label', $label['url']);
-      $member->addProperty('label', $bundle->label);
-      $member->addContextItem('description', 'hydra:description');
+      $this->addResourceProperty($member, $label, $bundle->label . ' Collection');
+      $member->addContextItem('description', 'rdfs:comment');
       // Get the bundle description. If no description is provided then
       // use the term definition
-      $description = tripal_get_bundle_variable('description', $bundle->id);
+      $description = trim(tripal_get_bundle_variable('description', $bundle->id));
       if (!$description) {
-        $description = $term->definition;
+        $description = $term['definition'];
       }
       if (!$description) {
         $description = '';
       }
-      $member->addProperty('description', $description);
-      $member->setID(urlencode($bundle->label));
-      $member->setType($term->name);
-
+      $member->addProperty('description', 'A collection of ' . $bundle->label . ' resources: ' . lcfirst($description));
       $this->resource->addMember($member);
 
     }
   }
 
+
+  /**
+   * Adds the content collection class to the document for this service.
+   */
+  private function addDocContentCollectionClass() {
+    $details = array(
+      'id' => 'vocab:ContentCollection',
+      'title' => 'Content Collection',
+    );
+    $vocab = tripal_get_vocabulary_details('hydra');
+    $propeties = array();
+    $propeties[] = array(
+      'type' => $vocab['sw_url'],
+      'title' => 'member',
+      'description' => "The list of available content types.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE,
+    );
+    $propeties[] = array(
+      "type" => $vocab['sw_url'],
+      "title" => "totalItems",
+      "description" => "The total number of content types.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+    $propeties[] = array(
+      "type" => $vocab['sw_url'],
+      "title" => "label",
+      "description" => "The type content.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+
+    $operations = array();
+    $operations['GET'] = array(
+      'label' => 'Retrieves a collection (a list) of available content types.',
+      'type' => '_:content_collection_retrieve',
+      'expects' => NULL,
+      'returns' => 'vocab:ContentCollection',
+    );
+    $this->addDocClass($details,$operations, $propeties);
+  }
   /**
-   * @see TripalWebService::getSupportedClasses()
+   * Adds classes for every content type to the documentation for this service.
    */
-  public function getSupportedClasses() {
+  private function addDocBundleClasses() {
 
     global $user;
 
-    // An array of TripalWebServiceResources containing a
-    // description of the supported classes.
-    $supported_classes = array();
-
     // Get the list of published terms (these are the bundle IDs)
     $bundles = db_select('tripal_bundle', 'tb')
       ->fields('tb')
       ->orderBy('tb.label', 'ASC')
       ->execute();
 
-    // Iterate through the terms and add an entry in the collection.
+    // Iterate through the content types and add a class for each one.
     $i = 0;
     while ($bundle = $bundles->fetchObject()) {
       $entity =  entity_load('TripalTerm', array('id' => $bundle->term_id));
@@ -926,177 +1007,174 @@ class TripalEntityService_v0_1 extends TripalWebService {
         $description = $term->definition;
       }
 
-      $supported = new TripalWebServiceResource($this->getServicePath());
-      $supported->addContextItem('supportedOperation', 'hydra:supportedOperation');
-      $supported->addContextItem('supportedProperty', 'hydra:supportedProperty');
-      $supported->setID(urlencode($bundle->label));
-      $supported->setType('hydra:Class');
-      $supported->addProperty('hydra:title', $bundle->label);
-      $supported->addProperty('hydra:description', $description);
+      // Create the details array for the class.
+      $class_id = $this->getServicePath() . '/' . urlencode($bundle->label);
+      $details = array(
+        'id' => $term->url,
+        'title' => $bundle->label,
+        'description' => $description,
+      );
 
       // Add in the supported operations for this content type.
       $operations = array();
-
       // If the user can view this content type.
       if (user_access('view ' . $bundle->name)) {
-        // All content types allow GET (if the user has view permission).
-        $get_op = new TripalWebServiceResource($this->getServicePath());
-        $get_op->addContextItem('supportedOperation', 'hydra:supportedOperation');
-        $get_op->addContextItem('method', 'hydra:method');
-        $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $get_op->addContextItem('label', 'rdfs:label');
-        $get_op->addContextItem('description', 'rdfs:comment');
-        $get_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $get_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $get_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_retrieve');
-        $get_op->setType('hydra:Operation');
-        $get_op->addProperty('method', 'GET');
-        if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $get_op->addProperty('label', "Retrieves an " . $bundle->label . " entity.");
-        }
-        else {
-          $get_op->addProperty('label', "Retrieves a " . $bundle->label . " entity.");
-        }
-        $get_op->addProperty('description', NULL);
-        $get_op->addProperty('expects', NULL);
-        $get_op->addProperty('returns', $term->url);
-        $get_op->addProperty('statusCodes', array(
-          array(
-            'code' => 404,
-            'description' => 'The ' . $bundle->label . ' could not be found using the provided ID.'
-          ),
-        ));
-        $operations[] = $get_op;
-      }
-
-      // If the user can create this content type.
-      if (user_access('create ' . $bundle->name)) {
-        // All content types allow GET (if the user has view permission).
-        $create_op = new TripalWebServiceResource($this->getServicePath());
-        $create_op->addContextItem('method', 'hydra:method');
-        $create_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $create_op->addContextItem('label', 'rdfs:label');
-        $create_op->addContextItem('description', 'rdfs:comment');
-        $create_op->addContextItem('code', 'hydra:statusCode');
-        $create_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $create_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $create_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_create');
-        $create_op->setType('http://schema.org/CreateAction');
-        $create_op->addProperty('method', 'POST');
-        if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $create_op->addProperty('label', "Creates an " . $bundle->label . " entity.");
-        }
-        else {
-          $create_op->addProperty('label', "Creates a " . $bundle->label . " entity.");
-        }
-        $create_op->addProperty('description', NULL);
-        $create_op->addProperty('expects', $term->url);
-        $create_op->addProperty('returns', $term->url);
-        $create_op->addProperty('statusCodes', array(
-          array(
-            'code' => 404,
-            'description' => 'The ' . $bundle->label . ' could not be created.'
-          ),
-          array(
-            'code' => 409,
-            'description' => 'The ' . $bundle->label . ' already exists.'
-          ),
-        ));
-        $operations[] = $create_op;
+        $label = "Retrieves the " . $bundle->label . " resource.";
+        $operations['GET'] = array(
+          'label' => $label,
+          'description' => NULL,
+          'returns' => $term->url,
+          'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_retrieve',
+        );
       }
 
       // If the user can edit this content type.
       if (user_access('edit ' . $bundle->name)) {
-        // All content types allow GET (if the user has view permission).
-        $edit_op = new TripalWebServiceResource($this->getServicePath());
-        $edit_op->addContextItem('method', 'hydra:method');
-        $edit_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $edit_op->addContextItem('label', 'rdfs:label');
-        $edit_op->addContextItem('description', 'rdfs:comment');
-        $edit_op->addContextItem('code', 'hydra:statusCode');
-        $edit_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $edit_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $edit_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_update');
-        $edit_op->setType('http://schema.org/UpdateAction');
-        $edit_op->addProperty('method', 'PUT');
+        $label = "Update and replace the " . $bundle->label . " resource.";
         if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $edit_op->addProperty('label', "Update and replace an " . $bundle->label . " entity.");
-        }
-        else {
-          $edit_op->addProperty('label', "Update and replace a " . $bundle->label . " entity.");
+          $label = "Update and replace an " . $bundle->label . " resource.";
         }
-        $edit_op->addProperty('description', NULL);
-        $edit_op->addProperty('expects', $term->url);
-        $edit_op->addProperty('returns', $term->url);
-        $edit_op->addProperty('statusCodes', array(
-          array(
-            'code' => 404,
-            'description' => 'The ' . $bundle->label . ' could not be updated using the provided ID.'
-          ),
-        ));
-        $operations[] = $edit_op;
+// TODO: add this back in when web services support this method.
+//         $operations['PUT'] = array(
+//           'label' => $label,
+//           'description' => NULL,
+//           'returns' => $term->url,
+//           'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_update',
+//         );
       }
 
       // If the user can edit this content type.
       if (user_access('delete ' . $bundle->name)) {
-        // All content types allow GET (if the user has view permission).
-        $delete_op = new TripalWebServiceResource($this->getServicePath());
-        $delete_op->addContextItem('method', 'hydra:method');
-        $delete_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $delete_op->addContextItem('label', 'rdfs:label');
-        $delete_op->addContextItem('description', 'rdfs:comment');
-        $delete_op->addContextItem('code', 'hydra:statusCode');
-        $delete_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $delete_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $delete_op->setID('_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_delete');
-        $delete_op->setType('http://schema.org/DeleteAction');
-        $delete_op->addProperty('method', 'DELETE');
+        $label =  "Deletes the " . $bundle->label . " resource.";
         if (preg_match('/^[aeiou]/i', $bundle->label)) {
-          $delete_op->addProperty('label', "Deletes an " . $bundle->label . " entity.");
-        }
-        else {
-          $delete_op->addProperty('label', "Deletes a " . $bundle->label . " entity.");
+          $label =  "Deletes an " . $bundle->label . " resource.";
         }
-        $delete_op->addProperty('description', NULL);
-        $delete_op->addProperty('expects', $term->url);
-        $delete_op->addProperty('returns', $term->url);
-        $delete_op->addProperty('statusCodes', array(
-          array(
-            'code' => 404,
-            'description' => 'The ' . $bundle->label . ' could not be deleted using the provided ID.'
-          ),
-        ));
-        $operations[] = $delete_op;
+// TODO: add this back in when web services support this method.
+//         $operations['DELETE'] = array(
+//           'label' => $label,
+//           'description' => NULL,
+//           'returns' => $term->url,
+//           'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_delete',
+//         );
       }
 
-      $supported->addProperty('supportedOperation', $operations);
-      $supported_classes[] = $supported;
-    }
+      // Add in the properties that correspond to fields in the data.
+      $properties = $this->buildDocBundleFieldProperties($bundle);
+
+      $this->addDocClass($details, $operations, $properties);
+
+      // Now add the bundle collection class.
+      $this->addDocBundleCollectionClass($bundle, $term);
+
+    } // end while ($bundle = $bundles->fetchObject()) { ...
+  }
 
-    return $supported_classes;
+  /**
+   * Every content type (bundle) has fields that need to be set as properties.
+   */
+  private function buildDocBundleFieldProperties($bundle) {
+    $properties = array();
+
+    $instances = field_info_instances('TripalEntity', $bundle->name);
+    foreach ($instances as $instance) {
+      // Skip deleted fields.
+      if ($instance['deleted']) {
+        continue;
+      }
+      // Skip hidden fields.
+      if ($instance['display']['default']['type'] == 'hidden') {
+        continue;
+      }
+
+      $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;
+      }
+      $property = array(
+        'type' => $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'],
+        'title' => $instance['label'],
+        'description' => $instance['description'],
+        "required" => $instance['required'] ? TRUE : FALSE,
+        "readonly" => FALSE,
+        "writeonly" => TRUE,
+      );
+      $properties[] = $property;
+    }
+    return $properties;
   }
+  /**
+   * Every content type (bundle) needs a collection class in the documentation.
+   */
+  private function addDocBundleCollectionClass($bundle, $term) {
+    $details = array(
+      'id' => 'vocab:' . urlencode($bundle->label) . 'Collection',
+      'title' => $bundle->label . ' Collection',
+      'subClassOf' => 'hydra:Collection',
+      'description' => 'A collection (or list) of ' . $bundle->label . ' resources.',
+    );
+    $vocab = tripal_get_vocabulary_details('hydra');
+    $propeties = array();
+    $propeties[] = array(
+      'type' => $vocab['sw_url'],
+      'title' => 'member',
+      'description' => "The list of available " . $bundle->label . '(s).',
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE,
+    );
+    $propeties[] = array(
+      "type" => $vocab['sw_url'],
+      "title" => "totalItems",
+      "description" => "The total number of resources.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+    $propeties[] = array(
+      "type" => $vocab['sw_url'],
+      "title" => "label",
+      "description" => "A label or name for the resource.",
+      "required" => null,
+      "readonly" => FALSE,
+      "writeonly" => FALSE
+    );
+
+    $class_id = $this->getServicePath() . '/' . urlencode($bundle->label);
+    $operations = array();
+    $operations['GET'] = array(
+      'label' => 'Retrieves a list of all ' . $bundle->label . ' resources.',
+      'description' => NULL,
+      'expects' => NULL,
+      'returns' => $term->url,
+      'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_collection_retrieve',
+    );
+
+    // If the user can create this content type then we allow a POST on the
+    // collection type.
+    if (user_access('create ' . $bundle->name)) {
+      $label = "Creates a " . $bundle->label;
+      if (preg_match('/^[aeiou]/i', $bundle->label)) {
+        $label = "Creates an " . $bundle->label;
+      }
+// TODO: add this back in when web services support this method.
+//       $operations['POST'] = array(
+//         'label' => $label,
+//         'description' => NULL,
+//         'expects' => $term->url,
+//         'returns' => $term->url,
+//         'type' => '_:' . preg_replace('/[^\w]/', '_', strtolower($bundle->label)) . '_create',
+//         'statusCodes' => array(
+//           array(
+//             "code" => 201,
+//             "description" => "If the " . $bundle->label . " was created successfully."
+//           ),
+//         ),
+//       );
+    }
+    $this->addDocClass($details, $operations, $propeties);
+  }
+
 }

+ 124 - 115
tripal_ws/includes/TripalWebService/TripalVocabService_v0_1.inc

@@ -17,140 +17,149 @@ class TripalVocabService_v0_1 extends TripalWebService {
    */
   public static $type = 'vocab';
 
+  /**
+   * The list of web services.
+   */
+  private $services;
+
+
+  /**
+   * Constructor for the TripalVocabService_v0_1 class.
+   */
+  public function __construct($base_path) {
+    parent::__construct($base_path);
+
+    // Get the list of services provided by this Tripal site.
+    $this->services = tripal_get_web_services();
+    foreach ($this->services as $sindex => $service_class) {
+      // Load the class code.
+      tripal_load_include_web_service_class($service_class);
+
+      // Remove this class from the list of services.
+      if ($service_class::$type == 'vocab') {
+        unset($this->services[$sindex]);
+      }
+    }
+  }
 
   /**
    * @see TripalWebService::handleRequest()
    */
   public function handleRequest() {
-    $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
 
-    $this->resource->addContextItem('ApiDocumentation', 'hydra:ApiDocumentation');
+    // Create the vocabulary resource.
+    $this->resource->addContextItem('vocab', $this->getServicePath() . '#');
+    $this->resource->addContextItem('apiDocumentation', 'hydra:apiDocumentation');
     $this->resource->addContextItem('supportedClass', 'hydra:supportedClass');
-    $this->resource->setType('ApiDocumentation');
+    $this->resource->setType('apiDocumentation');
     $this->resource->setID('vocab/' . $this->getVersion());
 
-    // We need to list the content types as properties of the EntryPoint.
-    $properties = array();
-
+    // Add the EntryPoint class.
+    $this->addEntryPointClass();
+    foreach ($this->documentation as $supported) {
+      $this->resource->addProperty('supportedClass', $supported);
+    }
 
-    // Iterate through all of the web services and get their documentation
-    $services = tripal_get_web_services();
-    foreach ($services as $service_class) {
-      tripal_load_include_web_service_class($service_class);
+    // Iterate through all of the web services and build their documentation
+    foreach ($this->services as $service_class) {
       $service = new $service_class($this->base_path);
-      $supported_classes = $service->getSupportedClasses();
+      $supported_classes = $service->getDocumentation();
       foreach ($supported_classes as $supported) {
         $this->resource->addProperty('supportedClass', $supported);
       }
-      if ($service_class::$type != 'vocab') {
-        $event_prop = new TripalWebServiceResource($this->getServicePath());
-        $event_prop->addProperty('hydra:title', $service_class::$type);
-        $event_prop->addProperty('hydra:description', $service_class::$description);
-        $event_prop->addContextItem('required', 'hydra:reqiured');
-        $event_prop->addProperty('required', NULL);
-        $event_prop->addContextItem('readonly', 'hydra:readonly');
-        $event_prop->addProperty('readonly', TRUE);
-        $event_prop->addContextItem('writeonly', 'hydra:writeonly');
-        $event_prop->addProperty('writeonly', FALSE);
-        $event_prop->addContextItem('property', array(
-          "@id" => "hydra:property",
-          "@type" => "@id"
-        ));
-        //$event_prop->setID('');
-        //$event_prop->setType('');
-        $prop = new TripalWebServiceResource($this->getServicePath());
-        $prop->setID('vocab:EntryPoint/' . $service_class::$type);
-        $prop->setType('hydra:Link');
-        $prop->addContextItem('label', 'rdfs:label');
-        $prop->addProperty('label', $service_class::$label);
-        $prop->addContextItem('description', 'rdfs:comment');
-        $prop->addProperty('description', $service_class::$description);
-        $prop->addContextItem('domain', array(
-          "@id" => "rdfs:domain",
-          "@type" => "@id"
-        ));
-        $prop->addProperty('domain', 'vocab:EntryPoint');
-        $prop->addContextItem('range', array(
-          "@id" => "rdfs:range",
-          "@type" => "@id"
-        ));
-        $prop->addProperty('range', 'hydra:Collection');
-
-        $prop_ops = array();
-        $prop_op = new TripalWebServiceResource($this->getServicePath());
-        $prop_op->setID('_:event_collectionretrieve');
-        $prop_op->setType('hydra:Operation');
-        $prop_op->addContextItem('method', 'hydra:method');
-        $prop_op->addProperty('method', 'GET');
-        $prop_op->addContextItem('label', 'rdfs:label');
-        $prop_op->addProperty('label', 'Retrieves all ' . $service_class::$label . ' entities.');
-        $prop_op->addContextItem('description', 'rdfs:comment');
-        $prop_op->addProperty('description', NULL);
-        $prop_op->addContextItem('expects', array(
-          "@id" => "hydra:expects",
-          "@type" => "@id"
-        ));
-        $prop_op->addProperty('expects', NULL);
-        $prop_op->addContextItem('returns', array(
-          "@id" => "hydra:returns",
-          "@type" => "@id"
-        ));
-        $prop_op->addProperty('returns', 'hydra:Collection');
-        $prop_op->addContextItem('statusCodes', 'hydra:statusCodes');
-        $prop_op->addProperty('statusCodes', array());
-        $prop_ops[] = $prop_op;
-
-        $prop->addContextItem('supportedOperation', 'hydra:supportedOperation');
-        $prop->addProperty('supportedOperation', $prop_ops);
-        $event_prop->addProperty('property', $prop);
-        $properties[] = $event_prop;
-      }
     }
+  }
 
-    // Add in the generic supported classes.
-    $entry_class = new TripalWebServiceResource($this->base_path);
-    $entry_class->addContextItem('label', 'rdfs:label');
-    $entry_class->setID($this->getServicePath() . '#EntryPoint');
-    $entry_class->setType('hydra:Class');
-    $entry_class->addProperty('label', 'EntryPoint');
-    $entry_class->addContextItem('description', 'rdfs:comment');
-    $entry_class->addProperty('description', 'The main entry point or homepage of the API');
-    $entry_class->addContextItem('subClassOf', array(
-      "@id" => "rdfs:subClassOf",
-      "@type" => "@id"
-    ));
-    $entry_class->addProperty('subClassOf', NULL);
-    $entry_class->addContextItem('supportedOperation', 'hydra:supportedOperation');
-    $entry_class->addContextItem('supportedProperty', 'hydra:supportedProperty');
+  /**
+   * Generates the EntryPoint class for the API documents.
+   *
+   * @return TripalWebServiceResource
+   *
+   */
+  private function addEntryPointClass(){
 
+    $service_path = $this->getServicePath();
+    $details = array(
+      'id' => $service_path . '#EntryPoint',
+      'title' => 'EntryPoint',
+      'description' => 'The main entry point or homepage of the API',
+      'subClassOf' => NULL,
+    );
+
+    // Add each service as a property.
+    $properties = array();
+    foreach ($this->services as $service_class) {
+      $service = new $service_class($this->base_path);
+
+      // Create a WebServiceResource for the hydra:Link type.
+      $link = new TripalWebServiceResource($this->base_path);
+      $link->setID('vocab:EntryPoint/' . $service::$type);
+      $link->setType('hydra:Link');
+      $link->addContextItem('domain', array(
+        "@id" => "rdfs:domain",
+        "@type" => "@id"
+      ));
+      $link->addContextItem('range', array(
+        "@id" => "rdfs:range",
+        "@type" => "@id"
+      ));
+      $link->addContextItem('readable', 'hydra:readable');
+      $link->addContextItem('writeable', 'hydra:writeable');
+      $link->addContextItem('required', 'hydra:required');
+      $link->addContextItem('description', 'rdfs:comment');
+      $link->addContextItem('label', 'rdfs:label');
+      $link->addProperty('hydra:title', $service_class::$label);
+      $link->addProperty('hydra:description', $service_class::$description);
+//       $link->addProperty('domain', $service_path . '#EntryPoint');
+//       $link->addProperty('range', $service_class::$label);
+
+      $ops = array();
+      $op = new TripalWebServiceResource($this->base_path);
+
+      $op->setID('_:' . $service::$type . '_retrieve');
+      $op->setType('hydra:Operation');
+      $op->addContextItem('method', 'hydra:method');
+      $op->addContextItem('label', 'rdfs:label');
+      $op->addContextItem('description', 'rdfs:comment');
+      $op->addContextItem('expects', array(
+        "@id" => "hydra:expects",
+        "@type" => "@id"
+      ));
+      $op->addContextItem('returns', array(
+        "@id" => "hydra:returns",
+        "@type" => "@id"
+      ));
+      $op->addContextItem('statusCodes', 'hydra:statusCodes');
+      $op->addProperty('method', "GET");
+      $op->addProperty('label', 'Retrieves the ' . $service_class::$label . ' resource.');
+      $op->addProperty('description', NULL);
+      $op->addProperty('expects', NULL);
+      $op->addProperty('returns', 'vocab:EntryPoint/' . $service::$type);
+      $op->addProperty('statusCodes', array());
+      $ops[] = $op;
+      $link->addContextItem('supportedOperation', 'hydra:supportedOperation');
+      $link->addProperty('supportedOperation', $ops);
+
+      $property = array(
+        'type' => $link,
+        'title' => $service_class::$label,
+        'description' => $service_class::$description,
+        'domain', 'vocab:EntryPoint',
+        'range', $service->getServicePath(),
+      );
+      $properties[] = $property;
+    }
 
     $operations = array();
+    $operations['GET'] = array(
+      'label' => "The APIs main entry point.",
+      'description' => NULL,
+      'expects' => NULL,
+      'returns' => $service_path . '#EntryPoint',
+      'type' => '_:entry_point_retrieve'
+    );
+
 
-    $get_op = new TripalWebServiceResource($this->getServicePath());
-    $get_op->addContextItem('method', 'hydra:method');
-    $get_op->addProperty('method', 'GET');
-    $get_op->addContextItem('statusCodes', 'hydra:statusCodes');
-    $get_op->addProperty('statusCodes', array());
-    $get_op->addContextItem('label', 'rdfs:label');
-    $get_op->addProperty('label', "The APIs main entry point.");
-    $get_op->addContextItem('description', 'rdfs:comment');
-    $get_op->addProperty('description', NULL);
-    $get_op->addContextItem('expects', array(
-      "@id" => "hydra:expects",
-      "@type" => "@id"
-    ));
-    $get_op->addProperty('expects', NULL);
-    $get_op->addContextItem('returns', array(
-      "@id" => "hydra:returns",
-      "@type" => "@id"
-    ));
-    $get_op->addProperty('returns', "vocab:EntryPoint");
-    $get_op->setID('_:entry_point');
-    $get_op->setType('hydra:Operation');
-
-    $operations[] = $get_op;
-    $entry_class->addProperty('supportedOperation', $operations);
-    $entry_class->addProperty('supportedProperty', $properties);
-    $this->resource->addProperty('supportedClass', $entry_class);
+    $this->addDocClass($details, $operations, $properties);
   }
 }

+ 9 - 9
tripal_ws/includes/TripalWebServiceCollection.inc

@@ -54,12 +54,9 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     $this->members = array();
 
     // Add some terms to the context.
-    $term = tripal_get_term_details('hydra', 'Collection');
-    $this->addContextItem('Collection', $term['url']);
-    $term = tripal_get_term_details('hydra', 'totalItems');
-    $this->addContextItem('totalItems', $term['url']);
-    $term = tripal_get_term_details('hydra', 'member');
-    $this->addContextItem('member', $term['url']);
+    $this->addContextItem('Collection', 'hydra:Collection');
+    $this->addContextItem('totalItems', 'hydra:totalItems');
+    $this->addContextItem('member', 'hydra:member');
     parent::setType('Collection');
 
     // If the totalItems is set to -1 then this means paging is turned off and
@@ -105,7 +102,9 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
    * @see TripalWebServiceResource::setType()
    */
   public function setType($type) {
-    throw new Exception("The type for a Collection can only be collection.");
+    // TODO: There should be a check to make sure that the type is a
+    // subclass of the hydra:Collection term.
+    parent::setType($type);
   }
 
   /**
@@ -163,7 +162,7 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     foreach ($this->members as $key => $member) {
       $member_data[] = $member->getData();
     }
-    $data['members'] = $member_data;
+    $data['member'] = $member_data;
 
     // If paging of this collection is enabled then add the pager control links.
 
@@ -193,4 +192,5 @@ class TripalWebServiceCollection extends TripalWebServiceResource {
     }
     return $context;
   }
-}
+}
+

+ 81 - 24
tripal_ws/includes/TripalWebServiceResource.inc

@@ -43,19 +43,24 @@ class TripalWebServiceResource {
 
     // First, add the RDFS and Hydra vocabularies to the context.  All Tripal
     // web services should use these.
-    $this->addContextItem('rdf', "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-    $this->addContextItem('rdfs', 'http://www.w3.org/2000/01/rdf-schema#');
+    $vocab = tripal_get_vocabulary_details('rdf');
 
-    $this->addContextItem('hydra', "http://www.w3.org/ns/hydra/core#");
+    $this->addContextItem('rdf', $vocab['sw_url']);
+
+    $vocab = tripal_get_vocabulary_details('rdfs');
+    $this->addContextItem('rdfs', $vocab['sw_url']);
+
+    $vocab = tripal_get_vocabulary_details('hydra');
+    $this->addContextItem('hydra', $vocab['sw_url']);
 
     $vocab = tripal_get_vocabulary_details('dc');
-    $this->addContextItem('dc', $vocab['url']);
+    $this->addContextItem('dc', $vocab['sw_url']);
 
     $vocab = tripal_get_vocabulary_details('schema');
-    $this->addContextItem('schema', $vocab['url']);
+    $this->addContextItem('schema', $vocab['sw_url']);
 
     $vocab = tripal_get_vocabulary_details('local');
-    $this->addContextItem('local', url($vocab['url'], array('absolute' => TRUE)). '/');
+    $this->addContextItem('local', url($vocab['sw_url'], array('absolute' => TRUE)));
 
 
     $this->data['@id'] = $service_path;
@@ -65,13 +70,21 @@ class TripalWebServiceResource {
   /**
    * Adds a term to the '@context' section for this resource.
    *
+   * This function should not be called directory.  Rather, the
+   * addContextTerm() and addContextVocab() functions built
+   * into the TripalWebService class should be used as  these  will help
+   * ensure terms are added proper for the context of the web service.
+   *
    * @param $name
    *   The term name.
    * @param $iri
    *   The Internationalized Resource Identifiers or it's shortcut.
+   *
    */
   public function addContextItem($name, $iri) {
-    // TODO: make sure that if a shortcut is used that the parent is present.
+    if (array_key_exists($name, $this->context)) {
+      return;
+    }
     $this->context[$name] = $iri;
   }
 
@@ -136,15 +149,55 @@ class TripalWebServiceResource {
 
       // If the vocabulary is not in the.
       if (!in_array($vocab, $keys)) {
-        throw new Exception("The key, '$key', has a vocabulary that has not yet been added to the " .
-            "context. Use the addContextItem() function to add the vocabulary prior to adding a value for it.");
+        throw new Exception(t("The key, !key, has a vocabulary that has not yet been added to the " .
+            "context. Use the addContextItem() function to add the vocabulary prior to adding a value for it.", array('!key' => $key)));
       }
     }
     else {
       // If the key is not in the context then throw an error.
       if (!in_array($key, $keys)) {
-        throw new Exception("The key, '$key', has not yet been added to the " .
-            "context. Use the addContextItem() function to add this key prior to adding a value for it.");
+        throw new Exception(t("The key, !key, has not yet been added to the " .
+            "context. Use the addContextItem() function to add this key prior to adding a value for it.", array('!key' => $key)));
+      }
+    }
+  }
+
+  /**
+   * Checks the value to make sure there are no problems with.
+   *
+   * Will also expand any TriaplWebServiceResource by adding their
+   * context to this resource and substitute their data in place of the
+   * value.
+   *
+   * @param $value
+   *
+   */
+  private function checkValue(&$value) {
+    if (is_array($value)) {
+      foreach ($value as $k => $v) {
+        // If this is an integer then this is a numeric indexed array
+        // and we can just check the value.  If not, then make sure the
+        // key has been added to the context.
+        if (preg_match('/^\d+$/', $k)) {
+          $this->checkValue($value[$k]);
+        }
+        else {
+          if ($k != '@id' and $k != '@type') {
+            $this->checkKey($k);
+          }
+          $this->checkValue($value[$k]);
+        }
+      }
+    }
+    else {
+      // If this is a TripalWebServiceResource then get it's data and use that
+      // as the new value.  Also, add in the elements context to this resource.
+      if (is_a($value, 'TripalWebServiceResource') or is_subclass_of($value, 'TripalWebServiceResource')) {
+        $context = $value->getContext();
+        foreach ($context as $k => $v) {
+          $this->addContextItem($k, $v);
+        }
+        $value = $value->getData();
       }
     }
   }
@@ -233,8 +286,9 @@ class TripalWebServiceResource {
   public function addProperty($key, $value) {
 
     $this->checkKey($key);
+    $this->checkValue($value);
 
-    // If this is a numeric array then go through each element and add them.
+    // If this is a numeric keyed array then add each item.
     if (is_array($value) and count(array_filter(array_keys($value), 'is_int')) == count(array_keys($value))) {
       if (!array_key_exists($key, $this->data)) {
         $this->data[$key] = array();
@@ -245,17 +299,6 @@ class TripalWebServiceResource {
       return;
     }
 
-    // If this is a TripalWebServiceResource then get it's data and use that
-    // as the new value.
-    if (is_a($value, 'TripalWebServiceResource') or is_subclass_of($value, 'TripalWebServiceResource')) {
-      // Add in the context items to this resource.
-      $context = $value->getContext();
-      foreach ($context as $k => $v) {
-        $this->addContextItem($k, $v);
-      }
-      $value = $value->getData();
-    }
-
     // If this key doesn't exist then add the value directly to the key.
     if (!array_key_exists($key, $this->data)) {
       $this->data[$key] = $value;
@@ -274,6 +317,20 @@ class TripalWebServiceResource {
     }
   }
 
+  /**
+   * Retrieves the value of a property.
+   *
+   * @param $key
+   *   The name of the property.
+   *
+   * @param
+   *   The value of the property. This could be a scalar, array or
+   *   a TripalWebService object.
+   */
+  function getProperty($key) {
+    return $this->data[$key];
+  }
+
   /**
    * Retrieves the data section of the resource.
    *
@@ -310,7 +367,7 @@ class TripalWebServiceResource {
    */
   public function setContext($resource) {
     if (!is_a($resource, 'TripalWebServiceResource')) {
-      throw new Exception("The \$resource argument must be an instance of a TripalWebServiceResource.");
+      throw new Exception("The \$resource argument provided to the TripalWebServiceResource::setContext() function must be an instance of a TripalWebServiceResource.");
     }
     $this->context = $resource->getContext();
   }

+ 78 - 0
tripal_ws/includes/tripal_ws.field_storage.inc

@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Implements hook_field_storage_info().
+ */
+function tripal_ws_field_storage_info() {
+  return array(
+    'field_tripal_ws_storage' => array(
+      'label' => t('Tripal Web Services'),
+      'description' => t('Retreives fields data from a remote site using Tripal web services.'),
+      'settings' => array(),
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_storage_load().
+ *
+ * Responsible for loading the fields from the Chado database and adding
+ * their values to the entity.
+ */
+function tripal_ws_field_storage_load($entity_type, $entities, $age,
+    $fields, $options) {
+
+  $load_current = $age == FIELD_LOAD_CURRENT;
+  global $language;
+  $langcode = $language->language;
+
+  foreach ($entities as $id => $entity) {
+
+    // Iterate through the entity's fields so we can get the column names
+    // that need to be selected from each of the tables represented.
+    foreach ($fields as $field_id => $ids) {
+
+      // By the time this hook runs, the relevant field definitions have been
+      // populated and cached in FieldInfo, so calling field_info_field_by_id()
+      // on each field individually is more efficient than loading all fields in
+      // memory upfront with field_info_field_by_ids().
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+      $field_type = $field['type'];
+      $field_module = $field['module'];
+
+      // Get the instance for this field.  If no instance exists then skip
+      // loading of this field. This can happen when a field is deleted from
+      // a bundle using the user UI form.
+      // TODO: how to deal with deleted fields?
+      $instance = field_info_instance($entity_type, $field_name, $entity->bundle);
+
+	  if (!$instance) {
+        continue;
+      }
+
+      // Set an empty value by default, and let the hook function update it.
+      $entity->{$field_name}['und'][0]['value'] = '';
+      tripal_load_include_field_class($field_type);
+      if (class_exists($field_type) && method_exists($field_type, 'load')) {
+        $tfield = new $field_type($field, $instance);
+        $tfield->load($entity);
+      }
+
+    } // end: foreach ($fields as $field_id => $ids) {
+  } // end: foreach ($entities as $id => $entity) {
+}
+
+/**
+ * Implements hook_field_storage_query().
+ */
+function tripal_ws_field_storage_query($query) {
+
+}
+/**
+ * Implements hook_field_storage_write().
+ */
+function tripal_ws_field_storage_write($entity_type, $entity, $op, $fields) {
+
+}
+

+ 95 - 0
tripal_ws/includes/tripal_ws.fields.inc

@@ -0,0 +1,95 @@
+<?php
+/**
+ * @file
+ * Contains all field specific code outside the classes.
+ */
+
+/**
+ * Implements hook_bundle_fields_info().
+ */
+function tripal_ws_bundle_fields_info($entity_type, $bundle) {
+  $fields = array();
+
+  // No fields are added programmatically.
+  return $fields;
+}
+
+/**
+ * Implements hook_bundle_instances_info().
+ */
+function tripal_ws_bundle_instances_info($entity_type, $bundle) {
+  $instances = array();
+
+  // No field instances are added programmatically.
+  return $instances;
+}
+
+/**
+ * Implements hook_bundle_create_user_field().
+ *
+ * A priviledged user has the ability to add new fields to the bundle. The
+ * remote__data field is allowed to be added dynamically by the user.
+ * But, Drupal doesn't know how to deal with it, so this function is called
+ * for any field attached to a TripalEntity bundle type. Any fields whose
+ * TripalField::$module argument is set to 'tripal_ws' and that can be
+ * added dynamically will result in a call to this function.
+ */
+function tripal_ws_bundle_create_user_field($new_field, $bundle) {
+
+  // Get the table this bundle is mapped to.
+  $term = tripal_load_term_entity(array('term_id' => $bundle->term_id));
+  $vocab = $term->vocab;
+  $params = array(
+    'vocabulary' => $vocab->vocabulary,
+    'accession' => $term->accession,
+  );
+
+  // We allow site admins to add new chado_linker__prop fields to an entity.
+  // This function will allow us to properly add them.  But at this point we
+  // don't know the controlled vocabulary term.  We'll have to use the
+  // defaults and let the user set it using the interface.
+  if ($new_field['type'] == 'remote__data') {
+    $field_name = $new_field['field_name'];
+    $field_type = 'remote__data';
+
+    // First add the field.
+    field_create_field(array(
+      'field_name' => $field_name,
+      'type' => $field_type,
+      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+      'locked' => FALSE,
+      'storage' => array(
+        'type' => 'field_tripal_ws_storage',
+      ),
+    ));
+
+    // Now add the instance
+    field_create_instance(array(
+      'field_name' => $field_name,
+      'entity_type' => 'TripalEntity',
+      'bundle' => $bundle->name,
+      'label' => $new_field['label'],
+      'description' => '',
+      'required' => FALSE,
+      'settings' => array(
+        'auto_attach' => FALSE,
+        'term_vocabulary' => '',
+        'term_accession' => '',
+        'term_name' => ''
+      ),
+      'widget' => array(
+        'type' => 'remote__data_widget',
+        'settings' => array(
+          'display_label' => 1,
+        ),
+      ),
+      'display' => array(
+        'default' => array(
+          'label' => 'inline',
+          'type' => 'remote__data_formatter',
+          'settings' => array(),
+        ),
+      ),
+    ));
+  }
+}

+ 0 - 1064
tripal_ws/includes/tripal_ws.rest_v0.1.inc

@@ -1,1064 +0,0 @@
-<?php
-
-
-
-/**
- *
- */
-function tripal_ws_services_v0_1($api_url, $ws_path, $params) {
-
-  // Set some initial variables.
-  $response = array();
-  $version = 'v0.1';
-
-  // Set some defaults for the response.
-  $response['@context'] =  array();
-
-  // Lump everything ito a try block so that if there is a problem we can
-  // throw an error and have that returned in the response.
-  try {
-
-    // The services is the first argument
-    $service = (count($ws_path) > 0) ? $ws_path[0] : '';
-
-    switch ($service) {
-      case 'doc':
-        tripal_ws_services_v0_1_handle_doc_service($api_url, $response);
-        break;
-      case 'content':
-        tripal_ws_services_v0_1_handle_content_service($api_url, $response, $ws_path, $params);
-        break;
-      case 'vocab':
-        tripal_ws_services_v0_1_handle_vocab_service($api_url, $response, $ws_path);
-        break;
-      default:
-        tripal_ws_services_v0_1_handle_no_service($api_url, $response);
-    }
-  }
-  catch (Exception $e) {
-    watchdog('tripal_ws', $e->getMessage(), array(), WATCHDOG_ERROR);
-    $message = $e->getMessage();
-    drupal_add_http_header('Status', '400 Bad Request');
-
-  }
-
-  return $response;
-}
-
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_handle_content_service($api_url, &$response, $ws_path, $params) {
-
-  // Get the content type.
-  $ctype     = (count($ws_path) > 1) ? $ws_path[1] : '';
-  $entity_id = (count($ws_path) > 2) ? $ws_path[2] : '';
-
-  // If we have no content type then list all of the available content types.
-  if (!$ctype) {
-    tripal_ws_services_v0_1_get_content_types($api_url, $response);
-  }
-  // If we don't have an entity ID then show a paged list of entities with
-  // the given type.
-  else if ($ctype and !$entity_id) {
-   tripal_ws_services_v0_1_get_content_type($api_url, $response, $ws_path, $ctype, $params);
-  }
-  // If we have a content type and an entity ID then show the entity
-  else {
-    tripal_ws_services_v0_1_get_content($api_url, $response, $ws_path, $ctype, $entity_id, $params);
-  }
-}
-/**
-*
-* @param $api_url
-* @param $response
-* @param $ws_path
-*/
-function tripal_ws_services_v0_1_handle_vocab_service($api_url, &$response, $ws_path) {
-
-  // Get the vocab name.
-  $vocabulary = (count($ws_path) > 1) ? $ws_path[1] : '';
-  $accession = (count($ws_path) > 2) ? $ws_path[2] : '';
-
-  // If we have no $vocabulary type then list all of the available vocabs.
-  if (!$vocabulary) {
-    tripal_ws_services_v0_1_get_vocabs($api_url, $response);
-  }
-  // If we don't have a $vocabulary then show a paged list of terms.
-  else if ($vocabulary and !$accession) {
-    tripal_ws_services_v0_1_get_vocab($api_url, $response, $ws_path, $vocabulary);
-  }
-  // If we have a content type and an entity ID then show the entity
-  else if ($vocabulary and $accession) {
-    tripal_ws_services_v0_1_get_term($api_url, $response, $ws_path, $vocabulary, $accession);
-  }
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_get_vocabs($api_url, &$response) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/vocab', array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Content Types';
-  $response['member'] = array();
-
-  $vocabs = db_select('tripal_vocab', 'tv')
-    ->fields('tv')
-    ->execute();
-  // Iterate through the vocabularies and add an entry in the collection.
-  $i = 0;
-  while ($vocab = $vocabs->fetchObject()) {
-    // Add the bundle as a content type.
-    $response['member'][] = array(
-      '@id' => url($api_url . '/vocab/' . urlencode($vocab->vocabulary), array('absolute' => TRUE)),
-      '@type' => 'vocabulary',
-      'vocabulary' => $vocab->vocabulary,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_vocab($api_url, &$response, $ws_path, $vocabulary) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/vocab/' . $vocabulary, array('absolute' => TRUE));
-
-  // Get the vocabulary
-  $vocab = tripal_load_vocab_entity(array('vocabulary' => $vocabulary));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = vocabulary . " vocabulary collection";
-  $response['comment'] = 'The following list of terms may not be the full ' .
-      'list for the vocabulary.  The terms listed here are only those ' .
-      'that have associated content on this site.';
-
-  // Get the list of terms for this vocab.
-  $query = db_select('tripal_term', 'tt')
-    ->fields('tt', array('id'))
-    ->condition('vocab_id', $vocab->id)
-    ->orderBy('accession', 'DESC');
-
-  // Iterate through the entities and add them to the list.
-  $terms = $query->execute();
-  $i = 0;
-  while($term = $terms->fetchObject()) {
-    $term = tripal_load_term_entity(array('term_id' => $term->id));
-    $response['member'][] = array(
-      '@id' => url($api_url . '/vocab/' . urlencode($vocabulary) . '/' .  urlencode($term->accession), array('absolute' => TRUE)),
-      '@type' => 'vocabulary_term',
-      'vocabulary' => $vocab->vocabulary,
-      'accession' => $term->accession,
-      'name' => $term->name,
-      'definition' => $term->definition,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['comment'] = 'rdfs:comment';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_term($api_url, &$response, $ws_path, $vocabulary, $accession) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Get the term.
-  $term = tripal_load_term_entity(array('vocabulary' => $vocabulary, 'accession' => $accession));
-
-  // Next add in the ID and Type for this resources.
-  $response['@id'] = url($api_url . '/vocab/' . urlencode($vocabulary) . '/' . urlencode($accession), array('absolute' => TRUE));
-  $response['@type'] = 'vocabulary_term';
-  $response['label'] = $term->name;
-  $response['vocabulary'] = $vocabulary;
-  $response['accession'] = $accession;
-  $response['name'] = $term->name;
-  $response['definition'] = $term->definition;
-
-  if ($term->url) {
-    $response['URL'] = $term->url;
-  }
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-/**
- * Provides a collection (list) of all of the content types.
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_get_content_types($api_url, &$response) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url . '/content', array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Content Types';
-  $response['member'] = array();
-
-  // Get the list of published terms (these are the bundle IDs)
-  $bundles = db_select('tripal_bundle', 'tb')
-    ->fields('tb')
-    ->orderBy('tb.label', 'ASC')
-    ->execute();
-
-  // Iterate through the terms and add an entry in the collection.
-  $i = 0;
-  while ($bundle = $bundles->fetchObject()) {
-    $entity =  entity_load('TripalTerm', array('id' => $bundle->term_id));
-    $term = reset($entity);
-    $vocab = $term->vocab;
-
-    $response['@context'][$term->name] = $term->url;
-
-    // Get the bundle description. If no description is provided then
-    // use the term definition
-    $description = tripal_get_bundle_variable('description', $bundle->id);
-    if (!$description) {
-      $description = $term->definition;
-    }
-    // Add the bundle as a content type.
-    $response['member'][] = array(
-      '@id' => url($api_url . '/content/' . urlencode($bundle->label), array('absolute' => TRUE)),
-      '@type' => $term->name,
-      'label' => $bundle->label,
-      'description' => $description,
-    );
-    $i++;
-  }
-  $response['totalItems'] = $i;
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_get_content_type($api_url, &$response, $ws_path, $ctype, $params) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for this resource.
-  $URL = url($api_url . '/content/' . $ctype, array('absolute' => TRUE));
-  $response['@id'] = $URL;
-
-  // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-  $term = reset($term);
-  $response['@context'][$term->name] = $term->url;
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = $bundle->label . " collection";
-
-  // Iterate through the fields and create a $field_mapping array that makes
-  // it easier to determine which filter criteria belongs to which field. The
-  // key is the label for the field and the value is the field name. This way
-  // user's can use the field label or the field name to form a query.
-  $field_mapping = array();
-  $fields = field_info_fields();
-  foreach ($fields as $field) {
-    if (array_key_exists('TripalEntity', $field['bundles'])) {
-      foreach ($field['bundles']['TripalEntity'] as $bundle_name) {
-        if ($bundle_name == $bundle->name) {
-          $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
-          if (array_key_exists('term_accession', $instance['settings'])){
-            $vocabulary = $instance['settings']['term_vocabulary'];
-            $accession = $instance['settings']['term_accession'];
-            $term = tripal_get_term_details($vocabulary, $accession);
-            $key = $term['name'];
-            $key = strtolower(preg_replace('/ /', '_', $key));
-            $field_mapping[$key] = $field['field_name'];
-            $field_mapping[$field['field_name']] = $field['field_name'];
-          }
-        }
-      }
-    }
-  }
-
-  // Convert the filters to their field names
-  $new_params = array();
-  $order = array();
-  $order_dir = array();
-  $URL_add = array();
-  foreach ($params as $param => $value) {
-    $URL_add[] = "$param=$value";
-
-    // Ignore non filter parameters
-    if ($param == 'page' or $param == 'limit') {
-      continue;
-    }
-
-    // Handle order separately
-    if ($param == 'order') {
-      $temp = explode(',', $value);
-      foreach ($temp as $key) {
-        $matches = array();
-        $dir = 'ASC';
-        // The user can provide a direction by separating the field key and the
-        // direction with a '|' character.
-        if (preg_match('/^(.*)\|(.*)$/', $key, $matches)) {
-          $key = $matches[1];
-          if ($matches[2] == 'ASC' or $matches[2] == 'DESC') {
-            $dir = $matches[2];
-          }
-          else {
-            // TODO: handle error of providing an incorrect direction.
-          }
-        }
-        if (array_key_exists($key, $field_mapping)) {
-          $order[$field_mapping[$key]] = $key;
-          $order_dir[] = $dir;
-        }
-        else {
-          // TODO: handle error of providing a non existing field name.
-        }
-      }
-      continue;
-    }
-
-    // Break apart any operators
-    $key = $param;
-    $op = '=';
-    $matches = array();
-    if (preg_match('/^(.+);(.+)$/', $key, $matches)) {
-      $key = $matches[1];
-      $op = $matches[2];
-    }
-
-    // Break apart any subkeys and pull the first one out for the term name key.
-    $subkeys = explode(',', $key);
-    if (count($subkeys) > 0) {
-      $key = array_shift($subkeys);
-    }
-    $column_name = $key;
-
-    // Map the values in the filters to their appropriate field names.
-    if (array_key_exists($key, $field_mapping)) {
-      $field_name = $field_mapping[$key];
-      if (count($subkeys) > 0) {
-        $column_name .= '.' . implode('.', $subkeys);
-      }
-      $new_params[$field_name]['value'] = $value;
-      $new_params[$field_name]['op'] = $op;
-      $new_params[$field_name]['column'] = $column_name;
-    }
-    else {
-      throw new Exception("The filter term, '$key', is not available for use.");
-    }
-  }
-
-  // Get the list of entities for this bundle.
-  $query = new TripalFieldQuery();
-  $query->entityCondition('entity_type', 'TripalEntity');
-  $query->entityCondition('bundle', $bundle->name);
-  foreach($new_params as $field_name => $details) {
-    $value = $details['value'];
-    $column_name = $details['column'];
-    switch ($details['op']) {
-      case 'eq':
-        $op = '=';
-        break;
-      case 'gt':
-        $op = '>';
-        break;
-      case 'gte':
-        $op = '>=';
-        break;
-      case 'lt':
-        $op = '<';
-        break;
-      case 'lte':
-        $op = '<=';
-        break;
-      case 'ne':
-        $op = '<>';
-        break;
-      case 'contains':
-        $op = 'CONTAINS';
-        break;
-      case 'starts':
-        $op = 'STARTS WITH';
-        break;
-      default:
-        $op = '=';
-    }
-
-    //print_r(array($field_name, $column_name, $value, $op));
-
-    // We pass in the $column_name as an identifier for any sub fields
-    // that are present for the fields.
-    $query->fieldCondition($field_name, $column_name, $value, $op);
-  }
-
-  // Perform the query just as a count first to get the number of records.
-  $cquery = clone $query;
-  $cquery->count();
-  $num_records = $cquery->execute();
-  $num_records = count($num_records['TripalEntity']);
-
-  if (!$num_records) {
-    $num_records = 0;
-  }
-
-  // Add in the pager to the response.
-  $response['totalItems'] = $num_records;
-  $limit = array_key_exists('limit', $params) ? $params['limit'] : 25;
-
-  $total_pages = ceil($num_records / $limit);
-  $page = array_key_exists('page', $params) ? $params['page'] : 1;
-  if ($num_records > 0) {
-    $response['view'] = array(
-      '@id' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$page", "limit=$limit"))),
-      '@type' => 'PartialCollectionView',
-      'first' => $URL . '?' . implode('&', array_merge($URL_add, array("page=1", "limit=$limit"))),
-      'last' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$total_pages", "limit=$limit"))),
-    );
-    $prev = $page - 1;
-    $next = $page + 1;
-    if ($prev > 0) {
-      $response['view']['previous'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$prev", "limit=$limit")));
-    }
-    if ($next < $total_pages) {
-      $response['view']['next'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$next", "limit=$limit")));
-    }
-  }
-
-  // Set the query order
-  $order_keys = array_keys($order);
-  for($i = 0; $i < count($order_keys); $i++) {
-    $query->fieldOrderBy($order_keys[$i], $order[$order_keys[$i]], $order_dir[$i]);
-  }
-
-  // Set the query range
-  $start = ($page - 1) * $limit;
-  $query->range($start, $limit);
-
-  // Now perform the query.
-  $results = $query->execute();
-
-  // Iterate through the entities and add them to the list.
-  $i = 0;
-  foreach ($results['TripalEntity'] as $entity_id => $stub) {
-    $vocabulary = '';
-    $term_name = '';
-
-    // We don't need all of the attached fields for an entity so, we'll
-    // not use the entity_load() function.  Instead just pull it from the
-    // database table.
-    $query = db_select('tripal_entity', 'TE');
-    $query->join('tripal_term', 'TT', 'TE.term_id = TT.id');
-    $query->fields('TE');
-    $query->fields('TT', array('name'));
-    $query->condition('TE.id', $entity_id);
-    $entity = $query->execute()->fetchObject();
-
-    //$entity = tripal_load_entity('TripalEntity', array($entity->id));
-    $response['member'][] = array(
-      '@id' => url($api_url . '/content/' . urlencode($ctype) . '/' .  $entity->id, array('absolute' => TRUE)),
-      '@type' => $entity->name,
-      'label' => $entity->title,
-      'itemPage' => url('/bio_data/' . $entity->id, array('absolute' => TRUE)),
-    );
-    $i++;
-  }
-
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['itemPage'] = 'schema:itemPage';
-
-//   $response['operation'][] = array(
-//     '@type' => 'hydra:CreateResourceOperation',
-//     'hydra:method' => 'PUT'
-//   );
-
-//   $response['query'] = array(
-//     '@id' => $response['@id'],
-//     '@type' => 'IriTemplate',
-//     "template" => $response['@id'] . "{?name,}",
-//     "mapping" => array(
-//       array(
-//         "hydra:variable" => 'name',
-//         "hydra:property" => 'name',
-//       )
-//     )
-//   );
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- */
-function tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
-
-
-  // Get information about the fields attached to this bundle and sort them
-  // in the order they were set for the display.
-  $instances = field_info_instances('TripalEntity', $bundle->name);
-
-  uasort($instances, function($a, $b) {
-    $a_weight = (is_array($a) && isset($a['widget']['weight'])) ? $a['widget']['weight'] : 0;
-    $b_weight = (is_array($b) && isset($b['widget']['weight'])) ? $b['widget']['weight'] : 0;
-
-    if ($a_weight == $b_weight) {
-      return 0;
-    }
-    return ($a_weight < $b_weight) ? -1 : 1;
-  });
-
-  // Iterate through the fields and add each value to the response.
-  //$response['fields'] = $fields;
-  foreach ($instances as $field_name => $instance) {
-
-    // Ignore the content_type field provided by Tripal.
-    if ($field_name == 'content_type') {
-      continue;
-    }
-
-    // Skip hidden fields.
-    if ($instance['display']['default']['type'] == 'hidden') {
-      continue;
-    }
-
-    // Get the information about this field. It will have settings different
-    // from the instance.
-    $field = field_info_field($field_name);
-
-    // By default, the label for the key in the output should be the
-    // term from the vocabulary that the field is assigned. But in the
-    // case that the field is not assigned a term, we must use the field name.
-    $field_name = $instance['field_name'];
-    $vocabulary = $instance['settings']['term_vocabulary'];
-    $accession = $instance['settings']['term_accession'];
-    $term = tripal_get_term_details($vocabulary, $accession);
-    if ($term) {
-      $key = $term['name'];
-      $key_adj = strtolower(preg_replace('/ /', '_', $key));
-      // The term schema:url also points to a recource so we need
-      // to make sure we set the type to be '@id'.
-      if ($vocabulary == 'schema' and $accession == 'url') {
-        $response['@context'][$key_adj] = array(
-          '@id' => $term['url'],
-          '@type' => '@id',
-        );
-      }
-      else {
-        $response['@context'][$key_adj] = $term['url'];
-      }
-    }
-    else {
-      continue;
-    }
-
-    // If this field should not be attached by default then just add a link
-    // so that the caller can get the information separately.
-    $instance_settings = $instance['settings'];
-    if (array_key_exists('auto_attach', $instance['settings']) and
-        $instance_settings['auto_attach'] == FALSE) {
-      $response['@context'][$key_adj] = array(
-        '@id' => $response['@context'][$key_adj],
-        '@type' => '@id'
-      );
-      // Add a URL only if there are values. If there are no values then
-      // don't add a URL which would make the end-user think they can get
-      // that information.
-      $items = field_get_items('TripalEntity', $entity, $field_name);
-      if ($items and count($items) > 0 and $items[0]['value']) {
-        $response[$key_adj] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
-      }
-      else {
-        $response[$key_adj] = NULL;
-      }
-
-      continue;
-    }
-
-    // Get the details for this field for the JSON-LD response.
-    tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response);
-  }
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['label'] = 'https://www.w3.org/TR/rdf-schema/#ch_label';
-  $response['@context']['itemPage'] = 'https://schema.org/ItemPage';
-
-  //   $response['operation'][] = array(
-  //     '@type' => 'hydra:DeleteResourceOperation',
-  //     'hydra:method' => 'DELETE'
-  //   );
-  //   $response['operation'][] = array(
-  //     '@type' => 'hydra:ReplaceResourceOperation',
-  //     'hydra:method' => 'POST'
-  //   );
-}
-/**
- *
- * @param unknown $field_arg
- * @param unknown $api_url
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- */
-function tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id) {
-
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
-  $entity = reset($entity);
-  $term = NULL;
-
-  // Find the field whose term matches the one provied.
-  $value = array();
-  $instances = field_info_instances('TripalEntity', $bundle->name);
-  foreach ($instances as $instance) {
-    $field_name = $instance['field_name'];
-    $field = field_info_field($field_name);
-    $vocabulary = $instance['settings']['term_vocabulary'];
-    $accession = $instance['settings']['term_accession'];
-    $temp_term = tripal_get_term_details($vocabulary, $accession);
-    if ($temp_term['name'] == $field_arg) {
-      return array($entity, $bundle, $field, $instance, $temp_term);
-    }
-  }
-}
-/**
- *
- * @param unknown $api_url
- * @param unknown $response
- * @param unknown $ws_path
- * @param unknown $ctype
- * @param unknown $entity_id
- * @param unknown $params
- * @return number
- */
-function tripal_ws_services_v0_1_get_content($api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // If we have an argument in the 4th element (3rd index) then the user
-  // is requesting to expand the details of a field that was not
-  // initially attached to the enity.
-  $field_arg = '';
-  if (array_key_exists(3, $ws_path)) {
-
-    $field_arg = urldecode($ws_path[3]);
-    list($entity, $bundle, $field, $instance, $term) = tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id);
-
-    // If we couldn't match this field argument to a field and entity then return
-    if (!$entity or !$field) {
-      return;
-    }
-
-    // Next add in the ID and Type for this resources.
-    $key = $term['name'];
-    $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
-    $response['@context'][$key_adj] = $term['url'];
-    $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
-
-    // Attach the field and then add it's values to the response.
-    field_attach_load($entity->type, array($entity->id => $entity),
-      FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
-
-    tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response, TRUE);
-    tripal_ws_services_v0_1_write_context($response, $ctype);
-    return;
-  }
-
-  // If we don't have a 4th argument then we're loading the base record.
-  // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
-  $bundle = tripal_load_bundle_entity(array('label' => $ctype));
-  $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
-  $term = reset($term);
-  $vocab = $term->vocab;
-
-  // Add the vocabulary for this content type to the @context section.
-  if (!array_key_exists($vocab->vocabulary, $response['@context'])) {
-    // If there is no URL prefix then use this API's vocabulary API
-    if (property_exists($term, 'urlprefix')) {
-      $response['@context'][$vocab->vocabulary] = $term->urlprefix;
-    }
-    else {
-      $response['@context'][$vocab->vocabulary] = url($api_url . '/vocab/' . $vocab->vocabulary . '/', array('absolute' => TRUE));
-    }
-  }
-
-  // Get the TripalEntity
-  $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
-  $entity = reset($entity);
-
-  // Next add in the ID and Type for this resources.
-  $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity_id, array('absolute' => TRUE));
-  $response['@type'] = $term->name;
-  $response['@context'][$term->name] = $term->url;
-  $response['label'] = $entity->title;
-  $response['itemPage'] = url('/bio_data/' . $entity->id, array('absolute' => TRUE));
-
-  tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, $response, $ws_path, $ctype, $entity_id, $params);
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- *
- * @param $response
- * @param $ctype
- */
-function tripal_ws_services_v0_1_write_context(&$response, $ctype) {
-  // Save the response '@context' into a temporary file
-  $context = array('@context' => $response['@context']);
-  $file_name = drupal_tempnam(file_default_scheme() . '://', 'tws_context-') . '.json';
-  $context_file = file_save_data(json_encode($context), $file_name, FILE_EXISTS_REPLACE );
-
-  // Mark the file as temporary by setting it's status
-  $context_file->status = 0;
-  file_save($context_file);
-
-  // Return the response with the '@context' section replaced with the file URL.
-  $response['@context'] = file_create_url($context_file->uri);
-
-}
-
-/**
- *
- */
-function tripal_ws_services_v0_1_get_content_add_field($key, $entity, $field, $instance, $api_url, &$response, $is_field_page = NULL) {
-  // Get the field  settings.
-  $field_name = $field['field_name'];
-  $field_settings = $field['settings'];
-
-  $items = field_get_items('TripalEntity', $entity, $field_name);
-  if (!$items) {
-    return;
-  }
-
-  // Give modules the opportunity to edit values for web services. This hook
-  // really should be used sparingly. Where it helps is with non Tripal fields
-  // that are added to a TripalEntity content type and it doesn't follow
-  // the rules (e.g. Image field).
-  drupal_alter('tripal_ws_value', $items, $field, $instance);
-
-  $values = array();
-  for ($i = 0; $i < count($items); $i++) {
-    $values[$i] = tripal_ws_services_v0_1_rewrite_field_items_keys($items[$i]['value'], $response, $api_url);
-  }
-
-  // If the field cardinality is 1
-  if ($field[cardinality] == 1) {
-    // If the value is an array and this is the field page then all of those
-    // key/value pairs should be added directly to the response.
-    if (is_array($values[0])) {
-      if ($is_field_page) {
-        foreach ($values[0] as $k => $v) {
-          $response[$k] = $v;
-        }
-      }
-      else {
-        $response[$key] = $values[0];
-      }
-    }
-    // If the value is not an array it's a scalar so add it as is to the
-    // response.
-    else {
-      $response[$key] = $values[0];
-    }
-  }
-
-  // If the field cardinality is > 1
-  if ($field[cardinality] != 1) {
-
-    // If this is the field page then the Collection is added directly to the
-    // response, otherwise, it's added under the field $key.
-    if ($is_field_page) {
-      $response['@type'] = 'Collection';
-      $response['totalItems'] = count($values);
-      $response['label'] = $instance['label'];
-      $response['member'] = $values;
-    }
-    else {
-      $response[$key] = array(
-        '@type' => 'Collection',
-        'totalItems' => count($values),
-        'label' => $instance['label'],
-        'member' => $values,
-      );
-    }
-  }
-}
-/**
- *
- */
-function tripal_ws_services_v0_1_rewrite_field_items_keys($value, &$response, $api_url) {
-
-  $new_value = '';
-  // If the value is an array rather than a scalar then map the sub elements
-  // to controlled vocabulary terms.
-  if (is_array($value)) {
-    $temp = array();
-    foreach ($value as $k => $v) {
-      $matches = array();
-      if (preg_match('/^(.+):(.+)$/', $k, $matches)) {
-        $vocabulary = $matches[1];
-        $accession = $matches[2];
-        $term = tripal_get_term_details($vocabulary, $accession);
-        $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
-        if (is_array($v)) {
-          $temp[$key_adj] = tripal_ws_services_v0_1_rewrite_field_items_keys($v, $response, $api_url);
-        }
-        else {
-          $temp[$key_adj] = $v !== "" ? $v : NULL;
-        }
-        // The term schema:url also points to a recource so we need
-        // to make sure we set the type to be '@id'.
-        if ($vocabulary == 'schema' and $accession == 'url') {
-          $response['@context'][$key_adj] = array(
-            '@id' => $term['url'],
-            '@type' => '@id',
-          );
-        }
-        else {
-          $response['@context'][$key_adj] = $term['url'];
-        }
-      }
-      else {
-        $temp[$k] = $v;
-      }
-    }
-    $new_value = $temp;
-
-    // Recurse through the values array and set the entity elements
-    // and add the fields to the context.
-    tripal_ws_services_v0_1_rewrite_field_items_entity($new_value, $response, $api_url);
-
-  }
-  else {
-    $new_value = $value !== "" ? $value : NULL;
-  }
-
-  return $new_value;
-}
-/**
- *
- */
-function tripal_ws_services_v0_1_rewrite_field_items_entity(&$items, &$response, $api_url) {
-
-  if (!$items) {
-    return;
-  }
-  foreach ($items as $key => $value) {
-    if (is_array($value)) {
-      tripal_ws_services_v0_1_rewrite_field_items_entity($items[$key], $response, $api_url);
-      continue;
-    }
-
-    if ($key == 'entity') {
-      list($item_etype, $item_eid) = explode(':', $items['entity']);
-      if ($item_eid) {
-        $item_entity = tripal_load_entity($item_etype, array($item_eid));
-        $item_entity = reset($item_entity);
-        $bundle = tripal_load_bundle_entity(array('name' => $item_entity->bundle));
-        $items['@id'] = url($api_url . '/content/' . $bundle->label . '/' . $item_eid, array('absolute' => TRUE));
-      }
-      unset($items['entity']);
-    }
-  }
-}
-
-/**
- * Provides the Hydra compatible apiDocumentation page that describes this API.
- *
- * @param $api_url
- * @param $response
- */
-function tripal_ws_services_v0_1_handle_doc_service($api_url, &$response) {
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-
-  // Next add in the ID for tihs resource.
-  $site_name = variable_get('site_name', '');
-  $response['@id'] = url($api_url . '/doc/', array('absolute' => TRUE));
-  $response['title'] =  $site_name . ": RESTful Web Services API";
-  $response['entrypoint'] = url($api_url, array('absolute' => TRUE));
-  $response['description'] = "A fully queryable REST API using JSON-LD and " .
-      "discoverable using the WC3 Hydra specification.";
-
-  // Lastly, add in the terms used into the @context section.
-  $response['@context']['title'] = 'hydra:title';
-  $response['@context']['entrypoint'] = array(
-    "@id" => "hydra:entrypoint",
-    "@type" => "@id",
-  );
-  $response['@context']['description'] = 'hydra:description';
-
-  tripal_ws_services_v0_1_write_context($response, $ctype);
-}
-
-/**
- * This function specifies the types of resources avaiable via the API.
- *
- * @param $api_url
- * @param $response
- * @param $ws_path
- */
-function tripal_ws_services_v0_1_handle_no_service($api_url, &$response) {
-
-  // First, add the vocabularies used into the @context section.
-  $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
-  $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
-  $response['@context']['dc'] = 'http://purl.org/dc/dcmitype/';
-  $response['@context']['schema'] = 'https://schema.org/';
-
-  // Next add in the ID for tihs resource.
-  $response['@id'] = url($api_url, array('absolute' => TRUE));
-
-  // Start the list.
-  $response['@type'] = 'Collection';
-  $response['totalItems'] = 0;
-  $response['label'] = 'Services';
-  $response['member'] = array();
-
-  // Start the list.
-  $response['member'][] = array(
-    '@id' => url($api_url . '/content/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'Content Types',
-    'description' => 'Provides acesss to the biological and ' .
-    'ancilliary data available on this site. Each content type ' .
-    'represents biological data that is defined in a controlled vocabulary '.
-    '(e.g. Sequence Ontology term: gene (SO:0000704)).',
-  );
-  $response['member'][] = array(
-    '@id' => url($api_url . '/doc/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'API Documentation',
-    'description' => 'The WC3 Hydra compatible documentation for this API.',
-  );
-  $response['member'][] = array(
-    '@id' => url($api_url . '/vocab/', array('absolute' => TRUE)),
-    '@type' => 'Service',
-    'label' => 'Vocabulary',
-    'description' => 'Defines in-house locally defined vocabulary terms that ' .
-    'have been added specifically for this site.  These terms are typically ' .
-    'added because no other appropriate term exists in another community-vetted '.
-    'controlled vocabulary.',
-  );
-
-  $response['totalItems'] = count($response['member']);
-
-  $response['@context']['Collection'] = 'hydra:Collection';
-  $response['@context']['totalItems'] = 'hydra:totalItems';
-  $response['@context']['member'] = 'hydra:member';
-  $response['@context']['Service'] = 'dc:Service';
-  $response['@context']['label'] = 'rdfs:label';
-  $response['@context']['description'] = 'hydra:description';
-}
-
-/**
- * Implements hook_tripal_ws_value_alter().
- *
- * The hook_tripal_ws_value_alter is a hook created by the Tripal WS module.
- * It allows the modules to adjust the values of a field for display in
- * web services. This hook should be used sparingly. It is meant primarily
- * to adjust 3rd Party (non Tripal) fields so that they work with web
- * services.
- */
-function tripal_ws_tripal_ws_value_alter(&$items, $field, $instance) {
-  // The image module doesn't properly set the 'value' field, so we'll do it
-  // here.
-  if($field['type'] == 'image' and $field['module'] == 'image') {
-    foreach ($items as $delta => $details) {
-      if ($items[$delta] and array_key_exists('uri', $items[$delta])) {
-        $items[$delta]['value']['schema:url'] = file_create_url($items[$delta]['uri']);
-      }
-    }
-  }
-}

Some files were not shown because too many files changed in this diff