From afc7c4f6c9526ffeadbee79ad7ec35c12fe680c3 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 15 Mar 2019 07:36:09 +0300 Subject: [PATCH] Working prototype --- .gitignore | 2 + docker/.gitignore | 1 + docker/Dockerfile | 143 ++ docker/docker-compose.yml | 43 + docs/backup_config.txt | 100 ++ docs/roadmap.md | 39 + email-patch/AuthorForm.inc.php.patch | 11 + email-patch/README.md | 13 + email-patch/authorForm.patch | 10 + email-patch/source/AuthorForm.inc.old.php | 233 ++++ email-patch/source/AuthorForm.inc.php | 233 ++++ email-patch/source/authorForm.old.tpl | 66 + email-patch/source/authorForm.tpl | 67 + email-patch/source/userDetails.old.tpl | 188 +++ email-patch/source/userDetails.tpl | 194 +++ email-patch/userDetails.patch | 18 + package-lock.json | 1436 +++++++++++++++++++++ package.json | 33 + src/UPLOAD_ISSUES.bat | 3 + src/helpers.js | 5 + src/importer.js | 227 ++++ src/index.js | 34 + src/ojs/client.js | 186 +++ src/ojs/endpoints.js | 36 + src/ojs/form-data.js | 124 ++ src/ojs/issue.js | 56 + src/ojs/journal.js | 83 ++ src/ojs/submission.js | 452 +++++++ 28 files changed, 4036 insertions(+) create mode 100644 .gitignore create mode 100644 docker/.gitignore create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml create mode 100644 docs/backup_config.txt create mode 100644 docs/roadmap.md create mode 100644 email-patch/AuthorForm.inc.php.patch create mode 100644 email-patch/README.md create mode 100644 email-patch/authorForm.patch create mode 100644 email-patch/source/AuthorForm.inc.old.php create mode 100644 email-patch/source/AuthorForm.inc.php create mode 100644 email-patch/source/authorForm.old.tpl create mode 100644 email-patch/source/authorForm.tpl create mode 100644 email-patch/source/userDetails.old.tpl create mode 100644 email-patch/source/userDetails.tpl create mode 100644 email-patch/userDetails.patch create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/UPLOAD_ISSUES.bat create mode 100644 src/helpers.js create mode 100644 src/importer.js create mode 100644 src/index.js create mode 100644 src/ojs/client.js create mode 100644 src/ojs/endpoints.js create mode 100644 src/ojs/form-data.js create mode 100644 src/ojs/issue.js create mode 100644 src/ojs/journal.js create mode 100644 src/ojs/submission.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef7ab5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +samples/**/* \ No newline at end of file diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000..cd86fb9 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1 @@ +files/**/* \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..2bd16fe --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,143 @@ +FROM alpine:3.7 + +LABEL maintainer="Lucas G. Diedrich " + +WORKDIR /var/www/html + +ENV COMPOSER_ALLOW_SUPERUSER=1 \ + SERVERNAME="localhost" \ + OJS_VERSION="ojs-3_1_1-4" \ + OJS_CLI_INSTALL="1" \ + OJS_DB_HOST="mysql-ojs" \ + OJS_DB_USER="ojs" \ + OJS_DB_PASSWORD="ojs" \ + OJS_DB_NAME="ojs" \ + OJS_WEB_CONF="/etc/apache2/conf.d/ojs.conf" \ + OJS_CONF="/var/www/html/config.inc.php" \ + PACKAGES="supervisor dcron apache2 apache2-ssl apache2-utils php5 php5-fpm php5-cli php5-apache2 php5-zlib \ + php5-json php5-phar php5-openssl php5-mysql php5-curl php5-mcrypt php5-pdo_mysql php5-ctype \ + php5-gd php5-xml php5-dom php5-iconv curl nodejs git nano" \ + EXCLUDE="dbscripts/xml/data/locale/en_US/sample.xml \ + dbscripts/xml/data/sample.xml \ + docs/dev \ + tests \ + tools/buildpkg.sh \ + tools/genLocaleReport.sh \ + tools/genTestLocale.php \ + tools/test \ + lib/pkp/tools/travis \ + lib/pkp/plugins/*/*/tests \ + plugins/*/*/tests \ + plugins/auth/ldap \ + plugins/generic/announcementFeed \ + plugins/generic/backup \ + plugins/generic/browse \ + plugins/generic/coins \ + plugins/generic/cookiesAlert \ + plugins/generic/counter \ + plugins/generic/customLocale \ + plugins/generic/externalFeed \ + plugins/generic/lucene \ + plugins/generic/phpMyVisites \ + plugins/generic/recommendBySimilarity \ + plugins/generic/translator \ + plugins/importexport/sample \ + plugins/importexport/duracloud \ + plugins/reports/subscriptions \ + plugins/blocks/relatedItems \ + plugins/oaiMetadataFormats/jats \ + tests \ + lib/pkp/tests \ + .git \ + .openshift \ + .scrutinizer.yml \ + .travis.yml \ + lib/pkp/.git \ + lib/pkp/lib/components/*.js \ + lib/pkp/lib/components/*.css \ + lib/pkp/js/lib/pnotify/build-tools \ + lib/pkp/lib/vendor/alex198710/pnotify/.git \ + lib/pkp/lib/vendor/sebastian \ + lib/pkp/lib/vendor/oyejorge/less.php/test \ + lib/pkp/tools/travis \ + lib/pkp/lib/swordappv2/.git \ + lib/pkp/lib/swordappv2/.git \ + lib/pkp/lib/swordappv2/test \ + plugins/paymethod/paypal/vendor/omnipay/common/tests/ \ + plugins/paymethod/paypal/vendor/omnipay/paypal/tests/ \ + plugins/paymethod/paypal/vendor/guzzle/guzzle/docs/ \ + plugins/paymethod/paypal/vendor/guzzle/guzzle/tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/debug/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/console/Tests/ \ + plugins/paymethod/paypal/vendor/symfony/http-foundation/Tests/ \ + plugins/paymethod/paypal/vendor/symfony/event-dispatcher/ \ + plugins/paymethod/paypal/vendor/guzzle/guzzle/tests/Guzzle/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/filesystem/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/stopwatch/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/event-dispatcher/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/config/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/yaml/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/guzzle/guzzle/tests/Guzzle/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/symfony/config/Tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/citation-style-language/locales/.git \ + lib/pkp/lib/vendor/symfony/translation/Tests/ \ + lib/pkp/lib/vendor/symfony/process/Tests/ \ + lib/pkp/lib/vendor/pimple/pimple/src/Pimple/Tests/ \ + lib/pkp/lib/vendor/robloach/component-installer/tests/ComponentInstaller/Test/ \ + plugins/generic/citationStyleLanguage/lib/vendor/satooshi/php-coveralls/tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/guzzle/guzzle/tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/seboettg/collection/tests/ \ + plugins/generic/citationStyleLanguage/lib/vendor/seboettg/citeproc-php/tests/ \ + lib/pkp/lib/vendor/nikic/fast-route/test/ \ + lib/pkp/lib/vendor/ezyang/htmlpurifier/tests/ \ + lib/pkp/lib/vendor/ezyang/htmlpurifier/smoketests/ \ + lib/pkp/lib/vendor/pimple/pimple/ext/pimple/tests/ \ + lib/pkp/lib/vendor/robloach/component-installer/tests/ \ + lib/pkp/lib/vendor/phpmailer/phpmailer/test/ \ + node_modules \ + .babelrc \ + .editorconfig \ + .eslintignore \ + .eslintrc.js \ + .postcssrc.js \ + package.json \ + webpack.config.js \ + lib/ui-library \ + /usr/local/bin/composer \ + /root/.composer \ + /root/.npm \ + /var/cache/apk/* " + +RUN apk add --update --no-cache $PACKAGES && \ + ln -s /usr/bin/php5 /usr/bin/php && \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ + # Configure and download code from git + git config --global url.https://.insteadOf git:// && \ + git config --global advice.detachedHead false && \ + git clone --depth 1 --single-branch --branch $OJS_VERSION --progress https://github.com/pkp/ojs.git . && \ + git submodule update --init --recursive >/dev/null && \ + # Install NPM and Composer Deps + composer update -d lib/pkp --no-dev && \ + composer install -d plugins/paymethod/paypal --no-dev && \ + composer install -d plugins/generic/citationStyleLanguage --no-dev && \ + npm install -y && npm run build && \ + # Create directories + mkdir -p /var/www/html/files /run/apache2 /run/supervisord/ && \ + cp config.TEMPLATE.inc.php config.inc.php && \ + chown -R apache:apache /var/www/* && \ + # Prepare crontab + echo "0 * * * * ojs-run-scheduled" | crontab - && \ + # Prepare httpd.conf + sed -i -e '\##,\##d' /etc/apache2/httpd.conf && \ + sed -i -e "s/^ServerSignature.*/ServerSignature Off/" /etc/apache2/httpd.conf && \ + # Clear the image + apk del --no-cache nodejs git && rm -rf $EXCLUDE && \ + find . \( -name .gitignore -o -name .gitmodules -o -name .keepme \) -exec rm '{}' \; + +COPY files/ / + +EXPOSE 80 443 + +VOLUME [ "/var/www/html/files", "/var/www/html/public" ] + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..a7a8641 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,43 @@ +version: "3.3" +services: + mysql-ojs-3114: + image: mysql:5.7 + container_name: mysql-ojs-3114 + restart: always + environment: + MYSQL_ROOT_PASSWORD: ojs + MYSQL_DATABASE: ojs + MYSQL_USER: ojs + MYSQL_PASSWORD: ojs + pkp-ojs-3114: + build: + context: . + dockerfile: Dockerfile + container_name : pkp-ojs-3114 + restart: always + ports: + - "8080:80" + - "8443:443" + volumes: + - /etc/localtime:/etc/localtime # to sync the container clock with localhost. + - ./files/private:/var/www/files # ojs' private files + - ./files/public:/var/www/html/public # ojs' public files + - ./files/logs:/var/log/apache2 # apache logs + + # - ./config/ojs.config.inc.php:/var/www/html/config.inc.php # ojs' config + # - ./config/apache.htaccess:/var/www/html/.htaccess # ojs' htaccess + # - ./config/php.custom.ini:/usr/local/etc/php/conf.d/custom.ini # php config + environment: + SERVERNAME: 'localhost' + OJS_CLI_INSTALL: 1 + OJS_DB_HOST: 'mysql-ojs-3114' + depends_on: + - mysql-ojs-3114 + # labels: + # - traefik.backend=ojs + # - traefik.frontend.rule=Host:myserver.mydomain + # - traefik.docker.network=proxy + # - traefik.port=8080 + # networks: + # - internal + # - proxy diff --git a/docs/backup_config.txt b/docs/backup_config.txt new file mode 100644 index 0000000..a24b9ab --- /dev/null +++ b/docs/backup_config.txt @@ -0,0 +1,100 @@ + +Информация о сервере +Задаем название Задаем значение +ОС платформы Linux +Версия PHP 5.6.32-5.6.32+mh2 +Версия Apache Apache +Драйвер базы данных mysql +Версия СУБД 5.6.40-log +Конфигурация OJS +Задаем название Задаем значение +general +installed включено +base_url http://pkp.sfu.ca/ojs +session_cookie_name OJSSID +session_lifetime 30 +scheduled_tasks отключено +time_zone UTC +date_format_trunc %m-%d +date_format_short %Y-%m-%d +date_format_long %B %e, %Y +datetime_format_short %Y-%m-%d %I:%M %p +datetime_format_long %B %e, %Y - %I:%M %p +time_format %I:%M %p +disable_path_info отключено +allow_url_fopen отключено +restful_urls отключено +trust_x_forwarded_for отключено +enable_cdn включено +citation_checking_max_processes 3 +show_upgrade_warning включено +enable_minified отключено +enable_beacon отключено +database +driver mysql +host u51131.mysql.masterhost.ru +username u51131_vp +password ser2po6ato +name u51131_ojs3 +persistent отключено +debug отключено +cache +object_cache none +memcache_hostname localhost +memcache_port 11211 +web_cache отключено +web_cache_hours 1 +i18n +locale ru_RU +client_charset utf-8 +connection_charset отключено +database_charset отключено +charset_normalization отключено +files +files_dir /home/u51131/ojs3.gpmu.org/uploads +public_files_dir public +umask 18 +filename_revision_match 70 +finfo +Нет элементов +security +force_ssl отключено +force_login_ssl отключено +session_check_ip включено +encryption sha1 +salt YouMustSetASecretKeyHere!! +api_key_secret +reset_seconds 7200 +allowed_html a[href|target|title],em,strong,cite,code,ul,ol,li[class],dl,dt,dd,b,i,u,img[src|alt],sup,sub,br,p +email +time_between_emails 3600 +max_recipients 10 +require_validation отключено +validation_timeout 14 +search +min_word_length 3 +results_per_keyword 500 +result_cache_hours 1 +oai +oai включено +repository_id repid_1 +oai_max_records 100 +interface +items_per_page 25 +page_links 10 +captcha +recaptcha отключено +recaptcha_public_key your_public_key +recaptcha_private_key your_private_key +captcha_on_register включено +cli +perl /usr/bin/perl +tar /bin/tar +xslt_command +proxy +Нет элементов +debug +show_stacktrace отключено +display_errors отключено +deprecation_warnings отключено +log_web_service_info отключено \ No newline at end of file diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..c0ee9c8 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,39 @@ +# Journals: +- [x] Creation +- [ ] Error handling + +# Issues: +- [x] Creation + + +# Submissions: +- Creation + - [x] Get userGroupId & sectionId + - [x] Step1: checkboxes + get submissionId + - [x] Step2: Get genreId + - [x] Step2: upload material + - [x] Step2: save + - [x] Step3: metadata (title, abstact, keywords) + - [x] Step4: confirmation + - [ ] Galley + - [ ] Add galley + - [ ] Get assocId & genreId from upload form + - [x] Add author + - [x] Get userGroupId + - [x] Add author info + - [ ] Delete default author (admin) + - [x] Attach to issue + +# Importer: +- [x] Select journal to upload issue to +- [ ] XML processing + - [x] Find all XMLs in provided directory + - [x] Validate and parse each XML + - [x] Collect info object +- [ ] Create journal +- [x] Create issue +- [ ] +- [ ] + + + diff --git a/email-patch/AuthorForm.inc.php.patch b/email-patch/AuthorForm.inc.php.patch new file mode 100644 index 0000000..954712d --- /dev/null +++ b/email-patch/AuthorForm.inc.php.patch @@ -0,0 +1,11 @@ +--- AuthorForm.inc.php 2019-03-13 06:36:56.547370700 +0300 ++++ AuthorForm.inc.old.php 2019-03-13 06:36:46.571352100 +0300 +@@ -37,7 +37,7 @@ + // Validation checks for this form + $this->addCheck(new FormValidator($this, 'firstName', 'required', 'submission.submit.form.authorRequiredFields')); + $this->addCheck(new FormValidator($this, 'lastName', 'required', 'submission.submit.form.authorRequiredFields')); +- $this->addCheck(new FormValidatorEmail($this, 'email', 'optional', 'form.emailRequired')); ++ $this->addCheck(new FormValidatorEmail($this, 'email', 'required', 'form.emailRequired')); + $this->addCheck(new FormValidatorUrl($this, 'userUrl', 'optional', 'user.profile.form.urlInvalid')); + $this->addCheck(new FormValidator($this, 'userGroupId', 'required', 'submission.submit.form.contributorRoleRequired')); + $this->addCheck(new FormValidatorORCID($this, 'orcid', 'optional', 'user.orcid.orcidInvalid')); diff --git a/email-patch/README.md b/email-patch/README.md new file mode 100644 index 0000000..8c49af3 --- /dev/null +++ b/email-patch/README.md @@ -0,0 +1,13 @@ +## OJS patch for email fields to be optional on author form during submission creation + +Tested only on OJS v. 3.1.1.2 and 3.1.1.4 +Patch is probably incompatible with later versions + +## Installation: + +### Either: + Copy userDetails.tpl to [/var/www/html/lib/pkp/templates/common] + Copy authorForm.tpl to [/var/www/html/lib/pkp/templates/controllers/grid/users/author/form] + Copy AuthorForm.inc.php to [/var/www/lib/pkp/controllers/grid/users/author/form] + +### OR: just apply goddamn patches \ No newline at end of file diff --git a/email-patch/authorForm.patch b/email-patch/authorForm.patch new file mode 100644 index 0000000..ac3731a --- /dev/null +++ b/email-patch/authorForm.patch @@ -0,0 +1,10 @@ +--- authorForm_old.tpl 2019-03-13 05:30:57.527000000 +0300 ++++ authorForm.tpl 2019-03-13 05:30:57.527000000 +0300 +@@ -36,6 +36,7 @@ + disableSignatureSection=true + extraContentSectionUnfolded=true + countryRequired=true ++ emailIsOptional=true + } + + {fbvFormArea id="submissionSpecific"} diff --git a/email-patch/source/AuthorForm.inc.old.php b/email-patch/source/AuthorForm.inc.old.php new file mode 100644 index 0000000..14cc462 --- /dev/null +++ b/email-patch/source/AuthorForm.inc.old.php @@ -0,0 +1,233 @@ +setSubmission($submission); + $this->setAuthor($author); + $this->setSubmissionIdFieldName($submissionIdFieldName); + + // Validation checks for this form + $this->addCheck(new FormValidator($this, 'firstName', 'required', 'submission.submit.form.authorRequiredFields')); + $this->addCheck(new FormValidator($this, 'lastName', 'required', 'submission.submit.form.authorRequiredFields')); + $this->addCheck(new FormValidatorEmail($this, 'email', 'required', 'form.emailRequired')); + $this->addCheck(new FormValidatorUrl($this, 'userUrl', 'optional', 'user.profile.form.urlInvalid')); + $this->addCheck(new FormValidator($this, 'userGroupId', 'required', 'submission.submit.form.contributorRoleRequired')); + $this->addCheck(new FormValidatorORCID($this, 'orcid', 'optional', 'user.orcid.orcidInvalid')); + $this->addCheck(new FormValidatorPost($this)); + $this->addCheck(new FormValidatorCSRF($this)); + } + + // + // Getters and Setters + // + /** + * Get the author + * @return Author + */ + function getAuthor() { + return $this->_author; + } + + /** + * Set the author + * @param @author Author + */ + function setAuthor($author) { + $this->_author = $author; + } + + /** + * Get the Submission + * @return Submission + */ + function getSubmission() { + return $this->_submission; + } + + /** + * Set the Submission + * @param Submission + */ + function setSubmission($submission) { + $this->_submission = $submission; + } + + /** + * Get the Submission Id field name + * @return String + */ + function getSubmissionIdFieldName() { + return $this->_submissionIdFieldName; + } + + /** + * Set the Submission Id field name + * @param String + */ + function setSubmissionIdFieldName($submissionIdFieldName) { + $this->_submissionIdFieldName = $submissionIdFieldName; + } + + + // + // Overridden template methods + // + /** + * Initialize form data from the associated author. + * @param $author Author + */ + function initData() { + $author = $this->getAuthor(); + + if ($author) { + $this->_data = array( + 'authorId' => $author->getId(), + 'firstName' => $author->getFirstName(), + 'middleName' => $author->getMiddleName(), + 'lastName' => $author->getLastName(), + 'suffix' => $author->getSuffix(), + 'affiliation' => $author->getAffiliation(null), // Localized + 'country' => $author->getCountry(), + 'email' => $author->getEmail(), + 'userUrl' => $author->getUrl(), + 'orcid' => $author->getOrcid(), + 'userGroupId' => $author->getUserGroupId(), + 'biography' => $author->getBiography(null), + 'primaryContact' => $author->getPrimaryContact(), + 'includeInBrowse' => $author->getIncludeInBrowse(), + ); + } else { + // assume authors should be listed unless otherwise specified. + $this->_data = array('includeInBrowse' => true); + } + // in order to be able to use the hook + return parent::initData(); + } + + /** + * Fetch the form. + * @see Form::fetch() + */ + function fetch($request) { + $author = $this->getAuthor(); + + $templateMgr = TemplateManager::getManager($request); + $countryDao = DAORegistry::getDAO('CountryDAO'); + $countries = $countryDao->getCountries(); + $templateMgr->assign('countries', $countries); + + $router = $request->getRouter(); + $context = $router->getContext($request); + + $userGroupDao = DAORegistry::getDAO('UserGroupDAO'); + $authorUserGroups = $userGroupDao->getByRoleId($context->getId(), ROLE_ID_AUTHOR); + $templateMgr->assign('authorUserGroups', $authorUserGroups); + + $submission = $this->getSubmission(); + $templateMgr->assign('submissionIdFieldName', $this->getSubmissionIdFieldName()); + $templateMgr->assign('submissionId', $submission->getId()); + + return parent::fetch($request); + } + + /** + * Assign form data to user-submitted data. + * @see Form::readInputData() + */ + function readInputData() { + $this->readUserVars(array( + 'authorId', + 'firstName', + 'middleName', + 'lastName', + 'suffix', + 'affiliation', + 'country', + 'email', + 'userUrl', + 'orcid', + 'userGroupId', + 'biography', + 'primaryContact', + 'includeInBrowse', + )); + } + + /** + * Save author + * @see Form::execute() + * @see Form::execute() + */ + function execute() { + $authorDao = DAORegistry::getDAO('AuthorDAO'); + $submission = $this->getSubmission(); + + $author = $this->getAuthor(); + if (!$author) { + // this is a new submission contributor + $this->_author = new Author(); + $author = $this->getAuthor(); + $author->setSubmissionId($submission->getId()); + $existingAuthor = false; + } else { + $existingAuthor = true; + if ($submission->getId() !== $author->getSubmissionId()) fatalError('Invalid author!'); + } + + $author->setFirstName($this->getData('firstName')); + $author->setMiddleName($this->getData('middleName')); + $author->setLastName($this->getData('lastName')); + $author->setSuffix($this->getData('suffix')); + $author->setAffiliation($this->getData('affiliation'), null); // localized + $author->setCountry($this->getData('country')); + $author->setEmail($this->getData('email')); + $author->setUrl($this->getData('userUrl')); + $author->setOrcid($this->getData('orcid')); + $author->setUserGroupId($this->getData('userGroupId')); + $author->setBiography($this->getData('biography'), null); // localized + $author->setPrimaryContact(($this->getData('primaryContact') ? true : false)); + $author->setIncludeInBrowse(($this->getData('includeInBrowse') ? true : false)); + + // in order to be able to use the hook + parent::execute(); + + if ($existingAuthor) { + $authorDao->updateObject($author); + $authorId = $author->getId(); + } else { + $authorId = $authorDao->insertObject($author); + } + + return $authorId; + } +} + +?> diff --git a/email-patch/source/AuthorForm.inc.php b/email-patch/source/AuthorForm.inc.php new file mode 100644 index 0000000..2555b28 --- /dev/null +++ b/email-patch/source/AuthorForm.inc.php @@ -0,0 +1,233 @@ +setSubmission($submission); + $this->setAuthor($author); + $this->setSubmissionIdFieldName($submissionIdFieldName); + + // Validation checks for this form + $this->addCheck(new FormValidator($this, 'firstName', 'required', 'submission.submit.form.authorRequiredFields')); + $this->addCheck(new FormValidator($this, 'lastName', 'required', 'submission.submit.form.authorRequiredFields')); + $this->addCheck(new FormValidatorEmail($this, 'email', 'optional', 'form.emailRequired')); + $this->addCheck(new FormValidatorUrl($this, 'userUrl', 'optional', 'user.profile.form.urlInvalid')); + $this->addCheck(new FormValidator($this, 'userGroupId', 'required', 'submission.submit.form.contributorRoleRequired')); + $this->addCheck(new FormValidatorORCID($this, 'orcid', 'optional', 'user.orcid.orcidInvalid')); + $this->addCheck(new FormValidatorPost($this)); + $this->addCheck(new FormValidatorCSRF($this)); + } + + // + // Getters and Setters + // + /** + * Get the author + * @return Author + */ + function getAuthor() { + return $this->_author; + } + + /** + * Set the author + * @param @author Author + */ + function setAuthor($author) { + $this->_author = $author; + } + + /** + * Get the Submission + * @return Submission + */ + function getSubmission() { + return $this->_submission; + } + + /** + * Set the Submission + * @param Submission + */ + function setSubmission($submission) { + $this->_submission = $submission; + } + + /** + * Get the Submission Id field name + * @return String + */ + function getSubmissionIdFieldName() { + return $this->_submissionIdFieldName; + } + + /** + * Set the Submission Id field name + * @param String + */ + function setSubmissionIdFieldName($submissionIdFieldName) { + $this->_submissionIdFieldName = $submissionIdFieldName; + } + + + // + // Overridden template methods + // + /** + * Initialize form data from the associated author. + * @param $author Author + */ + function initData() { + $author = $this->getAuthor(); + + if ($author) { + $this->_data = array( + 'authorId' => $author->getId(), + 'firstName' => $author->getFirstName(), + 'middleName' => $author->getMiddleName(), + 'lastName' => $author->getLastName(), + 'suffix' => $author->getSuffix(), + 'affiliation' => $author->getAffiliation(null), // Localized + 'country' => $author->getCountry(), + 'email' => $author->getEmail(), + 'userUrl' => $author->getUrl(), + 'orcid' => $author->getOrcid(), + 'userGroupId' => $author->getUserGroupId(), + 'biography' => $author->getBiography(null), + 'primaryContact' => $author->getPrimaryContact(), + 'includeInBrowse' => $author->getIncludeInBrowse(), + ); + } else { + // assume authors should be listed unless otherwise specified. + $this->_data = array('includeInBrowse' => true); + } + // in order to be able to use the hook + return parent::initData(); + } + + /** + * Fetch the form. + * @see Form::fetch() + */ + function fetch($request) { + $author = $this->getAuthor(); + + $templateMgr = TemplateManager::getManager($request); + $countryDao = DAORegistry::getDAO('CountryDAO'); + $countries = $countryDao->getCountries(); + $templateMgr->assign('countries', $countries); + + $router = $request->getRouter(); + $context = $router->getContext($request); + + $userGroupDao = DAORegistry::getDAO('UserGroupDAO'); + $authorUserGroups = $userGroupDao->getByRoleId($context->getId(), ROLE_ID_AUTHOR); + $templateMgr->assign('authorUserGroups', $authorUserGroups); + + $submission = $this->getSubmission(); + $templateMgr->assign('submissionIdFieldName', $this->getSubmissionIdFieldName()); + $templateMgr->assign('submissionId', $submission->getId()); + + return parent::fetch($request); + } + + /** + * Assign form data to user-submitted data. + * @see Form::readInputData() + */ + function readInputData() { + $this->readUserVars(array( + 'authorId', + 'firstName', + 'middleName', + 'lastName', + 'suffix', + 'affiliation', + 'country', + 'email', + 'userUrl', + 'orcid', + 'userGroupId', + 'biography', + 'primaryContact', + 'includeInBrowse', + )); + } + + /** + * Save author + * @see Form::execute() + * @see Form::execute() + */ + function execute() { + $authorDao = DAORegistry::getDAO('AuthorDAO'); + $submission = $this->getSubmission(); + + $author = $this->getAuthor(); + if (!$author) { + // this is a new submission contributor + $this->_author = new Author(); + $author = $this->getAuthor(); + $author->setSubmissionId($submission->getId()); + $existingAuthor = false; + } else { + $existingAuthor = true; + if ($submission->getId() !== $author->getSubmissionId()) fatalError('Invalid author!'); + } + + $author->setFirstName($this->getData('firstName')); + $author->setMiddleName($this->getData('middleName')); + $author->setLastName($this->getData('lastName')); + $author->setSuffix($this->getData('suffix')); + $author->setAffiliation($this->getData('affiliation'), null); // localized + $author->setCountry($this->getData('country')); + $author->setEmail($this->getData('email')); + $author->setUrl($this->getData('userUrl')); + $author->setOrcid($this->getData('orcid')); + $author->setUserGroupId($this->getData('userGroupId')); + $author->setBiography($this->getData('biography'), null); // localized + $author->setPrimaryContact(($this->getData('primaryContact') ? true : false)); + $author->setIncludeInBrowse(($this->getData('includeInBrowse') ? true : false)); + + // in order to be able to use the hook + parent::execute(); + + if ($existingAuthor) { + $authorDao->updateObject($author); + $authorId = $author->getId(); + } else { + $authorId = $authorDao->insertObject($author); + } + + return $authorId; + } +} + +?> diff --git a/email-patch/source/authorForm.old.tpl b/email-patch/source/authorForm.old.tpl new file mode 100644 index 0000000..d1508a4 --- /dev/null +++ b/email-patch/source/authorForm.old.tpl @@ -0,0 +1,66 @@ +{** + * templates/controllers/grid/users/author/form/authorForm.tpl + * + * Copyright (c) 2014-2018 Simon Fraser University + * Copyright (c) 2003-2018 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * Submission Contributor grid form + * + *} + + + +
+ {csrf} + {include file="controllers/notification/inPlaceNotification.tpl" notificationId="authorFormNotification"} + + {include + file="common/userDetails.tpl" + disableUserNameSection=true + disableAuthSourceSection=true + disablePasswordSection=true + disableSendNotifySection=true + disableSalutationSection=true + disableInitialsSection=true + disablePhoneSection=true + disableLocaleSection=true + disableInterestsSection=true + disableMailingSection=true + disableSignatureSection=true + extraContentSectionUnfolded=true + countryRequired=true + } + + {fbvFormArea id="submissionSpecific"} + {fbvFormSection id="userGroupId" title="submission.submit.contributorRole" list=true required=true} + {iterate from=authorUserGroups item=userGroup} + {if $userGroupId == $userGroup->getId()}{assign var="checked" value=true}{else}{assign var="checked" value=false}{/if} + {fbvElement type="radio" id="userGroup"|concat:$userGroup->getId() name="userGroupId" value=$userGroup->getId() checked=$checked label=$userGroup->getLocalizedName() translate=false} + {/iterate} + {/fbvFormSection} + {fbvFormSection list="true"} + {fbvElement type="checkbox" label="submission.submit.selectPrincipalContact" id="primaryContact" checked=$primaryContact} + {fbvElement type="checkbox" label="submission.submit.includeInBrowse" id="includeInBrowse" checked=$includeInBrowse} + {/fbvFormSection} + {/fbvFormArea} + + {if $submissionId} + + {/if} + {if $gridId} + + {/if} + {if $rowId} + + {/if} + +

