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/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 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 841fe71..068563e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,34 @@ "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": [ { "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,43 @@ } ], "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": "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", @@ -113,27 +146,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 +179,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 +208,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 +263,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 +330,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 +378,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 +484,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 +532,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 +583,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 +629,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 +664,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -657,7 +693,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 +851,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 +872,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -860,20 +896,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 +922,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -909,20 +945,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 +976,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 +1029,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 +1132,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 +1168,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 +1281,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 +1333,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 +1386,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 +1426,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -1346,17 +1438,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 +1453,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", @@ -1648,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", @@ -1698,37 +1864,43 @@ }, { "name": "symfony/console", - "version": "v4.2.2", + "version": "v4.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522" + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522", - "reference": "b0a03c1bb0fcbe288629956cf2f1dd3f1dc97522", + "url": "https://api.github.com/repos/symfony/console/zipball/136c4bd62ea871d00843d1bc0316de4c4a84bb78", + "reference": "136c4bd62ea871d00843d1bc0316de4c4a84bb78", "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 +1908,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1763,48 +1935,40 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-01-04T15:13:53+00:00" + "time": "2019-10-30T12:58: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 +1977,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 +2035,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 +2102,145 @@ "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": "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.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 +2267,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 +2288,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 +2317,7 @@ "check", "validate" ], - "time": "2018-12-25T11:19:39+00:00" + "time": "2019-08-24T08:43:50+00:00" } ], "aliases": [], diff --git a/docs/_static/img/imports_userdash.png b/docs/_static/img/imports_userdash.png new file mode 100644 index 0000000..13ea942 Binary files /dev/null and b/docs/_static/img/imports_userdash.png differ 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. diff --git a/docs/introduction.rst b/docs/introduction.rst index 8148925..aff757e 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -9,12 +9,14 @@ What is Tripal HQ? Tripal HQ provides a user-contributed content control center and administrative toolbox for your Tripal site. This means that users are able to create whatever Chado content you'd like them, but withhold inserting it into the database until someone has approved it. +Tripal HQ Imports extends this functionality to data importers. In this way your users can submit any single-page Tripal importer and both the file and associated metadata will remain in holding for administrative review. Once approved, the importer will be run, inserting all data into the database. + HQ also provides a Chado-specific permission (currently only supporting organisms). If you have lots of organisms on your site, and want to allow users to be the admin of a specific organism or set of organisms, this feature is for you! 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 diff --git a/docs/setup/install.rst b/docs/setup/install.rst index 143437d..529f67a 100644 --- a/docs/setup/install.rst +++ b/docs/setup/install.rst @@ -13,7 +13,7 @@ Clone the repo with github and enable the module with drush. For example: cd /var/www/html/sites/all/modules/custom/ git clone https://github.com/statonlab/tripal_hq.git - drush pm-enable tripal_hq tripal_hq_permissions + drush pm-enable tripal_hq tripal_hq_imports tripal_hq_permissions diff --git a/docs/setup/permissions.rst b/docs/setup/permissions.rst index 06ab23f..33ec9ba 100644 --- a/docs/setup/permissions.rst +++ b/docs/setup/permissions.rst @@ -14,6 +14,7 @@ On installation, HQ defines the following permissions: * **Administer CHADO-specific Tripal HQ content**: Lets you specify what Chado content a user can administer. * **Create Tripal HQ content requests**: Allows users to submit content requests and view their dashboard. * **Propose Tripal HQ Content** permission for each of your defined bundles. This will let you configure which bundles can be proposed by users. +* **Propose Tripal HQ Data File** permission for each available Tripal Importer. This will let you configure which importers can be proposed by users. To get started, you'll need to create a role for your content submitters, and give them the "Create Tripal Content Requests" permission, plus whatever specific bundle permissions you'd like them to see. diff --git a/docs/usage.rst b/docs/usage.rst index e2adedb..d2d4b48 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -5,6 +5,9 @@ Module Usage Users ======= +Content Pages +--------------- + User content submission can be reached at ``tripal_hq/bio_data``. @@ -14,6 +17,17 @@ Approved, pending, and rejected content is displayed in the table. Clicking the Click the "Submit Content" button to submit Tripal Content. The submission form is the **exact same form** as the admin content creation form. +Data Files +------------ + +Data files can be submitted by users at ``tripal_hq/bio_data/import-data``. + +.. image:: /_static/img/imports_userdash.png + +As you can see in the above screenshot when Tripal HQ Imports is enabled, it adds a second table to the user dashboard. This makes it easy to see the status of both your content and data file submissions. + +To submit a new data file with associated metadata, click "Import data file". You can edit a pending submission by clicking the "Edit" link beside that particular data file. Both the submission add and edit forms match the administrative data import forms exactly which ensures consistent metadata and validation. + Admins ======= 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/tests/tripal_hq_imports/adminDashboardTest.php b/tests/tripal_hq_imports/adminDashboardTest.php new file mode 100644 index 0000000..d597a27 --- /dev/null +++ b/tests/tripal_hq_imports/adminDashboardTest.php @@ -0,0 +1,56 @@ +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/tripal_hq_imports/apiTest.php b/tests/tripal_hq_imports/apiTest.php new file mode 100644 index 0000000..7972caf --- /dev/null +++ b/tests/tripal_hq_imports/apiTest.php @@ -0,0 +1,282 @@ +assertIsArray($importers); + $this->assertNotEmpty($importers); + } + + /** + * Tests tripal_hq_load_include_importer_class(). + * + * @group api + */ + 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); + } + + /** + * 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 = [ + '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/tripal_hq_imports/approveRejectTest.php b/tests/tripal_hq_imports/approveRejectTest.php new file mode 100644 index 0000000..603d4dc --- /dev/null +++ b/tests/tripal_hq_imports/approveRejectTest.php @@ -0,0 +1,258 @@ +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(). + * + * @group admin + * @group approve-reject + * @group hq-imports + */ + 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/tripal_hq_imports/moduleFileTest.php b/tests/tripal_hq_imports/moduleFileTest.php new file mode 100644 index 0000000..e267356 --- /dev/null +++ b/tests/tripal_hq_imports/moduleFileTest.php @@ -0,0 +1,46 @@ +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."); + } + } + + /** + * Tests hook_permission(). Specifically, are all the required keys set. + * + * @group core-hook + * @group hq-imports + */ + 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/tripal_hq_imports/userDashboardTest.php b/tests/tripal_hq_imports/userDashboardTest.php new file mode 100644 index 0000000..0a765de --- /dev/null +++ b/tests/tripal_hq_imports/userDashboardTest.php @@ -0,0 +1,43 @@ +fields([ + 'uid' => $user->uid, + 'nid' => 1, + 'class' => 'GFF3Importer', + 'data' => $data, + 'status' => 'approved', + '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/tripal_hq_imports/userDataFormTest.php b/tests/tripal_hq_imports/userDataFormTest.php new file mode 100644 index 0000000..7ea6d14 --- /dev/null +++ b/tests/tripal_hq_imports/userDataFormTest.php @@ -0,0 +1,392 @@ +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. + $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."); + } + + /** + * Tests tripal_hq_user_importer_form(). + * + * @group submit-form + * @group user + */ + public function testUserImporterForm() { + $this->actingAs(1); + + // 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(). + * + * @group submit-form + * @group user + */ + public function testUserImporterEDITForm() { + $this->actingAs(1); + 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(). + * + * @group submit-form + * @group user + */ + 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(). + * + * @group submit-form + * @group user + */ + 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, + 'no_redirect' => TRUE, + '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."); + } + + /** + * Tests tripal_hq_user_importer_form_submit(). + * + * @group submit-form + * @group user + */ + 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, + 'no_redirect' => TRUE, + '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."); + } + + /** + * 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"); + + } +} 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_imports/README.md b/tripal_hq_imports/README.md new file mode 100644 index 0000000..a0e08bf --- /dev/null +++ b/tripal_hq_imports/README.md @@ -0,0 +1,31 @@ +# Tripal Headquarters: 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. + +## Features + + - 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. + +## Usage + +### 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/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. 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 + +## 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. diff --git a/tripal_hq_imports/includes/tripal_hq_imports.api.inc b/tripal_hq_imports/includes/tripal_hq_imports.api.inc new file mode 100644 index 0000000..71e0eba --- /dev/null +++ b/tripal_hq_imports/includes/tripal_hq_imports.api.inc @@ -0,0 +1,195 @@ +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. + * + * NOTE: We use this function rather then tripal_load_include_importer_class() + * directly due to a bug in core which caused tests to fail. This function + * will use newer versions of the function but fall back to our fix if an + * older version of Tripal is used. This is great for backwards compatiblity. + * + * @param string $class + * The TripalImporter class to include. + * + * @return bool + * TRUE if the field type class file was found, FALSE otherwise. + */ +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; +} + +/** + * Returns the full submission. + * + * @param int $submission_id + * The id of the submission you would like returned. + * + * @return object + * 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 object $submission + * An object describing the submission to be rejected. + * + * @return bool + * 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 object $submission + * An object describing the submission to be approved. + * + * @return bool + * Returns FALSE if an error is encountered, otherwise TRUE. + */ +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); + + return 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. + * + * @param array $form_field + * 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(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) { + 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/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc new file mode 100644 index 0000000..9d9b56f --- /dev/null +++ b/tripal_hq_imports/includes/tripal_hq_imports_admin_dashboard.form.inc @@ -0,0 +1,156 @@ +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, + ]; + + 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 new file mode 100644 index 0000000..a10adc0 --- /dev/null +++ b/tripal_hq_imports/includes/tripal_hq_imports_approve.form.inc @@ -0,0 +1,141 @@ + 'markup', + '#markup' => '
We were unable to find the submission. Please contact an administrator.
', + ]; + return $form; + } + + $importer_class = $submission->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 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(array $form, array &$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 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(array $form, array &$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'); + if (!isset($form_state['no_redirect'])) { + 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/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc new file mode 100644 index 0000000..d60401f --- /dev/null +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_dashboard.form.inc @@ -0,0 +1,110 @@ +' . 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 new file mode 100644 index 0000000..bda2e33 --- /dev/null +++ b/tripal_hq_imports/includes/tripal_hq_imports_user_data.form.inc @@ -0,0 +1,251 @@ + '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; + + // 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; + } + $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; + 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])) { + $submission_id = $form_state['build_info']['args'][1]; + $op = $form_state['build_info']['args'][2]; + + $submission = tripal_hq_imports_get_submission_by_id($submission_id); + 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; + } + + // Ensure the user has permission to propose that file type. + 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); + + // Retrieve the form for the importer. + 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'])) { + + // 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; +} + +/** + * Validate the user submission. + */ +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); + + // 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) { + + $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.' + ); + } + + if (!isset($form_state['no_redirect'])) { + 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); + $success = db_update('tripal_hq_importer_submission') + ->fields([ + 'data' => $data, + 'updated_at' => time(), + ]) + ->condition('id', $sid) + ->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); + $sid = db_insert('tripal_hq_importer_submission') + ->fields([ + 'uid' => $user->uid, + 'nid' => $nid, + 'class' => $importer_class, + 'data' => $data, + 'status' => 'pending', + 'created_at' => time(), + ])->execute(); + } + + return $sid; +} diff --git a/tripal_hq_imports/tripal_hq_imports.info b/tripal_hq_imports/tripal_hq_imports.info new file mode 100644 index 0000000..9f14eac --- /dev/null +++ b/tripal_hq_imports/tripal_hq_imports.info @@ -0,0 +1,7 @@ +name = Tripal Headquarters Imports +description = Provides support for data loaders. +core = 7.x +package = Tripal Extensions: Tripal Headquarters +dependencies[] = tripal +dependencies[] = tripal_chado +dependencies[] = tripal_hq diff --git a/tripal_hq_imports/tripal_hq_imports.install b/tripal_hq_imports/tripal_hq_imports.install new file mode 100644 index 0000000..68a83bc --- /dev/null +++ b/tripal_hq_imports/tripal_hq_imports.install @@ -0,0 +1,73 @@ + '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; +} diff --git a/tripal_hq_imports/tripal_hq_imports.module b/tripal_hq_imports/tripal_hq_imports.module new file mode 100644 index 0000000..3484d21 --- /dev/null +++ b/tripal_hq_imports/tripal_hq_imports.module @@ -0,0 +1,102 @@ + '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', + ]; + + $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', + ]; + + $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; +} + +/** + * 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; + + // 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] + ), + '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; +} 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