From 1a1dd95dba6d35c3a6dc9026fcb90edcbb0c4ee5 Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Sat, 12 Oct 2019 20:35:57 -0600 Subject: [PATCH 01/36] Initial commit --- .gitignore | 6 + LICENSE | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 347 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a67d42b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +composer.phar +/vendor/ + +# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +# composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -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. + + + Copyright (C) + + 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. + + , 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf4e1f8 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tripal_hq_imports +Extends Tripal HQ to support TripalImporters From 057b5299a286d41a92d5041a22971f760c508680 Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Sat, 12 Oct 2019 20:40:19 -0600 Subject: [PATCH 02/36] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf4e1f8..6c2472c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -# tripal_hq_imports -Extends Tripal HQ to support TripalImporters +# Tripal HQ Imports + +Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. Tripal HQ Imports extends [Tripal HQ](https://github.com/statonlab/tripal_hq) to support TripalImporters. Specifically, this allows users to submit Tripal Importers, administrators to review the submission and data is only inserted into Chado once the administrator approves the submission. + +## UNDER DEVELOPMENT + +This module is currently under active development. **It is not ready for use.** If you are interested in this module, please star it so we know there is need. Thank you! From 6111b52346e2aacdc472d0a6d788b5ec9dc73e28 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sat, 12 Oct 2019 21:20:41 -0600 Subject: [PATCH 03/36] Initialize module. --- tripal_hq_imports.info | 7 +++++++ tripal_hq_imports.module | 4 ++++ 2 files changed, 11 insertions(+) create mode 100644 tripal_hq_imports.info create mode 100644 tripal_hq_imports.module diff --git a/tripal_hq_imports.info b/tripal_hq_imports.info new file mode 100644 index 0000000..ce88c0e --- /dev/null +++ b/tripal_hq_imports.info @@ -0,0 +1,7 @@ +name = Tripal HQ Imports +description = Extends Tripal HQ to support TripalImporters +core = 7.x +package = Tripal Extensions +dependencies[] = tripal +dependencies[] = tripal_chado +dependencies[] = tripal_hq diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module new file mode 100644 index 0000000..05b0fc6 --- /dev/null +++ b/tripal_hq_imports.module @@ -0,0 +1,4 @@ + Date: Sun, 13 Oct 2019 14:02:45 -0600 Subject: [PATCH 04/36] List importers and allow users to submit them for consideration (includes tests). --- .gitignore | 3 + .travis.yml | 29 + composer.json | 5 + composer.lock | 2161 +++++++++++++++++ includes/tripal_hq_imports_user_data.form.inc | 118 + phpunit.xml | 22 + tests/DataFactory.php | 196 ++ .../examples/DevSeedSeeder.php | 383 +++ .../examples/UsersTableSeeder.php | 28 + tests/bootstrap.php | 4 + tests/example.env | 3 + tests/moduleFileTest.php | 28 + tests/userDataFormTest.php | 150 ++ tripal_hq_imports.install | 62 + tripal_hq_imports.module | 27 + 15 files changed, 3219 insertions(+) create mode 100644 .travis.yml create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 includes/tripal_hq_imports_user_data.form.inc create mode 100644 phpunit.xml create mode 100644 tests/DataFactory.php create mode 100644 tests/DatabaseSeeders/examples/DevSeedSeeder.php create mode 100644 tests/DatabaseSeeders/examples/UsersTableSeeder.php create mode 100644 tests/bootstrap.php create mode 100644 tests/example.env create mode 100644 tests/moduleFileTest.php create mode 100644 tests/userDataFormTest.php create mode 100644 tripal_hq_imports.install diff --git a/.gitignore b/.gitignore index a67d42b..2213d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ composer.phar /vendor/ +.env + +tests/_build # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7ca5d15 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: php + +# Add php version so composer doesn't complain +php: + - 7.1 + +services: + - docker + +env: + - DRUPAL_ROOT=/var/www/html IS_TRAVIS=TRUE CC_TEST_REPORTER_ID=1594460e199e9ae7f700d5852e6d3084e7101f13ba851cb952a88daa1b2a49db + +before_script: + - docker pull statonlab/tripal3 + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build --debug + - GIT_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH + - GIT_COMMIT_SHA=$TRAVIS_PULL_REQUEST_SHA + +script: + - docker run -it -d --rm --name tripal -v "$(pwd)":/modules/tripal_hq_imports statonlab/tripal3 + - sleep 30 # We pause here so postgres and apache complete booting up + - docker exec -it tripal drush pm-enable -y tripal_hq_imports + - docker exec -it tripal yum install -y php-pecl-xdebug.x86_64 + - docker exec -it tripal bash -c "cd /modules/tripal_hq_imports && composer install && DRUPAL_ROOT=/var/www/html ./vendor/bin/phpunit --coverage-clover ./clover.xml" + +after_script: + - ./cc-test-reporter after-build clover.xml --debug -t clover -p /var/www/html/sites/all/modules/custom/tripal_hq_imports --exit-code $TRAVIS_TEST_RESULT diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b0a6b22 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "statonlab/tripal-test-suite": "^1.6" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..0183113 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2161 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "69cac23032897dac56256c18daaf9aff", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-09-12T14:27:41+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-10-03T11:07:50+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "6.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.1 || ^4.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "suggest": { + "ext-xdebug": "^2.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-10-31T16:06:48+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "050bedf145a257b1ff02746c31894800e5122946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2018-09-13T20:33:42+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2019-06-07T04:22:29+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2019-09-17T06:23:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "7.5.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/316afa6888d2562e04aeb67ea7f2017a0eb41661", + "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpunit/phpunit-mock-objects": "*" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2019-09-14T09:08:39+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "shasum": "" + }, + "require": { + "php": "^7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-07-12T15:12:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "time": "2019-02-04T06:01:07+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2019-05-05T09:05:15+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2019-09-14T09:02:43+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2018-10-04T04:07:39+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "statonlab/tripal-test-suite", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/tripal/TripalTestSuite.git", + "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tripal/TripalTestSuite/zipball/3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4", + "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "^1.7", + "guzzlehttp/guzzle": "^6.3", + "phpunit/phpunit": "^5 || ^6 || ^7.0", + "symfony/console": "^3 || ^4.0" + }, + "bin": [ + "tripaltest" + ], + "type": "library", + "autoload": { + "psr-4": { + "StatonLab\\TripalTestSuite\\": "src/" + }, + "files": [ + "src/Helpers/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0" + ], + "authors": [ + { + "name": "Abdullah Almsaeed", + "email": "aalmsaee@utk.edu" + }, + { + "name": "Bradford Condon", + "email": "bcondon@utk.edu" + } + ], + "time": "2019-04-09T18:52:51+00:00" + }, + { + "name": "symfony/console", + "version": "v4.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "929ddf360d401b958f611d44e726094ab46a7369" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", + "reference": "929ddf360d401b958f611d44e726094ab46a7369", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-10-07T12:36:49+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T11:12:18+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc new file mode 100644 index 0000000..f9d22eb --- /dev/null +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -0,0 +1,118 @@ + 'markup', + '#prefix' => '

', + '#markup' => t('Please click a data type below to submit a new data file and associated metadata for admin approval.'), + '#suffix' => '

', + ]; + + $user_has_stuff = 0; + $importers = tripal_get_importers(); + foreach ($importers as $importer_class) { + + // Pull some important info out of the class. + $importer_label = $importer_class::$name; + $importer_machine_name = $importer_class::$machine_name; + $importer_description = $importer_class::$description; + + // @todo Ensure the user has permission to propose that file type. + //if (!user_access("propose $importer_machine_name")) { + //continue; + //} + $user_has_stuff = 1; + + + $link = l($importer_label, + "tripal_hq/bio_data/import-data/$importer_class"); + $page[$importer_machine_name] = [ + '#type' => 'item', + '#markup' => $link, + '#description' => $importer_description, + ]; + } + + if ($user_has_stuff === 0) { + $page['description'] = [ + '#type' => 'markup', + '#prefix' => '

', + '#markup' => t('You do not have site permissions to submit data files for consideration.'), + '#suffix' => '