{translate key="common.requiredField"}

+ {fbvFormButtons id="step2Buttons" submitText="common.save"} +
diff --git a/email-patch/source/authorForm.tpl b/email-patch/source/authorForm.tpl new file mode 100644 index 0000000..77e6dea --- /dev/null +++ b/email-patch/source/authorForm.tpl @@ -0,0 +1,67 @@ +{** + * templates/controllers/grid/users/author/form/authorForm.tpl + * + * Copyright (c) 2014-2018 Simon Fraser University + * Copyright (c) 2003-2018 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * Submission Contributor grid form + * + *} + + + +
+ {csrf} + {include file="controllers/notification/inPlaceNotification.tpl" notificationId="authorFormNotification"} + + {include + file="common/userDetails.tpl" + disableUserNameSection=true + disableAuthSourceSection=true + disablePasswordSection=true + disableSendNotifySection=true + disableSalutationSection=true + disableInitialsSection=true + disablePhoneSection=true + disableLocaleSection=true + disableInterestsSection=true + disableMailingSection=true + disableSignatureSection=true + extraContentSectionUnfolded=true + countryRequired=true + emailIsOptional=true + } + + {fbvFormArea id="submissionSpecific"} + {fbvFormSection id="userGroupId" title="submission.submit.contributorRole" list=true required=true} + {iterate from=authorUserGroups item=userGroup} + {if $userGroupId == $userGroup->getId()}{assign var="checked" value=true}{else}{assign var="checked" value=false}{/if} + {fbvElement type="radio" id="userGroup"|concat:$userGroup->getId() name="userGroupId" value=$userGroup->getId() checked=$checked label=$userGroup->getLocalizedName() translate=false} + {/iterate} + {/fbvFormSection} + {fbvFormSection list="true"} + {fbvElement type="checkbox" label="submission.submit.selectPrincipalContact" id="primaryContact" checked=$primaryContact} + {fbvElement type="checkbox" label="submission.submit.includeInBrowse" id="includeInBrowse" checked=$includeInBrowse} + {/fbvFormSection} + {/fbvFormArea} + + {if $submissionId} + + {/if} + {if $gridId} + + {/if} + {if $rowId} + + {/if} + +

