Working prototype

This commit is contained in:
Phil Zhitnikov 2019-03-15 07:36:09 +03:00
parent 3ff4a6bba1
commit afc7c4f6c9
28 changed files with 4036 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
samples/**/*

1
docker/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
files/**/*

143
docker/Dockerfile Normal file
View File

@ -0,0 +1,143 @@
FROM alpine:3.7
LABEL maintainer="Lucas G. Diedrich <lucas.diedrich@gmail.com>"
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 '\#<Directory />#,\#</Directory>#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"]

43
docker/docker-compose.yml Normal file
View File

@ -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

100
docs/backup_config.txt Normal file
View File

@ -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 отключено

39
docs/roadmap.md Normal file
View File

@ -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
- [ ]
- [ ]

View File

@ -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'));

13
email-patch/README.md Normal file
View File

@ -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

View File

@ -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"}

View File

@ -0,0 +1,233 @@
<?php
/**
* @file controllers/grid/users/author/form/AuthorForm.inc.php
*
* 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.
*
* @class AuthorForm
* @ingroup controllers_grid_users_author_form
*
* @brief Form for adding/editing a author
*/
import('lib.pkp.classes.form.Form');
class AuthorForm extends Form {
/** The submission associated with the submission contributor being edited **/
var $_submission;
/** Author the author being edited **/
var $_author;
/** The type of submission Id **/
var $_submissionIdFieldName;
/**
* Constructor.
*/
function __construct($submission, $author, $submissionIdFieldName) {
parent::__construct('controllers/grid/users/author/form/authorForm.tpl');
$this->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;
}
}
?>

View File

@ -0,0 +1,233 @@
<?php
/**
* @file controllers/grid/users/author/form/AuthorForm.inc.php
*
* 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.
*
* @class AuthorForm
* @ingroup controllers_grid_users_author_form
*
* @brief Form for adding/editing a author
*/
import('lib.pkp.classes.form.Form');
class AuthorForm extends Form {
/** The submission associated with the submission contributor being edited **/
var $_submission;
/** Author the author being edited **/
var $_author;
/** The type of submission Id **/
var $_submissionIdFieldName;
/**
* Constructor.
*/
function __construct($submission, $author, $submissionIdFieldName) {
parent::__construct('controllers/grid/users/author/form/authorForm.tpl');
$this->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;
}
}
?>

View File

@ -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
*
*}
<script>
$(function() {ldelim}
$('#editAuthor').pkpHandler(
'$.pkp.controllers.form.AjaxFormHandler'
);
{rdelim});
</script>
<form class="pkp_form" id="editAuthor" method="post" action="{url op="updateAuthor" authorId=$authorId}">
{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}
<input type="hidden" name="submissionId" value="{$submissionId|escape}" />
{/if}
{if $gridId}
<input type="hidden" name="gridId" value="{$gridId|escape}" />
{/if}
{if $rowId}
<input type="hidden" name="rowId" value="{$rowId|escape}" />
{/if}
<p><span class="formRequired">{translate key="common.requiredField"}</span></p>
{fbvFormButtons id="step2Buttons" submitText="common.save"}
</form>

View File

@ -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
*
*}
<script>
$(function() {ldelim}
$('#editAuthor').pkpHandler(
'$.pkp.controllers.form.AjaxFormHandler'
);
{rdelim});
</script>
<form class="pkp_form" id="editAuthor" method="post" action="{url op="updateAuthor" authorId=$authorId}">
{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}
<input type="hidden" name="submissionId" value="{$submissionId|escape}" />
{/if}
{if $gridId}
<input type="hidden" name="gridId" value="{$gridId|escape}" />
{/if}
{if $rowId}
<input type="hidden" name="rowId" value="{$rowId|escape}" />
{/if}
<p><span class="formRequired">{translate key="common.requiredField"}</span></p>
{fbvFormButtons id="step2Buttons" submitText="common.save"}
</form>

View File

@ -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}
<div id="userExtraFormFields" class="left full">
{include file="controllers/extrasOnDemand.tpl"
id="userExtras"
widgetWrapper="#userExtraFormFields"
moreDetailsText="grid.user.moreDetails"
lessDetailsText="grid.user.lessDetails"
extraContent=$extraContent
}
</div>
{/if}
{/fbvFormSection}

View File

@ -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}
<div id="userExtraFormFields" class="left full">
{include file="controllers/extrasOnDemand.tpl"
id="userExtras"
widgetWrapper="#userExtraFormFields"
moreDetailsText="grid.user.moreDetails"
lessDetailsText="grid.user.lessDetails"
extraContent=$extraContent
}
</div>
{/if}
{/fbvFormSection}

View File

@ -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}

1436
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -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"
}
}

3
src/UPLOAD_ISSUES.bat Normal file
View File

@ -0,0 +1,3 @@
cd /d "%~dp0"
node index.js %*
pause

5
src/helpers.js Normal file
View File

@ -0,0 +1,5 @@
exports.asyncForEach = async function (array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}

227
src/importer.js Normal file
View File

@ -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() || "<br>";
const abstract_eng = $article.find("abstract[lang=ENG]").text() || "<br>";
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;

34
src/index.js Normal file
View File

@ -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);
});
})();

186
src/ojs/client.js Normal file
View File

@ -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"); // <tr id="component-grid-issues-futureissuegrid-row-1" class="gridRow has_extras"></tr>
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;

36
src/ojs/endpoints.js Normal file
View File

@ -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'
},
}

124
src/ojs/form-data.js Normal file
View File

@ -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',
}
}
}

56
src/ojs/issue.js Normal file
View File

@ -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))
}

83
src/ojs/journal.js Normal file
View File

@ -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));
};

452
src/ojs/submission.js Normal file
View File

@ -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;