', + ]; + + } + + return $page; + } + + /** + * TripalImporter submission form. + * + * @param int $importer_class + * The class name for the importer to show the form for. + * @param int $sid + * Submission ID. + * + * @return array + * Renderable array. + */ +function tripal_hq_user_importer_form($form, $form_state) { + $importer_class = $form_state['build_info']['args'][0]; + tripal_load_include_importer_class($importer_class); + + drupal_set_title($importer_class::$name); + + // Retrieve the form for the importer. + module_load_include('inc', 'tripal', 'includes/tripal.importer'); + $form = tripal_get_importer_form($form, $form_state, $importer_class); + + return $form; +} + +/** + * Validate the user submission. + */ +function tripal_hq_user_importer_form_validate($form, $form_state) { + $importer_class = $form_state['build_info']['args'][0]; + tripal_load_include_importer_class($importer_class); + + dpm($form_state, 'form state'); + // Validate the user input in the same manner as the original importer. + module_load_include('inc', 'tripal', 'includes/tripal.importer'); + tripal_get_importer_form_validate($form, $form_state); +} + +/** + * Submit the submission. + */ +function tripal_hq_user_importer_form_submit($form, $form_state) { + global $user; + $importer_class = $form_state['build_info']['args'][0]; + + // Save it to the tripal_hq_imports_submission table! + $data = serialize($form_state); + db_insert('tripal_hq_importer_submission') + ->fields([ + 'uid' => $user->uid, + 'class' => $importer_class, + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ])->execute(); + +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..886b1e4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + tests + + + + + ./tripal_hq_imports.module + ./includes + ./includes + + + diff --git a/tests/DataFactory.php b/tests/DataFactory.php new file mode 100644 index 0000000..237efa3 --- /dev/null +++ b/tests/DataFactory.php @@ -0,0 +1,196 @@ + $faker->unique()->word . uniqid(), + // 'name' => $faker->unique($reset = TRUE)->word , + 'definition' => $faker->text, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.db', function (Faker\Generator $faker) { + return [ + 'name' => $faker->unique()->word . uniqid(), + 'description' => $faker->text, + 'urlprefix' => $faker->url, + 'url' => $faker->url, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.dbxref', function (Faker\Generator $faker) { + return [ + 'db_id' => factory('chado.db')->create()->db_id, + 'accession' => $faker->numberBetween(), + 'version' => $faker->numberBetween(), + 'description' => $faker->text, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.cvterm', function (Faker\Generator $faker) { + return [ + 'cv_id' => factory('chado.cv')->create()->cv_id, + 'dbxref_id' => factory('chado.dbxref')->create()->dbxref_id, + 'name' => $faker->word, + 'definition' => $faker->text, + 'is_obsolete' => 0, + 'is_relationshiptype' => 0, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.organism', function (Faker\Generator $faker) { + $genus = $faker->word; + $species = $faker->word; + $abbr = substr($genus, 0, 1) . ". " . $species; + + return [ + 'abbreviation' => $abbr, + 'genus' => $genus, + 'species' => $faker->name, + 'common_name' => $faker->word, + 'type_id' => factory('chado.cvterm')->create()->cvterm_id, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.feature', function (Faker\Generator $faker) { + return [ + 'name' => $faker->word, + 'uniquename' => $faker->unique()->word, + 'organism_id' => factory('chado.organism')->create()->organism_id, + 'type_id' => factory('chado.cvterm')->create()->cvterm_id, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.analysis', function (Faker\Generator $faker) { + return [ + 'name' => $faker->word, + 'description' => $faker->text, + 'program' => $faker->unique()->word, + 'programversion' => $faker->unique()->word, + 'sourcename' => $faker->unique()->word, + 'algorithm' => $faker->word, + 'sourcename' => $faker->word, + 'sourceversion' => $faker->word, + 'sourceuri' => $faker->word, + // 'timeexecuted' => $faker->time()// needs to match 2018-03-23 15:08:00.000000 + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.contact', function (Faker\Generator $faker) { + return [ + 'type_id' => factory('chado.cvterm')->create()->cvterm_id, + 'name' => $faker->name, + 'description' => $faker->text, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.biomaterial', function (Faker\Generator $faker) { + return [ + + 'taxon_id' => factory('chado.organism')->create()->organism_id, + 'biosourceprovider_id' => factory('chado.contact')->create()->contact_id, + 'dbxref_id' => factory('chado.dbxref')->create()->dbxref_id, + 'name' => $faker->unique()->word, + 'description' => $faker->text, + + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.featuremap', function (Faker\Generator $faker) { + return [ + 'name' => $faker->unique()->word, + 'description' => $faker->text, + 'unittype_id' => factory('chado.cvterm')->create()->cvterm_id, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.featurepos', function (Faker\Generator $faker) { + return [ + 'featuremap_id' => factory('chado.featuremap')->create()->featuremap_id, + 'feature_id' => factory('chado.feature')->create()->feature_id, + 'map_feature_id' => factory('chado.feature')->create()->feature_id, + 'mappos' => $faker->randomFloat, + ]; +}); + +/** + * IMPORTANT!!!! + * ============================================================== + * IF you use this factory, call + * + * $prev_db = chado_set_active('chado'); + * + * beforehand, and + * + * chado_set_active($prev_db); + * + * afterwards. + * + * @see StatonLab\TripalTestSuite\Database\Factory::define() + */ +Factory::define('chado.featureloc', function (Faker\Generator $faker) { + $a = $faker->randomNumber; + $b = $faker->randomNumber; + + return [ + 'feature_id' => factory('chado.feature')->create()->feature_id, + 'srcfeature_id' => factory('chado.feature')->create()->feature_id, + 'fmin' => min([$a, $b]), + 'is_fmin_partial' => 0, + 'fmax' => max([$a, $b]), + 'is_fmax_partial' => 0, + 'strand' => NULL, + 'phase' => NULL, + 'residue_info' => $faker->word, + 'locgroup' => 0, + 'rank' => 0, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.library', function (Faker\Generator $faker) { + return [ + 'organism_id' => factory('chado.organism')->create()->organism_id, + 'name' => $faker->word, + 'uniquename' => $faker->unique()->word, + 'type_id' => factory('chado.cvterm')->create()->cvterm_id, + 'is_obsolete' => 0, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.project', function (Faker\Generator $faker) { + return [ + 'name' => $faker->word, + 'description' => $faker->text, + ]; +}); + +/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ +Factory::define('chado.pub', function (Faker\Generator $faker) { + return [ + 'uniquename' => $faker->word, + 'type_id' => factory('chado.cvterm')->create()->cvterm_id, + ]; +}); diff --git a/tests/DatabaseSeeders/examples/DevSeedSeeder.php b/tests/DatabaseSeeders/examples/DevSeedSeeder.php new file mode 100644 index 0000000..5c84999 --- /dev/null +++ b/tests/DatabaseSeeders/examples/DevSeedSeeder.php @@ -0,0 +1,383 @@ + 'F. excelsior miniature', + 'genus' => 'Fraxinus', + 'species' => 'excelsior', + 'abbreviation' => 'F. excelsor', + 'comment' => 'Loaded with TripalDev Seed.', + ]; + +protected $sequence_analysis = [ + 'name' => 'Fraxinus exclesior miniature dataset', + 'description' => 'Tripal Dev Seed', + ]; + + protected $expression_analysis = [ + + 'name' => 'Fraxinus exclesior miniature dataset Expression Analysis', + 'description' => 'Tripal Dev Seed', + ]; + + protected $blastdb = [ + 'name' => 'DevSeed Database: TREMBL', + 'description' => 'A dummy database created by DevSeed', + ]; + + /** + * Part 2: + * Files. + * Each importer will take a file argument. This argument should be an array + * with one of the following two keys: file_remote => url where the file is + * located file_local => server path where the file is located. + */ + + protected $landmark_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/empty_landmarks.fasta']; + + protected $landmark_type = 'supercontig'; + + protected $mRNA_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/mrna_mini.fasta']; + + protected $protein_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/polypeptide_mini.fasta']; + + protected $gff_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/gff/filtered.gff']; + + protected $blast_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/gff/filtered.gff']; + + protected $biomaterial_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/biomaterials/biomaterials.xml']; + + protected $expression_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/expression/expression.tsv']; + + protected $interpro_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/ips/polypeptide_mini.fasta.xml']; + + // Regular expression that will link the protein name to the mRNA parent feature name. + // protected $prot_regexp = '/(FRA.*?)(?=:)/'; + + protected $prot_regexp = null; + + public function __construct() + { + + if ($this->organism) { + + try { + $organism = $this->fetch_chado_record('chado.organism', [ + 'common_name', + 'organism_id', + ], $this->organism); + } catch (\Exception $e) { + echo $e->getMessage(); + exit; + } + + $this->organism = $organism; + + if ($this->sequence_analysis) { + + try { + $seq_analysis = $this->fetch_chado_record('chado.analysis', ['analysis_id'], + $this->sequence_analysis); + } catch (\Exception $e) { + echo $e->getMessage(); + exit; + } + $this->sequence_analysis = $seq_analysis; + } + + if ($this->expression_analysis) { + try { + $expression_analysis = $this->fetch_chado_record('chado.analysis', ['analysis_id'], + $this->expression_analysis); + } catch (\Exception $e) { + echo $e->getMessage(); + exit; + } + + $this->expression_analysis = $expression_analysis; + } + } + + if ($this->blastdb) { + try { + $blastdb = $this->fetch_chado_record('chado.db', ['db_id'], $this->blastdb); + } catch (\Excetion $e) { + echo $e->getMessage(); + } + + $this->blastdb = $blastdb; + } + } + + /** + * Runs all loaders. + * Will only run loaders where the files have been uncommented at the start + * of the class. + */ + public function up() + { + + if ($this->landmark_file) { + + $run_args = [ + 'organism_id' => $this->organism->organism_id, + 'analysis_id' => $this->sequence_analysis->analysis_id, + 'seqtype' => $this->landmark_type, + 'method' => 2, //default insert and update + 'match_type' => 1, //unique name default + //optional + 're_name' => null, + 're_uname' => null, + 're_accession' => null, + 'db_id' => null, + 'rel_type' => null, + 're_subject' => null, + 'parent_type' => null, + ]; + $this->load_landmarks($run_args, $this->landmark_file); + } + + if ($this->gff_file) { + $run_args = [ + 'analysis_id' => $this->sequence_analysis->analysis_id, + 'organism_id' => $this->organism->organism_id, + 'use_transaction' => 1, + 'add_only' => 0, + 'update' => 1, + 'create_organism' => 0, + 'create_target' => 0, + + ///regexps for mRNA and protein. + 're_mrna' => null, + 're_protein' => $this->prot_regexp, + //optional + 'target_organism_id' => null, + 'target_type' => null, + 'start_line' => null, + 'landmark_type' => null, + 'alt_id_attr' => null, + ]; + $this->load_GFF($run_args, $this->gff_file); + } + + if ($this->mRNA_file) { + + $run_args = [ + 'organism_id' => $this->organism->organism_id, + 'analysis_id' => $this->sequence_analysis->analysis_id, + 'seqtype' => 'mRNA', + 'method' => 2, //default insert and update + 'match_type' => 1, //unique name default + //optional + 're_name' => null, + 're_uname' => null, + 're_accession' => null, + 'db_id' => null, + 'rel_type' => null, + 're_subject' => null, + 'parent_type' => null, + ]; + $this->load_mRNA_FASTA($run_args, $this->mRNA_file); + } + + if ($this->protein_file) { + $run_args = [ + 'organism_id' => $this->organism->organism_id, + 'analysis_id' => $this->sequence_analysis->analysis_id, + 'seqtype' => 'polypeptide', + 'method' => 2, + 'match_type' => 1, + //optional + 're_name' => null, + 're_uname' => null, + 're_accession' => null, + 'db_id' => null, + ]; + + if ($this->prot_regexp) { + //links polypeptide to mRNA + $run_args['rel_type'] = 'derives_from'; + $run_args['re_subject'] = $this->prot_regexp; + $run_args['parent_type'] = 'mRNA'; + } + $this->load_polypeptide_FASTA($run_args, $this->protein_file); + } + + if ($this->interpro_file) { + + $run_args = [ + 'analysis_id' => $this->sequence_analysis->analysis_id, + //optional + 'query_type' => 'mRNA', + 'query_re' => $this->prot_regexp, + 'query_uniquename' => null, + 'parsego' => true, + ]; + + $this->load_interpro_annotations($run_args, $this->interpro_file); + } + + if ($this->blast_file) { + $run_args = [ + 'analysis_id' => $this->sequence_analysis->analysis_id, + 'no_parsed' => 25,//number results to parse + 'query_type' => 'mRNA', + //optional + 'blastdb' => $this->blastdb->db_id, + 'blastfile_ext' => null, + 'is_concat' => 0, + 'query_re' => null, + 'query_uniquename' => 0, + ]; + + $this->load_blast_annotations($run_args, $this->blast_file); + } + + if ($this->biomaterial_file) { + $run_args = [ + 'organism_id' => $this->organism->organism_id, + 'analysis_id' => $this->sequence_analysis->analysis_id, + ]; + //optional: specifies specific CVterms for properties/property values. Not used here. + //'cvterm_configuration' => NULL, + //'cvalue_configuration' => NULL]; + + $this->load_biomaterials($run_args, $this->biomaterial_file); + } + + if ($this->expression_file) { + $run_args = [ + 'filetype' => 'mat', //matrix file type + 'organism_id' => $this->organism->organism_id, + 'analysis_id' => $this->sequence_analysis->analysis_id, + //optional + 'fileext' => null, + 're_start' => null, + 're_stop' => null, + 'feature_uniquenames' => null, + 'quantificationunits' => null, + 'seqtype' => 'mRNA', + ]; + $this->load_expression($run_args, $this->expression_file); + } + } + + private function load_landmarks($run_args, $file) + { + module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); + + $importer = new \FASTAImporter(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_mRNA_FASTA($run_args, $file) + { + module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); + + $importer = new \FASTAImporter(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_polypeptide_FASTA($run_args, $file) + { + module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); + + $importer = new \FASTAImporter(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_interpro_annotations($run_args, $file) + { + module_load_include('inc', 'tripal_analysis_interpro', 'includes/TripalImporter/InterProImporter'); + + $importer = new \InterProImporter(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_GFF($run_args, $file) + { + module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/GFF3Importer'); + + $importer = new \GFF3Importer(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_blast_annotations($run_args, $file) + { + module_load_include('inc', 'tripal_analysis_blast', 'includes/TripalImporter/BlastImporter'); + + $importer = new \BlastImporter(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_biomaterials($run_args, $file) + { + module_load_include('inc', 'tripal_biomaterial', 'includes/TripalImporter/tripal_biomaterial_loader_v3'); + + $importer = new \tripal_biomaterial_loader_v3(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function load_expression($run_args, $file) + { + module_load_include('inc', 'tripal_analysis_expression', + 'includes/TripalImporter/tripal_expression_data_loader'); + + $importer = new \tripal_expression_data_loader(); + $importer->create($run_args, $file); + $importer->prepareFiles(); + $importer->run(); + } + + private function fetch_chado_record($table, $fields, $factory_array) + { + $query = db_select($table, 't')->fields('t', $fields); + + foreach ($factory_array as $key => $value) { + $query->condition($key, $value); + } + + $count_query = $query; + $count = (int) $count_query->countQuery()->execute()->fetchField(); + + if ($count === 0) { + return factory($table)->create($factory_array); + } + + if ($count === 1) { + return $query->execute()->fetchObject(); + } + + throw new Exception("Error creating object for: ".$table.".\n Array supplied matches ".$count_query." results, must match 1."); + } +} diff --git a/tests/DatabaseSeeders/examples/UsersTableSeeder.php b/tests/DatabaseSeeders/examples/UsersTableSeeder.php new file mode 100644 index 0000000..2d28f29 --- /dev/null +++ b/tests/DatabaseSeeders/examples/UsersTableSeeder.php @@ -0,0 +1,28 @@ + 'test user', + 'pass' => 'secret', + 'mail' => 'test@example.com', + 'status' => 1, + 'init' => 'Email', + 'roles' => [ + DRUPAL_AUTHENTICATED_RID => 'authenticated user', + ], + ]; + + // The first parameter is sent blank so a new user is created. + user_save(new \stdClass(), $new_user); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..6bf56ce --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,4 @@ +assertIsArray($menu_items); + foreach($menu_items as $path => $item) { + $this->assertArrayHasKey('title', $item, + "$path menu item is missing a title."); + $this->assertArrayHasKey('page callback', $item, + "$path menu item is missing a page callback."); + $this->assertArrayHasKey('access arguments', $item, + "$path menu item is missing access arguments."); + $this->assertIsArray($item['access arguments'], + "$path menu item access arguments must be an array."); + } + } +} diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php new file mode 100644 index 0000000..dba5ddf --- /dev/null +++ b/tests/userDataFormTest.php @@ -0,0 +1,150 @@ +actingAs(1); + + $page = tripal_hq_import_list_importers_page(); + + // This page should at least list the Tripal core importers. + $this->assertArrayHasKey('chado_gff3_loader', $page, + "GFF3 Importer was not present on the listing page."); + $this->assertArrayHasKey('chado_fasta_loader', $page, + "FASTA Importer was not present on the listing page."); + } + + /** + * Tests tripal_hq_user_importer_form(). + */ + public function testUserImporterForm() { + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + ]; + // Now execute the form function with the mock form state. + $form = []; + $form = tripal_hq_user_importer_form($form, $form_state); + + // Now check key parts of the GFF3 Importer form are present. + $this->assertArrayHasKey('importer_class', $form, + "Form array did not specify the importer class."); + $this->assertEquals('GFF3Importer', $form['importer_class']['#value'], + "Form array importer class did not match what we expected."); + $this->assertArrayHasKey('file', $form, + "Form array did not specify the file."); + $this->assertArrayHasKey('analysis_id', $form, + "Form array did not specify the analysis."); + $this->assertArrayHasKey('organism_id', $form, + "Form array did not specify the organism."); + + } + + /** + * Tests tripal_hq_user_importer_form_validate(). + */ + public function testUserImporterFormValidate() { + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'redirect' => NULL, + 'values' => [ + 'importer_class' => 'GFF3Importer', + ], + ]; + // Now execute the form function with the mock form state. + $form = []; + tripal_hq_user_importer_form_validate($form, $form_state); + + // The form state we provided did not have all the expected values + // Therefore, there should be errors! + $errors = form_get_errors(); + $this->assertNotEmpty($errors, + "The form validate did not return errors even though we did not submit all values."); + } + + /** + * Tests tripal_hq_user_importer_form_submit(). + */ + public function testUserImporterFormSubmit() { + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'redirect' => NULL, + 'values' => [ + 'importer_class' => 'GFF3Importer', + ], + ]; + // Now execute the form function with the mock form state. + $form = []; + tripal_hq_user_importer_form_submit($form, $form_state); + + // Now check the submission was created. + $record = db_select('tripal_hq_importer_submission', 'T') + ->fields('T') + ->condition('class', 'GFF3Importer') + ->orderby('T.id', 'DESC') + ->execute()->fetchObject(); + $this->assertIsObject($record, + "We were unable to select the new submission."); + $this->assertEquals(serialize($form_state), $record->data, + "The most recent GFF3Importer submission data, did not match our submitted form state."); + } +} diff --git a/tripal_hq_imports.install b/tripal_hq_imports.install new file mode 100644 index 0000000..c9f2f8f --- /dev/null +++ b/tripal_hq_imports.install @@ -0,0 +1,62 @@ + 'Store pending user requests to import files.', + 'fields' => [ + 'id' => [ + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'uid' => [ + 'description' => "Drupal user ID of submitter", + 'type' => 'int', + 'not null' => TRUE, + ], + 'class' => [ + 'description' => "The importer class the file and metadata is associated with.", + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ], + 'data' => [ + 'description' => 'Serialized tripal importer data.', + 'type' => 'blob', + 'size' => 'big', + 'serialize' => TRUE, + ], + 'status' => [ + 'description' => 'One of pending, published, rejected, obsolete', + 'type' => 'varchar', + 'length' => '60', + 'not null' => TRUE, + ], + 'created_at' => [ + 'description' => 'Date submission created', + 'type' => 'int', + 'size' => 'big', + 'not null' => TRUE, + ], + 'updated_at' => [ + 'description' => 'Date submission updated', + 'type' => 'int', + 'size' => 'big', + 'not null' => FALSE, + ], + ], + 'primary key' => [ + 'id', + ], + ]; + + return $schema; +} diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index 05b0fc6..e180171 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -2,3 +2,30 @@ /** * Contains core drupal hook implementations. */ + +/** + * Implements hook_menu(). + */ +function tripal_hq_imports_menu() { + $items = []; + + // Content control (adding/deleting/editing). + $items['tripal_hq/bio_data/import-data'] = [ + 'title' => 'Import data file', + 'description' => 'Submit a data file for import.', + 'page callback' => 'tripal_hq_import_list_importers_page', + 'access arguments' => ['access tripal_hq user'], + 'file' => 'includes/tripal_hq_imports_user_data.form.inc', + 'type' => MENU_LOCAL_ACTION, + ]; + + $items['tripal_hq/bio_data/import-data/%'] = [ + 'title' => 'Import data file', + 'page callback' => 'drupal_get_form', + 'page arguments' => ['tripal_hq_user_importer_form', 3], + 'access arguments' => ['access tripal_hq user'], + 'file' => 'includes/tripal_hq_imports_user_data.form.inc', + ]; + + return $items; +} From a2a96cbb8441170ca5b86d098cf8fdc277d0e4fe Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 14:10:05 -0600 Subject: [PATCH 05/36] Trying to fix travis by ensuring dependencies are present. --- .travis.yml | 4 ++++ includes/tripal_hq_imports_user_data.form.inc | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ca5d15..b8f24de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,11 @@ before_script: script: - docker run -it -d --rm --name tripal -v "$(pwd)":/modules/tripal_hq_imports statonlab/tripal3 - sleep 30 # We pause here so postgres and apache complete booting up + ## Install dependencies. + - docker exec -it tripal bash -c "cd /modules/ && git clone https://github.com/statonlab/tripal_hq.git" + ## Enable our module. - docker exec -it tripal drush pm-enable -y tripal_hq_imports + ## Install XDEBUG for coverage. - docker exec -it tripal yum install -y php-pecl-xdebug.x86_64 - docker exec -it tripal bash -c "cd /modules/tripal_hq_imports && composer install && DRUPAL_ROOT=/var/www/html ./vendor/bin/phpunit --coverage-clover ./clover.xml" diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index f9d22eb..653181b 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -90,7 +90,6 @@ function tripal_hq_user_importer_form_validate($form, $form_state) { $importer_class = $form_state['build_info']['args'][0]; tripal_load_include_importer_class($importer_class); - dpm($form_state, 'form state'); // Validate the user input in the same manner as the original importer. module_load_include('inc', 'tripal', 'includes/tripal.importer'); tripal_get_importer_form_validate($form, $form_state); From f30ae444a8b413fc6468dd16de7b269c12075aa7 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 14:15:38 -0600 Subject: [PATCH 06/36] Remove tests which fail due to tripal core bug. --- tests/userDataFormTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index dba5ddf..1fe03f9 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -23,10 +23,14 @@ public function testListImportersPage() { $page = tripal_hq_import_list_importers_page(); // This page should at least list the Tripal core importers. + /** Cannot test due to bug in Tripal. $this->assertArrayHasKey('chado_gff3_loader', $page, "GFF3 Importer was not present on the listing page."); $this->assertArrayHasKey('chado_fasta_loader', $page, "FASTA Importer was not present on the listing page."); + */ + $this->assertArrayHasKey('description', $page, + "Ensure the listing page shows help text to the user."); } /** From 9aa199194cca37cf8857133364c3b8f0922f3699 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 14:26:15 -0600 Subject: [PATCH 07/36] Skipping tests due to bug in core :-( --- tests/userDataFormTest.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index 1fe03f9..7aadffe 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -17,18 +17,15 @@ class userDataFormTest extends TripalTestCase { */ public function testListImportersPage() { - // Authenticate the superuser who has an id 1 - $this->actingAs(1); + $this->markTestSkipped('Test skipped due to bug in core Tripal.'); $page = tripal_hq_import_list_importers_page(); // This page should at least list the Tripal core importers. - /** Cannot test due to bug in Tripal. $this->assertArrayHasKey('chado_gff3_loader', $page, "GFF3 Importer was not present on the listing page."); $this->assertArrayHasKey('chado_fasta_loader', $page, "FASTA Importer was not present on the listing page."); - */ $this->assertArrayHasKey('description', $page, "Ensure the listing page shows help text to the user."); } @@ -38,6 +35,9 @@ public function testListImportersPage() { */ public function testUserImporterForm() { + $this->markTestSkipped('Test skipped due to bug in core Tripal.'); + + $this-> // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ @@ -86,6 +86,8 @@ public function testUserImporterForm() { */ public function testUserImporterFormValidate() { + $this->markTestSkipped('Test skipped due to bug in core Tripal.'); + // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ @@ -119,6 +121,8 @@ public function testUserImporterFormValidate() { */ public function testUserImporterFormSubmit() { + $this->markTestSkipped('Test skipped due to bug in core Tripal.'); + // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ From 8919cad5083b21ce07d9601289bb440b8b12a695 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 22:01:54 -0600 Subject: [PATCH 08/36] List data submissions on the users dashboard. --- .../tripal_hq_imports_user_dashboard.form.inc | 84 +++++++++++++++++++ includes/tripal_hq_imports_user_data.form.inc | 7 +- tripal_hq_imports.install | 10 +++ tripal_hq_imports.module | 2 + 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 includes/tripal_hq_imports_user_dashboard.form.inc diff --git a/includes/tripal_hq_imports_user_dashboard.form.inc b/includes/tripal_hq_imports_user_dashboard.form.inc new file mode 100644 index 0000000..3e060ad --- /dev/null +++ b/includes/tripal_hq_imports_user_dashboard.form.inc @@ -0,0 +1,84 @@ +' . t('Content') . ''; + + // Now start to build my table. + $header = $form['my_submissions']['#header']; + $header['Title']['field'] = 'class'; + + $rows = []; + $submissions = db_select('tripal_hq_importer_submission', 't') + ->fields('t') + ->condition('uid', $user->uid) + ->extend('TableSort') + ->orderByHeader($header) + ->extend('PagerDefault') + ->limit(20) + ->execute() + ->fetchAll(); + + $date_format = 'M d Y H:i:s'; + $importer_labels = []; + + foreach ($submissions as $submission) { + + $id = $submission->id; + $status = $submission->status; + $importer_class = $submission->class; + $comment_count = tripal_hq_get_comments_count($submission); + + // Determine the label. + if (!isset($importer_labels[$importer_class])) { + + tripal_load_include_importer_class($importer_class); + $label = $importer_class::$name; + + $importer_labels[$importer_class] = $label; + } + $label = $importer_labels[$importer_class]; + + // Dates. + $created_at = date($date_format, $submission->created_at); + $updated_at = $submission->updated_at ? date( + $date_format, $submission->updated_at + ) : ''; + + // Comments. + $comments_link = $comment_count; + if ($submission->nid) { + $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $submission->nid); + } + + // Now compile the row. + $rows[] = [ + $label, + 'Data File', + ucwords($status), + $created_at, + $updated_at, + $comments_link, + '' + ]; + } + + $form['my_importer_submissions'] = [ + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#caption' => '

' . t('Data Files') . '

', + '#empty' => t("You have no pending data file submissions."), + ]; + + return $form; +} diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index 653181b..be0cea1 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -101,17 +101,22 @@ function tripal_hq_user_importer_form_validate($form, $form_state) { function tripal_hq_user_importer_form_submit($form, $form_state) { global $user; $importer_class = $form_state['build_info']['args'][0]; + tripal_load_include_importer_class($importer_class); + + // Create the comments node. + $title = $importer_class::$name . ' Submission from ' . date('M d Y H:i:s'); + $nid = tripal_hq_create_node($title); // Save it to the tripal_hq_imports_submission table! $data = serialize($form_state); db_insert('tripal_hq_importer_submission') ->fields([ 'uid' => $user->uid, + 'nid' => $nid, 'class' => $importer_class, 'data' => $data, 'status' => 'pending', 'created_at' => time(), - 'updated_at' => time(), ])->execute(); } diff --git a/tripal_hq_imports.install b/tripal_hq_imports.install index c9f2f8f..874fe76 100644 --- a/tripal_hq_imports.install +++ b/tripal_hq_imports.install @@ -22,6 +22,16 @@ function tripal_hq_imports_schema() { 'type' => 'int', 'not null' => TRUE, ], + 'nid' => [ + 'description' => "Comments node ID", + 'type' => 'int', + 'not null' => FALSE, + ], + 'job_id' => [ + 'description' => "The id of the tripal job which ran the importer once approved.", + 'type' => 'int', + 'not null' => FALSE, + ], 'class' => [ 'description' => "The importer class the file and metadata is associated with.", 'type' => 'varchar', diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index e180171..1da1e83 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -3,6 +3,8 @@ * Contains core drupal hook implementations. */ +require_once 'includes/tripal_hq_imports_user_dashboard.form.inc'; + /** * Implements hook_menu(). */ From 31a18017ec1c84b0c1df790380873d5b1d6e56d9 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 22:47:59 -0600 Subject: [PATCH 09/36] Bring back the tests. --- includes/tripal_hq_imports.api.inc | 65 +++++++++++++++++++ .../tripal_hq_imports_user_dashboard.form.inc | 2 +- includes/tripal_hq_imports_user_data.form.inc | 18 ++--- tests/apiTest.php | 34 ++++++++++ tests/userDashboardTest.php | 39 +++++++++++ tests/userDataFormTest.php | 16 ++--- tripal_hq_imports.module | 2 +- 7 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 includes/tripal_hq_imports.api.inc create mode 100644 tests/apiTest.php create mode 100644 tests/userDashboardTest.php diff --git a/includes/tripal_hq_imports.api.inc b/includes/tripal_hq_imports.api.inc new file mode 100644 index 0000000..8fe2eff --- /dev/null +++ b/includes/tripal_hq_imports.api.inc @@ -0,0 +1,65 @@ +name; + module_load_include('inc', $module, 'includes/TripalImporter/' . $class); + if (class_exists($class) and is_subclass_of($class, 'TripalImporter')) { + $importers[] = $class; + } + } + } + } + + return $importers; +} + +/** + * Loads the TripalImporter class file into scope. + * + * @param $class + * The TripalImporter class to include. + * + * @return + * TRUE if the field type class file was found, FALSE otherwise. + * + * @ingroup tripal_importer_api + */ +function tripal_hq_load_include_importer_class($class) { + + $success = tripal_load_include_importer_class($class); + if (!$success) { + $modules = module_list(TRUE); + foreach ($modules as $module) { + $file_path = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . '/includes/TripalImporter/' . $class . '.inc'; + if (file_exists($file_path)) { + module_load_include('inc', $module, 'includes/TripalImporter/' . $class); + if (class_exists($class)) { + return TRUE; + } + } + } + } + + return $success; +} diff --git a/includes/tripal_hq_imports_user_dashboard.form.inc b/includes/tripal_hq_imports_user_dashboard.form.inc index 3e060ad..a6ff82f 100644 --- a/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/includes/tripal_hq_imports_user_dashboard.form.inc @@ -41,7 +41,7 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) // Determine the label. if (!isset($importer_labels[$importer_class])) { - tripal_load_include_importer_class($importer_class); + tripal_hq_load_include_importer_class($importer_class); $label = $importer_class::$name; $importer_labels[$importer_class] = $label; diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index be0cea1..17035c6 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -11,6 +11,7 @@ * Array of markup describing bundles. */ function tripal_hq_import_list_importers_page() { + global $user; $page = []; // We might also add a warning for *admin* users to use the actual tripal import forms. @@ -22,7 +23,7 @@ ]; $user_has_stuff = 0; - $importers = tripal_get_importers(); + $importers = tripal_hq_get_importers(); foreach ($importers as $importer_class) { // Pull some important info out of the class. @@ -31,9 +32,10 @@ $importer_description = $importer_class::$description; // @todo Ensure the user has permission to propose that file type. - //if (!user_access("propose $importer_machine_name")) { - //continue; - //} + // Currently we are just restricting anonymous users. + if ($user->uid === 0) { + continue; + } $user_has_stuff = 1; @@ -70,9 +72,9 @@ * @return array * Renderable array. */ -function tripal_hq_user_importer_form($form, $form_state) { +function tripal_hq_user_importer_form($form, &$form_state) { $importer_class = $form_state['build_info']['args'][0]; - tripal_load_include_importer_class($importer_class); + $success = tripal_hq_load_include_importer_class($importer_class); drupal_set_title($importer_class::$name); @@ -88,7 +90,7 @@ function tripal_hq_user_importer_form($form, $form_state) { */ function tripal_hq_user_importer_form_validate($form, $form_state) { $importer_class = $form_state['build_info']['args'][0]; - tripal_load_include_importer_class($importer_class); + tripal_hq_load_include_importer_class($importer_class); // Validate the user input in the same manner as the original importer. module_load_include('inc', 'tripal', 'includes/tripal.importer'); @@ -101,7 +103,7 @@ function tripal_hq_user_importer_form_validate($form, $form_state) { function tripal_hq_user_importer_form_submit($form, $form_state) { global $user; $importer_class = $form_state['build_info']['args'][0]; - tripal_load_include_importer_class($importer_class); + tripal_hq_load_include_importer_class($importer_class); // Create the comments node. $title = $importer_class::$name . ' Submission from ' . date('M d Y H:i:s'); diff --git a/tests/apiTest.php b/tests/apiTest.php new file mode 100644 index 0000000..738a99c --- /dev/null +++ b/tests/apiTest.php @@ -0,0 +1,34 @@ +assertIsArray($importers); + $this->assertNotEmpty($importers); + } + + /** + * Tests tripal_hq_load_include_importer_class(). + */ + public function testLoadImporterClass() { + + // First test one which should work. + $success = tripal_hq_load_include_importer_class('GFF3Importer'); + $this->assertTrue($success); + + // Next test one which fails. + $success = tripal_hq_load_include_importer_class(uniqid()); + $this->assertFalse($success); + } +} diff --git a/tests/userDashboardTest.php b/tests/userDashboardTest.php new file mode 100644 index 0000000..c00f65c --- /dev/null +++ b/tests/userDashboardTest.php @@ -0,0 +1,39 @@ +fields([ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ])->execute(); + + // We run the Tripal HQ user dashboard which should trigger our alter fn. + $form = []; + $form_state = []; + $form = tripal_hq_user_dashboard_form($form, $form_state); + $this->assertArrayHasKey('my_importer_submissions', $form, + "Our submissions table is not in the page."); + + } +} diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index 7aadffe..0a604a8 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -17,8 +17,15 @@ class userDataFormTest extends TripalTestCase { */ public function testListImportersPage() { - $this->markTestSkipped('Test skipped due to bug in core Tripal.'); + // First test as the anonymous user. + $page = tripal_hq_import_list_importers_page(); + $this->assertArrayHasKey('description', $page, + "Ensure the listing page shows help text to the user."); + $this->assertStringContainsString('do not', $page['description']['#markup'], + "Make sure we actually tell them they do not have permission."); + // Then test as an administrator. + $this->actingAs(1); $page = tripal_hq_import_list_importers_page(); // This page should at least list the Tripal core importers. @@ -35,9 +42,6 @@ public function testListImportersPage() { */ public function testUserImporterForm() { - $this->markTestSkipped('Test skipped due to bug in core Tripal.'); - - $this-> // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ @@ -86,8 +90,6 @@ public function testUserImporterForm() { */ public function testUserImporterFormValidate() { - $this->markTestSkipped('Test skipped due to bug in core Tripal.'); - // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ @@ -121,8 +123,6 @@ public function testUserImporterFormValidate() { */ public function testUserImporterFormSubmit() { - $this->markTestSkipped('Test skipped due to bug in core Tripal.'); - // Mock the form state specifying the GFF3 importer. $form_state = [ 'build_info' => [ diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index 1da1e83..027afc7 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -2,7 +2,7 @@ /** * Contains core drupal hook implementations. */ - +require_once 'includes/tripal_hq_imports.api.inc'; require_once 'includes/tripal_hq_imports_user_dashboard.form.inc'; /** From e69a438b99dcc8d487f8953e20ce9c8006667013 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Sun, 13 Oct 2019 22:56:01 -0600 Subject: [PATCH 10/36] Skip user dashboard test until alter PR makes it into Tripal HQ master. --- tests/userDashboardTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/userDashboardTest.php b/tests/userDashboardTest.php index c00f65c..b4b6c30 100644 --- a/tests/userDashboardTest.php +++ b/tests/userDashboardTest.php @@ -14,6 +14,8 @@ class userDashboardTest extends TripalTestCase { public function testUserDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_user_dashboard.form'); + $this->markTestSkipped('This test needs to be skipped until our PR makes it into Tripal HQ master.'); + // Add a submission for better testing. $data = serialize([1,2,3]); global $user; From d36acf2abf9d15eeb3c9f56e5eeacbbb4bc7bf29 Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Mon, 14 Oct 2019 08:58:36 -0600 Subject: [PATCH 11/36] Update README.md --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 6c2472c..48162b6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +![Tripal Dependency](https://img.shields.io/badge/tripal-%3E=3.0-brightgreen) +![Module is Generic](https://img.shields.io/badge/generic-confirmed-brightgreen) + +[![Build Status](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports.svg?branch=master)](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports) +[![Test Coverage](https://api.codeclimate.com/v1/badges/598206b15a4410687d38/test_coverage)](https://codeclimate.com/github/UofS-Pulse-Binfo/tripal_hq_imports/test_coverage) + # Tripal HQ Imports Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. Tripal HQ Imports extends [Tripal HQ](https://github.com/statonlab/tripal_hq) to support TripalImporters. Specifically, this allows users to submit Tripal Importers, administrators to review the submission and data is only inserted into Chado once the administrator approves the submission. @@ -5,3 +11,18 @@ Tripal HQ provides a user-contributed content control center and administrative ## UNDER DEVELOPMENT This module is currently under active development. **It is not ready for use.** If you are interested in this module, please star it so we know there is need. Thank you! + +## Current Features + +Only current features are listed below. This does not reflect all features which will be available once development is complete but is meant to provide you with an idea of development progress. + +### Adds Data Import support to Tripal HQ user Dashboard + +Users with permission to submit data through Tripal HQ, now have access to an "Import data file" action link on their dashboard. Once clicked, users are presented with the full list of Tripal Importers (including custom ones) which allows them to pick their data file type, enter the required metadata and upload the file. At this point the submission is put into a holding area waiting for administrator approval. + +![User Dashboard Screenshot](https://user-images.githubusercontent.com/1566301/66760602-afc8ff80-ee5f-11e9-9934-b8c065573f83.png). + +Their submissions will be summarized on the same dashboard as their Tripal Content for a unified experience! + +### Administration +- Data file import forms are created automatically based on the TripalImporter::form() and TripalImporter:validate() is run on submission to ensure meta data matches standards. From 0990b839224a4368b76a60d4c7dae7e6fd30ca6d Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Mon, 14 Oct 2019 22:27:38 -0600 Subject: [PATCH 12/36] Override the administration dashboard, adding data file submissions. --- ...tripal_hq_imports_admin_dashboard.form.inc | 127 ++++++++++++++++++ tripal_hq_imports.module | 1 + 2 files changed, 128 insertions(+) create mode 100644 includes/tripal_hq_imports_admin_dashboard.form.inc diff --git a/includes/tripal_hq_imports_admin_dashboard.form.inc b/includes/tripal_hq_imports_admin_dashboard.form.inc new file mode 100644 index 0000000..12c059d --- /dev/null +++ b/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -0,0 +1,127 @@ +uid; + + // Add a caption to differentiate the two tables. + $form['table']['#caption'] = '

' . t('Content') . '

'; + + // Check if the user is a deputy. + // If so, they only get access to a subset of data. + $deputy = user_access('tripal_hq_permissions deputy'); + + $header = [ + 'User' => [ + 'data' => t('User'), + 'field' => 't.uid', + ], + 'Title' => [ + 'data' => t('Title'), + 'field' => 'class', + ], + 'Type' => [ + 'data' => t('Content Type'), + ], + 'Status' => [ + 'data' => t('Approval Status'), + 'field' => 'status', + ], + 'Date Created' => [ + 'data' => t('Date Created'), + 'field' => 'created_at', + 'sort' => 'dsc', + ], + 'Comments' => [ + 'data' => t('Comments'), + ], + 'Approve', + 'Reject', + ]; + + // @todo support HQ deputy permissmissions! + //if ($deputy && $uid != 0 && $uid != 1) + + $query = db_select('tripal_hq_importer_submission', 't') + ->extend('TableSort') + ->orderByHeader($header) + ->extend('PagerDefault'); + + if ($status) { + $query->condition('t.status', $status); + } + $requests = $query->fields('t') + ->orderBy('id', 'desc') + ->limit(10) + ->execute() + ->fetchAll(); + + $date_format = 'M d Y H:i:s'; + $importer_labels = []; + + $rows = []; + foreach ($requests as $request) { + + $id = $request->id; + $status = $request->status; + $importer_class = $request->class; + $comment_count = tripal_hq_get_comments_count($request); + + // Determine the label. + if (!isset($importer_labels[$importer_class])) { + + tripal_hq_load_include_importer_class($importer_class); + $label = $importer_class::$name; + + $importer_labels[$importer_class] = $label; + } + $label = $importer_labels[$importer_class]; + + // User. + $submitter = user_load($request->uid); + if (!$submitter) { + tripal_set_message(t("Error looking up user !user", + ['!user' => $submission->uid], TRIPAL_WARNING)); + continue; + } + $user_row = l($submitter->name . ' (' . $submitter->mail . ')', 'user/' . $submitter->uid); + + // Dates. + $created_at = date($date_format, $request->created_at); + + // Comments. + $comments_link = $comment_count; + if ($request->nid) { + $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $request->nid); + } + + // Now compile the row. + $rows[] = [ + $user_row, + $label, + 'Data File', + ucwords($status), + $created_at, + $comments_link, + '', + '' + ]; + } + + $form['my_importer_requests'] = [ + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#caption' => '

' . t('Data Files') . '

', + '#empty' => t("There are no $status data file submissions."), + ]; + + return $form; + } diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index 027afc7..4441c85 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -4,6 +4,7 @@ */ require_once 'includes/tripal_hq_imports.api.inc'; require_once 'includes/tripal_hq_imports_user_dashboard.form.inc'; +require_once 'includes/tripal_hq_imports_admin_dashboard.form.inc'; /** * Implements hook_menu(). From d96f50a346e4cddb7be46d3eea22921af9d8c425 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 15 Oct 2019 11:53:15 -0600 Subject: [PATCH 13/36] Add pagers. --- includes/tripal_hq_imports_admin_dashboard.form.inc | 9 ++++++++- includes/tripal_hq_imports_user_dashboard.form.inc | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/includes/tripal_hq_imports_admin_dashboard.form.inc b/includes/tripal_hq_imports_admin_dashboard.form.inc index 12c059d..617061f 100644 --- a/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -11,6 +11,8 @@ global $user; $uid = $user->uid; + $items_per_page = 10; + // Add a caption to differentiate the two tables. $form['table']['#caption'] = '

' . t('Content') . '

'; @@ -59,7 +61,7 @@ } $requests = $query->fields('t') ->orderBy('id', 'desc') - ->limit(10) + ->limit($items_per_page) ->execute() ->fetchAll(); @@ -123,5 +125,10 @@ '#empty' => t("There are no $status data file submissions."), ]; + $form['importer_pager'] = [ + '#theme' => 'pager', + '#element' => 1, + ]; + return $form; } diff --git a/includes/tripal_hq_imports_user_dashboard.form.inc b/includes/tripal_hq_imports_user_dashboard.form.inc index a6ff82f..3d1104a 100644 --- a/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/includes/tripal_hq_imports_user_dashboard.form.inc @@ -10,6 +10,9 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) { global $user; + // Number of items per page. + $items_per_page = 5; + // Add a caption to differentiate the two tables. $form['my_submissions']['#caption'] = '

' . t('Content') . '

'; @@ -24,7 +27,7 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) ->extend('TableSort') ->orderByHeader($header) ->extend('PagerDefault') - ->limit(20) + ->limit($items_per_page) ->execute() ->fetchAll(); @@ -80,5 +83,10 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) '#empty' => t("You have no pending data file submissions."), ]; + $form['importer_pager'] = [ + '#theme' => 'pager', + '#element' => 1, + ]; + return $form; } From 0fa718e7542ee084865b688371ae5bfd8bee8dea Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 15 Oct 2019 12:05:34 -0600 Subject: [PATCH 14/36] Small bug fix and added tests for admin dashboard. --- ...tripal_hq_imports_admin_dashboard.form.inc | 2 +- tests/adminDashboardTest.php | 51 +++++++++++++++++++ tests/userDashboardTest.php | 2 - 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/adminDashboardTest.php diff --git a/includes/tripal_hq_imports_admin_dashboard.form.inc b/includes/tripal_hq_imports_admin_dashboard.form.inc index 617061f..80125ed 100644 --- a/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -90,7 +90,7 @@ $submitter = user_load($request->uid); if (!$submitter) { tripal_set_message(t("Error looking up user !user", - ['!user' => $submission->uid], TRIPAL_WARNING)); + ['!user' => $request->uid]), TRIPAL_WARNING); continue; } $user_row = l($submitter->name . ' (' . $submitter->mail . ')', 'user/' . $submitter->uid); diff --git a/tests/adminDashboardTest.php b/tests/adminDashboardTest.php new file mode 100644 index 0000000..e0be3a0 --- /dev/null +++ b/tests/adminDashboardTest.php @@ -0,0 +1,51 @@ +fields([ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ])->execute(); + // Add a user id that doesn't exist. + $max_uid = db_query('SELECT max(uid) FROM {users}')->fetchField(); + db_insert('tripal_hq_importer_submission') + ->fields([ + 'uid' => $max_uid+100, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ])->execute(); + + // We run the Tripal HQ user dashboard which should trigger our alter fn. + $form = []; + $form_state = []; + $form = tripal_hq_admin_dashboard_form($form, $form_state); + $this->assertArrayHasKey('my_importer_requests', $form, + "Our submission requests table is not in the page."); + + } +} diff --git a/tests/userDashboardTest.php b/tests/userDashboardTest.php index b4b6c30..c00f65c 100644 --- a/tests/userDashboardTest.php +++ b/tests/userDashboardTest.php @@ -14,8 +14,6 @@ class userDashboardTest extends TripalTestCase { public function testUserDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_user_dashboard.form'); - $this->markTestSkipped('This test needs to be skipped until our PR makes it into Tripal HQ master.'); - // Add a submission for better testing. $data = serialize([1,2,3]); global $user; From 2118119a5a3c2ee0bf67c75f771917b45c8f62f6 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 15 Oct 2019 16:56:20 -0600 Subject: [PATCH 15/36] Support editing of submissions. --- includes/tripal_hq_imports.api.inc | 30 +++ .../tripal_hq_imports_user_dashboard.form.inc | 9 +- includes/tripal_hq_imports_user_data.form.inc | 95 ++++++++-- tests/apiTest.php | 64 +++++++ tests/userDashboardTest.php | 2 +- tests/userDataFormTest.php | 172 ++++++++++++++++++ tripal_hq_imports.module | 16 ++ 7 files changed, 369 insertions(+), 19 deletions(-) diff --git a/includes/tripal_hq_imports.api.inc b/includes/tripal_hq_imports.api.inc index 8fe2eff..7f9492d 100644 --- a/includes/tripal_hq_imports.api.inc +++ b/includes/tripal_hq_imports.api.inc @@ -63,3 +63,33 @@ function tripal_hq_load_include_importer_class($class) { return $success; } + +/** + * Updates a given form field for edit (default value) or view (read-only) mode. + * + * @param $form_field + * A form field defined using the Drupal Form API. + * @param $values + * The full values list for the form. + * @param $op + * One of 'edit' or 'view' where the second will be read-only. + */ +function tripal_hq_editview_form_field(&$form_field, $values, $element_key, $op) { + + if (isset($form_field['#type']) AND ($form_field['#type'] == 'fieldset')) { + foreach(element_children($form_field) as $child_element) { + tripal_hq_editview_form_field($form_field[$child_element], $values, $child_element, $op); + } + } + else { + if (isset($values[$element_key])) { + $form_field['#default_value'] = $values[$element_key]; + } + + if ($op == 'view') { + $form_field['#disabled'] = TRUE; + $form_field['#attributes'] = array('readonly' => 'readonly'); + } + } + +} diff --git a/includes/tripal_hq_imports_user_dashboard.form.inc b/includes/tripal_hq_imports_user_dashboard.form.inc index 3d1104a..2857782 100644 --- a/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/includes/tripal_hq_imports_user_dashboard.form.inc @@ -63,6 +63,13 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $submission->nid); } + // Edit or View link. + $link = l(t('Edit'), '/tripal_hq/bio_data/import-data/edit/' . $importer_class . '/' . $id); + if ($status != 'pending') { + $link = l(t('View'), '/tripal_hq/bio_data/import-data/view/' . $importer_class . '/' . $id); + } + + // Now compile the row. $rows[] = [ $label, @@ -71,7 +78,7 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) $created_at, $updated_at, $comments_link, - '' + $link ]; } diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index 17035c6..4aa49ca 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -76,11 +76,59 @@ function tripal_hq_user_importer_form($form, &$form_state) { $importer_class = $form_state['build_info']['args'][0]; $success = tripal_hq_load_include_importer_class($importer_class); + // Check if we should allow editing/viewing of an existing submission. + if (isset($form_state['build_info']['args'][1])) { + $submission_id = $form_state['build_info']['args'][1]; + $op = $form_state['build_info']['args'][2]; + + $submission = db_select('tripal_hq_importer_submission', 't') + ->fields('t') + ->condition('id', $submission_id) + ->execute()->fetchObject(); + if (is_object($submission)) { + $importer_form_state = unserialize($submission->data); + $status = $submission->status; + + $form['submission_id'] = [ + '#type' => 'hidden', + '#value' => $submission_id, + ]; + } + else { + $form['warning'] = [ + '#type' => 'markup', + '#markup' => '
Unable to find your previous submission. Please contact an administrator.
', + ]; + return $form; + } + } + else { + $importer_form_state = $form_state; + } + drupal_set_title($importer_class::$name); // Retrieve the form for the importer. module_load_include('inc', 'tripal', 'includes/tripal.importer'); - $form = tripal_get_importer_form($form, $form_state, $importer_class); + $form = tripal_get_importer_form($form, $importer_form_state, $importer_class); + + // Most importers don't actually fill in defaults since they expect to be + // sumbitted only once. As such, we should still fill in the defaults ourselves. + if (isset($importer_form_state['values'])) { + + // Ensure people do not try to edit submissions which are no longer pending. + if (($op != 'edit') OR ($status != 'pending')) { + $op = 'view'; + } + + // Fill the default for the full form and disable if $op is view. + foreach (element_children($form) as $element_key) { + tripal_hq_editview_form_field($form[$element_key], $importer_form_state['values'], $element_key, $op); + } + } + + // Change the name of the button! + $form['button']['#value'] = 'Submit'; return $form; } @@ -105,20 +153,33 @@ function tripal_hq_user_importer_form_submit($form, $form_state) { $importer_class = $form_state['build_info']['args'][0]; tripal_hq_load_include_importer_class($importer_class); - // Create the comments node. - $title = $importer_class::$name . ' Submission from ' . date('M d Y H:i:s'); - $nid = tripal_hq_create_node($title); - - // Save it to the tripal_hq_imports_submission table! - $data = serialize($form_state); - db_insert('tripal_hq_importer_submission') - ->fields([ - 'uid' => $user->uid, - 'nid' => $nid, - 'class' => $importer_class, - 'data' => $data, - 'status' => 'pending', - 'created_at' => time(), - ])->execute(); - + // If we have a submission id then update. + if (isset($form_state['values']['submission_id'])) { + $data = serialize($form_state); + db_update('tripal_hq_importer_submission') + ->fields([ + 'data' => $data, + 'updated_at' => time(), + ]) + ->condition('id', $form_state['values']['submission_id']) + ->execute(); + } + // Otherwise, insert. + else { + // Create the comments node. + $title = $importer_class::$name . ' Submission from ' . date('M d Y H:i:s'); + $nid = tripal_hq_create_node($title); + + // Save it to the tripal_hq_imports_submission table! + $data = serialize($form_state); + db_insert('tripal_hq_importer_submission') + ->fields([ + 'uid' => $user->uid, + 'nid' => $nid, + 'class' => $importer_class, + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + ])->execute(); + } } diff --git a/tests/apiTest.php b/tests/apiTest.php index 738a99c..226594f 100644 --- a/tests/apiTest.php +++ b/tests/apiTest.php @@ -31,4 +31,68 @@ public function testLoadImporterClass() { $success = tripal_hq_load_include_importer_class(uniqid()); $this->assertFalse($success); } + + /** + * Tests tripal_hq_editview_form_field(). + */ + public function testEditViewFormField() { + $form = [ + 'element1' => [ + '#type' => 'textfield', + '#disabled' => FALSE, + ], + 'element2' => [ + '#type' => 'select', + '#options' => [1,2,3,4], + ], + 'containerblahblah' => [ + '#type' => 'fieldset', + 'element3' => [ + '#type' => 'textfield', + '#disabled' => FALSE, + ], + 'element4' => [ + '#type' => 'select', + '#options' => [1,2,3,4], + ], + 'container222' => [ + '#type' => 'fieldset', + 'element5' => [ + '#type' => 'select', + '#options' => [1,2,3,4], + ], + ], + ], + ]; + $values = [ + 'element1' => 'fred', + 'element2' => 3, + 'element3' => 'sarah', + 'element4' => 1, + 'element5' => 4, + ]; + + foreach (element_children($form) as $element_key) { + tripal_hq_editview_form_field($form[$element_key], $values, $element_key, 'view'); + } + + // Check that everything is disabled. + $this->assertTrue($form['element1']['#disabled'], 'element1 was not disabled.'); + $this->assertTrue($form['element2']['#disabled'], 'element2 was not disabled.'); + $this->assertTrue($form['containerblahblah']['element3']['#disabled'], 'element3 was not disabled.'); + $this->assertTrue($form['containerblahblah']['element4']['#disabled'], 'element4 was not disabled.'); + $this->assertTrue($form['containerblahblah']['container222']['element5']['#disabled'], 'element5 was not disabled.'); + + // Check that their default value is set correctly. + $this->assertEquals('fred', $form['element1']['#default_value'], + 'element1 default value was not set properly.'); + $this->assertEquals(3, $form['element2']['#default_value'], + 'element2 default value was not set properly.'); + $this->assertEquals('sarah', $form['containerblahblah']['element3']['#default_value'], + 'element3 default value was not set properly.'); + $this->assertEquals(1, $form['containerblahblah']['element4']['#default_value'], + 'element4 default value was not set properly.'); + $this->assertEquals(4, $form['containerblahblah']['container222']['element5']['#default_value'], + 'element5 default value was not set properly.'); + } } diff --git a/tests/userDashboardTest.php b/tests/userDashboardTest.php index c00f65c..7d23363 100644 --- a/tests/userDashboardTest.php +++ b/tests/userDashboardTest.php @@ -23,7 +23,7 @@ public function testUserDashboard() { 'nid' => 1, 'class' => 'GFF3Importer', 'data' => $data, - 'status' => 'pending', + 'status' => 'approved', 'created_at' => time(), 'updated_at' => time(), ])->execute(); diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index 0a604a8..3499aea 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -85,6 +85,99 @@ public function testUserImporterForm() { } + /** + * Tests tripal_hq_user_importer_form(). + */ + public function testUserImporterEDITForm() { + global $user; + + // Create some elements to use in our mock. + $organism = factory('chado.organism')->create(); + $analysis = factory('chado.analysis')->create(); + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'approved', + 'created_at' => time(), + 'updated_at' => time(), + ]; + db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + $submission_id = db_select('tripal_hq_importer_submission', 't') + ->fields('t', ['id']) + ->condition('class', $fields['class']) + ->condition('created_at', $fields['created_at']) + ->execute()->fetchField(); + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + $submission_id, + 'view' + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + 'values' => [ + 'submission_id' => $submission_id, + 'analysis_id' => $analysis->analysis_id, + 'organism_id' => $organism->organism_id, + ], + ]; + db_update('tripal_hq_importer_submission') + ->fields(['data' => serialize($form_state)]) + ->condition('id', $submission_id) + ->execute(); + + // Now execute the form function with the mock form state. + $form = []; + $form = tripal_hq_user_importer_form($form, $form_state); + + // Now check key parts of the GFF3 Importer form are present. + $this->assertArrayHasKey('importer_class', $form, + "Form array did not specify the importer class."); + $this->assertEquals('GFF3Importer', $form['importer_class']['#value'], + "Form array importer class did not match what we expected."); + $this->assertArrayHasKey('file', $form, + "Form array did not specify the file."); + $this->assertArrayHasKey('analysis_id', $form, + "Form array did not specify the analysis."); + $this->assertArrayHasKey('organism_id', $form, + "Form array did not specify the organism."); + + // Try again with a non-existent submission_id. + $max_id = db_query('SELECT max(id) FROM {tripal_hq_importer_submission}')->fetchField(); + $form_state['values']['submission_id'] = $max_id + 100; + $form_state['build_info']['args'][1] = $max_id + 100; + db_update('tripal_hq_importer_submission') + ->fields(['data' => serialize($form_state)]) + ->condition('id', $submission_id) + ->execute(); + $form = tripal_hq_user_importer_form($form, $form_state); + $this->assertArrayHasKey('warning', $form); + + } + /** * Tests tripal_hq_user_importer_form_validate(). */ @@ -155,4 +248,83 @@ public function testUserImporterFormSubmit() { $this->assertEquals(serialize($form_state), $record->data, "The most recent GFF3Importer submission data, did not match our submitted form state."); } + + /** + * Tests tripal_hq_user_importer_form_submit(). + */ + public function testUserImporterEDITFormSubmit() { + global $user; + + // Create some elements to use in our mock. + $organism = factory('chado.organism')->create(); + $analysis = factory('chado.analysis')->create(); + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'approved', + 'created_at' => time(), + 'updated_at' => time(), + ]; + db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + $submission_id = db_select('tripal_hq_importer_submission', 't') + ->fields('t', ['id']) + ->condition('class', $fields['class']) + ->condition('created_at', $fields['created_at']) + ->execute()->fetchField(); + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + $submission_id, + 'view' + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + 'values' => [ + 'submission_id' => $submission_id, + 'analysis_id' => $analysis->analysis_id, + 'organism_id' => $organism->organism_id, + ], + ]; + db_update('tripal_hq_importer_submission') + ->fields(['data' => serialize($form_state)]) + ->condition('id', $submission_id) + ->execute(); + // Now execute the form function with the mock form state. + $form = []; + tripal_hq_user_importer_form_submit($form, $form_state); + + // Now check the submission was created. + $record = db_select('tripal_hq_importer_submission', 'T') + ->fields('T') + ->condition('class', 'GFF3Importer') + ->orderby('T.id', 'DESC') + ->execute()->fetchObject(); + $this->assertIsObject($record, + "We were unable to select the new submission."); + $this->assertEquals(serialize($form_state), $record->data, + "The most recent GFF3Importer submission data, did not match our submitted form state."); + } } diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index 4441c85..bd269af 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -30,5 +30,21 @@ function tripal_hq_imports_menu() { 'file' => 'includes/tripal_hq_imports_user_data.form.inc', ]; + $items['tripal_hq/bio_data/import-data/edit/%/%'] = [ + 'title' => 'Import data file', + 'page callback' => 'drupal_get_form', + 'page arguments' => ['tripal_hq_user_importer_form', 4, 5, 3], + 'access arguments' => ['access tripal_hq user'], + 'file' => 'includes/tripal_hq_imports_user_data.form.inc', + ]; + + $items['tripal_hq/bio_data/import-data/view/%/%'] = [ + 'title' => 'Import data file', + 'page callback' => 'drupal_get_form', + 'page arguments' => ['tripal_hq_user_importer_form', 4, 5, 3], + 'access arguments' => ['access tripal_hq user'], + 'file' => 'includes/tripal_hq_imports_user_data.form.inc', + ]; + return $items; } From cbe799c229feb9d936a38775f789738a86068b67 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Wed, 16 Oct 2019 09:50:56 -0600 Subject: [PATCH 16/36] Improve user submission with messages and redirect. --- includes/tripal_hq_imports_user_data.form.inc | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index 4aa49ca..b1114fe 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -136,7 +136,7 @@ function tripal_hq_user_importer_form($form, &$form_state) { /** * Validate the user submission. */ -function tripal_hq_user_importer_form_validate($form, $form_state) { +function tripal_hq_user_importer_form_validate($form, &$form_state) { $importer_class = $form_state['build_info']['args'][0]; tripal_hq_load_include_importer_class($importer_class); @@ -149,20 +149,58 @@ function tripal_hq_user_importer_form_validate($form, $form_state) { * Submit the submission. */ function tripal_hq_user_importer_form_submit($form, $form_state) { + + $sid = tripal_hq_imports_save_submission($form_state); + if ($sid) { + + // @todo currently we cant use tripal_hq_send_emails() since it retrieves + // the submission with the assumption of db table. + //tripal_hq_send_emails($sid, 'submit'); + + if (isset($form_state['values']['submission_id'])) { + drupal_set_message( + 'Submission created successfully. We will review your submission and get + back to you shortly.' + ); + } + else { + drupal_set_message( + 'Submission updated successfully. We will review your submission and get + back to you shortly.' + ); + } + + drupal_goto('tripal_hq/bio_data'); + } + else { + drupal_set_message( + 'Unable to save your submission. Please contact us to fix the issue.', + 'error' + ); + } +} + +/** + * Save the submission. + */ +function tripal_hq_imports_save_submission($form_state) { + global $user; $importer_class = $form_state['build_info']['args'][0]; tripal_hq_load_include_importer_class($importer_class); // If we have a submission id then update. if (isset($form_state['values']['submission_id'])) { + $sid = $form_state['values']['submission_id']; $data = serialize($form_state); - db_update('tripal_hq_importer_submission') + $success = db_update('tripal_hq_importer_submission') ->fields([ 'data' => $data, 'updated_at' => time(), ]) - ->condition('id', $form_state['values']['submission_id']) + ->condition('id', $sid) ->execute(); + } // Otherwise, insert. else { @@ -172,7 +210,7 @@ function tripal_hq_user_importer_form_submit($form, $form_state) { // Save it to the tripal_hq_imports_submission table! $data = serialize($form_state); - db_insert('tripal_hq_importer_submission') + $sid = db_insert('tripal_hq_importer_submission') ->fields([ 'uid' => $user->uid, 'nid' => $nid, @@ -182,4 +220,6 @@ function tripal_hq_user_importer_form_submit($form, $form_state) { 'created_at' => time(), ])->execute(); } + + return $sid; } From de837a2142392bc5246f45ac1db32be77ea7d70e Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Wed, 16 Oct 2019 15:20:42 -0600 Subject: [PATCH 17/36] Add Approve/Reject Support. --- includes/tripal_hq_imports.api.inc | 81 ++++++++ ...tripal_hq_imports_admin_dashboard.form.inc | 12 +- includes/tripal_hq_imports_approve.form.inc | 132 +++++++++++++ includes/tripal_hq_imports_user_data.form.inc | 9 +- tests/adminDashboardTest.php | 4 + tests/apiTest.php | 187 +++++++++++++++++- tests/moduleFileTest.php | 2 + tests/userDashboardTest.php | 3 + tests/userDataFormTest.php | 23 ++- tripal_hq_imports.module | 16 ++ 10 files changed, 457 insertions(+), 12 deletions(-) create mode 100644 includes/tripal_hq_imports_approve.form.inc diff --git a/includes/tripal_hq_imports.api.inc b/includes/tripal_hq_imports.api.inc index 7f9492d..3bbebaf 100644 --- a/includes/tripal_hq_imports.api.inc +++ b/includes/tripal_hq_imports.api.inc @@ -64,6 +64,87 @@ function tripal_hq_load_include_importer_class($class) { return $success; } +/** + * Returns the full submission. + * + * @param $submission_id + * The id of the submission you would like returned. + * @return + * An object describing the submission. + */ +function tripal_hq_imports_get_submission_by_id($submission_id) { + + $submission = db_select('tripal_hq_importer_submission', 't') + ->fields('t') + ->condition('id', $submission_id) + ->execute()->fetchObject(); + return $submission; +} + +/** + * Reject a specific submission. + * + * @param $submission + * An object describing the submission to be rejected. + * @return + * True if the rejection was successful, FALSE otherwise. + */ +function tripal_hq_imports_reject_submission($submission) { + + if (!is_object($submission)) { + return FALSE; + } + if ($submission->status !== 'pending') { + return FALSE; + } + + $submission_id = $submission->id; + return db_update('tripal_hq_importer_submission') + ->fields([ + 'status' => 'rejected', + 'updated_at' => time(), + ]) + ->condition('id', $submission_id) + ->execute(); +} + +/** + * Approve a specific submission. + * + * @param $submission + * An object describing the submission to be approved. + */ +function tripal_hq_imports_approve_submission($submission) { + + if (!is_object($submission)) { + return FALSE; + } + if ($submission->status !== 'pending') { + return FALSE; + } + $submission_id = $submission->id; + $importer_class = $submission->class; + + // Grab the form state from the submission. + $form_state = unserialize($submission->data); + + // Retrieve the form for the importer. + module_load_include('inc', 'tripal', 'includes/tripal.importer'); + $form = []; + $form = tripal_get_importer_form($form, $form_state, $importer_class); + + // Now submit the tripal importer which will create the job. + tripal_get_importer_form_submit($form, $form_state); + + db_update('tripal_hq_importer_submission') + ->fields([ + 'status' => 'approved', + 'updated_at' => time(), + ]) + ->condition('id', $submission_id) + ->execute(); +} + /** * Updates a given form field for edit (default value) or view (read-only) mode. * diff --git a/includes/tripal_hq_imports_admin_dashboard.form.inc b/includes/tripal_hq_imports_admin_dashboard.form.inc index 80125ed..fdda1c1 100644 --- a/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -104,6 +104,14 @@ $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $request->nid); } + // Approve or Deny. + $approve = $reject = ''; + if ($status == 'pending') { + $approve = l(t('Approve'), 'tripal_hq/admin/data-imports/approve/' . $request->id); + $reject = l(t('Reject'), 'tripal_hq/admin/data-imports/reject/' . $request->id); + } + + // Now compile the row. $rows[] = [ $user_row, @@ -112,8 +120,8 @@ ucwords($status), $created_at, $comments_link, - '', - '' + $approve, + $reject ]; } diff --git a/includes/tripal_hq_imports_approve.form.inc b/includes/tripal_hq_imports_approve.form.inc new file mode 100644 index 0000000..cf58830 --- /dev/null +++ b/includes/tripal_hq_imports_approve.form.inc @@ -0,0 +1,132 @@ +class; + + // Determine the label. + tripal_hq_load_include_importer_class($importer_class); + $importer_label = $importer_class::$name; + + // Title. + $submission_title = $importer_label . ' submitted on ' . date('M d Y', $submission->created_at); + + // Message based on approve/reject. + if ($op === 'reject') { + $message = "Are you sure you want to reject the " . l($submission_title, + '/tripal_hq/bio_data/import-data/edit/' . $importer_class . '/' . $sid) . "?"; + } + else { + $message = "Are you sure you want to approve the " . l($submission_title, + '/tripal_hq/bio_data/import-data/edit/' . $importer_class . '/' . $sid) . "?"; + } + + // Now for the form. + $form['submission_id'] = [ + '#type' => 'hidden', + '#value' => $sid, + ]; + + $form['operation'] = [ + '#type' => 'hidden', + '#value' => $op, + ]; + + $form['confirmation_message'] = [ + '#type' => 'item', + '#markup' => $message, + ]; + + $form['submit'] = [ + '#type' => 'submit', + '#value' => $op === 'reject' ? 'Reject Submission' : 'Approve and Import Submission', + ]; + + return $form; +} + +/** + * Validate the request. + * + * @param $form + * @param $form_state + */ +function tripal_hq_imports_admin_control_form_validate($form, &$form_state) { + $values = $form_state['values']; + $op = isset($values['operation']) ? $values['operation'] : ''; + $sid = isset($values['submission_id']) ? $values['submission_id'] : ''; + + if (empty($sid) || empty($op)) { + form_set_error('submission_id', + 'Please provide a valid submission id and a valid operation type.'); + } +} + +/** + * Approve and publish or reject a submission. + * + * @param $form + * @param $form_state + */ +function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { + $values = $form_state['values']; + $op = $values['operation']; + $sid = $values['submission_id']; + + $submission = tripal_hq_imports_get_submission_by_id($sid); + + if ($op === 'reject') { + // @todo implement api function. + $rejected = tripal_hq_imports_reject_submission($submission); + if ($rejected) { + + + // @todo currently we cant use tripal_hq_send_emails() since it retrieves + // the submission with the assumption of db table. + //tripal_hq_send_emails($sid, 'reject'); + + drupal_set_message('Submission rejected successfully'); + drupal_goto('tripal_hq/admin'); + return TRUE; + } + else { + drupal_set_message('Unable to reject submission', 'error'); + return FALSE; + } + + } + + tripal_hq_imports_approve_submission($submission); + + // @todo currently we cant use tripal_hq_send_emails() since it retrieves + // the submission with the assumption of db table. + //tripal_hq_send_emails($sid, 'approve'); + + if (!isset($form_state['no_redirect'])) { + drupal_goto('tripal_hq/admin'); + } + +} diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index b1114fe..eddc86b 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -81,10 +81,7 @@ function tripal_hq_user_importer_form($form, &$form_state) { $submission_id = $form_state['build_info']['args'][1]; $op = $form_state['build_info']['args'][2]; - $submission = db_select('tripal_hq_importer_submission', 't') - ->fields('t') - ->condition('id', $submission_id) - ->execute()->fetchObject(); + $submission = tripal_hq_imports_get_submission_by_id($submission_id); if (is_object($submission)) { $importer_form_state = unserialize($submission->data); $status = $submission->status; @@ -170,7 +167,9 @@ function tripal_hq_user_importer_form_submit($form, $form_state) { ); } - drupal_goto('tripal_hq/bio_data'); + if (!isset($form_state['no_redirect'])) { + drupal_goto('tripal_hq/bio_data'); + } } else { drupal_set_message( diff --git a/tests/adminDashboardTest.php b/tests/adminDashboardTest.php index e0be3a0..9a79a42 100644 --- a/tests/adminDashboardTest.php +++ b/tests/adminDashboardTest.php @@ -10,6 +10,9 @@ class adminDashboardTest extends TripalTestCase { /** * Tests tripal_hq_imports_tripal_hq_user_dashboard_alter(). + * + * @group dashboard + * @group admin */ public function testAdminDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_admin_dashboard.form'); @@ -48,4 +51,5 @@ public function testAdminDashboard() { "Our submission requests table is not in the page."); } + } diff --git a/tests/apiTest.php b/tests/apiTest.php index 226594f..051ea80 100644 --- a/tests/apiTest.php +++ b/tests/apiTest.php @@ -3,13 +3,16 @@ use StatonLab\TripalTestSuite\DBTransaction; use StatonLab\TripalTestSuite\TripalTestCase; +use Faker\Factory; class apiTest extends TripalTestCase { // Uncomment to auto start and rollback db transactions per test method. - // use DBTransaction; + use DBTransaction; /** * Tests tripal_hg_get_importers(). + * + * @group api */ public function testGetImporters() { @@ -20,6 +23,8 @@ public function testGetImporters() { /** * Tests tripal_hq_load_include_importer_class(). + * + * @group api */ public function testLoadImporterClass() { @@ -32,8 +37,186 @@ public function testLoadImporterClass() { $this->assertFalse($success); } + /** + * Tests tripal_hq_imports_get_submission_by_id(). + * + * @group api + */ + public function testGetSubmissionByID() { + global $user; + + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'approved', + 'created_at' => time(), + 'updated_at' => time(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + + $submission = tripal_hq_imports_get_submission_by_id($sid); + $this->assertIsObject($submission, + "Uanble to retrieve submission with tripal_hq_imports_get_submission_by_id()."); + $this->assertEquals($sid, $submission->id, + "We retrieved a submission but it is not the one we asked for?"); + $this->assertEquals($fields['class'], $submission->class, + "We retrieved a submission but it is not the one we asked for?"); + + } + + /** + * Tests tripal_hq_imports_reject_submission(). + */ + public function testRejectSubmission() { + + // Test an invalid submission. + $success = tripal_hq_imports_reject_submission([]); + $this->assertFalse($success, + "Must pass an object to tripal_hq_imports_reject_submission()."); + + // Try rejecting a non-pending submission. + $faker = Factory::create(); + $submission = [ + 'uid' => 1, + 'id' => $faker->randomDigit(), + 'nid' => $faker->randomDigit(), + 'class' => 'GFF3Importer', + 'data' => serialize([1,2,3]), + 'status' => 'approved', + 'created_at' => $faker->unixTime(), + 'updated_at' => $faker->unixTime(), + ]; + $submission = (object) $submission; + $success = tripal_hq_imports_reject_submission($submission); + $this->assertFalse($success, + "Must be pending to reject a submission."); + + // Try really rejecting something ;-p. + $submission = [ + 'uid' => 1, + 'nid' => $faker->randomDigit(), + 'class' => 'GFF3Importer', + 'data' => serialize([1,2,3]), + 'status' => 'pending', + 'created_at' => $faker->unixTime(), + 'updated_at' => $faker->unixTime(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($submission)->execute(); + $submission = tripal_hq_imports_get_submission_by_id($sid); + $success = tripal_hq_imports_reject_submission($submission); + + $retrieved_submission = tripal_hq_imports_get_submission_by_id($sid); + $this->assertEquals(1, $success, + "Unable to reject a valid submission."); + $this->assertEquals('rejected', $retrieved_submission->status, + "The retrieved submission is not showing rejected."); + + } + + /** + * Tests tripal_hq_imports_approve_submission(). + */ + public function testApproveSubmission() { + + // Test an invalid submission. + $success = tripal_hq_imports_approve_submission([]); + $this->assertFalse($success, + "Must pass an object to tripal_hq_imports_reject_submission()."); + + // Try rejecting a non-pending submission. + $faker = Factory::create(); + $submission = [ + 'uid' => 1, + 'id' => $faker->randomDigit(), + 'nid' => $faker->randomDigit(), + 'class' => 'GFF3Importer', + 'data' => serialize([1,2,3]), + 'status' => 'approved', + 'created_at' => $faker->unixTime(), + 'updated_at' => $faker->unixTime(), + ]; + $submission = (object) $submission; + $success = tripal_hq_imports_approve_submission($submission); + $this->assertFalse($success, + "Must be pending to reject a submission."); + + // Try really approving something ;-p. + // -- Firsst create a submission. + $submission = [ + 'uid' => 1, + 'nid' => $faker->randomDigit(), + 'class' => 'GFF3Importer', + 'data' => serialize([1,2,3]), + 'status' => 'pending', + 'created_at' => $faker->unixTime(), + 'updated_at' => $faker->unixTime(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($submission)->execute(); + + // -- Mock the form state specifying the GFF3 importer. + $analysis = factory('chado.analysis')->create(); + $organism = factory('chado.organism')->create(); + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + $sid, + 'view' + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + 'values' => [ + 'submission_id' => $sid, + 'analysis_id' => $analysis->analysis_id, + 'organism_id' => $organism->organism_id, + 'importer_class' => 'GFF3Importer', + ], + ]; + + // -- Update the submission with the new form state. + db_update('tripal_hq_importer_submission') + ->fields(['data' => serialize($form_state)]) + ->condition('id', $sid) + ->execute(); + + // -- Finally retrieve and approve it! + $submission = tripal_hq_imports_get_submission_by_id($sid); + $success = tripal_hq_imports_approve_submission($submission); + + $retrieved_submission = tripal_hq_imports_get_submission_by_id($sid); + $this->assertNotFalse($success, + "Unable to approve a valid submission."); + $this->assertEquals('approved', $retrieved_submission->status, + "The retrieved submission is not showing approved."); + + } + /** * Tests tripal_hq_editview_form_field(). + * + * @group api */ public function testEditViewFormField() { $form = [ @@ -92,7 +275,7 @@ public function testEditViewFormField() { 'element3 default value was not set properly.'); $this->assertEquals(1, $form['containerblahblah']['element4']['#default_value'], 'element4 default value was not set properly.'); - $this->assertEquals(4, $form['containerblahblah']['container222']['element5']['#default_value'], + $this->assertEquals(4, $form['containerblahblah']['container222']['element5']['#default_value'], 'element5 default value was not set properly.'); } } diff --git a/tests/moduleFileTest.php b/tests/moduleFileTest.php index ae7ca7f..e220da2 100644 --- a/tests/moduleFileTest.php +++ b/tests/moduleFileTest.php @@ -10,6 +10,8 @@ class moduleFileTest extends TripalTestCase { /** * Tests hook_menu(). Specifically, are all the required keys set. + * + * @group core-hook */ public function testHookMenu() { $menu_items = tripal_hq_imports_menu(); diff --git a/tests/userDashboardTest.php b/tests/userDashboardTest.php index 7d23363..4d2c02d 100644 --- a/tests/userDashboardTest.php +++ b/tests/userDashboardTest.php @@ -10,6 +10,9 @@ class userDashboardTest extends TripalTestCase { /** * Tests tripal_hq_imports_tripal_hq_user_dashboard_alter(). + * + * @group dashboard + * @group user */ public function testUserDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_user_dashboard.form'); diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index 3499aea..3ee9f67 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -11,9 +11,9 @@ class userDataFormTest extends TripalTestCase { use DBTransaction; /** - * Basic test example. - * Tests must begin with the word "test". - * See https://phpunit.readthedocs.io/en/latest/ for more information. + * List importers. + * + * @group user */ public function testListImportersPage() { @@ -39,6 +39,9 @@ public function testListImportersPage() { /** * Tests tripal_hq_user_importer_form(). + * + * @group submit-form + * @group user */ public function testUserImporterForm() { @@ -87,6 +90,9 @@ public function testUserImporterForm() { /** * Tests tripal_hq_user_importer_form(). + * + * @group submit-form + * @group user */ public function testUserImporterEDITForm() { global $user; @@ -180,6 +186,9 @@ public function testUserImporterEDITForm() { /** * Tests tripal_hq_user_importer_form_validate(). + * + * @group submit-form + * @group user */ public function testUserImporterFormValidate() { @@ -213,6 +222,9 @@ public function testUserImporterFormValidate() { /** * Tests tripal_hq_user_importer_form_submit(). + * + * @group submit-form + * @group user */ public function testUserImporterFormSubmit() { @@ -228,6 +240,7 @@ public function testUserImporterFormSubmit() { ], ], 'rebuild' => FALSE, + 'no_redirect' => TRUE, 'redirect' => NULL, 'values' => [ 'importer_class' => 'GFF3Importer', @@ -251,6 +264,9 @@ public function testUserImporterFormSubmit() { /** * Tests tripal_hq_user_importer_form_submit(). + * + * @group submit-form + * @group user */ public function testUserImporterEDITFormSubmit() { global $user; @@ -292,6 +308,7 @@ public function testUserImporterEDITFormSubmit() { 'rebuild' => FALSE, 'rebuild_info' => [], 'redirect' => NULL, + 'no_redirect' => TRUE, 'temporary' => [], 'submitted' => FALSE, 'executed' => FALSE, diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index bd269af..bbdc1ca 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -46,5 +46,21 @@ function tripal_hq_imports_menu() { 'file' => 'includes/tripal_hq_imports_user_data.form.inc', ]; + $items['tripal_hq/admin/data-imports/approve/%'] = [ + 'title' => 'Tripal HQ Data Import Approve', + 'page callback' => 'drupal_get_form', + 'file' => 'includes/tripal_hq_imports_approve.form.inc', + 'access arguments' => ['access tripal_hq admin'], + 'page arguments' => ['tripal_hq_imports_admin_control_form', 'approve', 4], + ]; + + $items['tripal_hq/admin/data-imports/reject/%'] = [ + 'title' => 'Tripal HQ Data Import Reject', + 'page callback' => 'drupal_get_form', + 'file' => 'includes/tripal_hq_imports_approve.form.inc', + 'access arguments' => ['access tripal_hq admin'], + 'page arguments' => ['tripal_hq_imports_admin_control_form', 'reject', 4], + ]; + return $items; } From 92914f6116759ed279283f89f2678427c2e9b85f Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Wed, 16 Oct 2019 15:32:19 -0600 Subject: [PATCH 18/36] Update README.md --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 48162b6..db7dae9 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,30 @@ Tripal HQ provides a user-contributed content control center and administrative This module is currently under active development. **It is not ready for use.** If you are interested in this module, please star it so we know there is need. Thank you! +- [x] Allow users to submit Tripal Importers for consideration/approval. + - [x] Re-use the TripalImporter form to ensure all metadata is collected. + - [x] Support all TripalImporters including custom ones. +- [x] Display submissions on the Tripal HQ user adn administration dashboards. +- [x] Allow administrators to approve/reject submissions + - [x] Ensure TripalImporter jobs are submitted on approval +- [ ] Support emails in the same manner as Tripal HQ +- [ ] Rich permissions for controlling which users have access to specific importers +- [ ] Rich permissions for deputizing users to approve/reject data imports as you can with Tripal HQ. + ## Current Features -Only current features are listed below. This does not reflect all features which will be available once development is complete but is meant to provide you with an idea of development progress. +Only current features are listed below. This does not reflect all features which will be available once development is complete. -### Adds Data Import support to Tripal HQ user Dashboard +### Adds Data Import support to Tripal HQ User Dashboard Users with permission to submit data through Tripal HQ, now have access to an "Import data file" action link on their dashboard. Once clicked, users are presented with the full list of Tripal Importers (including custom ones) which allows them to pick their data file type, enter the required metadata and upload the file. At this point the submission is put into a holding area waiting for administrator approval. -![User Dashboard Screenshot](https://user-images.githubusercontent.com/1566301/66760602-afc8ff80-ee5f-11e9-9934-b8c065573f83.png). +![User Dashboard Screenshot](https://user-images.githubusercontent.com/1566301/66960196-38df6280-f029-11e9-8154-259031bbaa7a.png). Their submissions will be summarized on the same dashboard as their Tripal Content for a unified experience! ### Administration -- Data file import forms are created automatically based on the TripalImporter::form() and TripalImporter:validate() is run on submission to ensure meta data matches standards. +- Data file import forms are created automatically based on the TripalImporter::form() and TripalImporter:validate() is run on submission to ensure meta data matches standards. Administrators of this module do not need to create forms! +- Tripal HQ administration dashboard support approve/reject for Tripal Importer submissions. +Screen Shot 2019-10-16 at 3 25 18 PM + From bc9265b99a4d4d13e98a41bbd6d2648951865d26 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Thu, 17 Oct 2019 12:17:30 -0600 Subject: [PATCH 19/36] Support per-importer permissions. --- includes/tripal_hq_imports_user_data.form.inc | 15 ++++++++-- tripal_hq_imports.module | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index eddc86b..23d5f74 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -31,11 +31,13 @@ $importer_machine_name = $importer_class::$machine_name; $importer_description = $importer_class::$description; - // @todo Ensure the user has permission to propose that file type. - // Currently we are just restricting anonymous users. + // Ensure the user has permission to propose that file type. if ($user->uid === 0) { continue; } + if (!user_access("propose $importer_label")) { + continue; + } $user_has_stuff = 1; @@ -75,6 +77,7 @@ function tripal_hq_user_importer_form($form, &$form_state) { $importer_class = $form_state['build_info']['args'][0]; $success = tripal_hq_load_include_importer_class($importer_class); + $importer_label = $importer_class::$name; // Check if we should allow editing/viewing of an existing submission. if (isset($form_state['build_info']['args'][1])) { @@ -103,6 +106,14 @@ function tripal_hq_user_importer_form($form, &$form_state) { $importer_form_state = $form_state; } + // Ensure the user has permission to propose that file type. + if ($user->uid === 0) { + drupal_not_found(); + } + if (!user_access("propose $importer_label")) { + drupal_not_found(); + } + drupal_set_title($importer_class::$name); // Retrieve the form for the importer. diff --git a/tripal_hq_imports.module b/tripal_hq_imports.module index bbdc1ca..763d9b7 100644 --- a/tripal_hq_imports.module +++ b/tripal_hq_imports.module @@ -64,3 +64,31 @@ function tripal_hq_imports_menu() { return $items; } + +/** + * Implements hook_permission(). + */ +function tripal_hq_imports_permission() { + $permissions = []; + + // Add permissions for each importer. + $importers = tripal_hq_get_importers(); + foreach ($importers as $importer_class) { + + // Pull some important info out of the class. + $importer_label = $importer_class::$name; + $importer_machine_name = $importer_class::$machine_name; + $importer_description = $importer_class::$description; + + $permissions['propose ' . $importer_label] = [ + 'title' => t( + '%label: Propose Tripal HQ Data File', ['%label' => $importer_label] + ), + 'description' => t( + 'Allow the user to propose %label data files for import into your site. No data will be imported until an administrator approves the submission.', ['%label' => $importer_label] + ), + ]; + } + + return $permissions; +} From 65653320ac06265d804e3d9c0fe400ff012e9d1f Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Thu, 17 Oct 2019 15:32:09 -0600 Subject: [PATCH 20/36] Improve tests. --- includes/tripal_hq_imports_approve.form.inc | 11 +- includes/tripal_hq_imports_user_data.form.inc | 16 +- tests/approveRejectTest.php | 250 ++++++++++++++++++ tests/moduleFileTest.php | 14 + tests/userDataFormTest.php | 44 +++ 5 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 tests/approveRejectTest.php diff --git a/includes/tripal_hq_imports_approve.form.inc b/includes/tripal_hq_imports_approve.form.inc index cf58830..db55979 100644 --- a/includes/tripal_hq_imports_approve.form.inc +++ b/includes/tripal_hq_imports_approve.form.inc @@ -21,8 +21,11 @@ function tripal_hq_imports_admin_control_form($form, &$form_state, $op, $sid) { $submission = tripal_hq_imports_get_submission_by_id($sid); if (empty($submission)) { - drupal_not_found(); - return []; + $form['warning'] = [ + '#type' => 'markup', + '#markup' => '
We were unable to find the submission. Please contact an administrator.
', + ]; + return $form; } $importer_class = $submission->class; @@ -109,7 +112,9 @@ function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { //tripal_hq_send_emails($sid, 'reject'); drupal_set_message('Submission rejected successfully'); - drupal_goto('tripal_hq/admin'); + if (!isset($form_state['no_redirect'])) { + drupal_goto('tripal_hq/admin'); + } return TRUE; } else { diff --git a/includes/tripal_hq_imports_user_data.form.inc b/includes/tripal_hq_imports_user_data.form.inc index 23d5f74..fc5d547 100644 --- a/includes/tripal_hq_imports_user_data.form.inc +++ b/includes/tripal_hq_imports_user_data.form.inc @@ -32,10 +32,7 @@ $importer_description = $importer_class::$description; // Ensure the user has permission to propose that file type. - if ($user->uid === 0) { - continue; - } - if (!user_access("propose $importer_label")) { + if (($user->uid === 0) OR (!user_access("propose $importer_label"))) { continue; } $user_has_stuff = 1; @@ -107,11 +104,12 @@ function tripal_hq_user_importer_form($form, &$form_state) { } // Ensure the user has permission to propose that file type. - if ($user->uid === 0) { - drupal_not_found(); - } - if (!user_access("propose $importer_label")) { - drupal_not_found(); + if (($user->uid === 0) OR (!user_access("propose $importer_label"))) { + $form['warning'] = [ + '#type' => 'markup', + '#markup' => '
You do not have access to propose data files of this type. Please contact an administrator.
', + ]; + return $form; } drupal_set_title($importer_class::$name); diff --git a/tests/approveRejectTest.php b/tests/approveRejectTest.php new file mode 100644 index 0000000..bddfff3 --- /dev/null +++ b/tests/approveRejectTest.php @@ -0,0 +1,250 @@ +actingAs(1); + global $user; + + // Mock the args. + // -- form state. + $form_state = [ + 'build_info' => [ + 'form_id' => 'tripal_hq_imports_admin_control_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + ]; + // -- form. + $form = []; + // -- operation. + $op = 'approve'; + // -- submission. + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + + // Then execute the form function. + $form = tripal_hq_imports_admin_control_form($form, $form_state, $op, $sid); + + $this->assertIsArray($form, 'Unable to return the form array.'); + $this->assertArrayHasKey('submission_id', $form, + "The returned form did not have the submission id available?"); + $this->assertEquals($sid, $form['submission_id']['#value'], + "The submission id was not corrent???"); + $this->assertStringContainsString('approve', $form['confirmation_message']['#markup'], + "The confirmation message should contain the word approve."); + + // Now do the same for reject. + $op = 'reject'; + $form = tripal_hq_imports_admin_control_form($form, $form_state, $op, $sid); + + $this->assertIsArray($form, 'Unable to return the form array.'); + $this->assertArrayHasKey('submission_id', $form, + "The returned form did not have the submission id available?"); + $this->assertEquals($sid, $form['submission_id']['#value'], + "The submission id was not corrent???"); + $this->assertStringContainsString('reject', $form['confirmation_message']['#markup'], + "The confirmation message should contain the word reject."); + + // Try with a non-existent submission. + $max_id = db_query('SELECT max(id) FROM {tripal_hq_importer_submission}') + ->fetchField(); + $form = tripal_hq_imports_admin_control_form($form, $form_state, $op, $max_id+100); + $this->assertIsArray($form, 'Unable to return the form array.'); + $this->assertArrayHasKey('warning', $form, + "The form should show a warning to the user that the submission could not be found."); + + } + + /** + * Tests tripal_hq_imports_admin_control_form_validate(). + */ + public function testAdminControlFormValidate() { + $this->actingAs(1); + global $user; + + // Mock the args. + // -- form state. + $form_state = [ + 'build_info' => [ + 'form_id' => 'tripal_hq_imports_admin_control_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + ]; + // -- form. + $form = []; + // -- operation. + $op = 'approve'; + // -- submission. + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + // -- Finally update form state. + $form_state['build_info']['args'][0] = $sid; + $form_state['values'] = [ + 'operation' => 'approve', + 'submission_id' => $sid, + ]; + + // Now test a good submission. + tripal_hq_imports_admin_control_form_validate($form, $form_state); + + // The form state we provided did not have all the expected values + // Therefore, there should be errors! + $errors = form_get_errors(); + $this->assertEmpty($errors, + "The form validate should not return errors for a valid confirmation."); + + // Now remove the values and watch it fail. + $form_state['values'] = []; + tripal_hq_imports_admin_control_form_validate($form, $form_state); + $errors = form_get_errors(); + $this->assertNotEmpty($errors, + "The form validate did not return errors even though we did not submit all values."); + } + + /** + * Tests tripal_hq_imports_admin_control_form_submit(). + */ + public function testAdminControlFormSubmit() { + $this->actingAs(1); + global $user; + + // Mock the args. + // -- form state. + $form_state = [ + 'build_info' => [ + 'form_id' => 'tripal_hq_imports_admin_control_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'no_redirect' => TRUE, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + ]; + // -- form. + $form = []; + // -- operation. + $op = 'approve'; + // -- submission. + $data = serialize([1,2,3]); + $fields = [ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + 'updated_at' => time(), + ]; + $sid = db_insert('tripal_hq_importer_submission') + ->fields($fields)->execute(); + // -- Finally update form state. + $form_state['build_info']['args'][0] = $sid; + $form_state['values'] = [ + 'operation' => 'approve', + 'submission_id' => $sid, + 'importer_class' => 'GFF3Importer', + ]; + db_update('tripal_hq_importer_submission')->fields([ + 'data' => serialize($form_state), + 'updated_at' => time(), + ])->condition('id', $sid)->execute(); + + // Now test a good approval. + $success = tripal_hq_imports_admin_control_form_submit($form, $form_state); + $this->assertNotFalse($success, + "Submission failed when it shouldn't have."); + + // And test a bad rejection (submission is not pending). + $form_state['values']['operation'] = 'reject'; + db_update('tripal_hq_importer_submission')->fields([ + 'data' => serialize($form_state), + 'updated_at' => time(), + ])->condition('id', $sid)->execute(); + $success = tripal_hq_imports_admin_control_form_submit($form, $form_state); + $this->assertFalse($success, + "Submission should have failed since it is no longer pending."); + + // And a good rejection. + db_update('tripal_hq_importer_submission')->fields([ + 'status' => 'pending', + 'updated_at' => time(), + ])->condition('id', $sid)->execute(); + $success = tripal_hq_imports_admin_control_form_submit($form, $form_state); + $this->assertNotFalse($success, + "Submission failed when it shouldn't have."); + + } +} diff --git a/tests/moduleFileTest.php b/tests/moduleFileTest.php index e220da2..107a6f5 100644 --- a/tests/moduleFileTest.php +++ b/tests/moduleFileTest.php @@ -27,4 +27,18 @@ public function testHookMenu() { "$path menu item access arguments must be an array."); } } + + /** + * Tests hook_permission(). Specifically, are all the required keys set. + * + * @group core-hook + */ + public function testHookPerm() { + $permissions = tripal_hq_imports_permission(); + $this->assertIsArray($permissions); + foreach($permissions as $path => $item) { + $this->assertArrayHasKey('title', $item, + "$path permission item is missing a title."); + } + } } diff --git a/tests/userDataFormTest.php b/tests/userDataFormTest.php index 3ee9f67..8ee9d5f 100644 --- a/tests/userDataFormTest.php +++ b/tests/userDataFormTest.php @@ -44,6 +44,7 @@ public function testListImportersPage() { * @group user */ public function testUserImporterForm() { + $this->actingAs(1); // Mock the form state specifying the GFF3 importer. $form_state = [ @@ -95,6 +96,7 @@ public function testUserImporterForm() { * @group user */ public function testUserImporterEDITForm() { + $this->actingAs(1); global $user; // Create some elements to use in our mock. @@ -344,4 +346,46 @@ public function testUserImporterEDITFormSubmit() { $this->assertEquals(serialize($form_state), $record->data, "The most recent GFF3Importer submission data, did not match our submitted form state."); } + + /** + * Check that anonymous users do not have access to the forms. + */ + public function testFormPermissions() { + // Ensure we are anonymous. + $this->actingAs(0); + + // Mock the form state specifying the GFF3 importer. + $form_state = [ + 'build_info' => [ + 'args' => [ + 'GFF3Importer', + ], + 'form_id' => 'tripal_hq_user_importer_form', + 'files' => [ + 'menu' => 'sites/all/modules/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc', + ], + ], + 'rebuild' => FALSE, + 'rebuild_info' => [], + 'redirect' => NULL, + 'temporary' => [], + 'submitted' => FALSE, + 'executed' => FALSE, + 'programmed' => FALSE, + 'programmed_bypass_access_check' => TRUE, + 'cache' => FALSE, + 'method' => 'post', + 'groups' => [], + 'buttons' => [], + 'input' => [], + ]; + // Now execute the form function with the mock form state. + $form = []; + $form = tripal_hq_user_importer_form($form, $form_state); + $this->assertArrayHasKey('warning', $form, + "Form should have contained a warning but has not."); + $this->assertStringContainsString('do not have access', $form['warning']['#markup'], + "Warning should specify that the user does not have access"); + + } } From 2a9be85c47ab6fcfd6be9cd6ab7b54f71edb5fb9 Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Thu, 17 Oct 2019 16:15:41 -0600 Subject: [PATCH 21/36] Update README.md --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index db7dae9..ffae394 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,19 @@ Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. Tripal HQ Imports extends [Tripal HQ](https://github.com/statonlab/tripal_hq) to support TripalImporters. Specifically, this allows users to submit Tripal Importers, administrators to review the submission and data is only inserted into Chado once the administrator approves the submission. -## UNDER DEVELOPMENT +## Features -This module is currently under active development. **It is not ready for use.** If you are interested in this module, please star it so we know there is need. Thank you! + - Users submit data files using the existing Tripal Importer forms -no extra forms! + - Users and administrators can access data file submissions on the same dashboard as Tripal HQ content submissions + - All TripalImporters should be supported! (excludes multi-page forms; e.g AnalyzedPhenotypes) + - You can specify which importers should be available to users through native Drupal Permissions. -- [x] Allow users to submit Tripal Importers for consideration/approval. - - [x] Re-use the TripalImporter form to ensure all metadata is collected. - - [x] Support all TripalImporters including custom ones. -- [x] Display submissions on the Tripal HQ user adn administration dashboards. -- [x] Allow administrators to approve/reject submissions - - [x] Ensure TripalImporter jobs are submitted on approval -- [ ] Support emails in the same manner as Tripal HQ -- [ ] Rich permissions for controlling which users have access to specific importers -- [ ] Rich permissions for deputizing users to approve/reject data imports as you can with Tripal HQ. +## Dependencies -## Current Features +1. [Tripal Core](https://github.com/tripal/tripal) +2. [Tripal HQ](https://github.com/statonlab/tripal_hq) -Only current features are listed below. This does not reflect all features which will be available once development is complete. +## Usage ### Adds Data Import support to Tripal HQ User Dashboard @@ -39,3 +35,8 @@ Their submissions will be summarized on the same dashboard as their Tripal Conte - Tripal HQ administration dashboard support approve/reject for Tripal Importer submissions. Screen Shot 2019-10-16 at 3 25 18 PM +## Future Development + +- Support emails in the same manner as Tripal HQ +- Rich permissions for controlling which users have access to specific importers +- Rich permissions for deputizing users to approve/reject data imports as you can with Tripal HQ. From e778d7d53ee55757ee3a4fea6ed8f766ff27dbc1 Mon Sep 17 00:00:00 2001 From: Lacey-Anne Sanderson Date: Thu, 17 Oct 2019 16:35:14 -0600 Subject: [PATCH 22/36] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ffae394..d5bc357 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![Tripal Dependency](https://img.shields.io/badge/tripal-%3E=3.0-brightgreen) ![Module is Generic](https://img.shields.io/badge/generic-confirmed-brightgreen) +![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/UofS-Pulse-Binfo/tripal_hq_imports?include_prereleases) [![Build Status](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports.svg?branch=master)](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports) [![Test Coverage](https://api.codeclimate.com/v1/badges/598206b15a4410687d38/test_coverage)](https://codeclimate.com/github/UofS-Pulse-Binfo/tripal_hq_imports/test_coverage) From c022197e067936a26da63a6f2c5bbb7222f702a2 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Wed, 30 Oct 2019 18:23:37 -0600 Subject: [PATCH 23/36] Move all Headquarters modules into separate package. --- tripal_hq.info | 4 ++-- tripal_hq_permissions/tripal_hq_permissions.info | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tripal_hq.info b/tripal_hq.info index 55a751d..7962e17 100644 --- a/tripal_hq.info +++ b/tripal_hq.info @@ -1,8 +1,8 @@ name = Tripal Headquarters -description = coming soon +description = Provides a user and administrative dashboard for Chado content creation. core = 7.x version = 7.x-1.0-rc1 project = tripal_hq -package = Tripal Extensions +package = Tripal Extensions: Tripal Headquarters dependencies[] = tripal dependencies[] = tripal_hq_permissions diff --git a/tripal_hq_permissions/tripal_hq_permissions.info b/tripal_hq_permissions/tripal_hq_permissions.info index 9d762af..fd25584 100644 --- a/tripal_hq_permissions/tripal_hq_permissions.info +++ b/tripal_hq_permissions/tripal_hq_permissions.info @@ -3,6 +3,6 @@ description = Chado-level permissions. core = 7.x version = 7.x-1.0-rc1 project = tripal_hq -package = Tripal Extensions +package = Tripal Extensions: Tripal Headquarters dependencies[] = tripal dependencies[] = tripal_hq From 5b883548b14bbc064c8d89e9bd599f6144bea29a Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Wed, 30 Oct 2019 20:17:52 -0600 Subject: [PATCH 24/36] Move Tripal HQ Import tests. --- composer.lock | 526 +++++++++++------- .../tripal_hq_imports}/adminDashboardTest.php | 1 + .../tripal_hq_imports}/apiTest.php | 1 + .../tripal_hq_imports}/approveRejectTest.php | 8 + .../tripal_hq_imports}/moduleFileTest.php | 2 + .../tripal_hq_imports}/userDashboardTest.php | 1 + .../tripal_hq_imports}/userDataFormTest.php | 1 + tripal_hq_imports/tests/DataFactory.php | 196 ------- .../examples/DevSeedSeeder.php | 383 ------------- .../examples/UsersTableSeeder.php | 28 - tripal_hq_imports/tests/bootstrap.php | 4 - tripal_hq_imports/tests/example.env | 3 - 12 files changed, 332 insertions(+), 822 deletions(-) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/adminDashboardTest.php (98%) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/apiTest.php (99%) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/approveRejectTest.php (98%) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/moduleFileTest.php (96%) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/userDashboardTest.php (98%) rename {tripal_hq_imports/tests => tests/tripal_hq_imports}/userDataFormTest.php (99%) delete mode 100644 tripal_hq_imports/tests/DataFactory.php delete mode 100644 tripal_hq_imports/tests/DatabaseSeeders/examples/DevSeedSeeder.php delete mode 100644 tripal_hq_imports/tests/DatabaseSeeders/examples/UsersTableSeeder.php delete mode 100644 tripal_hq_imports/tests/bootstrap.php delete mode 100644 tripal_hq_imports/tests/example.env diff --git a/composer.lock b/composer.lock index 841fe71..5265370 100644 --- a/composer.lock +++ b/composer.lock @@ -9,27 +9,29 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -54,12 +56,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "fzaninotto/faker", @@ -113,27 +115,28 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "6.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "0895c932405407fd3a7368b6910c09a24d26db11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11", + "reference": "0895c932405407fd3a7368b6910c09a24d26db11", "shasum": "" }, "require": { + "ext-json": "*", "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", + "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5" }, "require-dev": { "ext-curl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "psr/log": "^1.1" }, "suggest": { "psr/log": "Required for using the Log middleware" @@ -145,12 +148,12 @@ } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" - } + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -174,7 +177,7 @@ "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "time": "2019-10-23T15:58:00+00:00" }, { "name": "guzzlehttp/promises", @@ -229,33 +232,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.5.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9f83dded91781a01c63574e387eaa769be769115" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", - "reference": "9f83dded91781a01c63574e387eaa769be769115", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-zlib": "*", "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -292,20 +299,20 @@ "uri", "url" ], - "time": "2018-12-04T20:46:45+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "shasum": "" }, "require": { @@ -340,7 +347,7 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" + "time": "2019-08-09T12:45:53+00:00" }, { "name": "phar-io/manifest", @@ -446,35 +453,33 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "~6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -496,30 +501,30 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", "webmozart/assert": "^1.0" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", + "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", "phpunit/phpunit": "^6.4" }, @@ -547,41 +552,40 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2019-09-12T14:27:41+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -594,26 +598,27 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", + "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, @@ -628,8 +633,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -657,7 +662,7 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-10-03T11:07:50+00:00" }, { "name": "phpunit/php-code-coverage", @@ -815,16 +820,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -836,7 +841,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -860,20 +865,20 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { @@ -886,7 +891,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -909,20 +914,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.2", + "version": "7.5.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7c89093bd00f7d5ddf0ab81dee04f801416b4944" + "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7c89093bd00f7d5ddf0ab81dee04f801416b4944", - "reference": "7c89093bd00f7d5ddf0ab81dee04f801416b4944", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a", + "reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a", "shasum": "" }, "require": { @@ -940,7 +945,7 @@ "phpunit/php-code-coverage": "^6.0.7", "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", + "phpunit/php-timer": "^2.1", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", "sebastian/environment": "^4.0", @@ -993,7 +998,56 @@ "testing", "xunit" ], - "time": "2019-01-15T08:19:08+00:00" + "time": "2019-10-28T10:37:36+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/http-message", @@ -1047,24 +1101,24 @@ }, { "name": "ralouphie/getallheaders", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~3.7.0", - "satooshi/php-coveralls": ">=1.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -1083,7 +1137,7 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2016-02-11T07:05:27+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1196,23 +1250,23 @@ }, { "name": "sebastian/diff", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "366541b989927187c4ca70490a35615d3fef2dce" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/366541b989927187c4ca70490a35615d3fef2dce", - "reference": "366541b989927187c4ca70490a35615d3fef2dce", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.5 || ^8.0", "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", @@ -1248,32 +1302,35 @@ "unidiff", "unified diff" ], - "time": "2018-06-10T07:54:39+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "4.0.1", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/febd209a219cea7b56ad799b30ebbea34b71eb8f", - "reference": "febd209a219cea7b56ad799b30ebbea34b71eb8f", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^7.4" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1298,20 +1355,20 @@ "environment", "hhvm" ], - "time": "2018-11-25T09:31:21+00:00" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -1338,6 +1395,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -1346,17 +1407,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -1365,7 +1422,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", @@ -1698,37 +1755,43 @@ }, { "name": "symfony/console", - "version": "v4.2.2", + "version": "v4.3.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522" + "reference": "929ddf360d401b958f611d44e726094ab46a7369" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522", - "reference": "b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522", + "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", + "reference": "929ddf360d401b958f611d44e726094ab46a7369", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -1736,7 +1799,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1763,48 +1826,40 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-01-04T15:13:53+00:00" + "time": "2019-10-07T12:36:49+00:00" }, { - "name": "symfony/contracts", - "version": "v1.0.2", + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/contracts.git", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { - "php": "^7.1.3" - }, - "require-dev": { - "psr/cache": "^1.0", - "psr/container": "^1.0" + "php": ">=5.3.3" }, "suggest": { - "psr/cache": "When using the Cache contracts", - "psr/container": "When using the Service contracts", - "symfony/cache-contracts-implementation": "", - "symfony/service-contracts-implementation": "", - "symfony/translation-contracts-implementation": "" + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\": "" + "Symfony\\Polyfill\\Ctype\\": "" }, - "exclude-from-classmap": [ - "**/Tests/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1813,55 +1868,53 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A set of abstractions extracted out of the Symfony components", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "ctype", + "polyfill", + "portable" ], - "time": "2018-12-05T08:06:11+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { "php": ">=5.3.3" }, "suggest": { - "ext-ctype": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" }, "files": [ "bootstrap.php" @@ -1873,56 +1926,57 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", + "mbstring", "polyfill", - "portable" + "portable", + "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "name": "symfony/polyfill-php73", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", "shasum": "" }, "require": { "php": ">=5.3.3" }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Php73\\": "" }, "files": [ "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1939,29 +1993,86 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-09-17T11:12:18+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -1988,20 +2099,20 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", - "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "shasum": "" }, "require": { @@ -2009,8 +2120,7 @@ "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", "extra": { @@ -2039,7 +2149,7 @@ "check", "validate" ], - "time": "2018-12-25T11:19:39+00:00" + "time": "2019-08-24T08:43:50+00:00" } ], "aliases": [], diff --git a/tripal_hq_imports/tests/adminDashboardTest.php b/tests/tripal_hq_imports/adminDashboardTest.php similarity index 98% rename from tripal_hq_imports/tests/adminDashboardTest.php rename to tests/tripal_hq_imports/adminDashboardTest.php index 9a79a42..d597a27 100644 --- a/tripal_hq_imports/tests/adminDashboardTest.php +++ b/tests/tripal_hq_imports/adminDashboardTest.php @@ -13,6 +13,7 @@ class adminDashboardTest extends TripalTestCase { * * @group dashboard * @group admin + * @group hq-imports */ public function testAdminDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_admin_dashboard.form'); diff --git a/tripal_hq_imports/tests/apiTest.php b/tests/tripal_hq_imports/apiTest.php similarity index 99% rename from tripal_hq_imports/tests/apiTest.php rename to tests/tripal_hq_imports/apiTest.php index 051ea80..7972caf 100644 --- a/tripal_hq_imports/tests/apiTest.php +++ b/tests/tripal_hq_imports/apiTest.php @@ -13,6 +13,7 @@ class apiTest extends TripalTestCase { * Tests tripal_hg_get_importers(). * * @group api + * @group hq-imports */ public function testGetImporters() { diff --git a/tripal_hq_imports/tests/approveRejectTest.php b/tests/tripal_hq_imports/approveRejectTest.php similarity index 98% rename from tripal_hq_imports/tests/approveRejectTest.php rename to tests/tripal_hq_imports/approveRejectTest.php index bddfff3..603d4dc 100644 --- a/tripal_hq_imports/tests/approveRejectTest.php +++ b/tests/tripal_hq_imports/approveRejectTest.php @@ -12,6 +12,10 @@ class approveRejectTest extends TripalTestCase { /** * Tests tripal_hq_imports_admin_control_form(). + * + * @group admin + * @group approve-reject + * @group hq-imports */ public function testAdminControlForm() { $this->actingAs(1); @@ -93,6 +97,10 @@ public function testAdminControlForm() { /** * Tests tripal_hq_imports_admin_control_form_validate(). + * + * @group admin + * @group approve-reject + * @group hq-imports */ public function testAdminControlFormValidate() { $this->actingAs(1); diff --git a/tripal_hq_imports/tests/moduleFileTest.php b/tests/tripal_hq_imports/moduleFileTest.php similarity index 96% rename from tripal_hq_imports/tests/moduleFileTest.php rename to tests/tripal_hq_imports/moduleFileTest.php index 107a6f5..e267356 100644 --- a/tripal_hq_imports/tests/moduleFileTest.php +++ b/tests/tripal_hq_imports/moduleFileTest.php @@ -12,6 +12,7 @@ class moduleFileTest extends TripalTestCase { * Tests hook_menu(). Specifically, are all the required keys set. * * @group core-hook + * @group hq-imports */ public function testHookMenu() { $menu_items = tripal_hq_imports_menu(); @@ -32,6 +33,7 @@ public function testHookMenu() { * Tests hook_permission(). Specifically, are all the required keys set. * * @group core-hook + * @group hq-imports */ public function testHookPerm() { $permissions = tripal_hq_imports_permission(); diff --git a/tripal_hq_imports/tests/userDashboardTest.php b/tests/tripal_hq_imports/userDashboardTest.php similarity index 98% rename from tripal_hq_imports/tests/userDashboardTest.php rename to tests/tripal_hq_imports/userDashboardTest.php index 4d2c02d..0a765de 100644 --- a/tripal_hq_imports/tests/userDashboardTest.php +++ b/tests/tripal_hq_imports/userDashboardTest.php @@ -13,6 +13,7 @@ class userDashboardTest extends TripalTestCase { * * @group dashboard * @group user + * @group hq-imports */ public function testUserDashboard() { module_load_include('inc', 'tripal_hq', 'includes/tripal_hq_user_dashboard.form'); diff --git a/tripal_hq_imports/tests/userDataFormTest.php b/tests/tripal_hq_imports/userDataFormTest.php similarity index 99% rename from tripal_hq_imports/tests/userDataFormTest.php rename to tests/tripal_hq_imports/userDataFormTest.php index 8ee9d5f..7ea6d14 100644 --- a/tripal_hq_imports/tests/userDataFormTest.php +++ b/tests/tripal_hq_imports/userDataFormTest.php @@ -14,6 +14,7 @@ class userDataFormTest extends TripalTestCase { * List importers. * * @group user + * @group hq-imports */ public function testListImportersPage() { diff --git a/tripal_hq_imports/tests/DataFactory.php b/tripal_hq_imports/tests/DataFactory.php deleted file mode 100644 index 237efa3..0000000 --- a/tripal_hq_imports/tests/DataFactory.php +++ /dev/null @@ -1,196 +0,0 @@ - $faker->unique()->word . uniqid(), - // 'name' => $faker->unique($reset = TRUE)->word , - 'definition' => $faker->text, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.db', function (Faker\Generator $faker) { - return [ - 'name' => $faker->unique()->word . uniqid(), - 'description' => $faker->text, - 'urlprefix' => $faker->url, - 'url' => $faker->url, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.dbxref', function (Faker\Generator $faker) { - return [ - 'db_id' => factory('chado.db')->create()->db_id, - 'accession' => $faker->numberBetween(), - 'version' => $faker->numberBetween(), - 'description' => $faker->text, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.cvterm', function (Faker\Generator $faker) { - return [ - 'cv_id' => factory('chado.cv')->create()->cv_id, - 'dbxref_id' => factory('chado.dbxref')->create()->dbxref_id, - 'name' => $faker->word, - 'definition' => $faker->text, - 'is_obsolete' => 0, - 'is_relationshiptype' => 0, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.organism', function (Faker\Generator $faker) { - $genus = $faker->word; - $species = $faker->word; - $abbr = substr($genus, 0, 1) . ". " . $species; - - return [ - 'abbreviation' => $abbr, - 'genus' => $genus, - 'species' => $faker->name, - 'common_name' => $faker->word, - 'type_id' => factory('chado.cvterm')->create()->cvterm_id, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.feature', function (Faker\Generator $faker) { - return [ - 'name' => $faker->word, - 'uniquename' => $faker->unique()->word, - 'organism_id' => factory('chado.organism')->create()->organism_id, - 'type_id' => factory('chado.cvterm')->create()->cvterm_id, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.analysis', function (Faker\Generator $faker) { - return [ - 'name' => $faker->word, - 'description' => $faker->text, - 'program' => $faker->unique()->word, - 'programversion' => $faker->unique()->word, - 'sourcename' => $faker->unique()->word, - 'algorithm' => $faker->word, - 'sourcename' => $faker->word, - 'sourceversion' => $faker->word, - 'sourceuri' => $faker->word, - // 'timeexecuted' => $faker->time()// needs to match 2018-03-23 15:08:00.000000 - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.contact', function (Faker\Generator $faker) { - return [ - 'type_id' => factory('chado.cvterm')->create()->cvterm_id, - 'name' => $faker->name, - 'description' => $faker->text, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.biomaterial', function (Faker\Generator $faker) { - return [ - - 'taxon_id' => factory('chado.organism')->create()->organism_id, - 'biosourceprovider_id' => factory('chado.contact')->create()->contact_id, - 'dbxref_id' => factory('chado.dbxref')->create()->dbxref_id, - 'name' => $faker->unique()->word, - 'description' => $faker->text, - - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.featuremap', function (Faker\Generator $faker) { - return [ - 'name' => $faker->unique()->word, - 'description' => $faker->text, - 'unittype_id' => factory('chado.cvterm')->create()->cvterm_id, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.featurepos', function (Faker\Generator $faker) { - return [ - 'featuremap_id' => factory('chado.featuremap')->create()->featuremap_id, - 'feature_id' => factory('chado.feature')->create()->feature_id, - 'map_feature_id' => factory('chado.feature')->create()->feature_id, - 'mappos' => $faker->randomFloat, - ]; -}); - -/** - * IMPORTANT!!!! - * ============================================================== - * IF you use this factory, call - * - * $prev_db = chado_set_active('chado'); - * - * beforehand, and - * - * chado_set_active($prev_db); - * - * afterwards. - * - * @see StatonLab\TripalTestSuite\Database\Factory::define() - */ -Factory::define('chado.featureloc', function (Faker\Generator $faker) { - $a = $faker->randomNumber; - $b = $faker->randomNumber; - - return [ - 'feature_id' => factory('chado.feature')->create()->feature_id, - 'srcfeature_id' => factory('chado.feature')->create()->feature_id, - 'fmin' => min([$a, $b]), - 'is_fmin_partial' => 0, - 'fmax' => max([$a, $b]), - 'is_fmax_partial' => 0, - 'strand' => NULL, - 'phase' => NULL, - 'residue_info' => $faker->word, - 'locgroup' => 0, - 'rank' => 0, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.library', function (Faker\Generator $faker) { - return [ - 'organism_id' => factory('chado.organism')->create()->organism_id, - 'name' => $faker->word, - 'uniquename' => $faker->unique()->word, - 'type_id' => factory('chado.cvterm')->create()->cvterm_id, - 'is_obsolete' => 0, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.project', function (Faker\Generator $faker) { - return [ - 'name' => $faker->word, - 'description' => $faker->text, - ]; -}); - -/** @see StatonLab\TripalTestSuite\Database\Factory::define() */ -Factory::define('chado.pub', function (Faker\Generator $faker) { - return [ - 'uniquename' => $faker->word, - 'type_id' => factory('chado.cvterm')->create()->cvterm_id, - ]; -}); diff --git a/tripal_hq_imports/tests/DatabaseSeeders/examples/DevSeedSeeder.php b/tripal_hq_imports/tests/DatabaseSeeders/examples/DevSeedSeeder.php deleted file mode 100644 index 5c84999..0000000 --- a/tripal_hq_imports/tests/DatabaseSeeders/examples/DevSeedSeeder.php +++ /dev/null @@ -1,383 +0,0 @@ - 'F. excelsior miniature', - 'genus' => 'Fraxinus', - 'species' => 'excelsior', - 'abbreviation' => 'F. excelsor', - 'comment' => 'Loaded with TripalDev Seed.', - ]; - -protected $sequence_analysis = [ - 'name' => 'Fraxinus exclesior miniature dataset', - 'description' => 'Tripal Dev Seed', - ]; - - protected $expression_analysis = [ - - 'name' => 'Fraxinus exclesior miniature dataset Expression Analysis', - 'description' => 'Tripal Dev Seed', - ]; - - protected $blastdb = [ - 'name' => 'DevSeed Database: TREMBL', - 'description' => 'A dummy database created by DevSeed', - ]; - - /** - * Part 2: - * Files. - * Each importer will take a file argument. This argument should be an array - * with one of the following two keys: file_remote => url where the file is - * located file_local => server path where the file is located. - */ - - protected $landmark_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/empty_landmarks.fasta']; - - protected $landmark_type = 'supercontig'; - - protected $mRNA_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/mrna_mini.fasta']; - - protected $protein_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/sequences/polypeptide_mini.fasta']; - - protected $gff_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/gff/filtered.gff']; - - protected $blast_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/gff/filtered.gff']; - - protected $biomaterial_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/biomaterials/biomaterials.xml']; - - protected $expression_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/expression/expression.tsv']; - - protected $interpro_file = ['file_remote' => 'https://raw.githubusercontent.com/statonlab/tripal_dev_seed/master/Fexcel_mini/ips/polypeptide_mini.fasta.xml']; - - // Regular expression that will link the protein name to the mRNA parent feature name. - // protected $prot_regexp = '/(FRA.*?)(?=:)/'; - - protected $prot_regexp = null; - - public function __construct() - { - - if ($this->organism) { - - try { - $organism = $this->fetch_chado_record('chado.organism', [ - 'common_name', - 'organism_id', - ], $this->organism); - } catch (\Exception $e) { - echo $e->getMessage(); - exit; - } - - $this->organism = $organism; - - if ($this->sequence_analysis) { - - try { - $seq_analysis = $this->fetch_chado_record('chado.analysis', ['analysis_id'], - $this->sequence_analysis); - } catch (\Exception $e) { - echo $e->getMessage(); - exit; - } - $this->sequence_analysis = $seq_analysis; - } - - if ($this->expression_analysis) { - try { - $expression_analysis = $this->fetch_chado_record('chado.analysis', ['analysis_id'], - $this->expression_analysis); - } catch (\Exception $e) { - echo $e->getMessage(); - exit; - } - - $this->expression_analysis = $expression_analysis; - } - } - - if ($this->blastdb) { - try { - $blastdb = $this->fetch_chado_record('chado.db', ['db_id'], $this->blastdb); - } catch (\Excetion $e) { - echo $e->getMessage(); - } - - $this->blastdb = $blastdb; - } - } - - /** - * Runs all loaders. - * Will only run loaders where the files have been uncommented at the start - * of the class. - */ - public function up() - { - - if ($this->landmark_file) { - - $run_args = [ - 'organism_id' => $this->organism->organism_id, - 'analysis_id' => $this->sequence_analysis->analysis_id, - 'seqtype' => $this->landmark_type, - 'method' => 2, //default insert and update - 'match_type' => 1, //unique name default - //optional - 're_name' => null, - 're_uname' => null, - 're_accession' => null, - 'db_id' => null, - 'rel_type' => null, - 're_subject' => null, - 'parent_type' => null, - ]; - $this->load_landmarks($run_args, $this->landmark_file); - } - - if ($this->gff_file) { - $run_args = [ - 'analysis_id' => $this->sequence_analysis->analysis_id, - 'organism_id' => $this->organism->organism_id, - 'use_transaction' => 1, - 'add_only' => 0, - 'update' => 1, - 'create_organism' => 0, - 'create_target' => 0, - - ///regexps for mRNA and protein. - 're_mrna' => null, - 're_protein' => $this->prot_regexp, - //optional - 'target_organism_id' => null, - 'target_type' => null, - 'start_line' => null, - 'landmark_type' => null, - 'alt_id_attr' => null, - ]; - $this->load_GFF($run_args, $this->gff_file); - } - - if ($this->mRNA_file) { - - $run_args = [ - 'organism_id' => $this->organism->organism_id, - 'analysis_id' => $this->sequence_analysis->analysis_id, - 'seqtype' => 'mRNA', - 'method' => 2, //default insert and update - 'match_type' => 1, //unique name default - //optional - 're_name' => null, - 're_uname' => null, - 're_accession' => null, - 'db_id' => null, - 'rel_type' => null, - 're_subject' => null, - 'parent_type' => null, - ]; - $this->load_mRNA_FASTA($run_args, $this->mRNA_file); - } - - if ($this->protein_file) { - $run_args = [ - 'organism_id' => $this->organism->organism_id, - 'analysis_id' => $this->sequence_analysis->analysis_id, - 'seqtype' => 'polypeptide', - 'method' => 2, - 'match_type' => 1, - //optional - 're_name' => null, - 're_uname' => null, - 're_accession' => null, - 'db_id' => null, - ]; - - if ($this->prot_regexp) { - //links polypeptide to mRNA - $run_args['rel_type'] = 'derives_from'; - $run_args['re_subject'] = $this->prot_regexp; - $run_args['parent_type'] = 'mRNA'; - } - $this->load_polypeptide_FASTA($run_args, $this->protein_file); - } - - if ($this->interpro_file) { - - $run_args = [ - 'analysis_id' => $this->sequence_analysis->analysis_id, - //optional - 'query_type' => 'mRNA', - 'query_re' => $this->prot_regexp, - 'query_uniquename' => null, - 'parsego' => true, - ]; - - $this->load_interpro_annotations($run_args, $this->interpro_file); - } - - if ($this->blast_file) { - $run_args = [ - 'analysis_id' => $this->sequence_analysis->analysis_id, - 'no_parsed' => 25,//number results to parse - 'query_type' => 'mRNA', - //optional - 'blastdb' => $this->blastdb->db_id, - 'blastfile_ext' => null, - 'is_concat' => 0, - 'query_re' => null, - 'query_uniquename' => 0, - ]; - - $this->load_blast_annotations($run_args, $this->blast_file); - } - - if ($this->biomaterial_file) { - $run_args = [ - 'organism_id' => $this->organism->organism_id, - 'analysis_id' => $this->sequence_analysis->analysis_id, - ]; - //optional: specifies specific CVterms for properties/property values. Not used here. - //'cvterm_configuration' => NULL, - //'cvalue_configuration' => NULL]; - - $this->load_biomaterials($run_args, $this->biomaterial_file); - } - - if ($this->expression_file) { - $run_args = [ - 'filetype' => 'mat', //matrix file type - 'organism_id' => $this->organism->organism_id, - 'analysis_id' => $this->sequence_analysis->analysis_id, - //optional - 'fileext' => null, - 're_start' => null, - 're_stop' => null, - 'feature_uniquenames' => null, - 'quantificationunits' => null, - 'seqtype' => 'mRNA', - ]; - $this->load_expression($run_args, $this->expression_file); - } - } - - private function load_landmarks($run_args, $file) - { - module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); - - $importer = new \FASTAImporter(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_mRNA_FASTA($run_args, $file) - { - module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); - - $importer = new \FASTAImporter(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_polypeptide_FASTA($run_args, $file) - { - module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/FASTAImporter'); - - $importer = new \FASTAImporter(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_interpro_annotations($run_args, $file) - { - module_load_include('inc', 'tripal_analysis_interpro', 'includes/TripalImporter/InterProImporter'); - - $importer = new \InterProImporter(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_GFF($run_args, $file) - { - module_load_include('inc', 'tripal_chado', 'includes/TripalImporter/GFF3Importer'); - - $importer = new \GFF3Importer(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_blast_annotations($run_args, $file) - { - module_load_include('inc', 'tripal_analysis_blast', 'includes/TripalImporter/BlastImporter'); - - $importer = new \BlastImporter(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_biomaterials($run_args, $file) - { - module_load_include('inc', 'tripal_biomaterial', 'includes/TripalImporter/tripal_biomaterial_loader_v3'); - - $importer = new \tripal_biomaterial_loader_v3(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function load_expression($run_args, $file) - { - module_load_include('inc', 'tripal_analysis_expression', - 'includes/TripalImporter/tripal_expression_data_loader'); - - $importer = new \tripal_expression_data_loader(); - $importer->create($run_args, $file); - $importer->prepareFiles(); - $importer->run(); - } - - private function fetch_chado_record($table, $fields, $factory_array) - { - $query = db_select($table, 't')->fields('t', $fields); - - foreach ($factory_array as $key => $value) { - $query->condition($key, $value); - } - - $count_query = $query; - $count = (int) $count_query->countQuery()->execute()->fetchField(); - - if ($count === 0) { - return factory($table)->create($factory_array); - } - - if ($count === 1) { - return $query->execute()->fetchObject(); - } - - throw new Exception("Error creating object for: ".$table.".\n Array supplied matches ".$count_query." results, must match 1."); - } -} diff --git a/tripal_hq_imports/tests/DatabaseSeeders/examples/UsersTableSeeder.php b/tripal_hq_imports/tests/DatabaseSeeders/examples/UsersTableSeeder.php deleted file mode 100644 index 2d28f29..0000000 --- a/tripal_hq_imports/tests/DatabaseSeeders/examples/UsersTableSeeder.php +++ /dev/null @@ -1,28 +0,0 @@ - 'test user', - 'pass' => 'secret', - 'mail' => 'test@example.com', - 'status' => 1, - 'init' => 'Email', - 'roles' => [ - DRUPAL_AUTHENTICATED_RID => 'authenticated user', - ], - ]; - - // The first parameter is sent blank so a new user is created. - user_save(new \stdClass(), $new_user); - } -} diff --git a/tripal_hq_imports/tests/bootstrap.php b/tripal_hq_imports/tests/bootstrap.php deleted file mode 100644 index 6bf56ce..0000000 --- a/tripal_hq_imports/tests/bootstrap.php +++ /dev/null @@ -1,4 +0,0 @@ - Date: Wed, 30 Oct 2019 20:18:24 -0600 Subject: [PATCH 25/36] Ensure Tripal HQ imports is in the same package as the rest of Tripal HQ. --- tripal_hq_imports/tripal_hq_imports.info | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tripal_hq_imports/tripal_hq_imports.info b/tripal_hq_imports/tripal_hq_imports.info index ce88c0e..9f14eac 100644 --- a/tripal_hq_imports/tripal_hq_imports.info +++ b/tripal_hq_imports/tripal_hq_imports.info @@ -1,7 +1,7 @@ -name = Tripal HQ Imports -description = Extends Tripal HQ to support TripalImporters +name = Tripal Headquarters Imports +description = Provides support for data loaders. core = 7.x -package = Tripal Extensions +package = Tripal Extensions: Tripal Headquarters dependencies[] = tripal dependencies[] = tripal_chado dependencies[] = tripal_hq From 26d93b14aa8b9bac473880716c0f6277b7089ee1 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Wed, 30 Oct 2019 20:37:47 -0600 Subject: [PATCH 26/36] Merge redundant files to import Tripal HQ Imports. --- .gitignore | 1 + .travis.yml | 2 +- phpunit.xml | 5 + tripal_hq_imports/.gitignore | 9 - tripal_hq_imports/.travis.yml | 33 - tripal_hq_imports/LICENSE | 339 ----- tripal_hq_imports/composer.json | 5 - tripal_hq_imports/composer.lock | 2161 ------------------------------- tripal_hq_imports/phpunit.xml | 22 - 9 files changed, 7 insertions(+), 2570 deletions(-) delete mode 100644 tripal_hq_imports/.gitignore delete mode 100644 tripal_hq_imports/.travis.yml delete mode 100644 tripal_hq_imports/LICENSE delete mode 100644 tripal_hq_imports/composer.json delete mode 100644 tripal_hq_imports/composer.lock delete mode 100644 tripal_hq_imports/phpunit.xml diff --git a/.gitignore b/.gitignore index 3c5fb98..5255381 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ vendor/ .idea/ docs/_build/* .DS_Store +tests/_build diff --git a/.travis.yml b/.travis.yml index f47fd81..c96c199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ before_script: script: - docker run -it -d --rm --name tripal -v "$(pwd)":/modules/tripal_hq statonlab/tripal3 - sleep 30 # We pause here so postgres and apache complete booting up - - docker exec -it tripal drush pm-enable -y tripal_hq tripal_hq_permissions + - docker exec -it tripal drush pm-enable -y tripal_hq tripal_hq_permissions tripal_hq_imports - docker exec -it tripal yum install -y php-pecl-xdebug.x86_64 - docker exec -it tripal bash -c "cd /modules/tripal_hq && composer install && DRUPAL_ROOT=/var/www/html ./vendor/bin/phpunit --coverage-clover ./clover.xml" diff --git a/phpunit.xml b/phpunit.xml index cb62c85..089a78f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,10 +16,15 @@ + ./tripal_hq.module ./includes ./includes + ./tripal_hq_permissions/tripal_hq_permissions.module ./tripal_hq_permissions/includes ./tripal_hq_permissions/includes + ./tripal_hq_imports/tripal_hq_imports.module + ./tripal_hq_imports/includes + ./tripal_hq_imports/includes diff --git a/tripal_hq_imports/.gitignore b/tripal_hq_imports/.gitignore deleted file mode 100644 index 2213d9f..0000000 --- a/tripal_hq_imports/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -composer.phar -/vendor/ -.env - -tests/_build - -# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock diff --git a/tripal_hq_imports/.travis.yml b/tripal_hq_imports/.travis.yml deleted file mode 100644 index b8f24de..0000000 --- a/tripal_hq_imports/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: php - -# Add php version so composer doesn't complain -php: - - 7.1 - -services: - - docker - -env: - - DRUPAL_ROOT=/var/www/html IS_TRAVIS=TRUE CC_TEST_REPORTER_ID=1594460e199e9ae7f700d5852e6d3084e7101f13ba851cb952a88daa1b2a49db - -before_script: - - docker pull statonlab/tripal3 - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build --debug - - GIT_BRANCH=$TRAVIS_PULL_REQUEST_BRANCH - - GIT_COMMIT_SHA=$TRAVIS_PULL_REQUEST_SHA - -script: - - docker run -it -d --rm --name tripal -v "$(pwd)":/modules/tripal_hq_imports statonlab/tripal3 - - sleep 30 # We pause here so postgres and apache complete booting up - ## Install dependencies. - - docker exec -it tripal bash -c "cd /modules/ && git clone https://github.com/statonlab/tripal_hq.git" - ## Enable our module. - - docker exec -it tripal drush pm-enable -y tripal_hq_imports - ## Install XDEBUG for coverage. - - docker exec -it tripal yum install -y php-pecl-xdebug.x86_64 - - docker exec -it tripal bash -c "cd /modules/tripal_hq_imports && composer install && DRUPAL_ROOT=/var/www/html ./vendor/bin/phpunit --coverage-clover ./clover.xml" - -after_script: - - ./cc-test-reporter after-build clover.xml --debug -t clover -p /var/www/html/sites/all/modules/custom/tripal_hq_imports --exit-code $TRAVIS_TEST_RESULT diff --git a/tripal_hq_imports/LICENSE b/tripal_hq_imports/LICENSE deleted file mode 100644 index d159169..0000000 --- a/tripal_hq_imports/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - 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. - - - Copyright (C) - - 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. - - , 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. diff --git a/tripal_hq_imports/composer.json b/tripal_hq_imports/composer.json deleted file mode 100644 index b0a6b22..0000000 --- a/tripal_hq_imports/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require-dev": { - "statonlab/tripal-test-suite": "^1.6" - } -} diff --git a/tripal_hq_imports/composer.lock b/tripal_hq_imports/composer.lock deleted file mode 100644 index 0183113..0000000 --- a/tripal_hq_imports/composer.lock +++ /dev/null @@ -1,2161 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "69cac23032897dac56256c18daaf9aff", - "packages": [], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.13", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-shim": "^0.11", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2019-03-17T17:37:11+00:00" - }, - { - "name": "fzaninotto/faker", - "version": "v1.8.0", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^1.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2018-07-12T10:23:15+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "6.3.3", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "shasum": "" - }, - "require": { - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.3-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "time": "2018-04-22T15:46:56+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "time": "2016-12-20T10:07:11+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", - "reference": "239400de7a173fe9901b9ac7c06497751f00727a", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" - }, - "suggest": { - "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Schultze", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2019-07-01T23:21:34+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.9.3", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "replace": { - "myclabs/deep-copy": "self.version" - }, - "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2019-08-09T12:45:53+00:00" - }, - { - "name": "phar-io/manifest", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2018-07-08T19:23:20+00:00" - }, - { - "name": "phar-io/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "time": "2018-07-08T19:19:57+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2018-08-07T13:53:10+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", - "shasum": "" - }, - "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", - "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "doctrine/instantiator": "^1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-09-12T14:27:41+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "shasum": "" - }, - "require": { - "php": "^7.1", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", - "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2019-10-03T11:07:50+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "6.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-xdebug": "^2.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2018-10-31T16:06:48+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "050bedf145a257b1ff02746c31894800e5122946" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", - "reference": "050bedf145a257b1ff02746c31894800e5122946", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2018-09-13T20:33:42+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "2.1.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", - "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2019-06-07T04:22:29+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", - "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2019-09-17T06:23:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "7.5.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/316afa6888d2562e04aeb67ea7f2017a0eb41661", - "reference": "316afa6888d2562e04aeb67ea7f2017a0eb41661", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.5-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2019-09-14T09:08:39+00:00" - }, - { - "name": "psr/container", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "time": "2017-02-14T16:28:37+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "time": "2016-08-06T14:39:51+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" - }, - { - "name": "sebastian/comparator", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", - "shasum": "" - }, - "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2018-07-12T15:12:46+00:00" - }, - { - "name": "sebastian/diff", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "time": "2019-02-04T06:01:07+00:00" - }, - { - "name": "sebastian/environment", - "version": "4.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2019-05-05T09:05:15+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", - "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2019-09-14T09:02:43+00:00" - }, - { - "name": "sebastian/global-state", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2017-04-27T15:39:26+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2018-10-04T04:07:39+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "statonlab/tripal-test-suite", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/tripal/TripalTestSuite.git", - "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tripal/TripalTestSuite/zipball/3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4", - "reference": "3e877af0204c59b9aa6b7ef0324ca4b985a7e3b4", - "shasum": "" - }, - "require": { - "fzaninotto/faker": "^1.7", - "guzzlehttp/guzzle": "^6.3", - "phpunit/phpunit": "^5 || ^6 || ^7.0", - "symfony/console": "^3 || ^4.0" - }, - "bin": [ - "tripaltest" - ], - "type": "library", - "autoload": { - "psr-4": { - "StatonLab\\TripalTestSuite\\": "src/" - }, - "files": [ - "src/Helpers/helpers.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-3.0" - ], - "authors": [ - { - "name": "Abdullah Almsaeed", - "email": "aalmsaee@utk.edu" - }, - { - "name": "Bradford Condon", - "email": "bcondon@utk.edu" - } - ], - "time": "2019-04-09T18:52:51+00:00" - }, - { - "name": "symfony/console", - "version": "v4.3.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "929ddf360d401b958f611d44e726094ab46a7369" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", - "reference": "929ddf360d401b958f611d44e726094ab46a7369", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3", - "symfony/process": "<3.3" - }, - "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0", - "symfony/var-dumper": "^4.3" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2019-10-07T12:36:49+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", - "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v1.1.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffcde9615dc5bb4825b9f6aed07716f1f57faae0", - "reference": "ffcde9615dc5bb4825b9f6aed07716f1f57faae0", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-09-17T11:12:18+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-06-13T22:48:21+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0", - "symfony/polyfill-ctype": "^1.8" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2019-08-24T08:43:50+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} diff --git a/tripal_hq_imports/phpunit.xml b/tripal_hq_imports/phpunit.xml deleted file mode 100644 index 886b1e4..0000000 --- a/tripal_hq_imports/phpunit.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - tests - - - - - ./tripal_hq_imports.module - ./includes - ./includes - - - From e7141ab44d711958a5c818ba083b94f80e72235d Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Thu, 31 Oct 2019 21:38:53 -0600 Subject: [PATCH 27/36] Update Tripal HQ docs to include Tripal HQ Imports. --- docs/_static/img/imports_userdash.png | Bin 0 -> 96849 bytes docs/introduction.rst | 4 +++- docs/setup/install.rst | 2 +- docs/setup/permissions.rst | 1 + docs/usage.rst | 14 ++++++++++++++ .../tripal_hq_imports_user_data.form.inc | 1 + 6 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 docs/_static/img/imports_userdash.png diff --git a/docs/_static/img/imports_userdash.png b/docs/_static/img/imports_userdash.png new file mode 100644 index 0000000000000000000000000000000000000000..13ea942638a8b74082121f0470a001457827706a GIT binary patch literal 96849 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 Date: Fri, 1 Nov 2019 09:18:32 -0600 Subject: [PATCH 28/36] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0b3e80..c85ed7c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ For module installation and usage instructions, [please see our documentation we ## Module Features -* Users create data using your existing Bundle configuration- no extra forms! +* Users create data using your existing Bundle configuration or Importers- no extra forms! * User dashboard area for viewing pending submissions * Admin dashboard for viewing submissions * Chado-based permissions to create admins for certain projects or organisms From a3191b65740479c18ec7ce39d7f72a193a80ab3e Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Fri, 1 Nov 2019 12:23:16 -0600 Subject: [PATCH 29/36] Update Tripal HQ Imports readme. --- tripal_hq_imports/README.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/tripal_hq_imports/README.md b/tripal_hq_imports/README.md index d5bc357..a0e08bf 100644 --- a/tripal_hq_imports/README.md +++ b/tripal_hq_imports/README.md @@ -1,13 +1,6 @@ -![Tripal Dependency](https://img.shields.io/badge/tripal-%3E=3.0-brightgreen) -![Module is Generic](https://img.shields.io/badge/generic-confirmed-brightgreen) -![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/UofS-Pulse-Binfo/tripal_hq_imports?include_prereleases) +# Tripal Headquarters: Imports -[![Build Status](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports.svg?branch=master)](https://travis-ci.org/UofS-Pulse-Binfo/tripal_hq_imports) -[![Test Coverage](https://api.codeclimate.com/v1/badges/598206b15a4410687d38/test_coverage)](https://codeclimate.com/github/UofS-Pulse-Binfo/tripal_hq_imports/test_coverage) - -# Tripal HQ Imports - -Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. Tripal HQ Imports extends [Tripal HQ](https://github.com/statonlab/tripal_hq) to support TripalImporters. Specifically, this allows users to submit Tripal Importers, administrators to review the submission and data is only inserted into Chado once the administrator approves the submission. +Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. Tripal HQ Imports extends [Tripal HQ](https://github.com/statonlab/tripal_hq) to support TripalImporters. Specifically, this allows users to submit Tripal Importers, administrators to review the submission and data is only inserted into Chado once the administrator approves the submission. ## Features @@ -16,11 +9,6 @@ Tripal HQ provides a user-contributed content control center and administrative - All TripalImporters should be supported! (excludes multi-page forms; e.g AnalyzedPhenotypes) - You can specify which importers should be available to users through native Drupal Permissions. -## Dependencies - -1. [Tripal Core](https://github.com/tripal/tripal) -2. [Tripal HQ](https://github.com/statonlab/tripal_hq) - ## Usage ### Adds Data Import support to Tripal HQ User Dashboard @@ -36,7 +24,7 @@ Their submissions will be summarized on the same dashboard as their Tripal Conte - Tripal HQ administration dashboard support approve/reject for Tripal Importer submissions. Screen Shot 2019-10-16 at 3 25 18 PM -## Future Development +## Future Development - Support emails in the same manner as Tripal HQ - Rich permissions for controlling which users have access to specific importers From e531945037b3e15e484014489d10c45c87ec7f69 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Fri, 1 Nov 2019 12:47:30 -0600 Subject: [PATCH 30/36] Make some changes code climate suggested. --- composer.lock | 10 ++-- .../includes/tripal_hq_imports.api.inc | 49 ++++++++++++------- ...tripal_hq_imports_admin_dashboard.form.inc | 27 +++++++--- .../tripal_hq_imports_approve.form.inc | 16 ++++-- .../tripal_hq_imports_user_dashboard.form.inc | 17 ++++++- .../tripal_hq_imports_user_data.form.inc | 6 ++- tripal_hq_imports/tripal_hq_imports.module | 3 ++ 7 files changed, 89 insertions(+), 39 deletions(-) diff --git a/composer.lock b/composer.lock index 5265370..a86db15 100644 --- a/composer.lock +++ b/composer.lock @@ -1755,16 +1755,16 @@ }, { "name": "symfony/console", - "version": "v4.3.5", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "929ddf360d401b958f611d44e726094ab46a7369" + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/929ddf360d401b958f611d44e726094ab46a7369", - "reference": "929ddf360d401b958f611d44e726094ab46a7369", + "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", "shasum": "" }, "require": { @@ -1826,7 +1826,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-10-07T12:36:49+00:00" + "time": "2019-10-30T12:58:49+00:00" }, { "name": "symfony/polyfill-ctype", diff --git a/tripal_hq_imports/includes/tripal_hq_imports.api.inc b/tripal_hq_imports/includes/tripal_hq_imports.api.inc index 3bbebaf..b4d0474 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports.api.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports.api.inc @@ -1,4 +1,5 @@ fields([ 'status' => 'approved', 'updated_at' => time(), @@ -148,17 +161,17 @@ function tripal_hq_imports_approve_submission($submission) { /** * Updates a given form field for edit (default value) or view (read-only) mode. * - * @param $form_field + * @param array $form_field * A form field defined using the Drupal Form API. - * @param $values - * The full values list for the form. - * @param $op - * One of 'edit' or 'view' where the second will be read-only. + * @param array $values + * The full values list for the form. + * @param string $op + * One of 'edit' or 'view' where the second will be read-only. */ function tripal_hq_editview_form_field(&$form_field, $values, $element_key, $op) { - if (isset($form_field['#type']) AND ($form_field['#type'] == 'fieldset')) { - foreach(element_children($form_field) as $child_element) { + if (isset($form_field['#type']) and ($form_field['#type'] == 'fieldset')) { + foreach (element_children($form_field) as $child_element) { tripal_hq_editview_form_field($form_field[$child_element], $values, $child_element, $op); } } diff --git a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc index fdda1c1..9a128f7 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -1,11 +1,21 @@ [ 'data' => t('Comments'), ], - 'Approve', - 'Reject', + 'approve_link' => 'Approve', + 'reject_link' => 'Reject', ]; // @todo support HQ deputy permissmissions! @@ -101,7 +111,10 @@ // Comments. $comments_link = $comment_count; if ($request->nid) { - $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $request->nid); + $comments_link = l( + t('Add/View Comments (:count)', [':count' => $comment_count]), + 'node/' . $request->nid + ); } // Approve or Deny. @@ -111,7 +124,6 @@ $reject = l(t('Reject'), 'tripal_hq/admin/data-imports/reject/' . $request->id); } - // Now compile the row. $rows[] = [ $user_row, @@ -121,7 +133,7 @@ $created_at, $comments_link, $approve, - $reject + $reject, ]; } @@ -130,7 +142,8 @@ '#header' => $header, '#rows' => $rows, '#caption' => '

' . t('Data Files') . '

', - '#empty' => t("There are no $status data file submissions."), + '#empty' => t('There are no :status data file submissions.', + [':status' => $status]), ]; $form['importer_pager'] = [ diff --git a/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc index db55979..68896a5 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc @@ -9,13 +9,16 @@ * Approve or reject a submission based on $op. * * @param array $form + * The array describing the form. * @param array $form_state + * An array describing the state of the form. * @param string $op * Either "approve" or "reject". * @param int $sid * Submission id. * * @return array + * The form render array. */ function tripal_hq_imports_admin_control_form($form, &$form_state, $op, $sid) { @@ -74,8 +77,10 @@ function tripal_hq_imports_admin_control_form($form, &$form_state, $op, $sid) { /** * Validate the request. * - * @param $form - * @param $form_state + * @param array $form + * The array describing the form. + * @param array $form_state + * An array describing the state of the form. */ function tripal_hq_imports_admin_control_form_validate($form, &$form_state) { $values = $form_state['values']; @@ -91,8 +96,10 @@ function tripal_hq_imports_admin_control_form_validate($form, &$form_state) { /** * Approve and publish or reject a submission. * - * @param $form - * @param $form_state + * @param array $form + * The array describing the form. + * @param array $form_state + * An array describing the state of the form. */ function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { $values = $form_state['values']; @@ -106,7 +113,6 @@ function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { $rejected = tripal_hq_imports_reject_submission($submission); if ($rejected) { - // @todo currently we cant use tripal_hq_send_emails() since it retrieves // the submission with the assumption of db table. //tripal_hq_send_emails($sid, 'reject'); diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc index 2857782..2580738 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc @@ -1,4 +1,5 @@ nid) { - $comments_link = l('Add/View Comments (' . $comment_count . ')', 'node/' . $submission->nid); + $comments_link = l( + t('Add/View Comments (:count)', [':count' => $comment_count]), + 'node/' . $submission->nid + ); } // Edit or View link. @@ -78,7 +91,7 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) $created_at, $updated_at, $comments_link, - $link + $link, ]; } diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc index fc5d547..361297d 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -1,4 +1,5 @@ 'markup', '#prefix' => '

', @@ -104,7 +106,7 @@ function tripal_hq_user_importer_form($form, &$form_state) { } // Ensure the user has permission to propose that file type. - if (($user->uid === 0) OR (!user_access("propose $importer_label"))) { + if (($user->uid === 0) or (!user_access("propose $importer_label"))) { $form['warning'] = [ '#type' => 'markup', '#markup' => '

You do not have access to propose data files of this type. Please contact an administrator.
', diff --git a/tripal_hq_imports/tripal_hq_imports.module b/tripal_hq_imports/tripal_hq_imports.module index 763d9b7..88b24c8 100644 --- a/tripal_hq_imports/tripal_hq_imports.module +++ b/tripal_hq_imports/tripal_hq_imports.module @@ -1,7 +1,10 @@ Date: Fri, 1 Nov 2019 20:16:32 -0600 Subject: [PATCH 31/36] Fix formatting and documentation issues. --- composer.json | 4 +- composer.lock | 170 +++++++++- .../includes/tripal_hq_imports.api.inc | 8 +- ...tripal_hq_imports_admin_dashboard.form.inc | 291 +++++++++--------- .../tripal_hq_imports_approve.form.inc | 16 +- .../tripal_hq_imports_user_dashboard.form.inc | 186 ++++++----- .../tripal_hq_imports_user_data.form.inc | 147 +++++---- tripal_hq_imports/tripal_hq_imports.install | 119 +++---- 8 files changed, 557 insertions(+), 384 deletions(-) diff --git a/composer.json b/composer.json index 3716721..4be3ad0 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,9 @@ }, "require-dev": { "statonlab/tripal-test-suite": "1.5.*", - "phpunit/php-code-coverage": "^6.1" + "phpunit/php-code-coverage": "^6.1", + "squizlabs/php_codesniffer": "^2.9.1", + "drupal/coder": "^8.2" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index a86db15..068563e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "133a7b77ddffc432ea97c9bb53fa2a5d", + "content-hash": "641337ac7c0ef1f645d7b31fe0c3da5c", "packages": [], "packages-dev": [ { @@ -63,6 +63,37 @@ ], "time": "2019-03-17T17:37:11+00:00" }, + { + "name": "drupal/coder", + "version": "8.2.12", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/coder.git", + "reference": "984c54a7b1e8f27ff1c32348df69712afd86b17f" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4.0", + "squizlabs/php_codesniffer": ">=2.8.1 <3.0", + "symfony/yaml": ">=2.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7 <6" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0+" + ], + "description": "Coder is a library to review Drupal code.", + "homepage": "https://www.drupal.org/project/coder", + "keywords": [ + "code review", + "phpcs", + "standards" + ], + "time": "2017-03-18T10:28:49+00:00" + }, { "name": "fzaninotto/faker", "version": "v1.8.0", @@ -1705,6 +1736,84 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, { "name": "statonlab/tripal-test-suite", "version": "1.5.2", @@ -2061,6 +2170,65 @@ ], "time": "2019-09-17T11:12:18+00:00" }, + { + "name": "symfony/yaml", + "version": "v4.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "324cf4b19c345465fad14f3602050519e09e361d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", + "reference": "324cf4b19c345465fad14f3602050519e09e361d", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-10-30T12:58:49+00:00" + }, { "name": "theseer/tokenizer", "version": "1.1.3", diff --git a/tripal_hq_imports/includes/tripal_hq_imports.api.inc b/tripal_hq_imports/includes/tripal_hq_imports.api.inc index b4d0474..71e0eba 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports.api.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports.api.inc @@ -53,6 +53,7 @@ function tripal_hq_get_importers() { * * @param string $class * The TripalImporter class to include. + * * @return bool * TRUE if the field type class file was found, FALSE otherwise. */ @@ -80,6 +81,7 @@ function tripal_hq_load_include_importer_class($class) { * * @param int $submission_id * The id of the submission you would like returned. + * * @return object * An object describing the submission. */ @@ -97,6 +99,7 @@ function tripal_hq_imports_get_submission_by_id($submission_id) { * * @param object $submission * An object describing the submission to be rejected. + * * @return bool * True if the rejection was successful, FALSE otherwise. */ @@ -124,6 +127,7 @@ function tripal_hq_imports_reject_submission($submission) { * * @param object $submission * An object describing the submission to be approved. + * * @return bool * Returns FALSE if an error is encountered, otherwise TRUE. */ @@ -165,10 +169,12 @@ function tripal_hq_imports_approve_submission($submission) { * A form field defined using the Drupal Form API. * @param array $values * The full values list for the form. + * @param string $element_key + * THe key of the element to be checked. * @param string $op * One of 'edit' or 'view' where the second will be read-only. */ -function tripal_hq_editview_form_field(&$form_field, $values, $element_key, $op) { +function tripal_hq_editview_form_field(array &$form_field, array $values, $element_key, $op) { if (isset($form_field['#type']) and ($form_field['#type'] == 'fieldset')) { foreach (element_children($form_field) as $child_element) { diff --git a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc index 9a128f7..18429d5 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -5,151 +5,152 @@ * Provides changes to the administrative dashboard for tripal HQ imports. */ - /** - * Alter the administration dashboard to add our submissions. - * - * @param array $form - * The default form to be altered. - * @param array $form_state - * The state of the form to be altered. - * @param string $status - * The status of the submissions to show (e.g. pending). - * @return array - * The altered form array. - */ - function tripal_hq_imports_tripal_hq_admin_dashboard_alter(&$form, &$form_state, $status) { - global $user; - $uid = $user->uid; - - $items_per_page = 10; - - // Add a caption to differentiate the two tables. - $form['table']['#caption'] = '

' . t('Content') . '

'; - - // Check if the user is a deputy. - // If so, they only get access to a subset of data. - $deputy = user_access('tripal_hq_permissions deputy'); - - $header = [ - 'User' => [ - 'data' => t('User'), - 'field' => 't.uid', - ], - 'Title' => [ - 'data' => t('Title'), - 'field' => 'class', - ], - 'Type' => [ - 'data' => t('Content Type'), - ], - 'Status' => [ - 'data' => t('Approval Status'), - 'field' => 'status', - ], - 'Date Created' => [ - 'data' => t('Date Created'), - 'field' => 'created_at', - 'sort' => 'dsc', - ], - 'Comments' => [ - 'data' => t('Comments'), - ], - 'approve_link' => 'Approve', - 'reject_link' => 'Reject', - ]; - - // @todo support HQ deputy permissmissions! - //if ($deputy && $uid != 0 && $uid != 1) - - $query = db_select('tripal_hq_importer_submission', 't') - ->extend('TableSort') - ->orderByHeader($header) - ->extend('PagerDefault'); - - if ($status) { - $query->condition('t.status', $status); - } - $requests = $query->fields('t') - ->orderBy('id', 'desc') - ->limit($items_per_page) - ->execute() - ->fetchAll(); - - $date_format = 'M d Y H:i:s'; - $importer_labels = []; - - $rows = []; - foreach ($requests as $request) { - - $id = $request->id; - $status = $request->status; - $importer_class = $request->class; - $comment_count = tripal_hq_get_comments_count($request); - - // Determine the label. - if (!isset($importer_labels[$importer_class])) { - - tripal_hq_load_include_importer_class($importer_class); - $label = $importer_class::$name; - - $importer_labels[$importer_class] = $label; - } - $label = $importer_labels[$importer_class]; - - // User. - $submitter = user_load($request->uid); - if (!$submitter) { - tripal_set_message(t("Error looking up user !user", - ['!user' => $request->uid]), TRIPAL_WARNING); - continue; - } - $user_row = l($submitter->name . ' (' . $submitter->mail . ')', 'user/' . $submitter->uid); - - // Dates. - $created_at = date($date_format, $request->created_at); - - // Comments. - $comments_link = $comment_count; - if ($request->nid) { - $comments_link = l( - t('Add/View Comments (:count)', [':count' => $comment_count]), - 'node/' . $request->nid - ); - } - - // Approve or Deny. - $approve = $reject = ''; - if ($status == 'pending') { - $approve = l(t('Approve'), 'tripal_hq/admin/data-imports/approve/' . $request->id); - $reject = l(t('Reject'), 'tripal_hq/admin/data-imports/reject/' . $request->id); - } - - // Now compile the row. - $rows[] = [ - $user_row, - $label, - 'Data File', - ucwords($status), - $created_at, - $comments_link, - $approve, - $reject, - ]; - } - - $form['my_importer_requests'] = [ - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#caption' => '

' . t('Data Files') . '

', - '#empty' => t('There are no :status data file submissions.', +/** + * Alter the administration dashboard to add our submissions. + * + * @param array $form + * The default form to be altered. + * @param array $form_state + * The state of the form to be altered. + * @param string $status + * The status of the submissions to show (e.g. pending). + * + * @return array + * The altered form array. + */ +function tripal_hq_imports_tripal_hq_admin_dashboard_alter(array &$form, array &$form_state, $status) { + global $user; + $uid = $user->uid; + + $items_per_page = 10; + + // Add a caption to differentiate the two tables. + $form['table']['#caption'] = '

' . t('Content') . '

'; + + // Check if the user is a deputy. + // If so, they only get access to a subset of data. + $deputy = user_access('tripal_hq_permissions deputy'); + + $header = [ + 'User' => [ + 'data' => t('User'), + 'field' => 't.uid', + ], + 'Title' => [ + 'data' => t('Title'), + 'field' => 'class', + ], + 'Type' => [ + 'data' => t('Content Type'), + ], + 'Status' => [ + 'data' => t('Approval Status'), + 'field' => 'status', + ], + 'Date Created' => [ + 'data' => t('Date Created'), + 'field' => 'created_at', + 'sort' => 'dsc', + ], + 'Comments' => [ + 'data' => t('Comments'), + ], + 'approve_link' => 'Approve', + 'reject_link' => 'Reject', + ]; + + // @todo support HQ deputy permissmissions! + // if ($deputy && $uid != 0 && $uid != 1) + $query = db_select('tripal_hq_importer_submission', 't') + ->extend('TableSort') + ->orderByHeader($header) + ->extend('PagerDefault'); + + if ($status) { + $query->condition('t.status', $status); + } + $requests = $query->fields('t') + ->orderBy('id', 'desc') + ->limit($items_per_page) + ->execute() + ->fetchAll(); + + $date_format = 'M d Y H:i:s'; + $importer_labels = []; + + $rows = []; + foreach ($requests as $request) { + + $id = $request->id; + $status = $request->status; + $importer_class = $request->class; + $comment_count = tripal_hq_get_comments_count($request); + + // Determine the label. + if (!isset($importer_labels[$importer_class])) { + + tripal_hq_load_include_importer_class($importer_class); + $label = $importer_class::$name; + + $importer_labels[$importer_class] = $label; + } + $label = $importer_labels[$importer_class]; + + // User. + $submitter = user_load($request->uid); + if (!$submitter) { + tripal_set_message(t("Error looking up user !user", + ['!user' => $request->uid]), TRIPAL_WARNING); + continue; + } + $user_row = l($submitter->name . ' (' . $submitter->mail . ')', 'user/' . $submitter->uid); + + // Dates. + $created_at = date($date_format, $request->created_at); + + // Comments. + $comments_link = $comment_count; + if ($request->nid) { + $comments_link = l( + t('Add/View Comments (:count)', [':count' => $comment_count]), + 'node/' . $request->nid + ); + } + + // Approve or Deny. + $approve = $reject = ''; + if ($status == 'pending') { + $approve = l(t('Approve'), 'tripal_hq/admin/data-imports/approve/' . $request->id); + $reject = l(t('Reject'), 'tripal_hq/admin/data-imports/reject/' . $request->id); + } + + // Now compile the row. + $rows[] = [ + $user_row, + $label, + 'Data File', + ucwords($status), + $created_at, + $comments_link, + $approve, + $reject, + ]; + } + + $form['my_importer_requests'] = [ + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#caption' => '

' . t('Data Files') . '

', + '#empty' => t('There are no :status data file submissions.', [':status' => $status]), - ]; + ]; + + $form['importer_pager'] = [ + '#theme' => 'pager', + '#element' => 1, + ]; - $form['importer_pager'] = [ - '#theme' => 'pager', - '#element' => 1, - ]; + return $form; - return $form; - } +} diff --git a/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc index 68896a5..a10adc0 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc @@ -20,7 +20,7 @@ * @return array * The form render array. */ -function tripal_hq_imports_admin_control_form($form, &$form_state, $op, $sid) { +function tripal_hq_imports_admin_control_form(array $form, array &$form_state, $op, $sid) { $submission = tripal_hq_imports_get_submission_by_id($sid); if (empty($submission)) { @@ -82,7 +82,7 @@ function tripal_hq_imports_admin_control_form($form, &$form_state, $op, $sid) { * @param array $form_state * An array describing the state of the form. */ -function tripal_hq_imports_admin_control_form_validate($form, &$form_state) { +function tripal_hq_imports_admin_control_form_validate(array $form, array &$form_state) { $values = $form_state['values']; $op = isset($values['operation']) ? $values['operation'] : ''; $sid = isset($values['submission_id']) ? $values['submission_id'] : ''; @@ -101,7 +101,7 @@ function tripal_hq_imports_admin_control_form_validate($form, &$form_state) { * @param array $form_state * An array describing the state of the form. */ -function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { +function tripal_hq_imports_admin_control_form_submit(array $form, array &$form_state) { $values = $form_state['values']; $op = $values['operation']; $sid = $values['submission_id']; @@ -114,9 +114,8 @@ function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { if ($rejected) { // @todo currently we cant use tripal_hq_send_emails() since it retrieves - // the submission with the assumption of db table. - //tripal_hq_send_emails($sid, 'reject'); - + // the submission with the assumption of db table. + // tripal_hq_send_emails($sid, 'reject'); drupal_set_message('Submission rejected successfully'); if (!isset($form_state['no_redirect'])) { drupal_goto('tripal_hq/admin'); @@ -133,9 +132,8 @@ function tripal_hq_imports_admin_control_form_submit($form, &$form_state) { tripal_hq_imports_approve_submission($submission); // @todo currently we cant use tripal_hq_send_emails() since it retrieves - // the submission with the assumption of db table. - //tripal_hq_send_emails($sid, 'approve'); - + // the submission with the assumption of db table. + // tripal_hq_send_emails($sid, 'approve'); if (!isset($form_state['no_redirect'])) { drupal_goto('tripal_hq/admin'); } diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc index 2580738..7c19cf8 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc @@ -12,101 +12,99 @@ * The default form to be altered. * @param array $form_state * The state of the form to be altered. - * @param string $status - * The status of the submissions to show (e.g. pending). + * * @return array * The altered form array. */ -function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) { - global $user; - - // Number of items per page. - $items_per_page = 5; - - // Add a caption to differentiate the two tables. - $form['my_submissions']['#caption'] = '

' . t('Content') . '

'; - - // Now start to build my table. - $header = $form['my_submissions']['#header']; - $header['Title']['field'] = 'class'; - - $rows = []; - $submissions = db_select('tripal_hq_importer_submission', 't') - ->fields('t') - ->condition('uid', $user->uid) - ->extend('TableSort') - ->orderByHeader($header) - ->extend('PagerDefault') - ->limit($items_per_page) - ->execute() - ->fetchAll(); - - $date_format = 'M d Y H:i:s'; - $importer_labels = []; - - foreach ($submissions as $submission) { - - $id = $submission->id; - $status = $submission->status; - $importer_class = $submission->class; - $comment_count = tripal_hq_get_comments_count($submission); - - // Determine the label. - if (!isset($importer_labels[$importer_class])) { - - tripal_hq_load_include_importer_class($importer_class); - $label = $importer_class::$name; - - $importer_labels[$importer_class] = $label; - } - $label = $importer_labels[$importer_class]; - - // Dates. - $created_at = date($date_format, $submission->created_at); - $updated_at = $submission->updated_at ? date( - $date_format, $submission->updated_at - ) : ''; - - // Comments. - $comments_link = $comment_count; - if ($submission->nid) { - $comments_link = l( - t('Add/View Comments (:count)', [':count' => $comment_count]), - 'node/' . $submission->nid - ); - } - - // Edit or View link. - $link = l(t('Edit'), '/tripal_hq/bio_data/import-data/edit/' . $importer_class . '/' . $id); - if ($status != 'pending') { - $link = l(t('View'), '/tripal_hq/bio_data/import-data/view/' . $importer_class . '/' . $id); - } - - - // Now compile the row. - $rows[] = [ - $label, - 'Data File', - ucwords($status), - $created_at, - $updated_at, - $comments_link, - $link, - ]; - } - - $form['my_importer_submissions'] = [ - '#theme' => 'table', - '#header' => $header, - '#rows' => $rows, - '#caption' => '

' . t('Data Files') . '

', - '#empty' => t("You have no pending data file submissions."), - ]; - - $form['importer_pager'] = [ - '#theme' => 'pager', - '#element' => 1, - ]; - - return $form; +function tripal_hq_imports_tripal_hq_user_dashboard_alter(array &$form, array &$form_state) { + global $user; + + // Number of items per page. + $items_per_page = 5; + + // Add a caption to differentiate the two tables. + $form['my_submissions']['#caption'] = '

' . t('Content') . '

'; + + // Now start to build my table. + $header = $form['my_submissions']['#header']; + $header['Title']['field'] = 'class'; + + $rows = []; + $submissions = db_select('tripal_hq_importer_submission', 't') + ->fields('t') + ->condition('uid', $user->uid) + ->extend('TableSort') + ->orderByHeader($header) + ->extend('PagerDefault') + ->limit($items_per_page) + ->execute() + ->fetchAll(); + + $date_format = 'M d Y H:i:s'; + $importer_labels = []; + + foreach ($submissions as $submission) { + + $id = $submission->id; + $status = $submission->status; + $importer_class = $submission->class; + $comment_count = tripal_hq_get_comments_count($submission); + + // Determine the label. + if (!isset($importer_labels[$importer_class])) { + + tripal_hq_load_include_importer_class($importer_class); + $label = $importer_class::$name; + + $importer_labels[$importer_class] = $label; + } + $label = $importer_labels[$importer_class]; + + // Dates. + $created_at = date($date_format, $submission->created_at); + $updated_at = $submission->updated_at ? date( + $date_format, $submission->updated_at + ) : ''; + + // Comments. + $comments_link = $comment_count; + if ($submission->nid) { + $comments_link = l( + t('Add/View Comments (:count)', [':count' => $comment_count]), + 'node/' . $submission->nid + ); + } + + // Edit or View link. + $link = l(t('Edit'), '/tripal_hq/bio_data/import-data/edit/' . $importer_class . '/' . $id); + if ($status != 'pending') { + $link = l(t('View'), '/tripal_hq/bio_data/import-data/view/' . $importer_class . '/' . $id); + } + + // Now compile the row. + $rows[] = [ + $label, + 'Data File', + ucwords($status), + $created_at, + $updated_at, + $comments_link, + $link, + ]; + } + + $form['my_importer_submissions'] = [ + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#caption' => '

' . t('Data Files') . '

', + '#empty' => t("You have no pending data file submissions."), + ]; + + $form['importer_pager'] = [ + '#theme' => 'pager', + '#element' => 1, + ]; + + return $form; } diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc index 252cb2a..f3e2574 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -5,75 +5,74 @@ * User submissions. */ - /** - * Provides the page view for user importer submissions. - * - * @return array - * Array of markup describing bundles. - */ - function tripal_hq_import_list_importers_page() { - global $user; - $page = []; - - // We might also add a warning for *admin* users to use the actual tripal - // import forms. - $page['description'] = [ - '#type' => 'markup', - '#prefix' => '

', - '#markup' => t('Please click a data type below to submit a new data file and associated metadata for admin approval.'), - '#suffix' => '

', - ]; - - $user_has_stuff = 0; - $importers = tripal_hq_get_importers(); - foreach ($importers as $importer_class) { - - // Pull some important info out of the class. - $importer_label = $importer_class::$name; - $importer_machine_name = $importer_class::$machine_name; - $importer_description = $importer_class::$description; - - // Ensure the user has permission to propose that file type. - if (($user->uid === 0) OR (!user_access("propose $importer_label"))) { - continue; - } - $user_has_stuff = 1; - - - $link = l($importer_label, - "tripal_hq/bio_data/import-data/$importer_class"); - $page[$importer_machine_name] = [ - '#type' => 'item', - '#markup' => $link, - '#description' => $importer_description, - ]; - } - - if ($user_has_stuff === 0) { - $page['description'] = [ - '#type' => 'markup', - '#prefix' => '

', - '#markup' => t('You do not have site permissions to submit data files for consideration.'), - '#suffix' => '

', - ]; - - } - - return $page; - } - - /** - * TripalImporter submission form. - * - * @param int $importer_class - * The class name for the importer to show the form for. - * @param int $sid - * Submission ID. - * - * @return array - * Renderable array. - */ -function tripal_hq_user_importer_form($form, &$form_state) { +/** + * Provides the page view for user importer submissions. + * + * @return array + * Array of markup describing bundles. + */ +function tripal_hq_import_list_importers_page() { + global $user; + $page = []; + + // We might also add a warning for *admin* users to use the actual tripal + // import forms. + $page['description'] = [ + '#type' => 'markup', + '#prefix' => '

', + '#markup' => t('Please click a data type below to submit a new data file and associated metadata for admin approval.'), + '#suffix' => '

', + ]; + + $user_has_stuff = 0; + $importers = tripal_hq_get_importers(); + foreach ($importers as $importer_class) { + + // Pull some important info out of the class. + $importer_label = $importer_class::$name; + $importer_machine_name = $importer_class::$machine_name; + $importer_description = $importer_class::$description; + + // Ensure the user has permission to propose that file type. + if (($user->uid === 0) or (!user_access("propose $importer_label"))) { + continue; + } + $user_has_stuff = 1; + + $link = l($importer_label, + "tripal_hq/bio_data/import-data/$importer_class"); + $page[$importer_machine_name] = [ + '#type' => 'item', + '#markup' => $link, + '#description' => $importer_description, + ]; + } + + if ($user_has_stuff === 0) { + $page['description'] = [ + '#type' => 'markup', + '#prefix' => '

', + '#markup' => t('You do not have site permissions to submit data files for consideration.'), + '#suffix' => '

', + ]; + + } + + return $page; +} + +/** + * TripalImporter submission form. + * + * @param array $form + * The form array. + * @param array $form_state + * The state of the form. + * + * @return array + * Renderable array. + */ +function tripal_hq_user_importer_form(array $form, array &$form_state) { $importer_class = $form_state['build_info']['args'][0]; $success = tripal_hq_load_include_importer_class($importer_class); $importer_label = $importer_class::$name; @@ -122,11 +121,12 @@ function tripal_hq_user_importer_form($form, &$form_state) { $form = tripal_get_importer_form($form, $importer_form_state, $importer_class); // Most importers don't actually fill in defaults since they expect to be - // sumbitted only once. As such, we should still fill in the defaults ourselves. + // sumbitted only once. As such, we should still fill in the + // defaults ourselves. if (isset($importer_form_state['values'])) { // Ensure people do not try to edit submissions which are no longer pending. - if (($op != 'edit') OR ($status != 'pending')) { + if (($op != 'edit') or ($status != 'pending')) { $op = 'view'; } @@ -163,9 +163,8 @@ function tripal_hq_user_importer_form_submit($form, $form_state) { if ($sid) { // @todo currently we cant use tripal_hq_send_emails() since it retrieves - // the submission with the assumption of db table. - //tripal_hq_send_emails($sid, 'submit'); - + // the submission with the assumption of db table. + // tripal_hq_send_emails($sid, 'submit'); if (isset($form_state['values']['submission_id'])) { drupal_set_message( 'Submission created successfully. We will review your submission and get diff --git a/tripal_hq_imports/tripal_hq_imports.install b/tripal_hq_imports/tripal_hq_imports.install index 874fe76..68a83bc 100644 --- a/tripal_hq_imports/tripal_hq_imports.install +++ b/tripal_hq_imports/tripal_hq_imports.install @@ -1,4 +1,5 @@ 'Store pending user requests to import files.', - 'fields' => [ - 'id' => [ - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - ], - 'uid' => [ - 'description' => "Drupal user ID of submitter", - 'type' => 'int', - 'not null' => TRUE, - ], - 'nid' => [ - 'description' => "Comments node ID", - 'type' => 'int', - 'not null' => FALSE, - ], - 'job_id' => [ - 'description' => "The id of the tripal job which ran the importer once approved.", - 'type' => 'int', - 'not null' => FALSE, - ], - 'class' => [ - 'description' => "The importer class the file and metadata is associated with.", - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ], - 'data' => [ - 'description' => 'Serialized tripal importer data.', - 'type' => 'blob', - 'size' => 'big', - 'serialize' => TRUE, - ], - 'status' => [ - 'description' => 'One of pending, published, rejected, obsolete', - 'type' => 'varchar', - 'length' => '60', - 'not null' => TRUE, - ], - 'created_at' => [ - 'description' => 'Date submission created', - 'type' => 'int', - 'size' => 'big', - 'not null' => TRUE, - ], - 'updated_at' => [ - 'description' => 'Date submission updated', - 'type' => 'int', - 'size' => 'big', - 'not null' => FALSE, - ], - ], - 'primary key' => [ - 'id', - ], - ]; + $schema['tripal_hq_importer_submission'] = [ + 'description' => 'Store pending user requests to import files.', + 'fields' => [ + 'id' => [ + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ], + 'uid' => [ + 'description' => "Drupal user ID of submitter", + 'type' => 'int', + 'not null' => TRUE, + ], + 'nid' => [ + 'description' => "Comments node ID", + 'type' => 'int', + 'not null' => FALSE, + ], + 'job_id' => [ + 'description' => "The id of the tripal job which ran the importer once approved.", + 'type' => 'int', + 'not null' => FALSE, + ], + 'class' => [ + 'description' => "The importer class the file and metadata is associated with.", + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ], + 'data' => [ + 'description' => 'Serialized tripal importer data.', + 'type' => 'blob', + 'size' => 'big', + 'serialize' => TRUE, + ], + 'status' => [ + 'description' => 'One of pending, published, rejected, obsolete', + 'type' => 'varchar', + 'length' => '60', + 'not null' => TRUE, + ], + 'created_at' => [ + 'description' => 'Date submission created', + 'type' => 'int', + 'size' => 'big', + 'not null' => TRUE, + ], + 'updated_at' => [ + 'description' => 'Date submission updated', + 'type' => 'int', + 'size' => 'big', + 'not null' => FALSE, + ], + ], + 'primary key' => [ + 'id', + ], + ]; - return $schema; + return $schema; } From ed142537235812125558388b50563c7caf3461b7 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Fri, 1 Nov 2019 20:39:37 -0600 Subject: [PATCH 32/36] Add helpful tips to make contribution easier. --- docs/contributing.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/contributing.rst b/docs/contributing.rst index d75009a..b361404 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -13,3 +13,23 @@ It provides a database seeder to make development a bit easier. Once you've inst .. warning:: **NEVER** run seeders on production sites. They will insert fictitious data into Chado. + +Coding Standards +------------------ + +This project uses code climate to ensure coding standards are met. We suggest you use php_codesniffer locally to check coding standards before submitting a Pull Request for a smoother experience. This can be done as follows: + +1. Run ``composer up`` within the Tripal HQ directory. This will install php_codesniffer locally. +2. Check coding standards by running ``./vendor/bin/phpcs --standard=vendor/drupal/coder/coder_sniffer/Drupal/ruleset.xml [file]`` where ``[file]`` contains your changes. This will output a report meant to help you improve your code. +3. php_codesniffer includes a tool for automatically fixing many warnings you may have encountered. To run it execute ``./vendor/bin/phpcbf --standard=vendor/drupal/coder/coder_sniffer/Drupal/ruleset.xml [file]`` on the same file. Make sure to review any changes it makes. +4. Manually fix any remaining errors and re-run step 2 to confirm. + +We truely appretiate your effort in keeping our project standards compliant! + +Automated Testing +------------------- + +This project uses TripalTestSuite and phpunit for automated testing. To run tests: + +1. Run ``composer up`` within the Tripal HQ directory. This will install phpunit locally. +2. Run ``.vendor/bin/phpunit`` to execute all tests. From 1d3cacd97fb40414a271dc5f975a452585232dd4 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 12 Nov 2019 13:35:57 -0600 Subject: [PATCH 33/36] Fix comment count display. --- .../includes/tripal_hq_imports_admin_dashboard.form.inc | 6 +++--- .../includes/tripal_hq_imports_user_dashboard.form.inc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc index 9a128f7..3396fa5 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -112,7 +112,7 @@ $comments_link = $comment_count; if ($request->nid) { $comments_link = l( - t('Add/View Comments (:count)', [':count' => $comment_count]), + t('Add/View Comments (@count)', ['@count' => $comment_count]), 'node/' . $request->nid ); } @@ -142,8 +142,8 @@ '#header' => $header, '#rows' => $rows, '#caption' => '

' . t('Data Files') . '

', - '#empty' => t('There are no :status data file submissions.', - [':status' => $status]), + '#empty' => t('There are no @status data file submissions.', + ['@status' => $status]), ]; $form['importer_pager'] = [ diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc index 2580738..4d94508 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc @@ -71,7 +71,7 @@ function tripal_hq_imports_tripal_hq_user_dashboard_alter(&$form, &$form_state) $comments_link = $comment_count; if ($submission->nid) { $comments_link = l( - t('Add/View Comments (:count)', [':count' => $comment_count]), + t('Add/View Comments (@count)', ['@count' => $comment_count]), 'node/' . $submission->nid ); } From d191c81b149e916fea461ee2120fd3909683847f Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 12 Nov 2019 13:36:18 -0600 Subject: [PATCH 34/36] Remove unsupported importers: obo and bulk publication. --- .../includes/tripal_hq_imports_user_data.form.inc | 11 +++++++++++ tripal_hq_imports/tripal_hq_imports.module | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc index 361297d..330d6da 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -33,6 +33,11 @@ $importer_machine_name = $importer_class::$machine_name; $importer_description = $importer_class::$description; + // Publication importer is not supported. + if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + continue; + } + // Ensure the user has permission to propose that file type. if (($user->uid === 0) OR (!user_access("propose $importer_label"))) { continue; @@ -77,6 +82,12 @@ function tripal_hq_user_importer_form($form, &$form_state) { $importer_class = $form_state['build_info']['args'][0]; $success = tripal_hq_load_include_importer_class($importer_class); $importer_label = $importer_class::$name; + global $user; + + // Tripal Bulk publication importer not supported. + if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + return drupal_not_found(); + } // Check if we should allow editing/viewing of an existing submission. if (isset($form_state['build_info']['args'][1])) { diff --git a/tripal_hq_imports/tripal_hq_imports.module b/tripal_hq_imports/tripal_hq_imports.module index 88b24c8..c5c1a28 100644 --- a/tripal_hq_imports/tripal_hq_imports.module +++ b/tripal_hq_imports/tripal_hq_imports.module @@ -83,6 +83,11 @@ function tripal_hq_imports_permission() { $importer_machine_name = $importer_class::$machine_name; $importer_description = $importer_class::$description; + // Publication importer is not supported. + if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + continue; + } + $permissions['propose ' . $importer_label] = [ 'title' => t( '%label: Propose Tripal HQ Data File', ['%label' => $importer_label] From 9150dfd5f03d3d2b60012c3420d2f73304807fc8 Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 12 Nov 2019 13:40:28 -0600 Subject: [PATCH 35/36] Disable local file upload. --- .../includes/tripal_hq_imports_user_data.form.inc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc index 330d6da..4c6c810 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -131,6 +131,12 @@ function tripal_hq_user_importer_form($form, &$form_state) { module_load_include('inc', 'tripal', 'includes/tripal.importer'); $form = tripal_get_importer_form($form, $importer_form_state, $importer_class); + // Disable local and remote file uplaod for security reasons. + if (isset($form['file'])) { + $form['file']['file_local']['#disabled'] = TRUE; + $form['file']['file_remote']['#disabled'] = TRUE; + } + // Most importers don't actually fill in defaults since they expect to be // sumbitted only once. As such, we should still fill in the defaults ourselves. if (isset($importer_form_state['values'])) { From f4e8066946de23d6123c3136cc40f7f49d90ce7d Mon Sep 17 00:00:00 2001 From: Lacey Sanderson Date: Tue, 12 Nov 2019 13:49:23 -0600 Subject: [PATCH 36/36] Code style fixes. --- .../includes/tripal_hq_imports_user_data.form.inc | 6 +++--- tripal_hq_imports/tripal_hq_imports.module | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc index 494d8d8..bda2e33 100644 --- a/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -34,10 +34,10 @@ function tripal_hq_import_list_importers_page() { $importer_description = $importer_class::$description; // Publication importer is not supported. - if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + if ($importer_class == 'PubBulkImporter' or $importer_class == 'OBOImporter') { continue; } - + // Ensure the user has permission to propose that file type. if (($user->uid === 0) or (!user_access("propose $importer_label"))) { continue; @@ -84,7 +84,7 @@ function tripal_hq_user_importer_form(array $form, array &$form_state) { global $user; // Tripal Bulk publication importer not supported. - if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + if ($importer_class == 'PubBulkImporter' or $importer_class == 'OBOImporter') { return drupal_not_found(); } diff --git a/tripal_hq_imports/tripal_hq_imports.module b/tripal_hq_imports/tripal_hq_imports.module index c5c1a28..3484d21 100644 --- a/tripal_hq_imports/tripal_hq_imports.module +++ b/tripal_hq_imports/tripal_hq_imports.module @@ -84,7 +84,7 @@ function tripal_hq_imports_permission() { $importer_description = $importer_class::$description; // Publication importer is not supported. - if ($importer_class == 'PubBulkImporter' OR $importer_class == 'OBOImporter') { + if ($importer_class == 'PubBulkImporter' or $importer_class == 'OBOImporter') { continue; }