first commit
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/buildSwagger.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class buildSwagger
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to compile a complete swagger.json file for hosting API
|
||||
* documentation.
|
||||
*/
|
||||
|
||||
use APP\core\Services;
|
||||
use PKP\decision\DecisionType;
|
||||
use PKP\file\FileManager;
|
||||
|
||||
define('APP_ROOT', dirname(__FILE__, 4));
|
||||
require(APP_ROOT . '/tools/bootstrap.php');
|
||||
|
||||
class buildSwagger extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
public $outputFile;
|
||||
public $parameters;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments (see usage)
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
$this->outputFile = array_shift($this->argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Command-line tool to compile swagger.json API definitions\n"
|
||||
. "Usage:\n"
|
||||
. "\t{$this->scriptName} [outputFile]: Compile swagger file and save to [outputFile]\n"
|
||||
. "\t{$this->scriptName} usage: Display usage information this tool\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and execute the import/export task.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if (empty($this->outputFile)) {
|
||||
$this->usage();
|
||||
exit;
|
||||
} elseif ((file_exists($this->outputFile) && !is_writable($this->outputFile)) ||
|
||||
(!is_writeable(dirname($this->outputFile)))) {
|
||||
echo "You do not have permission to write to this file.\n";
|
||||
exit;
|
||||
} else {
|
||||
$source = file_get_contents(APP_ROOT . '/docs/dev/swagger-source.json');
|
||||
$decisions = file_get_contents(APP_ROOT . '/docs/dev/swagger-source-decision-examples.json');
|
||||
if (!$source || !$decisions) {
|
||||
echo 'Unable to find source files at ' . APP_ROOT . '/docs/dev/';
|
||||
exit;
|
||||
}
|
||||
|
||||
$locales = ['en', 'fr_CA'];
|
||||
|
||||
$apiSchema = json_decode($source);
|
||||
foreach ($apiSchema->definitions as $definitionName => $definition) {
|
||||
// We assume a definition that is not a string does not need to be compiled
|
||||
// from the schema files. It has already been defined.
|
||||
if (!is_string($definition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$editDefinition = $summaryDefinition = $readDefinition = ['type' => 'object', 'properties' => []];
|
||||
$entitySchema = Services::get('schema')->get($definition, true);
|
||||
foreach ($entitySchema->properties as $propName => $propSchema) {
|
||||
$editPropSchema = clone $propSchema;
|
||||
$readPropSchema = clone $propSchema;
|
||||
$summaryPropSchema = clone $propSchema;
|
||||
|
||||
// Special handling to catch readOnly, writeOnly and apiSummary props in objects
|
||||
if (!empty($propSchema->{'$ref'})) {
|
||||
if (empty($propSchema->readOnly) || empty($propSchema->writeDisabledInApi)) {
|
||||
$editPropSchema->properties = $propSchema;
|
||||
}
|
||||
if (empty($propSchema->writeOnly)) {
|
||||
$readPropSchema->properties = $propSchema;
|
||||
}
|
||||
if (!empty($propSchema->apiSummary)) {
|
||||
$summaryPropSchema->properties = $propSchema;
|
||||
}
|
||||
} elseif ($propSchema->type === 'object') {
|
||||
$subPropsEdit = $subPropsRead = $subPropsSummary = [];
|
||||
foreach ($propSchema->properties as $subPropName => $subPropSchema) {
|
||||
if (empty($subPropSchema->readOnly) && empty($propSchema->writeDisabledInApi)) {
|
||||
$subPropsEdit[$subPropName] = $subPropSchema;
|
||||
}
|
||||
if (empty($subPropSchema->writeOnly)) {
|
||||
$subPropsRead[$subPropName] = $subPropSchema;
|
||||
}
|
||||
if (!empty($subPropSchema->apiSummary)) {
|
||||
$subPropsSummary[$subPropName] = $subPropSchema;
|
||||
}
|
||||
}
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
$subPropsSchemaEdit = $subPropsSchemaRead = $subPropsSchemaSummary = [
|
||||
'type' => 'object',
|
||||
'properties' => [],
|
||||
];
|
||||
foreach ($locales as $localeKey) {
|
||||
$subPropsSchemaEdit[$localeKey]['properties'] = $subPropsEdit;
|
||||
$subPropsSchemaRead[$localeKey]['properties'] = $subPropsRead;
|
||||
$subPropsSchemaSummary[$localeKey]['properties'] = $subPropsSummary;
|
||||
}
|
||||
} else {
|
||||
$subPropsSchemaEdit = $subPropsEdit;
|
||||
$subPropsSchemaRead = $subPropsRead;
|
||||
$subPropsSchemaSummary = $subPropsSummary;
|
||||
}
|
||||
if (empty($propSchema->readOnly) && empty($propSchema->writeDisabledInApi)) {
|
||||
$editPropSchema->properties = $subPropsSchemaEdit;
|
||||
}
|
||||
if (empty($propSchema->writeOnly)) {
|
||||
$readPropSchema->properties = $subPropsSchemaRead;
|
||||
}
|
||||
if (!empty($propSchema->apiSummary)) {
|
||||
$summaryPropSchema->properties = $subPropsSchemaSummary;
|
||||
}
|
||||
|
||||
// All non-object props
|
||||
} else {
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
if ($propSchema->type === 'array') {
|
||||
$subProperties = [];
|
||||
foreach ($locales as $localeKey) {
|
||||
$subProperties[$localeKey] = $propSchema->items;
|
||||
}
|
||||
if (empty($propSchema->readOnly) && empty($propSchema->writeDisabledInApi)) {
|
||||
$editPropSchema->properties = $subProperties;
|
||||
}
|
||||
if (empty($propSchema->writeOnly)) {
|
||||
$readPropSchema->properties = $subProperties;
|
||||
}
|
||||
if (!empty($propSchema->apiSummary)) {
|
||||
$summaryPropSchema->properties = $subProperties;
|
||||
}
|
||||
} else {
|
||||
if (empty($propSchema->readOnly) && empty($propSchema->writeDisabledInApi)) {
|
||||
$editPropSchema = ['$ref' => '#/definitions/LocaleObject'];
|
||||
}
|
||||
if (empty($propSchema->writeOnly)) {
|
||||
$readPropSchema = ['$ref' => '#/definitions/LocaleObject'];
|
||||
}
|
||||
if (!empty($propSchema->apiSummary)) {
|
||||
$summaryPropSchema = ['$ref' => '#/definitions/LocaleObject'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($propSchema->readOnly) && empty($propSchema->writeDisabledInApi)) {
|
||||
$editDefinition['properties'][$propName] = $editPropSchema;
|
||||
}
|
||||
if (empty($propSchema->writeOnly)) {
|
||||
$readDefinition['properties'][$propName] = $readPropSchema;
|
||||
}
|
||||
if (!empty($propSchema->apiSummary)) {
|
||||
$summaryDefinition['properties'][$propName] = $summaryPropSchema;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($editDefinition['properties'])) {
|
||||
$definitionEditableName = $definitionName . 'Editable';
|
||||
ksort($editDefinition['properties']);
|
||||
$apiSchema->definitions->{$definitionEditableName} = $this->setEnum($editDefinition);
|
||||
}
|
||||
if (!empty($readDefinition['properties'])) {
|
||||
ksort($readDefinition['properties']);
|
||||
$apiSchema->definitions->{$definitionName} = $this->setEnum($readDefinition);
|
||||
}
|
||||
if (!empty($summaryDefinition['properties'])) {
|
||||
$definitionSummaryName = $definitionName . 'Summary';
|
||||
ksort($summaryDefinition['properties']);
|
||||
$apiSchema->definitions->{$definitionSummaryName} = $this->setEnum($summaryDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addDecisionExamples($apiSchema, json_decode($decisions));
|
||||
|
||||
file_put_contents($this->outputFile, json_encode($apiSchema, JSON_PRETTY_PRINT));
|
||||
|
||||
echo "Done\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the `in:` validation rules to swagger's
|
||||
* `enum` specification
|
||||
*/
|
||||
protected function setEnum(array $definition): array
|
||||
{
|
||||
foreach ($definition['properties'] as $propName => $schema) {
|
||||
if (isset($schema->validation) && is_array($schema->validation)) {
|
||||
foreach ($schema->validation as $rule) {
|
||||
if (substr($rule, 0, 3) === 'in:') {
|
||||
$enum = explode(',', substr($rule, 3));
|
||||
if ($schema->type === 'integer') {
|
||||
$enum = array_map('intval', $enum);
|
||||
}
|
||||
$definition['properties'][$propName]->enum = $enum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the example request bodies for each decision
|
||||
*/
|
||||
protected function addDecisionExamples(stdClass $schema, stdClass $decisions): void
|
||||
{
|
||||
$examples = [];
|
||||
foreach ($decisions as $class => $decision) {
|
||||
/** @var DecisionType $object */
|
||||
$object = new $class();
|
||||
|
||||
$value = [
|
||||
'decision' => $object->getDecision(),
|
||||
];
|
||||
|
||||
if ($this->isDecisionInReview($object)) {
|
||||
$value['reviewRound'] = 123;
|
||||
$value['round'] = 1;
|
||||
}
|
||||
|
||||
if (!empty($decision->actions)) {
|
||||
$value['actions'] = array_map(
|
||||
function (stdClass $action) {
|
||||
if ($action->type === 'form') {
|
||||
return array_merge((array) $action->data, ['id' => $action->id]);
|
||||
} elseif ($action->type === 'email') {
|
||||
return [
|
||||
'attachments' => [
|
||||
[
|
||||
'name' => 'example-upload.pdf',
|
||||
'temporaryFileId' => 1,
|
||||
'documentType' => FileManager::DOCUMENT_TYPE_PDF
|
||||
],
|
||||
[
|
||||
'name' => 'example-submission-file.pdf',
|
||||
'submissionFileId' => 1,
|
||||
'documentType' => FileManager::DOCUMENT_TYPE_PDF
|
||||
],
|
||||
[
|
||||
'name' => 'example-library-file.pdf',
|
||||
'libraryFileId' => 1,
|
||||
'documentType' => FileManager::DOCUMENT_TYPE_PDF
|
||||
]
|
||||
],
|
||||
'bcc' => 'example@pkp.sfu.ca',
|
||||
'cc' => 'example@pkp.sfu.ca',
|
||||
'id' => $action->id,
|
||||
'locale' => 'en',
|
||||
'recipients' => $action->canChangeRecipients
|
||||
? [1,2]
|
||||
: [],
|
||||
'subject' => 'Example email subject',
|
||||
'body' => '<p>Example email body.</p>',
|
||||
];
|
||||
}
|
||||
throw new Exception('Unrecognized decision action type. Can not compile example request body for decision ' . $class);
|
||||
},
|
||||
$decision->actions ?? []
|
||||
);
|
||||
}
|
||||
|
||||
$examples[$class] = [
|
||||
'summary' => $object->getLabel(),
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
$schema
|
||||
->paths
|
||||
->{'/submissions/{submissionId}/decisions'}
|
||||
->post
|
||||
->requestBody
|
||||
->content
|
||||
->{'application/json'}
|
||||
->examples = $examples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the decision type in a review stage?
|
||||
*/
|
||||
protected function isDecisionInReview(DecisionType $decision): bool
|
||||
{
|
||||
return in_array($decision->getStageId(), [WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW]);
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new buildSwagger($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
|
||||
# @file tools/buildjs.sh
|
||||
#
|
||||
# Copyright (c) 2014-2021 Simon Fraser University
|
||||
# Copyright (c) 2010-2021 John Willinsky
|
||||
# Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
#
|
||||
# Script to check and minimize JavaScript for distribution.
|
||||
#
|
||||
# Requirements:
|
||||
# - Requires Python/Closure Linter and Java/Closure Compiler, see
|
||||
# <http://code.google.com/closure>. Install this using npm.
|
||||
# Please see the Closure Linter documentation for installation instructions
|
||||
# of that tool.
|
||||
#
|
||||
# - Requires jslint4java, see <http://code.google.com/p/jslint4java/>. Expects the
|
||||
# jslint4java.jar (must be renamed!) in the same path as the Closure compiler,
|
||||
# i.e. in TOOL_PATH as configured below.
|
||||
#
|
||||
# - This tool expects to be run from the application's main directory.
|
||||
#
|
||||
# Usage: lib/pkp/tools/buildjs.sh [-n]
|
||||
# ...where -n can be optionally specified to prevent caching.
|
||||
#
|
||||
|
||||
|
||||
### OS specific configuration ###
|
||||
|
||||
# Define a tab to be used inside of sed commands (sed on OSX does not recognize \t)
|
||||
TAB=$'\t'
|
||||
|
||||
# Determine what flag to use for extended regular expressions
|
||||
if [ `uname` == 'Darwin' ]; then
|
||||
EXTENDED_REGEX_FLAG='E'
|
||||
else
|
||||
EXTENDED_REGEX_FLAG='r'
|
||||
fi
|
||||
|
||||
### Configuration ###
|
||||
|
||||
TOOL_PATH=~/bin
|
||||
CLOSURE_COMPILER_JAR=./node_modules/google-closure-compiler-java/compiler.jar
|
||||
|
||||
JS_OUTPUT='js/pkp.min.js'
|
||||
|
||||
CLOSURE_EXTERNS='
|
||||
--externs lib/pkp/tools/closure-externs.js
|
||||
--externs lib/pkp/tools/closure-externs-check-only.js
|
||||
--externs lib/pkp/tools/jquery-externs.js'
|
||||
|
||||
|
||||
### Command Line Options ###
|
||||
|
||||
OPTIND=1
|
||||
DO_CACHE=1
|
||||
while getopts "n" opt; do
|
||||
case "$opt" in
|
||||
n) DO_CACHE=0 # No caching
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
### Start Processing ###
|
||||
echo >&2
|
||||
echo "Starting PKP JavaScript builder." >&2
|
||||
echo "Copyright (c) 2014-2021 Simon Fraser University" >&2
|
||||
echo "Copyright (c) 2010-2021 John Willinsky" >&2
|
||||
|
||||
|
||||
### Checking Requirements ###
|
||||
MISSING_REQUIREMENT=''
|
||||
if [ -z `which gjslint` ]; then
|
||||
echo >&2
|
||||
echo "Google Closure Linter not found in PATH. Please go" >&2
|
||||
echo "to <https://developers.google.com/closure/utilities/docs/linter_howto>" >&2
|
||||
echo "and make sure that you correctly install the tool before you run" >&2
|
||||
echo "buildjs.sh." >&2
|
||||
MISSING_REQUIREMENT='gjslint'
|
||||
fi
|
||||
|
||||
if [ ! -e "$TOOL_PATH/jslint4java.jar" ]; then
|
||||
echo >&2
|
||||
echo "JSLint4Java must be installed in the '$TOOL_PATH'" >&2
|
||||
echo "directory. Please download the tool from" >&2
|
||||
echo "<http://code.google.com/p/jslint4java/>," >&2
|
||||
echo "rename it to jslint4java.jar and try again." >&2
|
||||
MISSING_REQUIREMENT='jslint4java'
|
||||
fi
|
||||
|
||||
if [ ! -e "$CLOSURE_COMPILER_JAR" ]; then
|
||||
echo >&2
|
||||
echo "Google Closure Compiler not found in '$CLOSURE_COMPILER_JAR'" >&2
|
||||
echo "Please run 'npm npm install --save google-closure-compiler' and try again." >&2
|
||||
MISSING_REQUIREMENT='closure'
|
||||
fi
|
||||
|
||||
if [ -n "$MISSING_REQUIREMENT" ]; then
|
||||
echo >&2
|
||||
echo "Exiting!" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo >&2
|
||||
|
||||
# A list with all files to be compiled and minified. Expects
|
||||
# a complete list of script files in registry/minifiedScripts.txt.
|
||||
COMPILE_FILES=$(sed -n '/^[^#]/p' registry/minifiedScripts.txt)
|
||||
|
||||
# FIXME: For now we only check classes as the other
|
||||
# files contain too many errors to be fixed right now.
|
||||
LINT_FILES=`echo "$COMPILE_FILES" | egrep -v '^lib/pkp/js/(lib|functions)'`
|
||||
|
||||
# Create a working directory in the cache
|
||||
WORKDIR=`mktemp -dt tmp.XXXXXXXXXX` || { echo "The working directory could not be created\!"; exit 1; }
|
||||
|
||||
# Show a list of the files we are going to lint.
|
||||
echo "Lint..." >&2
|
||||
echo "Lint..." >"$WORKDIR/.compile-warnings.out"
|
||||
for JS_FILE in $LINT_FILES; do
|
||||
echo -n "...$JS_FILE" >&2
|
||||
echo "...$JS_FILE"
|
||||
|
||||
# Prepare file for gjslint and compiler check:
|
||||
# - transforms whitespace to comply with Google style guide
|
||||
# - wraps @extends type in curly braces to comply with Google style guide.
|
||||
# - works around http://code.google.com/p/closure-compiler/issues/detail?id=61 by removing the jQuery closure.
|
||||
mkdir -p `dirname "$WORKDIR/$JS_FILE"`
|
||||
sed \
|
||||
-e "s/^${TAB}//" \
|
||||
-e "s/${TAB}/ /g" \
|
||||
-e 's/^(function(\$) {//' \
|
||||
-e 's/^}(jQuery));//' \
|
||||
-e 's/@extends \(.*\)$/@extends {\1}/' \
|
||||
"$JS_FILE" > "$WORKDIR/$JS_FILE"
|
||||
|
||||
|
||||
# Only lint file if it has been changed since last compilation.
|
||||
if [ ! \( -e "$JS_OUTPUT" \) -o \( "$JS_FILE" -nt "$JS_OUTPUT" \) -o \( "$DO_CACHE" -eq 0 \) ]; then
|
||||
|
||||
#############################
|
||||
### Google Closure Linter ###
|
||||
#############################
|
||||
|
||||
# Run gjslint on the file.
|
||||
gjslint --strict --nosummary --custom_jsdoc_tags=defgroup,ingroup,file,brief "$WORKDIR/$JS_FILE" | grep '^Line' | sed "s/^/${TAB}/"
|
||||
|
||||
|
||||
##################################
|
||||
### Douglas Crockford's JSLint ###
|
||||
##################################
|
||||
|
||||
# Run JSLint on the file:
|
||||
# - Allow for loops without "hasOwnProperty()" check because we operate in an environment
|
||||
# where additions to the Object prototype are not allowed (same as jQuery).
|
||||
# - Do not alert on whitespace checking which we prefer to be checked by gjslint.
|
||||
# This is necessary to remove inconsistency between gjslint's and
|
||||
# jslint's whitespace rules.
|
||||
# - We allow dangling underscores (_) to mark private properties and let the
|
||||
# Closure compiler enforce it.
|
||||
# - We allow the ++ and == syntax
|
||||
# - We allow "continue"
|
||||
# - Multiple var statements in one function are allowed to reduce variable span.
|
||||
# - We allow code without the 'use strict' pragma as we need the callee property
|
||||
# for our class framework implementation.
|
||||
java -jar "$TOOL_PATH/jslint4java.jar" --white --forin --nomen --plusplus --continue \
|
||||
--eqeq --sloppy --browser --predef pkp,jQuery,alert,tinyMCE,confirm,plupload \
|
||||
--regexp "$JS_FILE" | sed "s/^/${TAB}/"
|
||||
echo "...processed!" >&2
|
||||
|
||||
else
|
||||
echo "...skipped!" >&2
|
||||
fi
|
||||
done >>"$WORKDIR/.compile-warnings.out"
|
||||
echo >&2
|
||||
|
||||
|
||||
###############################
|
||||
### Google Closure Compiler ###
|
||||
###############################
|
||||
|
||||
# Transform lint file list into Closure input parameter list.
|
||||
LINT_FILES=`echo "$LINT_FILES" | sed "s%^%$WORKDIR/%" | tr '\n' ' ' | sed -$EXTENDED_REGEX_FLAG 's/ $//;s/(^| )/ --js /g'`
|
||||
|
||||
# Run Closure - first pass to check with transformed files.
|
||||
echo >> "$WORKDIR/.compile-warnings.out"
|
||||
echo "Compile (Check)..." >> "$WORKDIR/.compile-warnings.out"
|
||||
echo "Compile (Check)..." >&2
|
||||
java -jar ${CLOSURE_COMPILER_JAR} --language_in=ECMASCRIPT5 --jscomp_warning visibility --warning_level DEFAULT \
|
||||
$CLOSURE_EXTERNS $LINT_FILES --js_output_file /dev/null 2>&1 \
|
||||
| sed "s/^/${TAB}/" >>"$WORKDIR/.compile-warnings.out"
|
||||
|
||||
# Only minify when there were no warnings.
|
||||
if [ -n "`cat $WORKDIR/.compile-warnings.out | grep '^ ' | grep -v 'Picked up _JAVA_OPTIONS'`" ]; then
|
||||
# Issue warnings. If interactive, use "less".
|
||||
case "$-" in
|
||||
*i*) less "$WORKDIR/.compile-warnings.out" ;;
|
||||
*) cat "$WORKDIR/.compile-warnings.out" ;;
|
||||
esac
|
||||
echo >&2
|
||||
echo "Found Errors! Not minified."
|
||||
echo "Exiting!"
|
||||
|
||||
# Remove the temporary directory.
|
||||
rm -r "$WORKDIR"
|
||||
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# Show the list of files we are going to compile:
|
||||
echo >&2
|
||||
echo "Compile (Minify)..." >&2
|
||||
echo "$COMPILE_FILES" | sed 's/^/.../' >&2
|
||||
|
||||
# Transform file list into Closure input parameter list.
|
||||
COMPILE_FILES=`echo "$COMPILE_FILES" | tr '\n' ' ' | sed -$EXTENDED_REGEX_FLAG 's/ $//;s/(^| )/ --js /g'`
|
||||
|
||||
# Run Closure - second pass to minify
|
||||
java -jar ${CLOSURE_COMPILER_JAR} --language_in=ECMASCRIPT5 --jscomp_off checkTypes --warning_level DEFAULT $COMPILE_FILES \
|
||||
$CLOSURE_EXTERNS --js_output_file "$JS_OUTPUT" 2>&1
|
||||
echo >&2
|
||||
|
||||
echo "Please don't forget to set enable_minified=On in your config.inc.php." >&2
|
||||
echo >&2
|
||||
echo "Done!" >&2
|
||||
|
||||
# Remove the temporary directory.
|
||||
rm -r "$WORKDIR"
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# @file tools/checkHelp.sh
|
||||
#
|
||||
# Copyright (c) 2014-2021 Simon Fraser University
|
||||
# Copyright (c) 2010-2021 John Willinsky
|
||||
# Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
#
|
||||
# Script to check help file mappings from code to Markdown.
|
||||
#
|
||||
# Usage: lib/pkp/tools/checkHelp.sh
|
||||
#
|
||||
|
||||
# Look for help filenames referred to in templates and check that they all exist (in English)
|
||||
ERRORS=0
|
||||
for filename in `find . -name \*.tpl -exec sed -n -e "s/.*{help[^}]file=\"\([^\"#]\+\)[#\"].*/\1/p" "{}" ";"`; do
|
||||
if [ ! -f docs/manual/en/$filename.md ]; then
|
||||
echo "Help file \"$filename.md\" referred to in template does not exist!"
|
||||
ERRORS=1
|
||||
fi
|
||||
done
|
||||
if [ $ERRORS -ne 0 ]; then
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# Generate a quick report of the differences between the files listed in templates and the available files.
|
||||
find . -name \*.tpl -exec sed -n -e "s/.*{help[^}]file=\"\([^\"]\+\)\".*/\1/p" "{}" ";" | sort | uniq > /tmp/template-help-references.txt
|
||||
cat docs/manual/en/SUMMARY.md | sed -n -e "s/.*(\([^)]\+\))/\1/p" | sort | uniq > /tmp/help-files.txt
|
||||
echo "Unreferenced help files:"
|
||||
diff /tmp/template-help-references.txt /tmp/help-files.txt | grep -e "^>"
|
||||
|
||||
# Successful completion
|
||||
exit 0
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* closure-externs-check-only.js
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2010-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* Import symbols into the closure compiler that are not defined
|
||||
* within the files compiled during the strict check phase of the build
|
||||
* script. (We only include classes for strict checking, not legacy
|
||||
* function.)
|
||||
*
|
||||
* @externs
|
||||
*/
|
||||
|
||||
// FIXME: Replace the reference to the ajaxAction() function
|
||||
// with an object/event oriented approach, see #6339.
|
||||
/**
|
||||
* @param {string} actOnId the ID of an element to be changed.
|
||||
* @param {string} callingElement selector of the element that triggers the ajax call
|
||||
* @param {string} url the url to be called, defaults to the form action in case of
|
||||
* action type 'post'.
|
||||
* @param {Object=} data (post action type only) the data to be posted, defaults to
|
||||
* the form data.
|
||||
* @param {string=} eventName the name of the event that triggers the action, default 'click'.
|
||||
* @param {string=} form the selector of a form element.
|
||||
*/
|
||||
function ajaxAction(actOnId, callingElement, url, data, eventName, form) {}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} jsonStr The string to parse.
|
||||
* @param {(function(string, *) : *)=} opt_reviver
|
||||
* @return {*} The JSON object.
|
||||
* @throws {Error}
|
||||
* @nosideeffects
|
||||
*/
|
||||
JSONType.prototype.parse = function(jsonStr, opt_reviver) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {*} jsonObj Input object.
|
||||
* @param {(Array.<string>|(function(string, *) : *)|null)=} opt_replacer
|
||||
* @param {(number|string)=} opt_space
|
||||
* @return {string} JSON string which represents jsonObj.
|
||||
* @throws {Error}
|
||||
* @nosideeffects
|
||||
*/
|
||||
JSONType.prototype.stringify = function(jsonObj, opt_replacer, opt_space) {};
|
||||
|
||||
|
||||
/**
|
||||
* @type {!JSONType}
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
var JSON;
|
||||
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* closure-externs.js
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2010-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* Import symbols into the closure compiler that are not defined
|
||||
* within the compiled files.
|
||||
*
|
||||
* See https://github.com/google/closure-compiler/tree/master/externs
|
||||
* for pre-extracted extern files, e.g. for jQuery.
|
||||
*
|
||||
* @externs
|
||||
*/
|
||||
|
||||
jQueryObject.prototype.browser = { msie: false };
|
||||
|
||||
/**
|
||||
* @param {Object} arg1
|
||||
*/
|
||||
jQueryObject.prototype.autocomplete = function(arg1) {};
|
||||
|
||||
/**
|
||||
* @param {string=} param1
|
||||
* @param {string|number=} param2
|
||||
* @param {string=} param3
|
||||
*/
|
||||
jQueryObject.prototype.button = function(param1, param2, param3) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object=} options
|
||||
*/
|
||||
jQueryObject.prototype.validate = function(options) {};
|
||||
jQueryObject.prototype.valid = function() {};
|
||||
|
||||
/**
|
||||
* @param {Function} param1
|
||||
*/
|
||||
jQueryObject.prototype.sortElements = function(param1) {};
|
||||
|
||||
/**
|
||||
* @param {Object|string=} param1
|
||||
*/
|
||||
jQueryObject.prototype.sortable = function(param1) {};
|
||||
|
||||
/**
|
||||
* @param {Object=} options
|
||||
*/
|
||||
jQueryObject.prototype.jLabel = function(options) {};
|
||||
|
||||
/**
|
||||
* @param {Object=} options
|
||||
*/
|
||||
jQueryObject.prototype.selectBox = function(options) {};
|
||||
|
||||
jQueryObject.prototype.equalizeElementHeights = function() {};
|
||||
|
||||
/**
|
||||
* @param {Object=} options
|
||||
*/
|
||||
jQueryObject.prototype.slider = function(options) {};
|
||||
|
||||
/**
|
||||
* @param {string|Object=} param1
|
||||
* @param {string|number|Object=} param2
|
||||
* @param {string|number|Object=} param3
|
||||
*/
|
||||
jQueryObject.prototype.tabs = function(param1, param2, param3) {};
|
||||
|
||||
/**
|
||||
* @param {string|Object} param1
|
||||
* @param {string|Object=} param2
|
||||
*/
|
||||
jQueryObject.prototype.datepicker = function(param1, param2) {};
|
||||
|
||||
/**
|
||||
* @param {string|Object} param1
|
||||
* @param {string|Object|boolean|number=} param2
|
||||
* @param {string|boolean=} param3
|
||||
*/
|
||||
jQueryObject.prototype.accordion = function(param1, param2, param3) {};
|
||||
|
||||
/**
|
||||
* Handler plug-in.
|
||||
* @param {string} handlerName The handler to be instantiated
|
||||
* and attached to the target HTML element(s).
|
||||
* @param {Object=} options Parameters to be passed on
|
||||
* to the handler.
|
||||
* @return {jQueryObject} Selected HTML elements for chaining.
|
||||
*/
|
||||
jQueryObject.prototype.pkpHandler = function(handlerName, options) {};
|
||||
|
||||
/**
|
||||
* Re-implementation of jQuery's html() method
|
||||
* with a remote source.
|
||||
* @param {string} url the AJAX endpoint from which to
|
||||
* retrieve the HTML to be inserted.
|
||||
* @param {Object=} callback function to be called on ajax success.
|
||||
* @return {jQueryObject} Selected HTML elements for chaining.
|
||||
*/
|
||||
jQueryObject.prototype.pkpAjaxHtml = function(url, callback) {};
|
||||
|
||||
/**
|
||||
* @param {string|Object=} param1
|
||||
* @param {string=} param2
|
||||
* @param {string|Object=} param3
|
||||
*/
|
||||
jQueryObject.prototype.dialog = function(param1, param2, param3) {};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object=} options
|
||||
* @param {jQueryObject=} form
|
||||
*/
|
||||
jQuery.validator = function(options, form) {};
|
||||
|
||||
jQuery.validator.prototype.checkForm = function() {};
|
||||
|
||||
jQuery.validator.prototype.defaultShowErrors = function() {};
|
||||
|
||||
jQuery.validator.prototype.settings = {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @param {string|boolean|Object=} param2
|
||||
*/
|
||||
jQueryObject.prototype.prop = function(param1, param2) {};
|
||||
|
||||
jQueryObject.prototype.panel = null;
|
||||
jQueryObject.prototype.newTab = null;
|
||||
jQueryObject.prototype.newTab.index = function() {};
|
||||
jQueryObject.prototype.newTab.find = function() {};
|
||||
jQueryObject.prototype.newPanel = null;
|
||||
jQueryObject.prototype.ajaxSettings = null;
|
||||
jQueryObject.prototype.jqXHR = null;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
function tinyMCEObject() {}
|
||||
|
||||
tinyMCEObject.prototype.PluginManager = {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @param {string} param2
|
||||
* @return {tinyMCEObject}
|
||||
*/
|
||||
tinyMCEObject.prototype.PluginManager.load = function(param1, param2) {};
|
||||
|
||||
tinyMCEObject.prototype.EditorManager = {};
|
||||
|
||||
tinyMCEObject.prototype.EditorManager.triggerSave = function() {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @param {Object} param2
|
||||
* @return {tinyMCEObject}
|
||||
*/
|
||||
tinyMCEObject.prototype.EditorManager.createEditor = function(param1, param2) {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @return {tinyMCEObject}
|
||||
*/
|
||||
tinyMCEObject.prototype.EditorManager.get = function(param1) {};
|
||||
|
||||
/**
|
||||
* @param {Object} param1
|
||||
*/
|
||||
tinyMCEObject.prototype.init = function(param1) {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @return {tinyMCEObject}
|
||||
*/
|
||||
tinyMCEObject.prototype.get = function(param1) {};
|
||||
|
||||
tinyMCEObject.prototype.target = {dom: {}, editorContainer: {}};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
*/
|
||||
tinyMCEObject.prototype.target.dom.get = function(param1) {};
|
||||
|
||||
tinyMCEObject.prototype.target.getContent = function() {};
|
||||
tinyMCEObject.prototype.getContent = function() {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
*/
|
||||
tinyMCEObject.prototype.setContent = function(param1) {};
|
||||
|
||||
tinyMCEObject.prototype.render = function() {};
|
||||
|
||||
/**
|
||||
* @param {string} param1
|
||||
* @param {Object} param2
|
||||
*/
|
||||
tinyMCEObject.prototype.on = function(param1, param2) {};
|
||||
|
||||
tinyMCEObject.prototype.off = function() {};
|
||||
|
||||
tinyMCEObject.prototype.editor = { dom: {}, id: '' };
|
||||
|
||||
tinyMCEObject.prototype.dom = {};
|
||||
|
||||
tinyMCEObject.prototype.editor.dom.getRoot = function() {};
|
||||
|
||||
/**
|
||||
* @type {string} c
|
||||
*/
|
||||
tinyMCEObject.prototype.id = '';
|
||||
|
||||
tinyMCEObject.prototype.getWin = function() {};
|
||||
|
||||
tinyMCEObject.prototype.getBody = function() {};
|
||||
|
||||
tinyMCEObject.prototype.getContainer = function() {};
|
||||
|
||||
tinyMCEObject.prototype.onSetContent = function() {};
|
||||
|
||||
/**
|
||||
* @param {Object} param1
|
||||
*/
|
||||
tinyMCEObject.prototype.onSetContent.add = function(param1) {};
|
||||
|
||||
/**
|
||||
* @param {Object} param1
|
||||
*/
|
||||
tinyMCEObject.prototype.onSetContent.remove = function(param1) {};
|
||||
|
||||
/**
|
||||
* @type {tinyMCEObject}
|
||||
*/
|
||||
var tinyMCE;
|
||||
|
||||
/**
|
||||
* @param {string} f
|
||||
*/
|
||||
jQueryObject.prototype.plupload = function(f) {};
|
||||
var plupload = {};
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @constructor
|
||||
*/
|
||||
plupload.Uploader = function (options) {};
|
||||
plupload.Uploader.prototype.id = null;
|
||||
plupload.Uploader.prototype.init = function() {};
|
||||
plupload.Uploader.prototype.refresh = function() {};
|
||||
|
||||
/**
|
||||
* @param {string|number} p
|
||||
*/
|
||||
plupload.Uploader.prototype.percent = function(p) {};
|
||||
|
||||
/**
|
||||
* @param {string} f
|
||||
*/
|
||||
plupload.Uploader.prototype.removeFile = function(f) {};
|
||||
|
||||
/**
|
||||
* @param {!string} eventName
|
||||
* @param {Function} f
|
||||
*/
|
||||
plupload.Uploader.prototype.bind = function(eventName, f) {};
|
||||
|
||||
$.pkp.app = {
|
||||
baseUrl: '',
|
||||
tinyMceContentCSS: '',
|
||||
};
|
||||
|
||||
$.pkp.cons = {
|
||||
WORKFLOW_STAGE_ID_SUBMISSION: 0,
|
||||
WORKFLOW_STAGE_ID_INTERNAL_REVIEW: 0,
|
||||
WORKFLOW_STAGE_ID_EXTERNAL_REVIEW: 0,
|
||||
WORKFLOW_STAGE_ID_EDITING: 0,
|
||||
WORKFLOW_STAGE_ID_PRODUCTION: 0,
|
||||
REALLY_BIG_NUMBER: 0,
|
||||
ORDER_CATEGORY_GRID_CATEGORIES_ONLY: 0,
|
||||
ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS: 0,
|
||||
LISTBUILDER_SOURCE_TYPE_SELECT: 0,
|
||||
LISTBUILDER_OPTGROUP_LABEL: 0,
|
||||
ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY: 0,
|
||||
UPLOAD_MAX_FILESIZE: 0,
|
||||
INSERT_TAG_VARIABLE_TYPE_PLAIN_TEXT: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
var _ = {
|
||||
isNull: function(object) {},
|
||||
each: function(array, callback) {},
|
||||
reject: function(array, callback) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
var pkp = {
|
||||
currentUser: {
|
||||
id: 0,
|
||||
csrfToken: '',
|
||||
roles: []
|
||||
},
|
||||
eventBus: {
|
||||
$emit: function(name, data) {},
|
||||
$on: function(name, callback) {},
|
||||
$off: function(name, callback) {}
|
||||
},
|
||||
registry: {
|
||||
_instances: []
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/constants.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class constants
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Get the value of application constants.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class constants extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (isset($argv[1]) && in_array($argv[1], ['--help', '-h'])) {
|
||||
$this->usage();
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($argv[1])) {
|
||||
$this->value = $argv[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Get the value of application constants.\n"
|
||||
. "\n"
|
||||
. "Usage: {$this->scriptName} [value]\n"
|
||||
. "[value] Get the name of constants that match this value.\n"
|
||||
. "\n"
|
||||
. "This will only match application-wide constants available after the initial bootstrap.\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test metrics
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$all = get_defined_constants(true);
|
||||
$app = $all['user'];
|
||||
|
||||
if ($this->value) {
|
||||
$constants = [];
|
||||
foreach ($app as $const => $value) {
|
||||
if ($this->value == $value) {
|
||||
$constants[] = $const;
|
||||
}
|
||||
}
|
||||
if (empty($constants)) {
|
||||
echo 'No constants were found with that value. This tool only matches constants loaded in the application bootstrap process and may miss other constants.';
|
||||
} else {
|
||||
print_r($constants);
|
||||
}
|
||||
} else {
|
||||
print_r($app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new constants($argv ?? []);
|
||||
$tool->execute();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/convertUsageStatsLogFile.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ConvertUsageStatsLogFile
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to convert an old usage stats log file (used in releases < 3.4) into the new format.
|
||||
*
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use PKP\cliTool\ConvertLogFileTool;
|
||||
use PKP\task\FileLoader;
|
||||
|
||||
class ConvertUsageStatsLogFile extends ConvertLogFileTool
|
||||
{
|
||||
/**
|
||||
* Weather the URL parameters are used instead of CGI PATH_INFO.
|
||||
* This is the former variable 'disable_path_info' in the config.inc.php
|
||||
*
|
||||
* This needs to be set to true if the URLs in the old log file contain the paramteres as URL query string.
|
||||
*/
|
||||
public const PATH_INFO_DISABLED = false;
|
||||
|
||||
/**
|
||||
* Regular expression that is used for parsing the old log file entries that should be converted to the new format.
|
||||
*
|
||||
* The default regex can parse the usageStats plugin's log files.
|
||||
*/
|
||||
public const PARSEREGEX = '/^(?P<ip>\S+) \S+ \S+ "(?P<date>.*?)" (?P<url>\S+) (?P<returnCode>\S+) "(?P<userAgent>.*?)"/';
|
||||
|
||||
/**
|
||||
* PHP format of the time in the log file.
|
||||
* S. https://www.php.net/manual/en/datetime.format.php
|
||||
*
|
||||
* This default format can parse the date in the usageStats plugin's log files.
|
||||
*/
|
||||
public const PHP_DATETIME_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
/**
|
||||
* Name of the log file that should be converted into the new format.
|
||||
*/
|
||||
public string $fileName;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments (see usage)
|
||||
*/
|
||||
public function __construct(array $argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
if (count($this->argv) != 1) {
|
||||
$this->usage();
|
||||
exit(8);
|
||||
}
|
||||
$this->fileName = array_shift($this->argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage(): void
|
||||
{
|
||||
$archivePath = $this->getLogFileDir();
|
||||
echo "\nConvert an old usage stats log file.\nThe old usage stats log file needs to be in the folder {$archivePath}.\n\n"
|
||||
. " Usage: php {$this->scriptName} [fileName]\n\n";
|
||||
}
|
||||
|
||||
public function getLogFileDir(): string
|
||||
{
|
||||
return StatisticsHelper::getUsageStatsDirPath() . '/' . FileLoader::FILE_LOADER_PATH_ARCHIVE;
|
||||
}
|
||||
|
||||
public function getParseRegex(): string
|
||||
{
|
||||
return self::PARSEREGEX;
|
||||
}
|
||||
|
||||
public function getPhpDateTimeFormat(): string
|
||||
{
|
||||
return self::PHP_DATETIME_FORMAT;
|
||||
}
|
||||
|
||||
public function isPathInfoDisabled(): bool
|
||||
{
|
||||
return self::PATH_INFO_DISABLED;
|
||||
}
|
||||
|
||||
public function isApacheAccessLogFile(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the file.
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
$this->convert($this->fileName);
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new ConvertUsageStatsLogFile($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file tools/events.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class commandEvents
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to list all events registered on the system
|
||||
*/
|
||||
|
||||
namespace PKP\tools\event;
|
||||
|
||||
use Illuminate\Console\Concerns\InteractsWithIO;
|
||||
use Illuminate\Console\OutputStyle;
|
||||
use PKP\cliTool\CommandLineTool;
|
||||
use PKP\core\EventServiceProvider;
|
||||
use PKP\core\PKPContainer;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Console\Helper\Helper;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Throwable;
|
||||
|
||||
define('APP_ROOT', dirname(__FILE__, 4));
|
||||
require_once APP_ROOT . '/tools/bootstrap.php';
|
||||
|
||||
class commandEvents extends CommandLineTool
|
||||
{
|
||||
use InteractsWithIO;
|
||||
|
||||
protected const AVAILABLE_OPTIONS = [
|
||||
'cache' => 'Create an Events cached version',
|
||||
'clear' => 'Clear the Events cached version',
|
||||
'list' => 'List all events on the system',
|
||||
'usage' => 'Display the command usage'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var null|string Which option will be call?
|
||||
*/
|
||||
protected $option = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
array_shift($argv);
|
||||
|
||||
$this->option = array_shift($argv);
|
||||
|
||||
if (!$this->option) {
|
||||
throw new CommandNotFoundException(
|
||||
'Option could not be empty! Check the usage method.',
|
||||
array_keys(self::AVAILABLE_OPTIONS)
|
||||
);
|
||||
}
|
||||
|
||||
$output = new OutputStyle(
|
||||
new StringInput(''),
|
||||
new StreamOutput(fopen('php://stdout', 'w'))
|
||||
);
|
||||
|
||||
$this->setOutput($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
$this->line('<comment>Usage:</comment>');
|
||||
$this->line('command [arguments]' . PHP_EOL);
|
||||
$this->line('<comment>Available commands for the "events" namespace:</comment>');
|
||||
|
||||
$width = $this->getColumnWidth(array_keys(self::AVAILABLE_OPTIONS));
|
||||
|
||||
foreach (self::AVAILABLE_OPTIONS as $commandName => $description) {
|
||||
$spacingWidth = $width - Helper::width($commandName);
|
||||
$this->line(
|
||||
sprintf(
|
||||
' <info>%s</info>%s%s',
|
||||
$commandName,
|
||||
str_repeat(' ', $spacingWidth),
|
||||
$description
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the columnWidth based on the commands text size
|
||||
*/
|
||||
protected function getColumnWidth(array $commands): int
|
||||
{
|
||||
$widths = [];
|
||||
|
||||
foreach ($commands as $command) {
|
||||
$widths[] = Helper::width($command);
|
||||
}
|
||||
|
||||
return $widths ? max($widths) + 2 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all events registered
|
||||
*/
|
||||
protected function list(): void
|
||||
{
|
||||
$eventServiceProvider = app()
|
||||
->makeWith(
|
||||
EventServiceProvider::class,
|
||||
['app' => PKPContainer::getInstance()]
|
||||
);
|
||||
|
||||
$events = [];
|
||||
|
||||
$rawEvents = $eventServiceProvider->getEvents();
|
||||
|
||||
foreach ($rawEvents as $event => $listeners) {
|
||||
$events[] = [$event, implode(', ', $listeners)];
|
||||
}
|
||||
|
||||
$this->table(['Event', 'Listeners'], $events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the Event cached file
|
||||
*/
|
||||
protected function clear(): void
|
||||
{
|
||||
EventServiceProvider::clearCache();
|
||||
|
||||
$this->getOutput()->success('Cache cleared!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Event cached file
|
||||
*/
|
||||
protected function cache(): void
|
||||
{
|
||||
EventServiceProvider::clearCache();
|
||||
|
||||
$eventServiceProvider = app()
|
||||
->makeWith(
|
||||
EventServiceProvider::class,
|
||||
['app' => PKPContainer::getInstance()]
|
||||
);
|
||||
|
||||
// Rebuild the cache
|
||||
$eventServiceProvider->getEvents();
|
||||
$this->getOutput()->success('Cache rebuilt!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and execute list event command
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if (!isset(self::AVAILABLE_OPTIONS[$this->option])) {
|
||||
throw new CommandNotFoundException(
|
||||
sprintf('Option "%s" does not exist.', $this->option),
|
||||
array_keys(self::AVAILABLE_OPTIONS)
|
||||
);
|
||||
}
|
||||
|
||||
$this->{$this->option}();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$tool = new commandEvents($argv ?? []);
|
||||
$tool->execute();
|
||||
} catch (Throwable $e) {
|
||||
if ($e instanceof CommandNotFoundException) {
|
||||
$output = new OutputStyle(
|
||||
new StringInput(''),
|
||||
new StreamOutput(fopen('php://stdout', 'w'))
|
||||
);
|
||||
|
||||
$alternatives = $e->getAlternatives();
|
||||
$message = count($alternatives) > 1 ? 'Did you mean one of those?' : 'Did you mean this?';
|
||||
$message .= PHP_EOL . implode(PHP_EOL, $alternatives);
|
||||
|
||||
$output->block(
|
||||
[$e->getMessage(), $message],
|
||||
null,
|
||||
'fg=white;bg=red',
|
||||
' ',
|
||||
true
|
||||
);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/generateTestMGeoetrics.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class generateTestGeoMetrics
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Generate example Geo metric data.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Sokil\IsoCodes\IsoCodesFactory;
|
||||
|
||||
class generateTestGeoMetrics extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
public $contextId;
|
||||
public $dateStart;
|
||||
public $dateEnd;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (sizeof($this->argv) < 3) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->contextId = (int) $argv[1];
|
||||
$this->dateStart = $argv[2];
|
||||
$this->dateEnd = $argv[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Generate fake usage data in the DB table metrics_submission_geo_monthly.\n"
|
||||
. "Usage: {$this->scriptName} [contextId] [dateStart] [dateEnd]\n"
|
||||
. "contextId The context to add metrics for.\n"
|
||||
. "dateStart Add monthly metrics after this date. YYYY-MM-DD\n"
|
||||
. "dateEnd Add monthly metrics before this date. YYYY-MM-DD\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test metrics
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$isoCodes = app(IsoCodesFactory::class);
|
||||
$countries = $isoCodes->getCountries()->toArray();
|
||||
$subDivisions = $isoCodes->getSubdivisions();
|
||||
|
||||
$submissionIds = $this->getPublishedSubmissionIds();
|
||||
|
||||
$currentDate = new DateTime($this->dateStart);
|
||||
$endDate = new DateTime($this->dateEnd);
|
||||
$endDateTimeStamp = $endDate->getTimestamp();
|
||||
|
||||
$count = 0;
|
||||
while ($currentDate->getTimestamp() < $endDateTimeStamp) {
|
||||
foreach ($submissionIds as $submissionId) {
|
||||
$randomCountryIndex = array_rand($countries);
|
||||
$randomCountry = $countries[$randomCountryIndex];
|
||||
|
||||
$countryRegions = $subDivisions->getAllByCountryCode($randomCountry->getAlpha2());
|
||||
|
||||
$randomRegion = '';
|
||||
if (!empty($countryRegions)) {
|
||||
$randomSubDivisionIndex = array_rand($countryRegions);
|
||||
$randomSubDivision = $countryRegions[$randomSubDivisionIndex];
|
||||
$regionIsoCodeArray = explode('-', $randomSubDivision->getCode());
|
||||
$randomRegion = $regionIsoCodeArray[1];
|
||||
}
|
||||
|
||||
$randomMetric = random_int(1, 10);
|
||||
|
||||
DB::table('metrics_submission_geo_monthly')->insert([
|
||||
'context_id' => $this->contextId,
|
||||
'submission_id' => $submissionId,
|
||||
'country' => $randomCountry->getAlpha2(),
|
||||
'region' => $randomRegion,
|
||||
'month' => $currentDate->format('Ym'),
|
||||
'metric' => $randomMetric,
|
||||
'metric_unique' => random_int(1, $randomMetric)
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
$currentDate->add(new DateInterval('P1M'));
|
||||
}
|
||||
|
||||
echo $count . ' records added for ' . count($submissionIds) . " submissions.\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all published submission IDs in the database
|
||||
*/
|
||||
public function getPublishedSubmissionIds()
|
||||
{
|
||||
return Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$this->contextId])
|
||||
->filterByStatus([Submission::STATUS_PUBLISHED])
|
||||
->getIds();
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new generateTestGeoMetrics($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/generateTestMetrics.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class generateTestMetrics
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Generate example metric data.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class generateTestMetrics extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
public $contextId;
|
||||
public $dateStart;
|
||||
public $dateEnd;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (sizeof($this->argv) < 3) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->contextId = (int) $argv[1];
|
||||
$this->dateStart = $argv[2];
|
||||
$this->dateEnd = $argv[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Generate fake usage data in the metrics table.\n"
|
||||
. "Usage: {$this->scriptName} [contextId] [dateStart] [dateEnd]\n"
|
||||
. "contextId The context to add metrics for.\n"
|
||||
. "dateStart Add metrics after this date. YYYY-MM-DD\n"
|
||||
. "dateEnd Add metrics after this date. YYYY-MM-DD\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test metrics
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$submissionIds = $this->getPublishedSubmissionIds();
|
||||
|
||||
$currentDate = new DateTime($this->dateStart);
|
||||
$endDate = new DateTime($this->dateEnd);
|
||||
$endDateTimeStamp = $endDate->getTimestamp();
|
||||
|
||||
$count = 0;
|
||||
while ($currentDate->getTimestamp() < $endDateTimeStamp) {
|
||||
foreach ($submissionIds as $submissionId) {
|
||||
DB::table('metrics_submission')->insert([
|
||||
'load_id' => 'test_events_' . $currentDate->format('Ymd'),
|
||||
'context_id' => $this->contextId,
|
||||
'submission_id' => $submissionId,
|
||||
'assoc_type' => Application::ASSOC_TYPE_SUBMISSION,
|
||||
'date' => $currentDate->format('Y-m-d'),
|
||||
'metric' => random_int(1, 10),
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
$currentDate->add(new DateInterval('P1D'));
|
||||
}
|
||||
|
||||
echo $count . ' records added for ' . count($submissionIds) . " submissions.\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all published submission IDs in the database
|
||||
*/
|
||||
public function getPublishedSubmissionIds()
|
||||
{
|
||||
return Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$this->contextId])
|
||||
->filterByStatus([Submission::STATUS_PUBLISHED])
|
||||
->getIds();
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new generateTestMetrics($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/getHooks.php
|
||||
*
|
||||
* Copyright (c) 2014-2020 Simon Fraser University
|
||||
* Copyright (c) 2003-2020 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class getHooks
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to compile documentation on hooks in markdown
|
||||
*/
|
||||
|
||||
define('APP_ROOT', dirname(dirname(dirname(dirname(__FILE__)))));
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class getHooks extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var array Hooks */
|
||||
public array $hooks = [];
|
||||
|
||||
/** @var array Directories to exclude from indexing. (.gitignore dirs will be added to this list) */
|
||||
public array $excludePaths = [
|
||||
'./.git',
|
||||
'./cache',
|
||||
'./cypress',
|
||||
'./docs',
|
||||
'./locale',
|
||||
'./lib/pkp/.git',
|
||||
'./lib/pkp/cypress',
|
||||
'./lib/pkp/lib/vendor',
|
||||
'./lib/pkp/locale',
|
||||
'./lib/ui-library',
|
||||
'./lib/pkp/tools/getHooks.php'
|
||||
];
|
||||
|
||||
/**
|
||||
* Parse and execute the import/export task.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$this->loadIgnoreDirs(APP_ROOT . '/.gitignore');
|
||||
$this->loadIgnoreDirs(APP_ROOT . '/lib/pkp/.gitignore', './lib/pkp/');
|
||||
|
||||
$this->processDir('./', function ($fileName) {
|
||||
if (substr($fileName, -4) === '.php') {
|
||||
$file = file_get_contents($fileName);
|
||||
|
||||
preg_match_all('/Hook\:\:call\(\s*\'([\d\D]*?)\'/', $file, $matches);
|
||||
if (count($matches) > 1) {
|
||||
foreach ($matches[1] as $hook) {
|
||||
$this->hooks[] = $hook;
|
||||
}
|
||||
}
|
||||
} elseif (substr($fileName, -4) !== '.tpl') {
|
||||
$file = file_get_contents($fileName);
|
||||
|
||||
preg_match_all('/call_hook[\s]*name\=\"([\d\D]*?)\"/', $file, $matches);
|
||||
if (count($matches) > 1) {
|
||||
foreach ($matches[1] as $hook) {
|
||||
$this->hooks[] = $hook;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sort($this->hooks);
|
||||
|
||||
echo join(',', $this->hooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to find hook docblocks in a directory
|
||||
*/
|
||||
public function processDir(string $dir, callable $function)
|
||||
{
|
||||
foreach (new DirectoryIterator($dir) as $fileInfo) {
|
||||
$isExcluded = false;
|
||||
foreach ($this->excludePaths as $excludePath) {
|
||||
if (strpos($fileInfo->getPathname(), $excludePath) === 0) {
|
||||
$isExcluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($isExcluded) {
|
||||
continue;
|
||||
}
|
||||
if (!$fileInfo->isDot()) {
|
||||
if ($fileInfo->isDir()) {
|
||||
$this->processDir($fileInfo->getPathname(), $function);
|
||||
} else {
|
||||
call_user_func($function, $dir . '/' . $fileInfo->getFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a .gitignore file and add to the excluded to directories
|
||||
*
|
||||
* @param string $path Path and filename for gitignore file
|
||||
* @param string $prefix A prefix to give to each of the paths in the gitignore file
|
||||
*/
|
||||
public function loadIgnoreDirs(string $path, $prefix = '')
|
||||
{
|
||||
$gitIgnore = file_get_contents($path);
|
||||
$gitIgnorePaths = explode("\n", $gitIgnore);
|
||||
foreach ($gitIgnorePaths as $gitIgnorePath) {
|
||||
if (!strlen(trim($gitIgnorePath))) {
|
||||
continue;
|
||||
} elseif (substr($gitIgnorePath, 0, 1) === '#') {
|
||||
continue;
|
||||
} elseif (substr($gitIgnorePath, 0, 1) === '/') {
|
||||
$gitIgnorePath = '.' . $gitIgnorePath;
|
||||
} elseif (strpos($gitIgnorePath, '.') === 0) {
|
||||
if (strpos($gitIgnorePath, '/') !== 1) {
|
||||
$gitIgnorePath = '';
|
||||
}
|
||||
} elseif (substr($gitIgnorePath, 0, 2) !== './') {
|
||||
$gitIgnorePath = './' . $gitIgnorePath;
|
||||
}
|
||||
if ($gitIgnorePath) {
|
||||
$this->excludePaths[] = $prefix . $gitIgnorePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new getHooks($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/installEmailTemplate.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class installEmailTemplate
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to install email templates from PO files into the database.
|
||||
*/
|
||||
|
||||
use PKP\cliTool\CommandLineTool;
|
||||
use PKP\facades\Repo;
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
|
||||
class installEmailTemplates extends CommandLineTool
|
||||
{
|
||||
/** @var string The email key of the email template to install. */
|
||||
public $_emailKey;
|
||||
|
||||
/** @var string The list of locales in which to install the template. */
|
||||
public $_locales;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->_emailKey = array_shift($this->argv);
|
||||
$this->_locales = array_shift($this->argv);
|
||||
|
||||
if ($this->_emailKey === null) {
|
||||
$this->usage();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Command-line tool for installing email templates.\n"
|
||||
. "Usage:\n"
|
||||
. "\t{$this->scriptName} emailKey aa_BB[,cc_DD,...] [path/to/emails.po]\n"
|
||||
. "\t\temailKey: The email key of the email to install, e.g. ANNOUNCEMENT\n"
|
||||
. "\t\taa_BB[,cc_DD,...]: The optional comma-separated list of locales to install. If none provided will be determined by site's installed locales\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute upgrade task
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Load the necessary locale data
|
||||
$locales = explode(',', $this->_locales ?? '');
|
||||
|
||||
// Install to the database
|
||||
Repo::emailTemplate()->dao->installEmailTemplates(
|
||||
Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(),
|
||||
$locales,
|
||||
$this->_emailKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new installEmailTemplates($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/installPluginVersionTool.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class InstallPluginVersionTool
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool for installing a plugin version descriptor.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\install\Upgrade;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\plugins\PluginRegistry;
|
||||
use PKP\site\VersionCheck;
|
||||
|
||||
class InstallPluginVersionTool extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var string Path to descriptor file to install */
|
||||
private $_descriptor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (!isset($this->argv[0]) || !file_exists($this->argv[0])) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->_descriptor = $this->argv[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Install plugin version tool\n"
|
||||
. "Usage: {$this->scriptName} path/to/version.xml\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified command.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$versionInfo = VersionCheck::parseVersionXML($this->_descriptor);
|
||||
$pluginVersion = $versionInfo['version'];
|
||||
|
||||
$productType = $pluginVersion->getProductType();
|
||||
if (!preg_match('/^plugins\.(.+)$/', $productType, $matches) || !in_array($matches[1], Application::get()->getPluginCategories())) {
|
||||
error_log("Invalid type \"{$productType}\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var VersionDAO */
|
||||
$versionDao = DAORegistry::getDAO('VersionDAO');
|
||||
$versionDao->insertVersion($pluginVersion, true);
|
||||
|
||||
$pluginPath = dirname($this->_descriptor);
|
||||
if (file_exists($wrapperName = "{$pluginPath}/index.php")) {
|
||||
// Old-style (non-FQCN) plugin class name
|
||||
$plugin = include("{$pluginPath}/index.php");
|
||||
} else {
|
||||
// Expect a wrapper-less plugin in a namespace.
|
||||
$fqcn = '\\APP\\' . strtr($pluginVersion->getProductType(), '.', '\\') . '\\' . $pluginVersion->getProduct() . '\\' . $pluginVersion->getProductClassName();
|
||||
$plugin = new $fqcn();
|
||||
}
|
||||
if ($plugin && is_object($plugin)) {
|
||||
PluginRegistry::register($matches[1], $plugin, $pluginPath);
|
||||
}
|
||||
$plugin = PluginRegistry::getPlugin($matches[1], $plugin->getName());
|
||||
|
||||
$installer = new Upgrade([]);
|
||||
$result = true;
|
||||
$param = [&$installer, &$result];
|
||||
|
||||
if ($plugin->getInstallMigration()) {
|
||||
$plugin->updateSchema('Installer::postInstall', $param);
|
||||
}
|
||||
if ($plugin->getInstallSitePluginSettingsFile()) {
|
||||
$plugin->installSiteSettings('Installer::postInstall', $param);
|
||||
}
|
||||
if ($plugin->getInstallEmailTemplatesFile()) {
|
||||
$plugin->installEmailTemplates('Installer::postInstall', $param);
|
||||
}
|
||||
if ($plugin->getInstallEmailTemplateDataFile()) {
|
||||
$plugin->installEmailTemplateData('Installer::postInstall', $param);
|
||||
}
|
||||
$plugin->installFilters('Installer::postInstall', $param);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
Application::upgrade();
|
||||
$tool = new InstallPluginVersionTool($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,720 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file tools/jobs.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2003-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class commandJobs
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to list, iterate and purge queued jobs on database
|
||||
*/
|
||||
|
||||
namespace PKP\tools;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Concerns\InteractsWithIO;
|
||||
use Illuminate\Console\OutputStyle;
|
||||
use Illuminate\Contracts\Queue\Job;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Queue\Events\JobFailed;
|
||||
use Illuminate\Queue\Events\JobProcessed;
|
||||
use Illuminate\Queue\Events\JobProcessing;
|
||||
use PKP\cliTool\CommandLineTool;
|
||||
use PKP\config\Config;
|
||||
use PKP\job\models\Job as PKPJobModel;
|
||||
use PKP\jobs\testJobs\TestJobFailure;
|
||||
use PKP\jobs\testJobs\TestJobSuccess;
|
||||
use PKP\queue\WorkerConfiguration;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException as CommandInvalidArgumentException;
|
||||
use Symfony\Component\Console\Helper\Helper;
|
||||
use Symfony\Component\Console\Helper\TableCell;
|
||||
use Symfony\Component\Console\Helper\TableCellStyle;
|
||||
use Symfony\Component\Console\Input\StringInput;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Throwable;
|
||||
|
||||
define('APP_ROOT', dirname(__FILE__, 4));
|
||||
require_once APP_ROOT . '/tools/bootstrap.php';
|
||||
|
||||
class commandInterface
|
||||
{
|
||||
use InteractsWithIO;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$output = new OutputStyle(
|
||||
new StringInput(''),
|
||||
new StreamOutput(fopen('php://stdout', 'w'))
|
||||
);
|
||||
|
||||
$this->setOutput($output);
|
||||
}
|
||||
|
||||
public function errorBlock(array $messages = [], ?string $title = null): void
|
||||
{
|
||||
$this->getOutput()->block(
|
||||
$messages,
|
||||
$title,
|
||||
'fg=white;bg=red',
|
||||
' ',
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class commandJobs extends CommandLineTool
|
||||
{
|
||||
protected const AVAILABLE_OPTIONS = [
|
||||
'list' => 'admin.cli.tool.jobs.available.options.list.description',
|
||||
'purge' => 'admin.cli.tool.jobs.available.options.purge.description',
|
||||
'test' => 'admin.cli.tool.jobs.available.options.test.description',
|
||||
'total' => 'admin.cli.tool.jobs.available.options.total.description',
|
||||
'help' => 'admin.cli.tool.jobs.available.options.help.description',
|
||||
'run' => 'admin.cli.tool.jobs.available.options.run.description',
|
||||
'work' => 'admin.cli.tool.jobs.available.options.work.description',
|
||||
'failed' => 'admin.cli.tool.jobs.available.options.failed.description',
|
||||
'restart' => 'admin.cli.tool.jobs.available.options.restart.description',
|
||||
'usage' => 'admin.cli.tool.jobs.available.options.usage.description',
|
||||
];
|
||||
|
||||
protected const CURRENT_PAGE = 'current';
|
||||
protected const PREVIOUS_PAGE = 'previous';
|
||||
protected const NEXT_PAGE = 'next';
|
||||
|
||||
/**
|
||||
* @var null|string Which option will be call?
|
||||
*/
|
||||
protected $option = null;
|
||||
|
||||
/**
|
||||
* @var null|array Parameters and arguments from CLI
|
||||
*/
|
||||
protected $parameterList = null;
|
||||
|
||||
/**
|
||||
* CLI interface, this object should extends InteractsWithIO
|
||||
*/
|
||||
protected $commandInterface = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
array_shift($argv);
|
||||
|
||||
$this->setParameterList($argv);
|
||||
|
||||
if (!isset($this->getParameterList()[0])) {
|
||||
throw new CommandNotFoundException(
|
||||
__('admin.cli.tool.jobs.empty.option'),
|
||||
array_keys(self::AVAILABLE_OPTIONS)
|
||||
);
|
||||
}
|
||||
|
||||
$this->option = $this->getParameterList()[0];
|
||||
|
||||
$this->setCommandInterface(new commandInterface());
|
||||
}
|
||||
|
||||
public function setCommandInterface(commandInterface $commandInterface): self
|
||||
{
|
||||
$this->commandInterface = $commandInterface;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommandInterface(): commandInterface
|
||||
{
|
||||
return $this->commandInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the parameter list passed on CLI
|
||||
*
|
||||
* @param array $items Array with parameters and arguments passed on CLI
|
||||
*
|
||||
*/
|
||||
public function setParameterList(array $items): self
|
||||
{
|
||||
$parameters = [];
|
||||
|
||||
foreach ($items as $param) {
|
||||
if (strpos($param, '=')) {
|
||||
[$key, $value] = explode('=', ltrim($param, '-'));
|
||||
$parameters[$key] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters[] = $param;
|
||||
}
|
||||
|
||||
$this->parameterList = $parameters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameter list passed on CLI
|
||||
*
|
||||
*/
|
||||
public function getParameterList(): ?array
|
||||
{
|
||||
return $this->parameterList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a specific parameter
|
||||
*
|
||||
* @param mixed $default
|
||||
*
|
||||
*/
|
||||
protected function getParameterValue(string $parameter, mixed $default = null): mixed
|
||||
{
|
||||
if (!isset($this->getParameterList()[$parameter])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->getParameterList()[$parameter];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
$this->getCommandInterface()->line('<comment>' . __('admin.cli.tool.usage.title') . '</comment>');
|
||||
$this->getCommandInterface()->line(__('admin.cli.tool.usage.parameters') . PHP_EOL);
|
||||
$this->getCommandInterface()->line('<comment>' . __('admin.cli.tool.available.commands', ['namespace' => 'jobs']) . '</comment>');
|
||||
|
||||
$this->printUsage(self::AVAILABLE_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for usage command
|
||||
*/
|
||||
public function help(): void
|
||||
{
|
||||
$this->usage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the columnWidth based on the commands text size
|
||||
*/
|
||||
protected function getColumnWidth(array $commands): int
|
||||
{
|
||||
$widths = [];
|
||||
|
||||
foreach ($commands as $command) {
|
||||
$widths[] = Helper::width($command);
|
||||
}
|
||||
|
||||
return $widths ? max($widths) + 2 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Failed jobs list/redispatch/remove
|
||||
*/
|
||||
protected function failed(): void
|
||||
{
|
||||
$parameterList = $this->getParameterList();
|
||||
|
||||
if (in_array('--redispatch', $parameterList) || ($jobIds = $this->getParameterValue('redispatch'))) {
|
||||
$jobsCount = Repo::failedJob()->redispatchToQueue(
|
||||
$this->getParameterValue('queue'),
|
||||
collect(explode(',', $jobIds ?? ''))
|
||||
->filter()
|
||||
->map(fn ($item) => (int)$item)
|
||||
->toArray()
|
||||
);
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.failed.redispatch.successful', ['jobsCount' => $jobsCount]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array('--clear', $parameterList) || ($jobIds = $this->getParameterValue('clear'))) {
|
||||
$jobsCount = Repo::failedJob()->deleteJobs(
|
||||
$this->getParameterValue('queue'),
|
||||
collect(explode(',', $jobIds ?? ''))
|
||||
->filter()
|
||||
->map(fn ($item) => (int)$item)
|
||||
->toArray()
|
||||
);
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.failed.clear.successful', ['jobsCount' => $jobsCount]));
|
||||
return;
|
||||
}
|
||||
|
||||
array_push($this->parameterList, '--failed');
|
||||
|
||||
$this->list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal the queue worker to quit gracefully
|
||||
*/
|
||||
protected function restart(): void
|
||||
{
|
||||
$cache = app()->get("cache.store"); /** @var \Illuminate\Contracts\Cache\Repository $cache */
|
||||
|
||||
$cache->forever('illuminate:queue:restart', Carbon::now()->getTimestamp());
|
||||
|
||||
$this
|
||||
->getCommandInterface()
|
||||
->getOutput()
|
||||
->info(__('admin.cli.tool.jobs.available.options.restart.confirm'));
|
||||
}
|
||||
|
||||
/**
|
||||
* List all queued jobs
|
||||
*/
|
||||
protected function list(): void
|
||||
{
|
||||
$perPage = $this->getParameterValue('perPage', '10');
|
||||
$page = $this->getParameterValue('page', '1');
|
||||
|
||||
$parameterList = $this->getparameterList();
|
||||
|
||||
$repository = in_array('--failed', $parameterList) ? Repo::failedJob() : Repo::job();
|
||||
|
||||
$data = $repository
|
||||
->setOutputFormat($repository::OUTPUT_CLI)
|
||||
->perPage((int) $perPage)
|
||||
->setPage((int) $page)
|
||||
->showJobs();
|
||||
|
||||
$this->total();
|
||||
|
||||
$this->getCommandInterface()->table(
|
||||
$this->getListTableFormat(),
|
||||
$data
|
||||
->map(fn(JsonResource $job) => $job->toArray(app('request')))
|
||||
->toArray()
|
||||
);
|
||||
|
||||
$pagination = [
|
||||
'pagination' => [
|
||||
self::CURRENT_PAGE => $data->currentPage(),
|
||||
self::PREVIOUS_PAGE => ($data->currentPage() - 1) > 0 ? $data->currentPage() - 1 : 1,
|
||||
self::NEXT_PAGE => $data->currentPage(),
|
||||
],
|
||||
];
|
||||
|
||||
if ($data->hasMorePages()) {
|
||||
$pagination['pagination'][self::NEXT_PAGE] = $data->currentPage() + 1;
|
||||
}
|
||||
|
||||
$this->getCommandInterface()
|
||||
->table(
|
||||
[
|
||||
[
|
||||
new TableCell(
|
||||
__('admin.cli.tool.jobs.pagination'),
|
||||
[
|
||||
'colspan' => 3,
|
||||
'style' => new TableCellStyle(['align' => 'center'])
|
||||
]
|
||||
)
|
||||
],
|
||||
[
|
||||
__('admin.cli.tool.jobs.pagination.current'),
|
||||
__('admin.cli.tool.jobs.pagination.previous'),
|
||||
__('admin.cli.tool.jobs.pagination.next'),
|
||||
]
|
||||
],
|
||||
$pagination
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table format for list view
|
||||
*/
|
||||
protected function getListTableFormat(): array
|
||||
{
|
||||
$listForFailedJobs = in_array('--failed', $this->getParameterList());
|
||||
|
||||
return [
|
||||
[
|
||||
new TableCell(
|
||||
$listForFailedJobs
|
||||
? __('admin.cli.tool.jobs.queued.jobs.failed.title')
|
||||
: __('admin.cli.tool.jobs.queued.jobs.title'),
|
||||
[
|
||||
'colspan' => $listForFailedJobs ? 6 : 7,
|
||||
'style' => new TableCellStyle(['align' => 'center'])
|
||||
]
|
||||
)
|
||||
],
|
||||
array_merge([
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.id'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.queue'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.job.display.name'),
|
||||
], $listForFailedJobs ? [
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.connection'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.failed.at'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.exception'),
|
||||
] : [
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.attempts'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.reserved.at'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.available.at'),
|
||||
__('admin.cli.tool.jobs.queued.jobs.fields.created.at')
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run daemon worker process to continue handle jobs
|
||||
*/
|
||||
protected function work(): void
|
||||
{
|
||||
$parameterList = $this->getParameterList();
|
||||
|
||||
if (in_array('--help', $parameterList)) {
|
||||
$this->workerOptionsHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application::isUnderMaintenance()) {
|
||||
$this->getCommandInterface()->getOutput()->error(__('admin.cli.tool.jobs.maintenance.message'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config::getVar('general', 'sandbox', false)) {
|
||||
$this->getCommandInterface()->getOutput()->error(__('admin.cli.tool.jobs.sandbox.message'));
|
||||
error_log(__('admin.cli.tool.jobs.sandbox.message'));
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = $parameterList['connection'] ?? Config::getVar('queues', 'default_connection', 'database');
|
||||
$queue = $parameterList['queue'] ?? Config::getVar('queues', 'default_queue', 'queue');
|
||||
|
||||
if (in_array('--test', $parameterList)) {
|
||||
$queue = PKPJobModel::TESTING_QUEUE;
|
||||
}
|
||||
|
||||
$this->listenForEvents();
|
||||
|
||||
app('pkpJobQueue')->runJobsViaDaemon(
|
||||
$connection,
|
||||
$queue,
|
||||
$this->gatherWorkerOptions($parameterList)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch jobs into the queue
|
||||
*/
|
||||
protected function run(): void
|
||||
{
|
||||
if (Application::isUnderMaintenance()) {
|
||||
$this->getCommandInterface()->getOutput()->error(__('admin.cli.tool.jobs.maintenance.message'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config::getVar('general', 'sandbox', false)) {
|
||||
$this->getCommandInterface()->getOutput()->error(__('admin.cli.tool.jobs.sandbox.message'));
|
||||
error_log(__('admin.cli.tool.jobs.sandbox.message'));
|
||||
return;
|
||||
}
|
||||
|
||||
$parameterList = $this->getParameterList();
|
||||
|
||||
$queue = $parameterList['queue'] ?? Config::getVar('queues', 'default_queue', 'queue');
|
||||
|
||||
if (in_array('--test', $parameterList)) {
|
||||
$queue = PKPJobModel::TESTING_QUEUE;
|
||||
}
|
||||
|
||||
$jobQueue = app('pkpJobQueue');
|
||||
|
||||
if ($queue && is_string($queue)) {
|
||||
$jobQueue = $jobQueue->forQueue($queue);
|
||||
}
|
||||
|
||||
$jobBuilder = $jobQueue->getJobModelBuilder();
|
||||
|
||||
if (($jobCount = $jobBuilder->count()) <= 0) {
|
||||
$this->getCommandInterface()->getOutput()->info(
|
||||
__(
|
||||
'admin.cli.tool.jobs.available.options.run.empty.description',
|
||||
['queueName' => $queue,]
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->listenForEvents();
|
||||
|
||||
while ($jobBuilder->count()) {
|
||||
$jobQueue->runJobInQueue();
|
||||
|
||||
if (in_array('--once', $parameterList)) {
|
||||
$jobCount = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getCommandInterface()->getOutput()->success(
|
||||
__(
|
||||
'admin.cli.tool.jobs.available.options.run.completed.description',
|
||||
['jobCount' => $jobCount, 'queueName' => $queue,]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge queued jobs
|
||||
*/
|
||||
protected function purge(): void
|
||||
{
|
||||
if (!isset($this->getParameterList()['queue']) && !isset($this->getParameterList()[1])) {
|
||||
throw new CommandInvalidArgumentException(__('admin.cli.tool.jobs.purge.without.id'));
|
||||
}
|
||||
|
||||
$parameterList = $this->getParameterList();
|
||||
|
||||
if (in_array('--all', $parameterList) || ($queue = $this->getParameterValue('queue'))) {
|
||||
if (!Repo::job()->deleteJobs($queue ?? null)) {
|
||||
$this->getCommandInterface()->getOutput()->warning(__('admin.cli.tool.jobs.purge.impossible.to.purge.empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.purge.successful.all'));
|
||||
return;
|
||||
}
|
||||
|
||||
$deleted = Repo::job()->delete((int) $this->getParameterList()[1]);
|
||||
|
||||
if (!$deleted) {
|
||||
throw new CommandInvalidArgumentException(__('admin.cli.tool.jobs.purge.invalid.id'));
|
||||
}
|
||||
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.purge.successful'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test queued job
|
||||
*/
|
||||
protected function test(): void
|
||||
{
|
||||
$queue = PKPJobModel::TESTING_QUEUE;
|
||||
$runnableJob = $this->getParameterList()['only'] ?? null;
|
||||
|
||||
if ($runnableJob && !in_array($runnableJob, ['failed', 'success'])) {
|
||||
throw new CommandInvalidArgumentException(__('admin.cli.tool.jobs.test.invalid.option'));
|
||||
}
|
||||
|
||||
if (!$runnableJob || $runnableJob === 'failed') {
|
||||
dispatch(new TestJobFailure());
|
||||
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.test.job.failed.dispatch.message', ['queueName' => $queue]));
|
||||
}
|
||||
|
||||
if (!$runnableJob || $runnableJob === 'success') {
|
||||
dispatch(new TestJobSuccess());
|
||||
|
||||
$this->getCommandInterface()->getOutput()->success(__('admin.cli.tool.jobs.test.job.success.dispatch.message', ['queueName' => $queue]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather worker daemon options
|
||||
*
|
||||
*/
|
||||
protected function gatherWorkerOptions(array $parameters = []): array
|
||||
{
|
||||
$workerConfig = new WorkerConfiguration();
|
||||
|
||||
return [
|
||||
'name' => $this->getParameterValue('name', $workerConfig->getName()),
|
||||
'backoff' => $this->getParameterValue('backoff', $workerConfig->getBackoff()),
|
||||
'memory' => $this->getParameterValue('memory', $workerConfig->getMemory()),
|
||||
'timeout' => $this->getParameterValue('timeout', $workerConfig->getTimeout()),
|
||||
'sleep' => $this->getParameterValue('sleep', $workerConfig->getSleep()),
|
||||
'maxTries' => $this->getParameterValue('tries', $workerConfig->getMaxTries()),
|
||||
'force' => $this->getParameterValue('force', in_array('force', $parameters) ? true : $workerConfig->getForce()),
|
||||
'stopWhenEmpty' => $this->getParameterValue('stop-when-empty', in_array('stop-when-empty', $parameters) ? true : $workerConfig->getStopWhenEmpty()),
|
||||
'maxJobs' => $this->getParameterValue('max-jobs', $workerConfig->getMaxJobs()),
|
||||
'maxTime' => $this->getParameterValue('max-time', $workerConfig->getMaxTime()),
|
||||
'rest' => $this->getParameterValue('rest', $workerConfig->getRest()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the queue events in order to update the console output.
|
||||
*
|
||||
*/
|
||||
protected function listenForEvents(): void
|
||||
{
|
||||
$events = app()['events'];
|
||||
|
||||
$events->listen(JobProcessing::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'starting');
|
||||
});
|
||||
|
||||
$events->listen(JobProcessed::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'success');
|
||||
});
|
||||
|
||||
$events->listen(JobFailed::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'failed');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the status output for the queue worker.
|
||||
*
|
||||
* @param string $status
|
||||
*
|
||||
*/
|
||||
protected function writeOutput(Job $job, $status): void
|
||||
{
|
||||
match ($status) {
|
||||
'starting' => $this->writeStatus($job, 'Processing', 'comment'),
|
||||
'success' => $this->writeStatus($job, 'Processed', 'info'),
|
||||
'failed' => $this->writeStatus($job, 'Failed', 'error'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the status output for the queue worker.
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $type
|
||||
*/
|
||||
protected function writeStatus(Job $job, $status, $type): void
|
||||
{
|
||||
$this->getCommandInterface()->getOutput()->writeln(sprintf(
|
||||
"<{$type}>[%s][%s] %s</{$type}> %s",
|
||||
Carbon::now()->format('Y-m-d H:i:s'),
|
||||
$job->getJobId(),
|
||||
str_pad("{$status}:", 11),
|
||||
$job->resolveName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print work command options information.
|
||||
*/
|
||||
protected function workerOptionsHelp(): void
|
||||
{
|
||||
$this->getCommandInterface()->line('<comment>' . __('admin.cli.tool.jobs.work.options.title') . '</comment>');
|
||||
$this->getCommandInterface()->line(__('admin.cli.tool.jobs.work.options.usage') . PHP_EOL);
|
||||
$this->getCommandInterface()->line('<comment>' . __('admin.cli.tool.jobs.work.options.description') . '</comment>');
|
||||
|
||||
$workerConfig = new WorkerConfiguration();
|
||||
|
||||
$options = [
|
||||
'--connection[=CONNECTION]' => __('admin.cli.tool.jobs.work.option.connection.description', ['default' => Config::getVar('queue', 'default_connection', 'database')]),
|
||||
'--queue[=QUEUE]' => __('admin.cli.tool.jobs.work.option.queue.description', ['default' => Config::getVar('queue', 'default_queue', 'queue')]),
|
||||
'--name[=NAME]' => __('admin.cli.tool.jobs.work.option.name.description', ['default' => $workerConfig->getName()]),
|
||||
'--backoff[=BACKOFF]' => __('admin.cli.tool.jobs.work.option.backoff.description', ['default' => $workerConfig->getBackoff()]),
|
||||
'--memory[=MEMORY]' => __('admin.cli.tool.jobs.work.option.memory.description', ['default' => $workerConfig->getMemory()]),
|
||||
'--timeout[=TIMEOUT]' => __('admin.cli.tool.jobs.work.option.timeout.description', ['default' => $workerConfig->getTimeout()]),
|
||||
'--sleep[=SLEEP]' => __('admin.cli.tool.jobs.work.option.sleep.description', ['default' => $workerConfig->getSleep()]),
|
||||
'--tries[=TRIES]' => __('admin.cli.tool.jobs.work.option.tries.description', ['default' => $workerConfig->getMaxTries()]),
|
||||
'--force' => __('admin.cli.tool.jobs.work.option.force.description', ['default' => $workerConfig->getForce() ? 'true' : 'false']),
|
||||
'--stop-when-empty' => __('admin.cli.tool.jobs.work.option.stopWhenEmpty.description', ['default' => $workerConfig->getStopWhenEmpty() ? 'true' : 'false']),
|
||||
'--max-jobs[=MAX-JOBS]' => __('admin.cli.tool.jobs.work.option.maxJobs.description', ['default' => $workerConfig->getMaxJobs()]),
|
||||
'--max-time[=MAX-TIME]' => __('admin.cli.tool.jobs.work.option.maxTime.description', ['default' => $workerConfig->getMaxTime()]),
|
||||
'--rest[=REST]' => __('admin.cli.tool.jobs.work.option.rest.description', ['default' => $workerConfig->getRest()]),
|
||||
'--test' => __('admin.cli.tool.jobs.work.option.test.description'),
|
||||
];
|
||||
|
||||
$this->printUsage($options, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print given options in a pretty way.
|
||||
*/
|
||||
protected function printUsage(array $options, bool $shouldTranslate = true): void
|
||||
{
|
||||
$width = $this->getColumnWidth(array_keys($options));
|
||||
|
||||
foreach ($options as $commandName => $description) {
|
||||
$spacingWidth = $width - Helper::width($commandName);
|
||||
$this->getCommandInterface()->line(
|
||||
sprintf(
|
||||
' <info>%s</info>%s%s',
|
||||
$commandName,
|
||||
str_repeat(' ', $spacingWidth),
|
||||
$shouldTranslate ? __($description) : $description
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the queued/failed jobs quantity
|
||||
*/
|
||||
protected function total(): void
|
||||
{
|
||||
$parameterList = $this->getParameterList();
|
||||
|
||||
$total = in_array('--failed', $parameterList)
|
||||
? Repo::failedJob()->total()
|
||||
: Repo::job()->total();
|
||||
|
||||
$outputInterface = $this->getCommandInterface()->getOutput();
|
||||
|
||||
if (in_array('--failed', $parameterList)) {
|
||||
$method = $total > 0 ? 'error' : 'success';
|
||||
$outputInterface->{$method}(__('admin.cli.tool.jobs.total.failed.jobs', ['total' => $total]));
|
||||
return;
|
||||
}
|
||||
|
||||
$outputInterface->warning(__('admin.cli.tool.jobs.total.jobs', ['total' => $total]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and execute the command
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if (!isset(self::AVAILABLE_OPTIONS[$this->option])) {
|
||||
throw new CommandNotFoundException(
|
||||
__('admin.cli.tool.jobs.option.doesnt.exists', ['option' => $this->option]),
|
||||
array_keys(self::AVAILABLE_OPTIONS)
|
||||
);
|
||||
}
|
||||
|
||||
$this->{$this->option}();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$tool = new commandJobs($argv ?? []);
|
||||
$tool->execute();
|
||||
} catch (Throwable $e) {
|
||||
$output = new commandInterface();
|
||||
|
||||
if ($e instanceof CommandInvalidArgumentException) {
|
||||
$output->errorBlock([$e->getMessage()]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($e instanceof CommandNotFoundException) {
|
||||
$alternatives = $e->getAlternatives();
|
||||
|
||||
$message = __('admin.cli.tool.jobs.mean.those') . PHP_EOL . implode(PHP_EOL, $alternatives);
|
||||
|
||||
$output->errorBlock([$e->getMessage(), $message]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
Vendored
+1329
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/migration.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class migrationTool
|
||||
*
|
||||
* @ingroup tools
|
||||
*/
|
||||
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class migrationTool extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var string Name (fully qualified) of migration class */
|
||||
protected $class;
|
||||
|
||||
/** @var string "up" or "down" */
|
||||
protected $direction;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
array_shift($argv); // Shift the tool name off the top
|
||||
|
||||
$this->class = array_shift($argv);
|
||||
$this->direction = array_shift($argv);
|
||||
|
||||
// The source file/directory must be specified and exist.
|
||||
if (empty($this->class)) {
|
||||
$this->usage();
|
||||
exit(2);
|
||||
}
|
||||
|
||||
// The migration direction.
|
||||
if (!in_array($this->direction, ['up', 'down'])) {
|
||||
$this->usage();
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Run a migration.\n\n"
|
||||
. "Usage: {$this->scriptName} \\fully\\qualified\\migration\\Name [up|down]\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Log install message to stdout.
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
public function log($message)
|
||||
{
|
||||
printf("[%s]\n", $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified migration.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$upgrade = new \APP\install\Upgrade([]);
|
||||
$upgrade->setLogger($this);
|
||||
$migration = new $this->class($upgrade, []);
|
||||
try {
|
||||
$direction = $this->direction;
|
||||
$migration->$direction();
|
||||
} catch (Exception $e) {
|
||||
echo 'ERROR: ' . $e->getMessage() . "\n\n";
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new migrationTool($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/moveLocaleKeysToLib.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class MoveLocaleKeysToLib
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Move a locale key from an application's locale files to the pkp-lib locale files.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class MoveLocaleKeysToLib extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var string The string to match in a msgid */
|
||||
public $msgidMatch = '';
|
||||
|
||||
/** @var string The application file to search for keys */
|
||||
public $sourceFile = '';
|
||||
|
||||
/** @var string The pkp-lib file to move the keys to */
|
||||
public $targetFile = '';
|
||||
|
||||
/** @var bool Whether to move locale keys from lib/pkp to the app */
|
||||
public $reverse = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
// discard first argument: script name
|
||||
array_shift($argv);
|
||||
|
||||
if (sizeof($this->argv) < 3) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($argv[0] === '-r') {
|
||||
$this->reverse = true;
|
||||
array_shift($argv);
|
||||
}
|
||||
|
||||
$this->msgidMatch = array_shift($argv);
|
||||
$this->sourceFile = array_shift($argv);
|
||||
$this->targetFile = array_shift($argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "\nMove matching locale keys from one file to another.\n\n"
|
||||
. "All matching locale keys will be moved from the source file to the target file. This will\n"
|
||||
. "effect all locales.\n\n"
|
||||
. " Usage: php {$this->scriptName} (options) [match] [sourceFile] [targetFile]\n\n"
|
||||
. " (options) Optional flags:\n"
|
||||
. " -r Move locale keys from lib/pkp into the app.\n"
|
||||
. " [match] The string to match in the locale key's msgid, Supports partial\n"
|
||||
. " matches from start of msgid. `example.key.` will match `msgid \"example.key.anything\"`.\n\n"
|
||||
. " [sourceFile] The file to look for keys to move, such as `emails.po`.\n\n"
|
||||
. " [targetFile] The file to move keys to, such as `emails.po`. Usually the same as `sourceFile`.\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the requested locale key
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$localeDirs = scandir('locale');
|
||||
if (!$localeDirs) {
|
||||
$this->output('Locale directories could not be found. Run this from the root directory of the application.');
|
||||
exit;
|
||||
}
|
||||
|
||||
$localeDirs = array_filter($localeDirs, function ($localeDir) {
|
||||
return $localeDir !== '.' && $localeDir !== '..';
|
||||
});
|
||||
|
||||
$fromDir = $this->reverse
|
||||
? 'lib/pkp/locale/'
|
||||
: 'locale/';
|
||||
$toDir = $this->reverse
|
||||
? 'locale/'
|
||||
: 'lib/pkp/locale/';
|
||||
|
||||
foreach (array_values($localeDirs) as $localeDir) {
|
||||
$localeSourceFile = $fromDir . $localeDir . '/' . $this->sourceFile;
|
||||
$localeTargetFile = $toDir . $localeDir . '/' . $this->targetFile;
|
||||
if (!file_exists($localeSourceFile)) {
|
||||
$this->output('No file exists at ' . $localeSourceFile . ' to move locale keys from. Skipping this locale.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a new file if no file exists at the target and add the weblate header
|
||||
if (!file_exists($localeTargetFile)) {
|
||||
if (!file_exists(dirname($localeTargetFile))) {
|
||||
mkdir(dirname($localeTargetFile));
|
||||
}
|
||||
$lines = explode("\n", file_get_contents($localeSourceFile));
|
||||
$headerLines = [];
|
||||
$endOfHeader = '"X-Generator';
|
||||
foreach ($lines as $line) {
|
||||
$headerLines[] = $line;
|
||||
if (substr($line, 0, strlen($endOfHeader)) == $endOfHeader) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$headerLines[] = "\n";
|
||||
file_put_contents($localeTargetFile, join("\n", $headerLines));
|
||||
$this->output('New file created at ' . $localeTargetFile . '.');
|
||||
}
|
||||
|
||||
$changedSourceLines = [];
|
||||
$newTargetLines = [];
|
||||
$isMovingLine = false;
|
||||
|
||||
$lines = explode("\n", file_get_contents($localeSourceFile));
|
||||
foreach ($lines as $i => $line) {
|
||||
if ($line === "msgid \"{$this->msgidMatch}\"") {
|
||||
$isMovingLine = true;
|
||||
} elseif (trim($line) === '#, fuzzy' || substr($line, 0, 5) === 'msgid') {
|
||||
$isMovingLine = false;
|
||||
}
|
||||
if ($isMovingLine) {
|
||||
// Check for fuzzy flag and make sure it's moved over
|
||||
if ($lines[$i - 1] === '#, fuzzy') {
|
||||
$newTargetLines[] = $lines[$i - 1];
|
||||
array_pop($changedSourceLines);
|
||||
}
|
||||
$newTargetLines[] = $line;
|
||||
} else {
|
||||
$changedSourceLines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($newTargetLines)) {
|
||||
file_put_contents($localeTargetFile, "\n" . join("\n", $newTargetLines), FILE_APPEND);
|
||||
$this->output(count($newTargetLines) . ' lines added to ' . $localeTargetFile . '.');
|
||||
}
|
||||
|
||||
$linesToRemove = count($lines) - count($changedSourceLines);
|
||||
if (count($lines) !== count($changedSourceLines)) {
|
||||
file_put_contents($localeSourceFile, join("\n", $changedSourceLines));
|
||||
$this->output($linesToRemove . ' lines removed from ' . $localeSourceFile . '.');
|
||||
}
|
||||
|
||||
if (!count($newTargetLines) && !$linesToRemove) {
|
||||
$this->output('No changes made to ' . $localeSourceFile . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function output(string $string)
|
||||
{
|
||||
echo "\n" . $string;
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new MoveLocaleKeysToLib($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/parseCitations.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class CitationsParsingTool
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to parse existing citations
|
||||
*/
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\db\DAORegistry;
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class CitationsParsingTool extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
public $parameters;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (!sizeof($this->argv)) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->parameters = $this->argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Parse and save submission(s) citations.\n"
|
||||
. "Usage:\n"
|
||||
. "{$this->scriptName} all\n"
|
||||
. "{$this->scriptName} context context_id [...]\n"
|
||||
. "{$this->scriptName} submission submission_id [...]\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse citations
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$citationDao = DAORegistry::getDAO('CitationDAO');
|
||||
$contextDao = Application::getContextDAO();
|
||||
|
||||
switch (array_shift($this->parameters)) {
|
||||
case 'all':
|
||||
$contexts = $contextDao->getAll();
|
||||
while ($context = $contexts->next()) {
|
||||
$submissions = Repo::submission()->getCollector()->filterByContextIds([$context->getId()])->getMany();
|
||||
foreach ($submissions as $submission) {
|
||||
$this->_parseSubmission($submission);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'context':
|
||||
foreach ($this->parameters as $contextId) {
|
||||
$context = $contextDao->getById($contextId);
|
||||
if (!isset($context)) {
|
||||
printf("Error: Skipping {$contextId}. Unknown context.\n");
|
||||
continue;
|
||||
}
|
||||
$submissions = Repo::submission()->getCollector()->filterByContextIds([$context->getId()])->getMany();
|
||||
foreach ($submissions as $submission) {
|
||||
$this->_parseSubmission($submission);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'submission':
|
||||
foreach ($this->parameters as $submissionId) {
|
||||
$submission = Repo::submission()->get($submissionId);
|
||||
if (!isset($submission)) {
|
||||
printf("Error: Skipping {$submissionId}. Unknown submission.\n");
|
||||
continue;
|
||||
}
|
||||
$this->_parseSubmission($submission);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the citations of one submission
|
||||
*
|
||||
* @param Submission $submission
|
||||
*/
|
||||
private function _parseSubmission($submission)
|
||||
{
|
||||
/** @var CitationDAO */
|
||||
$citationDao = DAORegistry::getDAO('CitationDAO');
|
||||
foreach ($submission->getData('publications') as $publication) {
|
||||
if (!empty($publication->getData('citationsRaw'))) {
|
||||
$citationDao->importCitations($publication->getId(), $publication->getData('citationsRaw'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new CitationsParsingTool($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/plugins.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PluginsTool
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to get information about installed/available plugins
|
||||
*/
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\plugins\PluginGalleryDAO;
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
|
||||
class PluginsTool extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (!isset($this->argv[0]) || !$this->validateArgs()) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate arguments
|
||||
*/
|
||||
public function validateArgs()
|
||||
{
|
||||
switch ($this->argv[0]) {
|
||||
case 'list':
|
||||
if (count($this->argv) > 2) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case 'info':
|
||||
if (count($this->argv) != 2) {
|
||||
return false;
|
||||
}
|
||||
if (count(explode('/', $this->argv[1])) != 2) {
|
||||
echo "\n\033[0;31m✘ The plugin path `" . $this->argv[1] . "` is not valid. It should be in the following format: generic/pln\033[0m\n\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "Plugin Gallery tool\n"
|
||||
. "Usage: {$this->scriptName} action [arguments]\n"
|
||||
. " Actions:\n"
|
||||
. "\tlist [search]: show latest compatible plugin(s). Optional \"search\" text against plugin class\n"
|
||||
. "\tinfo path: show detail for plugin identified by \"path\", such as generic/pln\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified command.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$result = false;
|
||||
/** @var PluginGalleryDAO $pluginGalleryDao */
|
||||
$pluginGalleryDao = DAORegistry::getDAO('PluginGalleryDAO');
|
||||
switch ($this->argv[0]) {
|
||||
case 'list':
|
||||
$plugins = $pluginGalleryDao->getNewestCompatible(
|
||||
Application::get(),
|
||||
null,
|
||||
count($this->argv) > 1 ? $this->argv[1] : null
|
||||
);
|
||||
$this->listPlugins($plugins);
|
||||
$result = true;
|
||||
break;
|
||||
case 'info':
|
||||
$opts = explode('/', $this->argv[1]);
|
||||
$plugin = $this->selectPlugin($opts[0], $opts[1]);
|
||||
if ($plugin) {
|
||||
foreach ($plugin->getAllData() as $key => $data) {
|
||||
if (is_array($data)) {
|
||||
echo $key . ': ' . str_replace("\n", '\n', $plugin->getLocalizedData($key)) . "\n";
|
||||
} else {
|
||||
echo $key . ': ' . str_replace("\n", '\n', $data) . "\n";
|
||||
}
|
||||
}
|
||||
$result = true;
|
||||
}
|
||||
if (!$result) {
|
||||
error_log('"' . $opts[1] . '" not found in "' . $opts[0] . '"');
|
||||
$result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!$result) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific plugin
|
||||
*
|
||||
* @param string $category a plugin category
|
||||
* @param string $name a plugin name
|
||||
*
|
||||
* @return GalleryPlugin|null
|
||||
*/
|
||||
public function selectPlugin($category, $name)
|
||||
{
|
||||
/** @var PluginGalleryDAO $pluginGalleryDao */
|
||||
$pluginGalleryDao = DAORegistry::getDAO('PluginGalleryDAO');
|
||||
$plugins = $pluginGalleryDao->getNewestCompatible(
|
||||
Application::get(),
|
||||
$category,
|
||||
$name
|
||||
);
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($plugin->getData('product') === $name) {
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the plugins as a list
|
||||
*
|
||||
* @param GalleryPlugin[] $plugins array of plugins
|
||||
*/
|
||||
public function listPlugins($plugins)
|
||||
{
|
||||
foreach ($plugins as $plugin) {
|
||||
$statusKey = '';
|
||||
switch ($plugin->getCurrentStatus()) {
|
||||
case PLUGIN_GALLERY_STATE_NEWER:
|
||||
$statusKey = 'manager.plugins.installedVersionNewer';
|
||||
break;
|
||||
case PLUGIN_GALLERY_STATE_UPGRADABLE:
|
||||
$statusKey = 'manager.plugins.installedVersionOlder';
|
||||
break;
|
||||
case PLUGIN_GALLERY_STATE_CURRENT:
|
||||
$statusKey = 'manager.plugins.installedVersionNewest';
|
||||
break;
|
||||
case PLUGIN_GALLERY_STATE_AVAILABLE:
|
||||
$statusKey = 'manager.plugins.noInstalledVersion';
|
||||
break;
|
||||
case PLUGIN_GALLERY_STATE_INCOMPATIBLE:
|
||||
$statusKey = 'manager.plugins.noCompatibleVersion';
|
||||
break;
|
||||
}
|
||||
$keyOut = explode('.', $statusKey);
|
||||
$keyOut = array_pop($keyOut);
|
||||
echo implode('/', ['plugins', $plugin->getData('category'), $plugin->getData('product')]) . ' ' . $plugin->getData('releasePackage') . ' ' . $keyOut . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new PluginsTool($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# @file tools/pre-commit.sh
|
||||
#
|
||||
# Copyright (c) 2014-2021 Simon Fraser University
|
||||
# Copyright (c) 2010-2021 John Willinsky
|
||||
# Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
#
|
||||
# A pre-commit hook to run php-cs-fixer on committed changes
|
||||
#
|
||||
|
||||
# Run php-cs-fixer on all committed changes
|
||||
git diff --name-only --diff-filter=d --cached | xargs ./lib/vendor/bin/php-cs-fixer fix --allow-risky=yes --path-mode=intersection --config=.php-cs-fixer.php -q
|
||||
|
||||
# Run php-cs-fixer again with --dry-run to throw an error if any files could not be automatically formatted
|
||||
git diff --name-only --diff-filter=d --cached | xargs ./lib/vendor/bin/php-cs-fixer fix --allow-risky=yes --dry-run --path-mode=intersection --config=.php-cs-fixer.php
|
||||
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo -e "\n\e[32m✔ Files formatted successfully.\e[0m\n"
|
||||
git diff --name-only --diff-filter=d --cached | xargs git add -u
|
||||
else
|
||||
echo -e "\n\e[31m✘ Commit aborted. Files could not be formatted.\e[0m\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/removeLocaleKey.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RemoveLocaleKey
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Remove a locale key from all locale files.
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class RemoveLocaleKey extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var string Locale key to be removed */
|
||||
public $localeKey = '';
|
||||
|
||||
/** @var array Which files to remove the locale key from */
|
||||
public $dirs = ['locale', 'lib/pkp/locale'];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
if (!sizeof($this->argv)) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
array_shift($argv);
|
||||
|
||||
$this->localeKey = array_shift($argv);
|
||||
|
||||
if (!empty($argv)) {
|
||||
$this->dirs = $argv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage()
|
||||
{
|
||||
echo "\nRemove a locale key from all locale files.\n\n"
|
||||
. " Usage: php {$this->scriptName} [localeKey] ([path] [path])\n\n"
|
||||
. " Remove locale keys from app:\n php {$this->scriptName} locale.key locale\n\n"
|
||||
. " Remove locale keys from pkp-lib:\n php {$this->scriptName} locale.key lib/pkp/locale\n\n"
|
||||
. " If no path is specified it will remove the locale\n key from files in both directories.\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the requested locale key
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$localeKeyLine = 'msgid "' . $this->localeKey . '"';
|
||||
$rootDir = dirname(__FILE__, 4);
|
||||
|
||||
foreach ($this->dirs as $dir) {
|
||||
$locales = scandir($rootDir . '/' . $dir);
|
||||
foreach ($locales as $locale) {
|
||||
if ($locale === '.' || $locale === '..') {
|
||||
continue;
|
||||
}
|
||||
$localeDir = join('/', [$rootDir, $dir, $locale]);
|
||||
$files = scandir($localeDir);
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..' || substr($file, -2) !== 'po') {
|
||||
continue;
|
||||
}
|
||||
$content = file_get_contents($localeDir . '/' . $file);
|
||||
$lines = explode("\n", $content);
|
||||
$newLines = [];
|
||||
$removing = false;
|
||||
foreach ($lines as $line) {
|
||||
if ($localeKeyLine === substr($line, 0, strlen($localeKeyLine))) {
|
||||
$removing = true;
|
||||
} elseif ($removing && 'msgid' === substr($line, 0, strlen('msgid'))) {
|
||||
$removing = false;
|
||||
}
|
||||
if (!$removing) {
|
||||
$newLines[] = $line;
|
||||
}
|
||||
}
|
||||
if (count($lines) !== count($newLines)) {
|
||||
file_put_contents($localeDir . '/' . $file, join("\n", $newLines));
|
||||
echo(count($lines) - count($newLines)) . " lines removed from {$localeDir}/{$file}.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new RemoveLocaleKey($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/replaceVariableInLocaleKey.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReplaceVariableInLocaleKey
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief Replace a {$variable} in a specific locale key across all locales
|
||||
*/
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class ReplaceVariableInLocaleKey extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** @var string The string to match in a msgid */
|
||||
public $msgidMatch = '';
|
||||
|
||||
/** @var string The {$variable} to search for */
|
||||
public $oldVariable = '';
|
||||
|
||||
/** @var string The {$variable} to replace */
|
||||
public $newVariable = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
// discard first argument: script name
|
||||
array_shift($argv);
|
||||
|
||||
if (sizeof($this->argv) < 3) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->msgidMatch = array_shift($argv);
|
||||
$this->oldVariable = '{$' . array_shift($argv) . '}';
|
||||
$this->newVariable = '{$' . array_shift($argv) . '}';
|
||||
}
|
||||
|
||||
public function usage()
|
||||
{
|
||||
echo "\nReplace a variable in a locale key.\n\n"
|
||||
. "A variable like {\$example} can be replaced will be moved from the source file to the target file. This will\n"
|
||||
. "effect all locales.\n\n"
|
||||
. " Usage: php {$this->scriptName} [match] [oldVariable] [newVariable]\n\n"
|
||||
. " [match] The msgid to modify to match in each locale file.\n\n"
|
||||
. " [oldVariable] The variable to replace, without the `{\$` and `}`, such as: old\n\n"
|
||||
. " [newVariable] The new variable value, without the `{\$` and `}`, such as: new\n\n"
|
||||
. " Example: php lib/pkp/tools/replaceVariableInLocaleKey.php emails.submissionAck.body signature contextSignature\n\n";
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$localeDirs = scandir('locale');
|
||||
if (!$localeDirs) {
|
||||
$this->output('Locale directories could not be found. Run this from the root directory of the application.');
|
||||
exit;
|
||||
}
|
||||
|
||||
$localeDirs = array_filter($localeDirs, function ($localeDir) {
|
||||
return $localeDir !== '.' && $localeDir !== '..';
|
||||
});
|
||||
|
||||
$searchDirs = [
|
||||
'locale/',
|
||||
'lib/pkp/locale/'
|
||||
];
|
||||
|
||||
foreach ($searchDirs as $searchDir) {
|
||||
foreach (array_values($localeDirs) as $localeDir) {
|
||||
$dir = $searchDir . $localeDir;
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
$this->output('No directory exists at ' . $dir . ' to modify. Skipping this locale.');
|
||||
continue;
|
||||
}
|
||||
|
||||
$localeFiles = array_filter(scandir($dir), function ($localeDir) {
|
||||
return $localeDir !== '.' && $localeDir !== '..';
|
||||
});
|
||||
|
||||
foreach (array_values($localeFiles) as $localeFile) {
|
||||
$countChanges = 0;
|
||||
$isInMsgid = false;
|
||||
$file = $dir . '/' . $localeFile;
|
||||
|
||||
if (is_dir($file)) {
|
||||
$this->output('Skipping directory ' . $file);
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = explode("\n", file_get_contents($file));
|
||||
foreach ($lines as $i => $line) {
|
||||
if ($line === "msgid \"{$this->msgidMatch}\"") {
|
||||
$isInMsgid = true;
|
||||
} elseif (trim($line) === '#, fuzzy' || substr($line, 0, 5) === 'msgid') {
|
||||
$isInMsgid = false;
|
||||
}
|
||||
if (!$isInMsgid) {
|
||||
continue;
|
||||
}
|
||||
if (str_contains($line, $this->oldVariable)) {
|
||||
$lines[$i] = str_replace($this->oldVariable, $this->newVariable, $line);
|
||||
$countChanges++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($countChanges) {
|
||||
file_put_contents($file, join("\n", $lines));
|
||||
$this->output('Replaced ' . $countChanges . ' lines in ' . $file . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function output(string $string)
|
||||
{
|
||||
echo "\n" . $string;
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new ReplaceVariableInLocaleKey($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/reprocessUsageStatsMonth.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class reprocessUsageStatsMonth
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to reprocess the usage stats log files for a month.
|
||||
*/
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\tasks\UsageStatsLoader;
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class ReprocessUsageStatsMonth extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/** Month that should be reprocessed and stats aggregated by. In the form [YYYYMM] */
|
||||
public string $month;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $argv command-line arguments (see usage)
|
||||
*/
|
||||
public function __construct(array $argv = [])
|
||||
{
|
||||
parent::__construct($argv);
|
||||
if (count($this->argv) != 1) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
$this->month = array_shift($this->argv);
|
||||
if (!preg_match('/[0-9]{6}/', $this->month)) {
|
||||
$this->usage();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print command usage information.
|
||||
*/
|
||||
public function usage(): void
|
||||
{
|
||||
echo "\nReprocess the usage stats log files for a month.\n\n"
|
||||
. " Usage: php {$this->scriptName} [YYYYMM]\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprocess usage stats log file for the given month.
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
// Remove the month from the monthly DB tables
|
||||
$counterService = Services::get('sushiStats');
|
||||
$geoService = Services::get('geoStats');
|
||||
$counterService->deleteMonthlyMetrics($this->month);
|
||||
$geoService->deleteMonthlyMetrics($this->month);
|
||||
// Check if all log files from that month are in usageEventLogs folder???
|
||||
$usageStatsLoader = new UsageStatsLoader([$this->month]);
|
||||
$usageStatsLoader->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new ReprocessUsageStatsMonth($argv ?? []);
|
||||
$tool->execute();
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# USAGE:
|
||||
# runAllTests.sh [options]
|
||||
# -C Include class tests in lib/pkp.
|
||||
# -P Include plugin tests in lib/pkp.
|
||||
# -c Include class tests in application.
|
||||
# -p Include plugin tests in application.
|
||||
# -d Display debug output from phpunit.
|
||||
# If no options are specified, then all tests will be executed.
|
||||
#
|
||||
# Some tests will certain require environment variables in order to cnfigure
|
||||
# the environment. In particular...
|
||||
# BASEURL="http://localhost/omp": Full URL to base URL, excluding index.php
|
||||
# DBHOST=localhost: Hostname of database server
|
||||
# DBNAME=yyy: Database name
|
||||
# DBUSERNAME=xxx: Username for database connections
|
||||
# DBPASSWORD=zzz: Database password
|
||||
# FILESDIR=files: Pathname to use for storing server-side submission files
|
||||
# DBTYPE=MySQL: Name of database driver (MySQL or PostgreSQL)
|
||||
#
|
||||
|
||||
set -e # Fail on first error
|
||||
|
||||
# We recommend using Travis (https://travis-ci.org/) for continuous-integration
|
||||
# based testing. Review the Travis configuration file (.travis.yml) as a
|
||||
# reference for running the test locally, should you choose to do so.
|
||||
|
||||
### Command Line Options ###
|
||||
|
||||
# Run all types of tests by default, unless one or more is specified
|
||||
DO_ALL=1
|
||||
|
||||
# Various types of tests
|
||||
DO_PKP_CLASSES=0
|
||||
DO_PKP_PLUGINS=0
|
||||
DO_APP_CLASSES=0
|
||||
DO_APP_PLUGINS=0
|
||||
DO_COVERAGE=0
|
||||
DEBUG=""
|
||||
|
||||
# Parse arguments
|
||||
while getopts "CPcpdR" opt; do
|
||||
case "$opt" in
|
||||
C) DO_ALL=0
|
||||
DO_PKP_CLASSES=1
|
||||
;;
|
||||
P) DO_ALL=0
|
||||
DO_PKP_PLUGINS=1
|
||||
;;
|
||||
c) DO_ALL=0
|
||||
DO_APP_CLASSES=1
|
||||
;;
|
||||
p) DO_ALL=0
|
||||
DO_APP_PLUGINS=1
|
||||
;;
|
||||
d) DEBUG="--debug"
|
||||
;;
|
||||
R) DO_COVERAGE=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
PHPUNIT='php lib/pkp/lib/vendor/phpunit/phpunit/phpunit --configuration lib/pkp/tests/phpunit.xml --testdox --no-interaction'
|
||||
|
||||
# Where to look for tests
|
||||
TEST_SUITES='--testsuite '
|
||||
|
||||
if [ \( "$DO_ALL" -eq 1 \) -o \( "$DO_PKP_CLASSES" -eq 1 \) ]; then
|
||||
TEST_SUITES="${TEST_SUITES}LibraryClasses,"
|
||||
fi
|
||||
|
||||
if [ \( "$DO_ALL" -eq 1 \) -o \( "$DO_PKP_PLUGINS" -eq 1 \) ]; then
|
||||
TEST_SUITES="${TEST_SUITES}LibraryPlugins,"
|
||||
fi
|
||||
|
||||
if [ \( "$DO_ALL" -eq 1 \) -o \( "$DO_APP_CLASSES" -eq 1 \) ]; then
|
||||
TEST_SUITES="${TEST_SUITES}ApplicationClasses,"
|
||||
fi
|
||||
|
||||
if [ \( "$DO_ALL" -eq 1 \) -o \( "$DO_APP_PLUGINS" -eq 1 \) ]; then
|
||||
TEST_SUITES="${TEST_SUITES}ApplicationPlugins,"
|
||||
fi
|
||||
|
||||
if [ "$DO_COVERAGE" -eq 1 ]; then
|
||||
export XDEBUG_MODE=coverage
|
||||
fi
|
||||
|
||||
$PHPUNIT $DEBUG -v ${TEST_SUITES%%,}
|
||||
|
||||
if [ "$DO_COVERAGE" -eq 1 ]; then
|
||||
cat lib/pkp/tests/results/coverage.txt
|
||||
fi
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file tools/setVersionTool.php
|
||||
*
|
||||
* Copyright (c) 2013-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SetVersionTool
|
||||
*
|
||||
* @ingroup tools
|
||||
*
|
||||
* @brief CLI tool to set a version number for each publication.
|
||||
*/
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
|
||||
require(dirname(__FILE__, 4) . '/tools/bootstrap.php');
|
||||
|
||||
class SetVersionTool extends \PKP\cliTool\CommandLineTool
|
||||
{
|
||||
/**
|
||||
* Set the version numbers
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
$contextIds = Services::get('context')->getIds();
|
||||
foreach ($contextIds as $contextId) {
|
||||
$submissions = Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->getIds();
|
||||
|
||||
foreach ($submissions as $submission) {
|
||||
$version = 1;
|
||||
foreach ($submission->getData('publications') as $publication) {
|
||||
Repo::publication()->edit($publication, ['version' => $version]);
|
||||
$version++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tool = new SetVersionTool($argv ?? []);
|
||||
$tool->execute();
|
||||
Reference in New Issue
Block a user