{translate key="common.requiredField"}

+ {fbvFormButtons id="step2Buttons" submitText="common.save"} +
diff --git a/email-patch/source/userDetails.old.tpl b/email-patch/source/userDetails.old.tpl new file mode 100644 index 0000000..5afa20d --- /dev/null +++ b/email-patch/source/userDetails.old.tpl @@ -0,0 +1,188 @@ +{** + * templates/common/userDetails.tpl + * + * Copyright (c) 2014-2018 Simon Fraser University + * Copyright (c) 2003-2018 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * Common user details form. + * + * Parameters: + * $disableUserNameSection: Disable UserName section + * $disableEmailSection: Disable Email section + * $disableAuthSourceSection: Disable Auth section + * $disablePasswordSection: Disable Password section + * $disableSendNotifySection: Disable SendNotify section + * $disableSalutationSection: Disable Salutation section + * $disableInitialsSection: Disable Initials section + * $disablePhoneSection: Disable Phone section + * $disableLocaleSection: Disable Locale section + * $disableInterestsSection: Disable Interests section + * $disableMailingSection: Disable Mailing section + * $disableSignatureSection: Disable Signature section + * + * $countryRequired: Whether or not the country select is a required field + * $extraContentSectionUnfolded: Whether or not the extra content section is unfolded by default + *} + +{fbvFormArea id="userDetails"} + {fbvFormSection title="user.name"} + {fbvElement type="text" label="user.firstName" required="true" id="firstName" value=$firstName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {fbvElement type="text" label="user.middleName" id="middleName" value=$middleName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {fbvElement type="text" label="user.lastName" required="true" id="lastName" value=$lastName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {/fbvFormSection} + + {if !$disableUserNameSection} + {if !$userId}{capture assign="usernameInstruction"}{translate key="user.register.usernameRestriction"}{/capture}{/if} + {fbvFormSection for="username" description=$usernameInstruction translate=false} + {if !$userId} + {fbvElement type="text" label="user.username" id="username" required="true" value=$username maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {fbvElement type="button" label="common.suggest" id="suggestUsernameButton" inline=true class="default"} + {else} + {fbvFormSection title="user.username" suppressId="true"} + {$username|escape} + {/fbvFormSection} + {/if} + {/fbvFormSection} + {/if} + + {if !$disableEmailSection} + {fbvFormSection title="about.contact"} + {fbvElement type="text" label="user.email" id="email" required="true" value=$email maxlength="90" size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + {/if} + + {if !$disableAuthSourceSection} + {fbvFormSection title="grid.user.authSource" for="authId"} + {fbvElement type="select" name="authId" id="authId" defaultLabel="" defaultValue="" from=$authSourceOptions translate="true" selected=$authId} + {/fbvFormSection} + {/if} + + {if !$disablePasswordSection} + {if $userId}{capture assign="passwordInstruction"}{translate key="user.profile.leavePasswordBlank"} {translate key="user.register.form.passwordLengthRestriction" length=$minPasswordLength}{/capture}{/if} + {fbvFormArea id="passwordSection" title="user.password"} + {fbvFormSection for="password" description=$passwordInstruction translate=false} + {fbvElement type="text" label="user.password" required=$passwordRequired name="password" id="password" password="true" value=$password maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {fbvElement type="text" label="user.repeatPassword" required=$passwordRequired name="password2" id="password2" password="true" value=$password2 maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + + {if !$userId} + {fbvFormSection title="grid.user.generatePassword" for="generatePassword" list=true} + {if $generatePassword} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="generatePassword" id="generatePassword" checked=$checked label="grid.user.generatePasswordDescription" translate="true"} + {/fbvFormSection} + {/if} + {fbvFormSection title="grid.user.mustChangePassword" for="mustChangePassword" list=true} + {if $mustChangePassword} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="mustChangePassword" id="mustChangePassword" checked=$checked label="grid.user.mustChangePasswordDescription" translate="true"} + {/fbvFormSection} + {/fbvFormArea} + {/if} + + {if $countryRequired} + {assign var="countryRequired" value=true} + {else} + {assign var="countryRequired" value=false} + {/if} + {fbvFormSection for="country" title="common.country"} + {fbvElement type="select" label="common.country" name="country" id="country" required=$countryRequired defaultLabel="" defaultValue="" from=$countries selected=$country translate="0" size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + + {if !$disableSendNotifySection} + {fbvFormSection title="grid.user.notifyUser" for="sendNotify" list=true} + {if $sendNotify} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="sendNotify" id="sendNotify" checked=$checked label="grid.user.notifyUserDescription" translate="true"} + {/fbvFormSection} + {/if} +{/fbvFormArea} +{call_hook name="Common::UserDetails::AdditionalItems"} +{capture assign="extraContent"} + {fbvFormArea id="userFormExtendedLeft"} + {fbvFormSection} + {if !$disableSalutationSection} + {fbvElement type="text" label="user.salutation" name="salutation" id="salutation" value=$salutation maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {/if} + {fbvElement type="text" label="user.suffix" id="suffix" value=$suffix size=$fbvStyles.size.SMALL inline=true} + {if !$disableInitialsSection} + {fbvElement type="text" label="user.initials" name="initials" id="initials" value=$initials maxlength="5" inline=true size=$fbvStyles.size.SMALL} + {/if} + {/fbvFormSection} + + {fbvFormSection} + {fbvElement type="text" label="user.url" name="userUrl" id="userUrl" value=$userUrl maxlength="255" inline=true size=$fbvStyles.size.SMALL} + {if !$disablePhoneSection} + {fbvElement type="text" label="user.phone" name="phone" id="phone" value=$phone maxlength="24" inline=true size=$fbvStyles.size.SMALL} + {/if} + {fbvElement type="text" label="user.orcid" name="orcid" id="orcid" value=$orcid maxlength="37" inline=true size=$fbvStyles.size.SMALL} + {/fbvFormSection} + + {if !$disableLocaleSection && count($availableLocales) > 1} + {fbvFormSection title="user.workingLanguages" list=true} + {foreach from=$availableLocales key=localeKey item=localeName} + {if $userLocales && in_array($localeKey, $userLocales)} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="userLocales[]" id="userLocales-$localeKey" value=$localeKey checked=$checked label=$localeName translate=false} + {/foreach} + {/fbvFormSection} + {/if} + + {if !$disableInterestsSection} + {fbvFormSection for="interests"} + {fbvElement type="interests" id="interests" interests=$interests label="user.interests"} + {/fbvFormSection} + {/if} + + {fbvFormSection for="affiliation"} + {fbvElement type="text" label="user.affiliation" multilingual="true" name="affiliation" id="affiliation" value=$affiliation inline=true size=$fbvStyles.size.LARGE} + {/fbvFormSection} + + {fbvFormSection} + {fbvElement type="textarea" label="user.biography" multilingual="true" name="biography" id="biography" rich=true value=$biography} + {/fbvFormSection} + + {if !$disableMailingSection} + {fbvFormSection} + {fbvElement type="textarea" label="common.mailingAddress" name="mailingAddress" id="mailingAddress" rich=true value=$mailingAddress} + {/fbvFormSection} + {/if} + + {if !$disableSignatureSection} + {fbvFormSection} + {fbvElement type="textarea" label="user.signature" multilingual="true" name="signature" id="signature" value=$signature rich=true} + {/fbvFormSection} + {/if} + {/fbvFormArea} +{/capture} + +{fbvFormSection} + {if $extraContentSectionUnfolded} + {fbvFormSection title="grid.user.userDetails"} + {$extraContent} + {/fbvFormSection} + {else} +
+ {include file="controllers/extrasOnDemand.tpl" + id="userExtras" + widgetWrapper="#userExtraFormFields" + moreDetailsText="grid.user.moreDetails" + lessDetailsText="grid.user.lessDetails" + extraContent=$extraContent + } +
+ {/if} +{/fbvFormSection} diff --git a/email-patch/source/userDetails.tpl b/email-patch/source/userDetails.tpl new file mode 100644 index 0000000..c2d1abd --- /dev/null +++ b/email-patch/source/userDetails.tpl @@ -0,0 +1,194 @@ +{** + * templates/common/userDetails.tpl + * + * Copyright (c) 2014-2018 Simon Fraser University + * Copyright (c) 2003-2018 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * Common user details form. + * + * Parameters: + * $disableUserNameSection: Disable UserName section + * $disableEmailSection: Disable Email section + * $disableAuthSourceSection: Disable Auth section + * $disablePasswordSection: Disable Password section + * $disableSendNotifySection: Disable SendNotify section + * $disableSalutationSection: Disable Salutation section + * $disableInitialsSection: Disable Initials section + * $disablePhoneSection: Disable Phone section + * $disableLocaleSection: Disable Locale section + * $disableInterestsSection: Disable Interests section + * $disableMailingSection: Disable Mailing section + * $disableSignatureSection: Disable Signature section + * + * $countryRequired: Whether or not the country select is a required field + * $extraContentSectionUnfolded: Whether or not the extra content section is unfolded by default + *} + +{fbvFormArea id="userDetails"} + {fbvFormSection title="user.name"} + {fbvElement type="text" label="user.firstName" required="true" id="firstName" value=$firstName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {fbvElement type="text" label="user.middleName" id="middleName" value=$middleName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {fbvElement type="text" label="user.lastName" required="true" id="lastName" value=$lastName maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {/fbvFormSection} + + {if !$disableUserNameSection} + {if !$userId}{capture assign="usernameInstruction"}{translate key="user.register.usernameRestriction"}{/capture}{/if} + {fbvFormSection for="username" description=$usernameInstruction translate=false} + {if !$userId} + {fbvElement type="text" label="user.username" id="username" required="true" value=$username maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {fbvElement type="button" label="common.suggest" id="suggestUsernameButton" inline=true class="default"} + {else} + {fbvFormSection title="user.username" suppressId="true"} + {$username|escape} + {/fbvFormSection} + {/if} + {/fbvFormSection} + {/if} + + {if !$disableEmailSection} + {if $emailIsOptional} + {assign var="emailRequired" value=false} + {else} + {assign var="emailRequired" value=true} + {/if} + + {fbvFormSection title="about.contact"} + {fbvElement type="text" label="user.email" id="email" required=$emailRequired value=$email maxlength="90" size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + {/if} + + {if !$disableAuthSourceSection} + {fbvFormSection title="grid.user.authSource" for="authId"} + {fbvElement type="select" name="authId" id="authId" defaultLabel="" defaultValue="" from=$authSourceOptions translate="true" selected=$authId} + {/fbvFormSection} + {/if} + + {if !$disablePasswordSection} + {if $userId}{capture assign="passwordInstruction"}{translate key="user.profile.leavePasswordBlank"} {translate key="user.register.form.passwordLengthRestriction" length=$minPasswordLength}{/capture}{/if} + {fbvFormArea id="passwordSection" title="user.password"} + {fbvFormSection for="password" description=$passwordInstruction translate=false} + {fbvElement type="text" label="user.password" required=$passwordRequired name="password" id="password" password="true" value=$password maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {fbvElement type="text" label="user.repeatPassword" required=$passwordRequired name="password2" id="password2" password="true" value=$password2 maxlength="32" inline=true size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + + {if !$userId} + {fbvFormSection title="grid.user.generatePassword" for="generatePassword" list=true} + {if $generatePassword} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="generatePassword" id="generatePassword" checked=$checked label="grid.user.generatePasswordDescription" translate="true"} + {/fbvFormSection} + {/if} + {fbvFormSection title="grid.user.mustChangePassword" for="mustChangePassword" list=true} + {if $mustChangePassword} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="mustChangePassword" id="mustChangePassword" checked=$checked label="grid.user.mustChangePasswordDescription" translate="true"} + {/fbvFormSection} + {/fbvFormArea} + {/if} + + {if $countryRequired} + {assign var="countryRequired" value=true} + {else} + {assign var="countryRequired" value=false} + {/if} + {fbvFormSection for="country" title="common.country"} + {fbvElement type="select" label="common.country" name="country" id="country" required=$countryRequired defaultLabel="" defaultValue="" from=$countries selected=$country translate="0" size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + + {if !$disableSendNotifySection} + {fbvFormSection title="grid.user.notifyUser" for="sendNotify" list=true} + {if $sendNotify} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="sendNotify" id="sendNotify" checked=$checked label="grid.user.notifyUserDescription" translate="true"} + {/fbvFormSection} + {/if} +{/fbvFormArea} +{call_hook name="Common::UserDetails::AdditionalItems"} +{capture assign="extraContent"} + {fbvFormArea id="userFormExtendedLeft"} + {fbvFormSection} + {if !$disableSalutationSection} + {fbvElement type="text" label="user.salutation" name="salutation" id="salutation" value=$salutation maxlength="40" inline=true size=$fbvStyles.size.SMALL} + {/if} + {fbvElement type="text" label="user.suffix" id="suffix" value=$suffix size=$fbvStyles.size.SMALL inline=true} + {if !$disableInitialsSection} + {fbvElement type="text" label="user.initials" name="initials" id="initials" value=$initials maxlength="5" inline=true size=$fbvStyles.size.SMALL} + {/if} + {/fbvFormSection} + + {fbvFormSection} + {fbvElement type="text" label="user.url" name="userUrl" id="userUrl" value=$userUrl maxlength="255" inline=true size=$fbvStyles.size.SMALL} + {if !$disablePhoneSection} + {fbvElement type="text" label="user.phone" name="phone" id="phone" value=$phone maxlength="24" inline=true size=$fbvStyles.size.SMALL} + {/if} + {fbvElement type="text" label="user.orcid" name="orcid" id="orcid" value=$orcid maxlength="37" inline=true size=$fbvStyles.size.SMALL} + {/fbvFormSection} + + {if !$disableLocaleSection && count($availableLocales) > 1} + {fbvFormSection title="user.workingLanguages" list=true} + {foreach from=$availableLocales key=localeKey item=localeName} + {if $userLocales && in_array($localeKey, $userLocales)} + {assign var="checked" value=true} + {else} + {assign var="checked" value=false} + {/if} + {fbvElement type="checkbox" name="userLocales[]" id="userLocales-$localeKey" value=$localeKey checked=$checked label=$localeName translate=false} + {/foreach} + {/fbvFormSection} + {/if} + + {if !$disableInterestsSection} + {fbvFormSection for="interests"} + {fbvElement type="interests" id="interests" interests=$interests label="user.interests"} + {/fbvFormSection} + {/if} + + {fbvFormSection for="affiliation"} + {fbvElement type="text" label="user.affiliation" multilingual="true" name="affiliation" id="affiliation" value=$affiliation inline=true size=$fbvStyles.size.LARGE} + {/fbvFormSection} + + {fbvFormSection} + {fbvElement type="textarea" label="user.biography" multilingual="true" name="biography" id="biography" rich=true value=$biography} + {/fbvFormSection} + + {if !$disableMailingSection} + {fbvFormSection} + {fbvElement type="textarea" label="common.mailingAddress" name="mailingAddress" id="mailingAddress" rich=true value=$mailingAddress} + {/fbvFormSection} + {/if} + + {if !$disableSignatureSection} + {fbvFormSection} + {fbvElement type="textarea" label="user.signature" multilingual="true" name="signature" id="signature" value=$signature rich=true} + {/fbvFormSection} + {/if} + {/fbvFormArea} +{/capture} + +{fbvFormSection} + {if $extraContentSectionUnfolded} + {fbvFormSection title="grid.user.userDetails"} + {$extraContent} + {/fbvFormSection} + {else} +
+ {include file="controllers/extrasOnDemand.tpl" + id="userExtras" + widgetWrapper="#userExtraFormFields" + moreDetailsText="grid.user.moreDetails" + lessDetailsText="grid.user.lessDetails" + extraContent=$extraContent + } +
+ {/if} +{/fbvFormSection} diff --git a/email-patch/userDetails.patch b/email-patch/userDetails.patch new file mode 100644 index 0000000..a8553ed --- /dev/null +++ b/email-patch/userDetails.patch @@ -0,0 +1,18 @@ +--- userDetails_old.tpl 2019-03-13 05:30:51.480000000 +0300 ++++ userDetails.tpl 2019-03-13 05:30:51.479000000 +0300 +@@ -47,8 +47,14 @@ + {/if} + + {if !$disableEmailSection} ++ {if $emailIsOptional} ++ {assign var="emailRequired" value=false} ++ {else} ++ {assign var="emailRequired" value=true} ++ {/if} ++ + {fbvFormSection title="about.contact"} +- {fbvElement type="text" label="user.email" id="email" required="true" value=$email maxlength="90" size=$fbvStyles.size.MEDIUM} ++ {fbvElement type="text" label="user.email" id="email" required=$emailRequired value=$email maxlength="90" size=$fbvStyles.size.MEDIUM} + {/fbvFormSection} + {/if} + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..61812b0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1436 @@ +{ + "name": "gpmu-ojs-import", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/node": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz", + "integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==" + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "axios": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.1.tgz", + "integrity": "sha512-NTcm6vQ+PTgN3UBsALw5BMhgO6i5EpIjQF/Xb5tIh3sk9QhrFafujUOczGz4J24JBlzWclSB9Vmx8d+9Z6bFCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.2", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "manakin": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/manakin/-/manakin-0.5.2.tgz", + "integrity": "sha512-pfDSB7QYoVg0Io4KMV9hhPoXpj6p0uBscgtyUSKCOFZe8bqgbpStfgnKIbF/ulnr6U3ICu4OqdyxAqBgOhZwBQ==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", + "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==" + }, + "readable-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "request-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", + "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "schm": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/schm/-/schm-0.4.1.tgz", + "integrity": "sha512-Ndl+URhRV3//jAmt2e4LrijQOoN/PMws6OsNVEl6SxCctiaZcU+laW1Ya2iLPn9HrAQMQbzznLBd6D2cvNO0Wg==", + "requires": { + "lodash": "^4.17.10" + } + }, + "schm-translate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/schm-translate/-/schm-translate-0.4.1.tgz", + "integrity": "sha512-QZVgh+4MdbqVsOXM23LBL2qQcFNQXc9ngt5vxvy68RdM65BtrtviHHX7yTXXSXNoAtgr5LsXvPJ1DlbFEV8J/A==", + "requires": { + "lodash": "^4.17.10" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "requires": { + "ansi-regex": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==" + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "dev": true, + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5296993 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "gpmu-ojs-import", + "version": "1.0.0", + "description": "gpmu-ojs-import-js", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "DEBUG=* node src/index.js" + }, + "repository": { + "type": "git", + "url": "git+https://fortunereject@bitbucket.org/fortunereject/gpmu-ojs-import-js.git" + }, + "author": "Phil Zhitnikov", + "license": "ISC", + "homepage": "https://bitbucket.org/fortunereject/gpmu-ojs-import-js#readme", + "dependencies": { + "axios": "^0.18.0", + "cheerio": "^1.0.0-rc.2", + "glob": "^7.1.3", + "inquirer": "^6.2.2", + "manakin": "^0.5.2", + "minimist": "^1.2.0", + "qs": "^6.6.0", + "request": "^2.88.0", + "request-promise": "^4.2.4", + "schm": "^0.4.1", + "schm-translate": "^0.4.1" + }, + "devDependencies": { + "eslint": "^5.15.1" + } +} diff --git a/src/UPLOAD_ISSUES.bat b/src/UPLOAD_ISSUES.bat new file mode 100644 index 0000000..cdacef5 --- /dev/null +++ b/src/UPLOAD_ISSUES.bat @@ -0,0 +1,3 @@ +cd /d "%~dp0" +node index.js %* +pause \ No newline at end of file diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..b3e9971 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,5 @@ +exports.asyncForEach = async function (array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} \ No newline at end of file diff --git a/src/importer.js b/src/importer.js new file mode 100644 index 0000000..4587dca --- /dev/null +++ b/src/importer.js @@ -0,0 +1,227 @@ +const fs = require("fs"); +const path = require("path"); + +const inquirer = require("inquirer"); +const glob = require("glob"); +const cheerio = require("cheerio"); + +const Journal = require("./ojs/journal"); +const Issue = require("./ojs/issue"); +const Submission = require("./ojs/submission"); +const helpers = require("./helpers"); + +class Importer { + constructor(client) { + this.client = client; + this.submission = new Submission(client); + + this.$ = cheerio.load("", { xmlMode: true }); + } + + async chooseJournal(journalChoices) { + journalChoices.push(new inquirer.Separator(), "[New journal]"); + + const questions = [ + { + type: "list", + name: "selectedJournal", + message: "Choose journal to upload issue to", + choices: journalChoices + }, + { + type: "input", + name: "selectedJournal", + message: "Enter english slug for journal", + when: answers => { + return answers.selectedJournal == "[New journal]"; + }, + validate: function(value) { + if (!value.match(/^[a-zA-Z0-9\/._-]+$/)) + return "Slug can only contain numbers, english letters, dashes (-) and underscores (_)"; + else if (journalChoices.includes(value)) + return "Slug exists. Choose another slug"; + else return true; + } + } + ]; + + return await inquirer.prompt(questions).then(answers => { + return answers.selectedJournal; + }); + } + + getXMLDatastores(paths) { + const arr = paths.map(p => { + p = path.resolve(p); + const searchPattern = `${p}\\**\\*.xml`; + return glob.sync(searchPattern); + }); + + const flattenArr = arr.reduce((acc, val) => acc.concat(val), []); + return flattenArr.filter((x, i, a) => a.indexOf(x) == i); // filter unique paths + } + + async importIssue(xmlFilePath, journalSlug) { + const cwd = path.dirname(xmlFilePath); + console.success("Processing directory:", path.relative(__dirname, cwd)); + + const xmlData = fs.readFileSync(xmlFilePath, "utf-16le"); + const $xml = cheerio.load(xmlData, { xmlMode: true }); + + // Create journal if needed + const exists = await this.client.journalExists(journalSlug); + if (!exists) { + // console.error(`Journal with slug ${journalSlug} doesnt exist`); + + const journalTitle_rus = $xml("journalInfo[lang=RUS]") + .find("title") + .text(); + const journalTitle_eng = $xml("journalInfo[lang=ENG]") + .find("title") + .text(); + + const journalInfo = { + name: { rus: journalTitle_rus, eng: journalTitle_eng }, + path: journalSlug + }; + // console.log("journalInfo", journalInfo); + await Journal.create(this.client, journalInfo); + } + + //Collect issue info + const $issue = $xml("issue"); + + const volume = $issue.children("volume").text(); + const number = $issue.children("number").text(); + const year = $issue + .children("dateUni") + .text() + .slice(0, 4); //dirty hack for parameters like '201733/2017' or '201733' + const title_rus = $issue.children("issTitle").text(); + + var info = { + volume: volume, + number: number, + year: year, + "title[ru_RU]": title_rus + }; + + if (!title_rus) info["showTitle"] = "0"; + + if (!volume) info["showVolume"] = "0"; + + if (!number) info["showNumber"] = "0"; + + if (!year) info["showYear"] = "0"; + + // console.log(info) + + const issueId = await Issue.create(this.client, journalSlug, info); + console.success("Got issue id:", issueId); + + // Process articles + const $articles = $xml("article"); + const totalArticles = $articles.length; + console.info(`Found ${totalArticles} articles`); + + await helpers.asyncForEach($articles, async (articleXML, i) => { + console.info( + `Creating submission ${i + 1}/${totalArticles} with issueId ${issueId}` + ); + var submissionInfo = await this.getArticleInfo(articleXML, cwd); + + submissionInfo["issueId"] = issueId; + + // TODO validate info + // TODO validate file + + await this.submission.create(journalSlug, submissionInfo); + }); + + await Issue.publish(this.client, journalSlug, issueId); + } + + getAuthorInfo($individInfo) { + var info = {}; + + const initials = $individInfo.find("initials").text(); + const splitted_initials = initials.split(/[\s.]+/).filter(Boolean); + // console.log(initials, '||', splitted_initials); + + const first_name = splitted_initials[0]; + info["firstName"] = first_name; + + const middle_name = + splitted_initials.length > 1 ? splitted_initials[1] : ""; + info["middleName"] = middle_name; + + const surname = $individInfo.find("surname").text(); + info["lastName"] = surname; + + const organization = $individInfo.find("orgName").text(); + const address = $individInfo.find("address").text(); + info["affiliation[ru_RU]"] = organization + " " + address; + + const bio = $individInfo.find("otherInfo").text(); + info["biography[ru_RU]"] = bio; + + const email = $individInfo.find("email").text(); + info["email"] = email; + + return info; + } + + async getArticleInfo(articleXML, cwd) { + const $article = this.$(articleXML); + + const title_rus = $article.find("artTitle[lang=RUS]").text(); + const title_eng = $article.find("artTitle[lang=ENG]").text(); + + const abstract_rus = $article.find("abstract[lang=RUS]").text() || "
"; + const abstract_eng = $article.find("abstract[lang=ENG]").text() || "
"; + + const pages = $article.find("pages").text(); + + const materialFileName = $article.find("file").text(); + const materialPath = path.join(cwd, materialFileName); + + const $keywords = $article.find("keyword"); + + var keywords = []; + $keywords.each((i, k) => { + const keyword = this.$(k).text(); + keywords.push(keyword); + }); + + // Collect authors + const $authors = $article.find("author"); + + var authorInfoCollection = []; + $authors.each((i, author) => { + const $author = this.$(author); + const $individInfo_rus = $author.find("individInfo[lang=RUS]"); + const $individInfo_eng = $author.find("individInfo[lang=ENG]"); + + const authorInfo = this.getAuthorInfo($individInfo_rus); + authorInfoCollection.push(authorInfo); + }); + + // console.log(title_rus) + // console.log(title_eng) + // console.log(abstract_rus) + // console.log(keywords); + + return { + title_rus: title_rus, + title_eng: title_eng, + abstract_rus: abstract_rus, + abstract_eng: abstract_eng, + keywords: keywords, + pages: pages, + materialFilePath: materialPath, + authors: authorInfoCollection + }; + } +} + +module.exports = Importer; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..64c8ada --- /dev/null +++ b/src/index.js @@ -0,0 +1,34 @@ +var argv = require("minimist")(process.argv.slice(2)); + +require("manakin").global; +const inquirer = require("inquirer"); + +const Client = require("./ojs/client"); +const Importer = require("./importer"); +const config = require("./config"); +const helpers = require("./helpers"); + +const client = new Client(config.PRODUCTION); +const importer = new Importer(client); + +if (!argv._) { + console.error("No dirs provided"); + return; +} + +const separateUpload = argv.separate || false; + +const datastores = importer.getXMLDatastores(argv._); +console.log("Got datastores:", datastores); + +(async () => { + await importer.client.init(); + await helpers.asyncForEach(datastores, async (d, i) => { + if (separateUpload || i == 0) { + const journals = await importer.client.getJournals(); + selectedJournal = await importer.chooseJournal(journals); + } + + await importer.importIssue(d, selectedJournal); + }); +})(); diff --git a/src/ojs/client.js b/src/ojs/client.js new file mode 100644 index 0000000..db204a2 --- /dev/null +++ b/src/ojs/client.js @@ -0,0 +1,186 @@ +const rp = require("request-promise"); +const cheerio = require("cheerio"); +const url = require("url"); + +const endpoints = require("./endpoints"); + +class Client { + constructor(config) { + this.config = config; + + this.host = config.host; + this.csrfToken = ""; + + this.apiDefaults = { + baseUrl: this.host, + jar: true, // enable cookies + gzip: this.config.gzip || false, + resolveWithFullResponse: true, + headers: { + // 'Host': '127.0.0.1:8080', + "User-Agent": + "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0", + Accept: "application/json, text/javascript, */*; q=0.01", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate", + Connection: "keep-alive", + "X-Requested-With": "XMLHttpRequest" + } + }; + + this.api = rp.defaults(this.apiDefaults); + } + + init() { + return this.getCSRF() + .then(token => (this.csrfToken = token)) + .then(() => + this.authorize(this.config.adminUsername, this.config.adminPassword) + ) + .catch(err => console.error("Client init failed:", err)); + } + + getCSRF() { + // console.log("getting CSRF token.."); + + return this.api + .get(endpoints.login.page) + .then(response => { + const $ = cheerio.load(response.body); + const csrfToken = $("input[name=csrfToken]").val(); + + return csrfToken; + }) + .catch(err => console.error("CSRF fetching failed:", err)); + } + + authorize(user, password) { + console.log("authorizing.."); + + return this.api + .post(endpoints.login.action, { + form: { + username: user, + password: password, + remember: "1" + }, + simple: false // because server responds with 302 code + }) + .then(response => { + // TODO check login success + console.success("authorized"); + + // Add cookie to headers + var cookie = response.headers["set-cookie"].toString(); + this.api.defaults({ headers: { Cookie: cookie } }); + }) + .catch(err => console.error("Login failed:", err)); + } + + send(options) { + if (options.method == "POST" && !options.formData) { + options.form = options.form || {}; + options.form["csrfToken"] = this.csrfToken; + } + return this.api(options); + } + + sendJson(options) { + options["json"] = true; + options["resolveWithFullResponse"] = false; + + return this.send(options); + } + + getJournals() { + // this.restoreBaseUrl(); + + return this.send({ + baseUrl: this.host, + method: "GET", + uri: endpoints.journal.getAll, + + json: true, + resolveWithFullResponse: false + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData) Promise.reject(); + + console.log("Parsing journals.."); + var journals = []; + + const $ = cheerio.load(jsonData.content); + const $labels = $(".gridRow td:not(.first_column) .label"); + + $labels.each((i, el) => { + const name = $(el) + .text() + .trim(); + journals.push(name); + }); + + return journals; + }) + .catch(err => console.error("Fetching journals failed:", err)); + } + + // TODO clean this mess + getJournalUrl(journalSlug) { + return url.resolve(this.host, journalSlug); + } + + setJournalUrl(journalSlug) { + // console.log('setting slug:', journalSlug); + this.apiDefaults.baseUrl = this.getJournalUrl(journalSlug); + this.api.defaults(this.apiDefaults); + } + + // restoreBaseUrl() { + // this.apiDefaults.baseUrl = this.host; + // this.api.defaults(this.apiDefaults) + // } + + async journalExists(slug) { + return await this.getJournals().then(journals => { + return journals.includes(slug); + }); + } + + getIssueIds(journalSlug) { + //TODO check journal exists + + const journalUrl = this.getJournalUrl(journalSlug); + + return this.send({ + baseUrl: journalUrl, + method: "GET", + uri: endpoints.issue.getAll, + + json: true, + resolveWithFullResponse: false + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData) Promise.reject(); + + const $ = cheerio.load(jsonData.content); + const $gridRows = $(".gridRow"); // + + var ids = []; + + $gridRows.each((i, el) => { + const idAttr = $(el).attr("id"); // component-grid-issues-futureissuegrid-row-1 + const id = idAttr.split("-").slice(-1)[0]; // 1 + ids.push(id); + }); + + return ids; + }) + .catch(err => console.error("getIssueIds failed:", err)); + } +} + +module.exports = Client; diff --git a/src/ojs/endpoints.js b/src/ojs/endpoints.js new file mode 100644 index 0000000..e6944ad --- /dev/null +++ b/src/ojs/endpoints.js @@ -0,0 +1,36 @@ +module.exports = { + login: { + page: '/index/login', + action: '/index/login/signIn' + }, + journal: { + getAll: 'index/$$$call$$$/grid/admin/journal/journal-grid/fetch-grid', + create: 'index/$$$call$$$/grid/admin/journal/journal-grid/update-context' + }, + issue: { + getAll: '$$$call$$$/grid/issues/future-issue-grid/fetch-grid', + create: '$$$call$$$/grid/issues/future-issue-grid/update-issue?issueId=', + publish: '$$$call$$$/grid/issues/future-issue-grid/publish-issue' + }, + submission: { + prepare: 'submission/step/1', + step1_save: 'submission/saveStep/1', + step2: { + getGenreId: '$$$call$$$/wizard/file-upload/file-upload-wizard/display-file-upload-form', + save: 'submission/saveStep/2' + }, + step3_metadata: 'submission/saveStep/3', + step4_confirm: 'submission/saveStep/4', + galley: { + create: '$$$call$$$/grid/article-galleys/article-galley-grid/update-galley', + getAssocTypeAssocId: '$$$call$$$/grid/article-galleys/article-galley-grid/fetch-row' + }, + getAuthors: '$$$call$$$/grid/users/author/author-grid/fetch-grid', + removeAuthor: '$$$call$$$/grid/users/author/author-grid/delete-author', + authorForm: '$$$call$$$/grid/users/author/author-grid/add-author', + addAuthor: '$$$call$$$/grid/users/author/author-grid/update-author?authorId=', + uploadFile: '$$$call$$$/wizard/file-upload/file-upload-wizard/upload-file', + + save: '$$$call$$$/tab/issue-entry/issue-entry-tab/save-publication-metadata-form' + }, +} \ No newline at end of file diff --git a/src/ojs/form-data.js b/src/ojs/form-data.js new file mode 100644 index 0000000..75ab1c2 --- /dev/null +++ b/src/ojs/form-data.js @@ -0,0 +1,124 @@ +module.exports = { + submission: { + step1_save: { + 'csrfToken': '', + 'sectionId': '14', + 'userGroupId': '240', + + 'submissionChecklist': '1', + 'locale': 'ru_RU', + 'checklist-0': '1', + 'checklist-1': '1', + 'checklist-2': '1', + 'checklist-3': '1', + 'checklist-4': '1', + 'commentsToEditor': '', + 'privacyConsent': '1', + }, + + step2: { + getGenreId: { + 'submissionId': '', + + 'stageId': '1', + 'fileStage': '2', + }, + uploadQuery: { + 'csrfToken': '', + 'submissionId': '', + + 'stageId': '1', + 'fileStage': '2', + 'reviewRoundId': '', + 'assocType': '', + 'assocId': '' + } + }, + + step3_metadata: { + 'csrfToken': '', + + 'prefix[ru_RU]': '', + 'subtitle[ru_RU]': '', + 'title[ru_RU]': 'testing', + 'abstract[ru_RU]': 'testing', + 'keywords[ru_RU-keywords]': ['lulz', 'testtest2'] + }, + + galley: { + create: { + 'csrfToken': '', + + 'label': 'PDF', + 'galleyLocale': 'ru_RU', + 'remoteURL': '' + }, + uploadQuery: { + 'csrfToken': '', + 'submissionId': '', + + 'stageId': '5', + 'fileStage': '10', + 'reviewRoundId': '', + 'assocType': '', + 'assocId': '' + } + }, + + addAuthor: { + 'csrfToken': '', + 'submissionId': '', + + 'firstName': 'Иван', + 'middleName': '', + 'lastName': 'Иванов', + 'email': '', + 'country': 'RU', + 'suffix': '', + 'userUrl': '', + 'orcid': '', + 'affiliation[ru_RU]': 'org', + 'biography[ru_RU]': '', + 'userGroupId': '', + 'includeInBrowse': 'on', + }, + + save: { + 'csrfToken': '', + 'submissionId': '', + + 'issueId': '', + 'stageId': '5', + 'copyrightHolder[ru_RU]': 'test-journal', + 'pages': '', + 'waivePublicationFee': '0', + 'markAsPaid': 0, + 'licenseURL': '', + 'copyrightYear': '', + } + }, + + issue: { + create: { + 'csrfToken': '', + + 'title[ru_RU]': 'issue-title', + 'volume': '1', + 'number': '1', + 'year': '2019', + 'showVolume': '1', + 'showNumber': '1', + 'showYear': '1', + 'showTitle': '1', + 'description[ru_RU]': '', + 'temporaryFileId': '' + }, + + publish: { + 'csrfToken': '', + + 'issueId': '', + 'confirmed': 'true', + } + } +} \ No newline at end of file diff --git a/src/ojs/issue.js b/src/ojs/issue.js new file mode 100644 index 0000000..e86e2b2 --- /dev/null +++ b/src/ojs/issue.js @@ -0,0 +1,56 @@ +const endpoints = require('./endpoints') +const form_data = require('./form-data') + +diff = (a, b) => a.filter(function (i) { return b.indexOf(i) < 0; }) + +exports.create = async (client, journalSlug, info = {}) => { + const journalUrl = client.getJournalUrl(journalSlug); + + const issueIdsBefore = await client.getIssueIds(journalSlug); + + // Create issue + await client + .sendJson({ + baseUrl: journalUrl, + method: 'POST', + uri: endpoints.issue.create, + form: { + ...form_data.issue.create, + ...info + } + }) + .then(jsonData => { + if (!jsonData) + Promise.reject('Wrong response'); + + console.info('Create issue result:', jsonData); + }) + .catch(err => console.error('Issue creation failed:', err)) + + const issueIdsAfter = await client.getIssueIds(journalSlug); + + return diff(issueIdsAfter, issueIdsBefore)[0] +} + +exports.publish = async (client, journalSlug, issueId) => { + const journalUrl = client.getJournalUrl(journalSlug); + + return client + .sendJson({ + baseUrl: journalUrl, + method: 'POST', + uri: endpoints.issue.publish, + form: { + ...form_data.issue.publish, + issueId: issueId, + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info('Issue.publish result:', jsonData); + + if (!jsonData) + Promise.reject(); + }) + .catch(err => console.error('Issue publishing failed:', err)) +} \ No newline at end of file diff --git a/src/ojs/journal.js b/src/ojs/journal.js new file mode 100644 index 0000000..6c910bb --- /dev/null +++ b/src/ojs/journal.js @@ -0,0 +1,83 @@ +const schema = require("schm"); +const translate = require("schm-translate"); + +const endpoints = require("./endpoints"); + +const journalCreateSchema = schema( + { + "name[ru_RU]": { + type: String, + required: true + }, + "name[en_US]": String, + "description[ru_RU]": String, + "description[en_US]": String, + path: { + type: String, + required: true + }, + enabled: 1 + }, + translate({ + "name[ru_RU]": "name.rus", + "name[en_US]": "name.eng", + "description[ru_RU]": "description.rus", + "description[en_US]": "description.eng", + path: "path", + enabled: "enabled" + }) +); + +/** +@param journalInfo: + +{ + name: {rus: 'лала', eng: 'lala'}, + description: {rus: 'лала', eng: 'lala'}, + path: 'test-journal', + [enabled: 0] +} +**/ +exports.create = async (client, journalInfo = {}) => { + await journalCreateSchema + .validate(journalInfo) + .then(async parsedData => { + // console.log('Parsed schema:', parsedData) + + await client + .send({ + method: "POST", + uri: endpoints.journal.create, + form: parsedData + }) + .then(response => { + // TODO Check JSON has certain info + console.info("Journal creation result:", response.body); + }) + .catch(err => console.error("Journal creation failed:", err)); + }) + .catch(err => console.error("Journal info validation failed", err)); +}; + +exports.create_no_validation = async (client, journalInfo = {}) => { + const info = { + "name[ru_RU]": journalInfo.name.rus || "", + "name[en_US]": journalInfo.name.eng || "", + "description[ru_RU]": "", + "description[en_US]": "", + path: journalInfo.path, + enabled: journalInfo.enabled || 1 + }; + + return await client + .send({ + method: "POST", + uri: endpoints.journal.create, + form: info + }) + .then(response => { + // TODO Check JSON has certain info + console.log(response.body); + }) + .catch(err => console.error("Journal creation failed:", err)); +}; diff --git a/src/ojs/submission.js b/src/ojs/submission.js new file mode 100644 index 0000000..d6eae2c --- /dev/null +++ b/src/ojs/submission.js @@ -0,0 +1,452 @@ +var fs = require("fs"); +var path = require("path"); +const url = require("url"); + +const cheerio = require("cheerio"); + +const helpers = require("../helpers"); +const endpoints = require("./endpoints"); +const form_data = require("./form-data"); + +class Submission { + constructor(client) { + this.client = client; + this.submissionId = 1; + + this.info; + } + + prepare() { + return this.client + .sendJson({ + method: "GET", + uri: endpoints.submission.prepare + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData) Promise.reject(); + + const $ = cheerio.load(jsonData.content); + const userGroupEl = $("input[id^='userGroup']").first(); + const sectionIdEl = $("#sectionId"); + + const result = { + userGroupId: userGroupEl.val(), + sectionId: sectionIdEl.val() + }; + + console.info(result); + + return result; + }) + .catch(err => + console.error( + "Prepare submission (userGroupId and sectionId fetching) failed:", + err + ) + ); + } + + step1_save(data) { + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.step1_save, + form: { ...form_data.submission.step1_save, ...data } + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData || !jsonData.events) Promise.reject(); + + // Get submissionId from HTML + const submissionUrl = jsonData.events[0].data; + const parsedUrl = url.parse(submissionUrl, true); + + this.submissionId = parsedUrl.query.submissionId; + console.info("got submissionId:", this.submissionId); + }) + .catch(err => console.error("Save step1 failed:", err)); + } + + step2_getGenreId() { + return this.client + .sendJson({ + method: "GET", + uri: endpoints.submission.step2.getGenreId, + qs: { + ...form_data.submission.step2.getGenreId, + submissionId: this.submissionId + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData) Promise.reject(); + + // Get genreId from HTML + const $ = cheerio.load(jsonData.content); + const genreIdEl = $("option[label^='Текст статьи']").first(); + const genreId = genreIdEl.val(); + + return { genreId: genreId }; + }) + .catch(err => + console.error( + "Fetching genreId (material pre-upload step) failed:", + err + ) + ); + } + + step2_upload(materialFilePath, genreId) { + console.log("Uploading:", materialFilePath); + + const fileName = path.basename(materialFilePath); + const fileStream = fs.createReadStream(materialFilePath); + + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.uploadFile, + headers: { + browser_user_agent: this.client.apiDefaults.headers["User-Agent"] + }, + qs: { + ...form_data.submission.step2.uploadQuery, + submissionId: this.submissionId + }, + formData: { + genreId: genreId, + name: fileName, + uploadedFile: fileStream + } + }) + .then(jsonData => { + console.info("Upload result:", jsonData); + + // TODO Check JSON has certain info + if (!jsonData || !jsonData.uploadedFile) Promise.reject(); + }) + .catch(err => console.error("Material upload failed:", err)); + } + + step2_save() { + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.step2.save, + form: { submissionId: this.submissionId } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("Save step2 result:", jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + }) + .catch(err => console.error("Save step2 failed:", err)); + } + + step3_metadata() { + const info = { + "title[ru_RU]": this.info.title_rus, + "abstract[ru_RU]": this.info.abstract_rus, + "keywords[ru_RU-keywords]": this.info.keywords + }; + + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.step3_metadata, + form: { + ...form_data.submission.step3_metadata, + ...info, + submissionId: this.submissionId + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("Step3 (metadata) result:", jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + }) + .catch(err => console.error("Step3 (metadata) failed:", err)); + } + + step4_confirm() { + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.step4_confirm, + form: { submissionId: this.submissionId } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("Step4 (confirmation) result:", jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + }) + .catch(err => console.error("Step4 (confirmation) failed:", err)); + } + + createGalley() { + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.galley.create, + qs: { submissionId: this.submissionId, representationId: "" }, + form: form_data.submission.galley.create + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("Create galley result:", jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + + const rowId = jsonData.events[0].data[0]; + console.log("Got rowId:", rowId); + + return rowId; + }) + .catch(err => console.error("Galley creation failed:", err)); + } + + galley_getAssocTypeAssocId(rowId) { + return this.client + .sendJson({ + method: "GET", + uri: endpoints.submission.galley.getAssocTypeAssocId, + qs: { + rowId: rowId, + submissionId: this.submissionId + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + // console.log('getAssocTypeAssocId data:', jsonData.content); + + if (!jsonData) Promise.reject(); + + const assocTypeParam = jsonData.content.match(/assocType=\d+/); + const assocType = assocTypeParam[0].split("=")[1]; + + const assocIdParam = jsonData.content.match(/assocId=\d+/); + const assocId = assocIdParam[0].split("=")[1]; + + return { assocId: assocId, assocType: assocType }; + }) + .catch(err => + console.error( + "Fetching assocType & assocId (material pre-upload step) failed:", + err + ) + ); + } + + galleyUpload(materialFilePath, genreId, assocType, assocId) { + console.log("Uploading:", materialFilePath); + + const fileName = path.basename(materialFilePath); + const fileStream = fs.createReadStream(materialFilePath); + + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.uploadFile, + headers: { + browser_user_agent: this.client.apiDefaults.headers["User-Agent"] + }, + qs: { + ...form_data.submission.galley.uploadQuery, + submissionId: this.submissionId, + assocType: assocType, + assocId: assocId + }, + formData: { + genreId: genreId, + name: fileName, + uploadedFile: fileStream + } + }) + .then(jsonData => { + console.info("Upload result:", jsonData); + + // TODO Check JSON has certain info + if (!jsonData || !jsonData.uploadedFile) Promise.reject(); + }) + .catch(err => console.error("Galley material upload failed:", err)); + } + + getAuthorsIds() { + return this.client + .sendJson({ + method: "GET", + uri: endpoints.submission.getAuthors, + qs: { submissionId: this.submissionId, stageId: 1 } + }) + .then(jsonData => { + // TODO Check JSON has certain info + // console.info('getAuthorsIds result:', jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + + var authorsIds = []; + const $ = cheerio.load(jsonData.content); + const $gridRows = $(".gridRow"); + $gridRows.each((i, r) => { + const $r = $(r); + const idAttr = $r.attr("id"); + const id = idAttr.split("-").slice(-1)[0]; + authorsIds.push(id); + }); + + console.log("Got author ids:", authorsIds); + + return authorsIds; + }) + .catch(err => console.error("Authors fetching failed:", err)); + } + + removeCreatorFromAuthorsList() { + return this.getAuthorsIds() + .then(authors => this.removeAuthor(authors[0])) + .catch(err => console.error("removing submission creator failed:", err)); + } + + removeAuthor(authorId) { + console.log("Removing author with id:", authorId); + + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.removeAuthor, + qs: { submissionId: this.submissionId, authorId: authorId } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("removeAuthor result:", jsonData); + + if (!jsonData || !jsonData.events) Promise.reject(); + }) + .catch(err => console.error("Author removal failed:", err)); + } + + authorForm_getUserGroupId() { + return this.client + .sendJson({ + method: "GET", + uri: endpoints.submission.authorForm, + qs: { submissionId: this.submissionId } + }) + .then(jsonData => { + // TODO Check JSON has certain info + + if (!jsonData) Promise.reject(); + + // Get genreId from HTML + const $ = cheerio.load(jsonData.content); + const userGroupEl = $("input[id^='userGroup']").first(); + const userGroupId = userGroupEl.val(); + + console.info("author form userGroupId:", userGroupId); + + return userGroupId; + }) + .catch(err => console.error("authorForm_getUserGroupId failed:", err)); + } + + async addAuthor(userGroupId, info = {}) { + return await this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.addAuthor, + form: { + ...form_data.submission.addAuthor, + ...info, + submissionId: this.submissionId, + userGroupId: userGroupId + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("addAuthor result:", jsonData); + + if (!jsonData) Promise.reject(); + }) + .catch(err => console.error("Author adding failed:", err)); + } + + async addAuthors(userGroupId) { + console.info(`Start adding authors for submissionId ${this.submissionId}`); + const totalAuthors = this.info.authors.length; + + await helpers.asyncForEach(this.info.authors, async (authorInfo, i) => { + console.info(`Adding author ${i + 1}/${totalAuthors}`); + // console.info(authorInfo); + await this.addAuthor(userGroupId, authorInfo); + }); + } + + attachIssue() { + return this.client + .sendJson({ + method: "POST", + uri: endpoints.submission.save, + form: { + ...form_data.submission.save, + submissionId: this.submissionId, + pages: this.info.pages, + issueId: this.info.issueId + } + }) + .then(jsonData => { + // TODO Check JSON has certain info + console.info("attachIssue result:", jsonData); + + if (!jsonData) Promise.reject(); + }) + .catch(err => console.error("Issue attaching failed:", err)); + } + + async create(journalSlug, submissionInfo = {}) { + this.client.setJournalUrl(journalSlug); + this.info = submissionInfo; + + return await this.prepare() + .then(data => this.step1_save(data)) + .then(() => this.step2_getGenreId()) + .then(data => this.step2_upload(this.info.materialFilePath, data.genreId)) + .then(() => this.step2_save()) + .then(() => this.step3_metadata()) + .then(() => this.step4_confirm()) + + .then(() => this.createGalley()) + .then(rowId => { + return Promise.all([ + this.galley_getAssocTypeAssocId(rowId), + this.step2_getGenreId() + ]); + }) + .then(data => { + data = { ...data[0], ...data[1] }; + + console.log("Galley data:", data); + this.galleyUpload( + this.info.materialFilePath, + data.genreId, + data.assocType, + data.assocId + ); + }) + .then(() => this.removeCreatorFromAuthorsList()) + .then(() => this.authorForm_getUserGroupId()) + .then(async userGroupId => await this.addAuthors(userGroupId)) + .then(() => this.attachIssue()) + + .then(() => console.success("Submission created successfully")) + .catch(err => console.error("Submission creation failed", err)); + } +} + +module.exports = Submission;