diff --git a/_data/versions.yml b/_data/versions.yml index 662b3eb9ec76..104a694af709 100644 --- a/_data/versions.yml +++ b/_data/versions.yml @@ -14,7 +14,8 @@ dev-documentation: 3.4: /dev/documentation/3.4/en 3.3: /dev/documentation/3.3/en dev-plugin-guide: - 3.4: /dev/plugin-guide/en + 3.5: /dev/plugin-guide/en + 3.4: /dev/plugin-guide/3.4/en 3.3: /dev/plugin-guide/3.3/en importing-exporting: current: /importing-exporting/en/ diff --git a/_includes/cards/dev/plugin-guide.md b/_includes/cards/dev/plugin-guide.md index 8d3ae41c8bc0..36695755c96f 100644 --- a/_includes/cards/dev/plugin-guide.md +++ b/_includes/cards/dev/plugin-guide.md @@ -5,4 +5,4 @@ Learn how to write plugins to customize almost anything in OJS and OMP. [View No --- -Versions: [3.4](/dev/plugin-guide/en), [3.3](/dev/plugin-guide/3.3/en) +Versions: [3.5](/dev/plugin-guide/en), [3.4](/dev/plugin-guide/3.4/en), [3.3](/dev/plugin-guide/3.3/en) diff --git a/dev/plugin-guide/de/SUMMARY.md b/dev/plugin-guide/3.4/de/SUMMARY.md similarity index 100% rename from dev/plugin-guide/de/SUMMARY.md rename to dev/plugin-guide/3.4/de/SUMMARY.md diff --git a/dev/plugin-guide/de/index.md b/dev/plugin-guide/3.4/de/index.md similarity index 100% rename from dev/plugin-guide/de/index.md rename to dev/plugin-guide/3.4/de/index.md diff --git a/dev/plugin-guide/3.4/en/SUMMARY.md b/dev/plugin-guide/3.4/en/SUMMARY.md new file mode 100644 index 000000000000..7cf67fdeb85f --- /dev/null +++ b/dev/plugin-guide/3.4/en/SUMMARY.md @@ -0,0 +1,27 @@ +# Summary + +* [Introduction](.) +* [Getting Started](./getting-started) +* [Plugin Categories](./categories) + * [Blocks](./categories#blocks) + * [Import/Export](./categories#import-export) + * [Reports](./categories#reports) + * [Themes](./categories#themes) + * [Generic](./categories#generic) + * [Other](./categories#other) +* [Translation](./translation) +* [Templates](./templates) +* [Plugin Settings](./settings) +* [Release a Plugin](./release) +* [Examples](./examples) + * [Plugin Template](https://github.com/pkp/pluginTemplate){:target="_blank"} + * [Add Styles and Scripts](./examples-styles-scripts) + * [Context and Site](./examples-context-site) + * [Get Data](./examples-get-data) + * [Get Data from the Template](./examples-get-data-template) + * [Add Custom Fields](./examples-custom-field) + * [Add Custom Page](./examples-custom-page) + * [Events and Listeners](./example-events) + * [Add Editorial Decision](./example-decision) + * [Mailgun](https://github.com/Vitaliy-1/mailgun/) + * [Extend a Map](./examples-extend-map) diff --git a/dev/plugin-guide/3.4/en/categories.md b/dev/plugin-guide/3.4/en/categories.md new file mode 100644 index 000000000000..cddec1e2053b --- /dev/null +++ b/dev/plugin-guide/3.4/en/categories.md @@ -0,0 +1,268 @@ +--- +title: Plugin Categories - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Plugin Categories + +A plugin's category determines when it is loaded and how it can modify the application. For example, a [block](#blocks) plugin can add a block of content to the sidebar in the reader-facing website. But it can't do anything else and won't be loaded on the backend. + +Each plugin must extend one of the plugin category classes. In the [Getting Started](./getting-started) tutorial, the Tutorial Example plugin extended the `GenericPlugin` class. + +```php +namespace APP\plugins\generic\tutorialExample; + +use PKP\plugins\GenericPlugin; + +class TutorialExamplePlugin extends GenericPlugin +{ + ... +} +``` + +A block plugin will extend the `BlockPlugin` class. + +```php +namespace APP\plugins\blocks\tutorialBlock; + +use PKP\plugins\BlockPlugin; + +class TutorialBlockPlugin extends BlockPlugin +{ + ... +} +``` + +Each plugin category class provides methods that must be implemented. For example, a [report](#reports) plugin extends the `ReportPlugin` class and implements the `ReportPlugin::display()` method to deliver a CSV file with the report contents. + +```php +namespace APP\plugins\reports\tutorialExample; + +use PKP\plugins\ReportPlugin; + +class TutorialExamplePlugin extends ReportPlugin +{ + public function display($args, $request) + { + header('content-type: text/comma-separated-values'); + header('content-disposition: attachment; filename=reviews.csv'); + $fp = fopen('php://output', 'wt'); + fputcsv($fp, [/* the review details in the report */]); + fclose($fp); + } +} +``` + +## Blocks {#blocks} + +Block plugins provide content that can be displayed in the sidebar on any page of the public-facing website. They require a template file. + +``` +ojs +│ +├─┬ plugins +│ │ +│ └─┬ blocks +│ │ +│ └─┬ madeBy +│ │ +│ ├─┬ templates +│ │ └── block.tpl +│ ├── MadeByPlugin.php +│ └── version.xml +``` + +The template file should include all of the HTML for your block. + +```html +
+ Made with ❤ by the Public Knowledge Project +
+``` + +Add a `getContents()` method to pass data to the template. + +```php +namespace APP\plugins\blocks\madeBy; + +use PKP\plugins\BlockPlugin; + +class MadeByPlugin extends BlockPlugin +{ + public function getContents($templateMgr, $request = null) + { + $templateMgr->assign([ + 'madeByText' => 'Made with ❤ by the Public Knowledge Project', + ]); + + return parent::getContents($templateMgr, $request); + } +} +``` + +```html +
+ {$madeByText|escape} +
+``` + +Block plugins can use any HTML code. However, themes provided by PKP expect blocks to use the following markup. + +```html +
+

+ +

+
+ +
+
+``` + +## Import/Export {#import-export} + +> View an [example import/export plugin](https://github.com/pkp/exampleImportExport). +{:.notice} + +Import/export plugins provide tools for getting data into and out of OJS, OMP, and OPS. They can be used when you are moving between our application and another platform to migrate users, submissions, back issues and more. + + +Each import/export plugin can be run on the command line. + +``` +$ php tools/importExport.php ExampleImportExportPlugin import filename.csv +``` + +Use the `{plugin_url ...}` smarty function in the template to submit a form to one of the import or export paths. + +``` +
+ +
+``` + +Check the `path` to detect when the form is submitted. + +```php +namespace APP\plugins\importexport\exampleImportExport; + +use PKP\plugins\ImportExportPlugin; + +class ExampleImportExportPlugin extends ImportExportPlugin +{ + // ... + + public function display($args, $request) + { + parent::display($args, $request); + + $path = array_shift($args); + + if ($path === 'exportAll') { + // do something + } + } +} +``` + +## Reports {#reports} + +> Reports can return any file format, but they usually generate a CSV file for use in spreadsheet software. +{:.notice} + +Report plugins provide a simple interface for generating a file download. Reports may be generated for article usage statistics, reviewers or anything you want. + +Report plugins extend the `ReportPlugin` class and implement the `display()` method. + +```php +namespace APP\plugins\reports\exampleReport; + +use APP\submission\Submission; +use PKP\plugins\ReportPlugin; + +class ExampleReportPlugin extends ReportPlugin +{ + public function display($args, $request) { + + // Get the first 100 submissions + $submissions = Repo::submission() + ->getCollector() + ->filterByContextIds([$context->getId()]) + ->limit(100) + ->getMany(); + + // Stream to a CSV file + header('content-type: text/comma-separated-values'); + header('content-disposition: attachment; filename=articles-' . date('Ymd') . '.csv'); + $fp = fopen('php://output', 'wt'); + fputcsv($fp, ['ID', 'Title']); + /** @var Submission $submission */ + foreach ($submissions as $submission) { + fputcsv($fp, [ + $submission->getId(), + $submission->getCurrentPublication()->getLocalizedTitle() + ]); + } + fclose($fp); + } +} +``` + +## Themes {#themes} + +Themes control the design and layout of a journal, press or preprint server. Read the [Theming Guide](/pkp-theming-guide/en) to learn how to build your own themes. + +## Generic {#generic} + +Generic plugins are loaded with every request. They hook into the application early in the [Request Lifecycle](/dev/documentation/en/architecture-request) and can be used to modify almost everything. + +Generic plugins use [Hooks](/dev/documentation/en/utilities-hooks) to intervene in the application. Hooks should be added in a plugin's `register()` method. + +> Always check if the plugin is enabled before adding a hook. Otherwise, your plugin will run even when it has been disabled. +{:.warning} + +```php +namespace APP\plugins\generic\tutorialExample; + +use PKP\plugins\GenericPlugin; +use PKP\plugins\Hook; + +class TutorialExamplePlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = null) + { + $success = parent::register($category, $path); + + if ($success && $this->getEnabled()) { + Hook::add('Example::hookName', [$this, 'doSomething']); + } + + return $success; + } + + public function doSomething(string $hookName, array $args): ?bool + { + // Do something... + + return false; + } +} +``` + +Generic plugins are very powerful and can use any hook in the application. Look at the [examples](./examples) for ideas and learn about the most [common hooks](/dev/documentation/en/utilities-hooks#common-hooks). + +## Other {#other} + +Other plugin categories are not often used. The best way to learn about them is to read the source code of one of the existing plugins. These categories include: + +- `auth` plugins allow you to authorize and synchronize user accounts with a third-party source. +- `gateways` plugins allow you to add a new URL and respond to requests to that URL. +- `metadata` plugins implement a description of a metadata format. +- `oaiMetadataFormats` plugins add a metadata format to the application's OAI endpoint. +- `paymethod` plugins allow you to implement your own payment handling when using subscription and article fees. +- `pubIds` plugins allow you to add support for publication identifiers like URNs. + +--- + +Learn how to [translate your plugin](./translation) so that it can be used by many journals. diff --git a/dev/plugin-guide/3.4/en/example-decision.md b/dev/plugin-guide/3.4/en/example-decision.md new file mode 100644 index 000000000000..c6fcdf970d28 --- /dev/null +++ b/dev/plugin-guide/3.4/en/example-decision.md @@ -0,0 +1,312 @@ +--- +title: Example - Add an Editorial Decision - Plugin Guide for OJS, OMP and OPS +description: How to add a custom editorial decision in a plugin for OJS, OMP or OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Add an Editorial Decision + +> Learn more about [decisions](/dev/documentation/en/decisions) in our developer documentation. +{:.notice} + +Plugins can add, edit and remove editorial decisions in each stage of the workflow. In the core application, decisions are used to accept and decline submissions, and send them from one stage of the workflow to another. However, decisions can be used to perform any routine editorial action. + +A plugin can modify the editorial decisions in the system to: + +- Remove an undesired decision, like the decision to skip review. +- Prevent an editorial decision from being recorded unless certain conditions are met. +- Add a decision to perform an action, like sending files to a third-party service provider. + +## Remove the skip review decision + +A journal, press or preprint server may want to remove the editorial decision to skip the review stage, to ensure an editor does not bypass peer review. First, add a hook to remove the decision from the list of valid decisions. + +```php +use APP\decision\types\SkipExternalReview; +use Illuminate\Database\Eloquent\Collection; +use PKP\plugins\Hook; +use PKP\decision\DecisionType; + +Hook:add('Decision::types', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var Collection */ + + $decisionTypes = $decisionTypes->filter( + function(DecisionType $type, int $i) { + return !is_a($type, SkipExternalReview::class); + } + ); + + return false; +}); +``` + +Then add a hook to remove the decision from the list of decisions in the submission stage. + +```php +use APP\decision\types\SkipExternalReview; +use PKP\plugins\Hook; +use PKP\decision\DecisionType; + +Hook:add('Workflow::Decisions', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var array */ + $stageId = $args[1]; /** @var int */ + + if ($stageId === WORKFLOW_STAGE_ID_SUBMISSION) { + $decisionTypes = array_filter( + $decisionTypes, + function(DecisionType $type) { + return !is_a($type, SkipExternalReview::class); + } + ); + } + + return false; +} +``` + +## Require completed reviews before accepting a submission + +A journal or press may want to require at least 3 review assignments to be completed before a submission can be accepted, to ensure all submissions receive an appropriate level of peer review. First, create a new decision that extends the `Accept` decision. + +```php +namespace APP\plugins\generic\example; + +use APP\submission\Submission; +use Illuminate\Validation\Validator; +use PKP\context\Context; +use APP\decision\types\Accept; +use PKP\db\DAORegistry; +use PKP\submission\reviewAssignment\ReviewAssignmentDAO; + +class AcceptReviewed extends Accept +{ + public function validate(array $props, Submission $submission, Context $context, Validator $validator, ?int $reviewRoundId = null) + { + parent::validate($props, $submission, $context, $validator, $reviewRoundId); + + /** @var ReviewAssignmentDAO $reviewRoundDao */ + $reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); + $reviewAssignments = $reviewAssignmentDao->getByReviewRoundId($reviewRoundId); + + $completed = 0; + foreach($reviewAssignments as $reviewAssignment) { + if ($reviewAssignment->getDateCompleted()) { + $completed++; + } + } + + if ($completed < 3) { + $validator->errors()->add('reviews', 'There must be at least 3 completed review assignments before a submission can be accepted.'); + } + } +} +``` + +Then replace this decision in the list of valid decisions and the list of decisions in the review stage. + +```php +use APP\decision\types\Accept; +use Illuminate\Database\Eloquent\Collection; +use PKP\plugins\Hook; +use PKP\decision\DecisionType; + +Hook:add('Decision::types', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var Collection */ + + $decisionTypes = $decisionTypes->map( + function(DecisionType $type, int $i) { + return is_a($type, Accept::class) + ? new AcceptReviewed() + : $type; + } + ); + + return false; +}); + +Hook:add('Workflow::Decisions', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var array */ + $stageId = $args[1]; /** @var int */ + + if ($stageId === WORKFLOW_STAGE_ID_EXTERNAL_REVIEW) { + $decisionTypes = array_map( + function(DecisionType $type) { + return is_a($type, Accept::class) + ? new AcceptReviewed() + : $type; + }, + $decisionTypes + ); + } + + return false; +} +``` + +## Select and send files + +A plugin may want to add an editorial decision to the production stage that allows an editor to select production-ready files to be sent to a third-party service. First, create a new decision type. + +```php +namespace PKP\plugins\generic\example; + +use APP\decision\Decision; +use APP\facades\Repo; +use APP\submission\Submission; +use Illuminate\Validation\Validator; +use PKP\context\Context; +use PKP\decision\DecisionType; +use PKP\decision\Steps; +use PKP\decision\steps\Form; +use PKP\submission\reviewRound\ReviewRound; +use PKP\submissionFile\SubmissionFile; +use PKP\user\User; + +class SendFilesToService extends DecisionType +{ + /** + * Create a unique constant for your decision type. + * + * To avoid clashes with the core application, use + * a number in the 900 range. + * + * @see Decision + */ + public const SEND_FILES_TO_SERVICE = 900; + + /** + * @see self::SEND_FILES_TO_SERVICE + */ + public function getDecision(): int + { + return self::SEND_FILES_TO_SERVICE; + } + + /** + * This decision can be recorded when the submission + * is in the production stage + */ + public function getStageId(): int + { + return WORKFLOW_STAGE_ID_PRODUCTION; + } + + /** + * Add a step to the record decision UI to select + * the files to be sent to the third-party service + * + * See the dev documentation about forms to learn + * how to create the SelectFilesForm. + * + * @see https://docs.pkp.sfu.ca/dev/documentation/en/frontend-forms + */ + public function getSteps(Submission $submission, Context $context, User $editor, ?ReviewRound $reviewRound): Steps + { + $steps = new Steps($this, $submission, $context); + + $steps->addStep( + new Form( + 'select-files', + 'Select Files', + 'Select the files that should be sent to the third-party service.', + new SelectFilesForm($submission) + ) + ); + + return $steps; + } + + /** + * Check that the files selected by the user match + * a valid production-ready submission file + */ + public function validate(array $props, Submission $submission, Context $context, Validator $validator, ?int $reviewRoundId = null) + { + parent::validate($props, $submission, $context, $validator, $reviewRoundId); + + if (!isset($props['actions'])) { + return; + } + + foreach ((array) $props['actions'] as $index => $action) { + + switch ($action['id']) { + + case 'select-files': + if (!is_array($action['selected-files']) || empty($action['selected-files'])) { + $validator->errors()->add('actions.' . $index . '.selected-files', 'You must select at least one file.'); + + } else { + $ids = Repo::submissionFile() + ->getCollector() + ->filterBySubmissionIds([$submission->getId()]) + ->filterByFilestages([ + SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY, + ]) + ->getIds(); + + foreach ($action['selected-files'] as $selectedFile) { + if (!$ids->contains($selectedFile)) { + $validator->errors()->add('actions.' . $index . '.selected-files', 'One or more of the selected files is not valid.'); + break; + } + } + } + break; + } + } + } + + /** + * Send the selected files to a third-party service + * + * This method is only run after validating and recording + * the decision. + */ + public function runAdditionalActions(Decision $decision, Submission $submission, User $editor, Context $context, array $actions) + { + parent::runAdditionalActions($decision, $submission, $editor, $context, $actions); + + foreach ($actions as $action) { + switch ($action['id']) { + case 'select-files': + + // Send the files + + break; + } + } + } +} +``` + +Then add this decision to the list of valid decisions and the list of decisions in the production stage. + +```php +use Illuminate\Database\Eloquent\Collection; +use PKP\plugins\Hook; + +Hook:add('Decision::types', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var Collection */ + + $decisionTypes = $decisionTypes->push(new SendFilesToService()); + + return false; +}); + +Hook:add('Workflow::Decisions', function(string $hookName, array $args) { + $decisionTypes &= $args[0]; /** @var array */ + $stageId = $args[1]; /** @var int */ + + if ($stageId === WORKFLOW_STAGE_ID_PRODUCTION) { + $decisionTypes[] = new SendFilesToService(); + } + + return false; +} +``` + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/example-events.md b/dev/plugin-guide/3.4/en/example-events.md new file mode 100644 index 000000000000..d4bd27cc0e91 --- /dev/null +++ b/dev/plugin-guide/3.4/en/example-events.md @@ -0,0 +1,87 @@ +--- +title: Example - Events and Listeners - Plugin Guide for OJS, OMP and OPS +description: How to use events and listeners in a plugin for OJS, OMP or OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Events and Listeners + +> Learn more about [events and listeners](/dev/documentation/en/utilities-events) in our developer documentation. +{:.notice} + +Plugins can create listeners to subscribe to events triggered by the core application. Events are new in 3.4. Only a small number of events are used now. In the future, we plan to use them more. + +Find all of the events in the `classes/observers/events` and `lib/pkp/classes/observers/events` directories. + +## Send Files to Production Pipeline + +A plugin may want to send copyedited files to a third-party service when a submission is sent to production. First, create the listener. + +```php +namespace APP\plugins\generic\example; + +use APP\facades\Repo; +use Illuminate\Events\Dispatcher; +use PKP\decision\types\SendToProduction; +use PKP\observers\events\DecisionAdded; +use PKP\submissionFile\SubmissionFile; + +class ExampleListener +{ + public function subscribe(Dispatcher $events): void + { + $events->listen( + DecisionAdded::class, + ExampleListener::class + ); + } + + public function handle(DecisionAdded $event) + { + if (!is_a($event->decisionType, SendToProduction::class)) { + return; + } + + $submissionFiles = Repo::submissionFile() + ->getCollector() + ->filterBySubmissionIds([$event->submission->getId()]) + ->filterByFileStages([ + SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY, + ]) + ->getMany(); + + if ($submissionFiles->count()) { + // Send files to third-party service. + } + } +} +``` + +Then subscribe the listener to the event in the plugin's registration method. + +```php +namespace APP\plugins\generic\example; + +use Illuminate\Support\Facades\Event; +use PKP\observers\events\DecisionAdded; +use PKP\plugins\GenericPlugin; + +class ExamplePlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = NULL) + { + $success = parent::register($category, $path); + + if ($success && $this->getEnabled()) { + Event::subscribe(new ExampleListener()); + } + + return $success; + } +} +``` + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-context-site.md b/dev/plugin-guide/3.4/en/examples-context-site.md new file mode 100644 index 000000000000..de1544adb3c5 --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-context-site.md @@ -0,0 +1,68 @@ +--- +title: Example - Managing Context and Site - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Managing Context and Site + +OJS and OMP can be used to run more than one journal or press. We call each journal or press a context. Plugins are usually run within a context but some may run at the site-wide level. You should write your plugins to work whether they are being run at the context or site level. + +You can always retrieve the current context from the `Request`. + +```php +use APP\core\Application; + +$context = Application::get() + ->getRequest() + ->getContext(); +``` + +In site-wide pages, such as the admin settings or the site-wide journal index page, the context will be `null`. + +```php +if ($context) { + // The current request is for a journal, press or preprint server +} else { + // The current request is for a site-wide page +} +``` + +If your plugin supports a [settings page](./settings), settings must be saved separately for each context. + +```php +$this->updateSetting($context->getId(), 'editorName', 'Daniel Barnes'); +``` + +Use the `Application::CONTEXT_SITE` constant to save settings to the site-wide level. + +```php +use APP\core\Application; + +$contextId = $context + ? $context->getId(); + : Application::CONTEXT_SITE; + +$this->updateSetting($contextId, 'editorName', 'Daniel Barnes'); +``` + +## Site-wide Plugins + +Add the `isSitePlugin` method to enable the plugin's settings form in the site-wide plugins list. + +```php +class TutorialExamplePlugin extends GenericPlugin +{ + // ... + + public function isSitePlugin() { + return true; + } +} +``` + +Site-wide plugins can also be enabled, disabled and configured for each context. + +--- + +View more [examples](./examples). \ No newline at end of file diff --git a/dev/plugin-guide/3.4/en/examples-custom-field.md b/dev/plugin-guide/3.4/en/examples-custom-field.md new file mode 100644 index 000000000000..6f9084cb114b --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-custom-field.md @@ -0,0 +1,102 @@ +--- +title: Example - Add Custom Fields - Plugin Guide for OJS, OMP and OPS +description: How to add data and form fields to extend the data model in OJS, OMP and OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Add Custom Fields + +> Learn more about how to extend [entities](/dev/documentation/en/architecture-entities) and [schemas](/dev/documentation/en/architecture-entities#schemas) in our developer documentation. +{:.notice} + +Plugins can add properties to any entity which uses a schema file, then add fields to an existing form to edit these properties. The example below adds an `institutionalHome` property to the `Context` entity, which represents a journal, press or preprint server. A field is added to the form at Settings > Journal > Masthead to edit the property. + +```php +namespace APP\plugins\generic\institutionalHome; + +use APP\core\Application; +use PKP\components\forms\FieldText; +use PKP\components\forms\FormComponent; +use PKP\plugins\GenericPlugin; +use PKP\plugins\Hook; +use stdClass; + +class InstitutionalHomePlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = null) + { + $success = parent::register($category, $path, $mainContextId); + if ($success && $this->getEnabled()) { + + // Use a hook to extend the context entity's schema + Hook::add('Schema::get::context', [$this, 'addToSchema']); + + // Use a hook to add a field to the masthead form context settings. + Hook::add('Form::config::before', [$this, 'addToForm']); + } + return $success; + } + + /** + * Extend the context entity's schema with an institutionalHome property + */ + public function addToSchema(string $hookName, array $args) + { + $schema = $args[0]; /** @var stdClass */ + $schema->properties->institutionalHome = (object) [ + 'type' => 'string', + 'apiSummary' => true, + 'multilingual' => true, + 'validation' => ['nullable'] + ]; + + return false; + } + + /** + * Extend the masthead form to add an institutionalHome input field + * in the journal/press settings + */ + public function addtoForm(string $hookName, FormComponent $form): bool + { + + // Only modify the masthead form + if (!defined('FORM_MASTHEAD') || $form->id !== FORM_MASTHEAD) { + return; + } + + // Don't do anything at the site-wide level + $context = Application::get()->getRequest()->getContext(); + if (!$context) { + return; + } + + // Add a field to the form + $form->addField(new FieldText('institutionalHome', [ + 'label' => 'Institutional Home', + 'groupId' => 'publishing', + 'value' => $context->getData('institutionalHome'), + ])); + + return false; + } +} +``` + +When the editor adds an institutional home in the masthead settings, you can retrieve it from the `DataObject` class for the context. + +```php +$context = Application::get()->getRequest()->getContext(); +$institutionalHome = $context->getLocalizedData('institutionalHome'); +``` + +Or use it in a template on the reader-facing frontend. + +```html +

Institutional Home: {$currentContext->getLocalizedData('institutionalHome')}

+``` + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-custom-page.md b/dev/plugin-guide/3.4/en/examples-custom-page.md new file mode 100644 index 000000000000..6f7f23c4b29f --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-custom-page.md @@ -0,0 +1,91 @@ +--- +title: Example - Add Custom Page - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Add Custom Page + +> You should understand the [Request Lifecycle](/dev/documentation/en/architecture-request) and be familiar with the application [architecture](/dev/documentation/en/architecture), especially [Handlers](/dev/documentation/en/architecture-handlers), before proceeding. +{:.notice} + +You may want your plugin to add a new page to the application. This may be a separate settings page or editorial dashboard on the backend, or a new public page on the reader-facing website. A generic plugin can do this by hooking into the request lifecycle and loading its own [PageHandler](/dev/documentation/en/architecture-handlers). + +Consider a request to the following page. + +``` +http://example.org/publicknowledge/example +``` + +The plugin hooks into `LoadHandler` and routes the request to a custom `PageHandler`. + +```php +namespace APP\plugins\generic\exampleCustomPage; + +use PKP\plugins\GenericPlugin; +use PKP\plugins\Hook; + +class ExampleCustomPagePlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = null) + { + $success = parent::register($category, $path, $mainContextId); + if ($success && $this->getEnabled()) { + Hook::add('LoadHandler', [$this, 'setPageHandler']); + } + return $success; + } + + /** + * Route requests for the `example` page to a custom page handler + */ + public function setPageHandler(string $hookName, array $args): bool + { + $page =& $args[0]; + $handler =& $args[3]; + if ($this->getEnabled() && $page === 'example') { + $handler = new ExampleCustompageHandler($this); + return true; + } + return false; + } +} +``` + +The `PageHandler` displays the `example.tpl` template. + +```php +namespace APP\plugins\generic\exampleCustomPage; + +use APP\template\TemplateManager; +use PKP\controllers\page\PageHandler; + +class ExampleCustompageHandler extends PageHandler +{ + public ExampleCustomPagePlugin $plugin; + + public function __construct(ExampleCustomPagePlugin $plugin) + { + parent::__construct(); + + $this->plugin = $plugin; + } + + public function index($args, $request) + { + $templateMgr = TemplateManager::getManager($request); + + return $templateMgr->display( + $this->plugin->getTemplateResource( + 'example.tpl' + ) + ); + } +} +``` + +A custom `PageHandler` can support more than one op by adding public methods in addition to `index()`. Read more about [Handlers](/dev/documentation/en/architecture-handlers) and be sure to add [authorization checks](/dev/documentation/en/architecture-authorization) to secure access to your page. + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-extend-map.md b/dev/plugin-guide/3.4/en/examples-extend-map.md new file mode 100644 index 000000000000..127912d79a49 --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-extend-map.md @@ -0,0 +1,36 @@ +--- +title: Example - Extend a Map - Plugin Guide for OJS, OMP and OPS +description: Example code showing how to extend a map in a plugin for OJS, OMP and OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Extend a Map + +[Maps](/dev/documentation/en/architecture-maps) are used to convert data objects to another format. The most common use of maps in the application is to convert data objects to associative arrays, so that they can be compiled into JSON and returned in REST API results. + +Plugins can extend a map to add data to the mapped output. This allows a plugin to integrate deeply with parts of the application like the REST API. For example, a plugin may want to add the number of days since an announcement was posted to the response data of the [Announcements](/dev/api/ojs/3.4#tag/Announcements) endpoint of the REST API. + +Use the `app('maps')` helper to extend the `Schema` map of the `Announcement` class. + +```php +use PKP\announcement\Announcement; +use PKP\announcement\maps\Schema; + +app('maps')->extend( + Schema::class, + function($output, Announcement $item, Schema $map) { + $then = new DateTime($item->getData('datePosted')); + $now = new DateTime(); + $output['daysSince'] = (int) $now->diff($then)->format('%a'); + + return $output; + } +); +``` + +REST API responses usually use a schema map. For example, the map for submissions is located at `APP\submission\maps\Schema`. Most entities use a similar pattern. + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-get-data-template.md b/dev/plugin-guide/3.4/en/examples-get-data-template.md new file mode 100644 index 000000000000..bdaa2961986b --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-get-data-template.md @@ -0,0 +1,84 @@ +--- +title: Example - Get Data from the Template - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Get Data from the Template + +When you [override a template](./templates#override-templates), you may need to get data that has already been assigned to the template. For example, consider the single issue template below. + +```html +
+

Published: {$issue->getPublished()}

+ ... +
+``` + +Your plugin overrides this template because it wants to show a unique ID with all issues. Your plugin's template looks like this. + +```html +
+

Published: {$issue->getPublished()}

+

Internal ID: {$issueInternalId}

+ ... +
+``` + +However, you first need to retrieve the `issueInternalId` from the plugin settings to display the ID. In the example below, the plugin does the following. + +1. Hook into the call that loads the `pages/issue.tpl` template. +2. Get the `Issue` object from the template variables. +3. Use the issue ID to get the correct plugin setting. +4. Assign the `issueInternalId` for use in the plugin's custom template. + +```php +namespace APP\plugins\generic\internalIssueId; + +use APP\core\Application; +use APP\issue\Issue; +use APP\template\TemplateManager; +use PKP\plugins\GenericPlugin; +use PKP\plugins\Hook; + +class InternalIssueIdPlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = null) { + $success = parent::register($category, $path, $mainContextId); + if ($success && $this->getEnabled()) { + + // 1. Hook in before the template is displayed... + Hook::add('TemplateManager::display',[$this, 'addIssueInternalId']); + } + return $success; + } + + public function addIssueInternalId($hookName, $args) + { + $templateMgr = $args[0]; /** @var TemplateManager */ + $template = $args[1]; /** @var string */ + $contextId = Application::get()->getRequest()->getContext()->getId(); + + // 1. ...only when it is the issue template. + if ($template !== 'frontend/pages/issue.tpl') { + return false; + } + + // 2. Get the `issue` variable from the assigned template variables. + /** @var Issue $issue */ + $issue = $templateMgr->getTemplateVars('issue'); + + // 3. Get the matching plugin setting. + $internalIssueId = $this->getSetting($contextId, 'issueInternalId' . $issue->getId()); + + // 4. Assign the internal issue id for use in the template. + $templateMgr->assign('internalIssueId', $internalIssueId); + + return false; + } +} +``` + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-get-data.md b/dev/plugin-guide/3.4/en/examples-get-data.md new file mode 100644 index 000000000000..6fd42474a6ea --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-get-data.md @@ -0,0 +1,66 @@ +--- +title: Example - Get Data - Plugin Guide for OJS, OMP and OPS +description: Example code showing how to get some data in a plugin for OJS, OMP and OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Get Data + +Your plugin may need to get data from the application, such as submissions, issues, authors, users and files. Use the [Repo facade](/dev/documentation/en/architecture-repositories) to retrieve information. + +```php +use APP\core\Application; +use APP\facades\Repo; + +$currentUser = Application::get()->getUser(); +$context = Application::get()->getContext(); + +$submissions = Repo::submission() + ->getCollector() + ->filterByContexts([$context->getId()]) + ->assignedTo([$currentUser->getId()]) + ->limit(20) + ->getMany(); +``` + +The `Collector`s are a wrapper around Laravel [QueryBuilder](https://laravel.com/docs/9.x/queries). Plugins can access the underlying `QueryBuilder` to run custom database queries. The example uses the submission collector to build a query of all published submissions in a context, then adds conditions to restrict results to those that have an author with the provided email address. + +```php +use APP\author\DAO as AuthorDAO; +use APP\core\Application; +use APP\facades\Repo; +use APP\submission\Submission; + +$context = Application::get()->getContext(); +$authorDao = app(AuthorDAO::class); + +$queryBuilder = Repo::submission() + ->getCollecter() + ->filterByContexts([$context->getId()]) + ->filterByStatus([Submission::STATUS_PUBLISHED]) + ->getQueryBuilder(); + +$submissions = $queryBuilder + ->leftJoin($authorDao->table . ' as a', 'po.publication_id', '=', 'a.email') + ->where('a.email', 'example@author.com') + ->get() + ->map( + function($row, $i) { + return Repo::submission()->dao->fromRow($row); + } + ); +``` + +If a `Repository` does not exist for the data you want, you may need to use a [DAO](/dev/documentation/en/architecture-daos). + +```php +$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); +$reviewAssignments = $reviewAssignmentDao->getByReviewRoundId($reviewRoundId); +``` + +Learn more about [Repositories](/dev/documentation/en/architecture-repositories), [Entities](/dev/documentation/en/architecture-entities) and [DAOs](/dev/documentation/en/architecture-daos) in our [developer documentation](/dev/documentation/en). + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples-styles-scripts.md b/dev/plugin-guide/3.4/en/examples-styles-scripts.md new file mode 100644 index 000000000000..b57b2cd8ac9d --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples-styles-scripts.md @@ -0,0 +1,84 @@ +--- +title: Example - Add Styles and Scripts - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Add Styles and Scripts + +Your plugin may require additional CSS or JavaScript code to run. The `TemplateManager` includes helper functions to load scripts and styles. + +```php +use APP\template\TemplateManager; + +$templateMgr = TemplateManager::getManager($request); + +$templateMgr->addStyleSheet('tutorialExampleStyles', 'http://example.com/my-css.css'); +$templateMgr->addJavaScript('tutorialExampleScript', 'http://example.com/my-script.js'); +``` + +Scripts and styles may be located in the plugin's directory. Use the `base_url` to get the URL to a plugin's root directory. + +```php +use APP\core\Application; +use APP\template\TemplateManager; + +$request = Application::get()->getRequest(); + +$url = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/css/my-css.css'; + +$templateMgr = TemplateManager::getManager($request); +$templateMgr->addStyleSheet('tutorialExampleStyles', $url); +``` + +Scripts and styles should be loaded in the plugin's `register` method. + +```php +namespace APP\plugins\generic\tutorialExample; + +use APP\core\Application; +use APP\template\TemplateManager; +use PKP\plugins\GenericPlugin; + +class TutorialExamplePlugin extends GenericPlugin +{ + public function register($category, $path, $mainContextId = NULL) + { + $success = parent::register($category, $path); + + if ($success && $this->getEnabled()) { + $request = Application::get()->getRequest(); + $url = $request->getBaseUrl() . '/' . $this->getPluginPath() . '/css/my-css.css'; + $templateMgr = TemplateManager::getManager($request); + $templateMgr->addStyleSheet('tutorialExampleStyles', $url); + } + + return $success; + } +} +``` + +By default, scripts and styles are loaded on the reader-facing website. Pass a `context` argument to load them in the editorial backend. + +```php +$templateMgr->addStyleSheet( + 'tutorialExampleStyles', + $url, + ['contexts' => 'backend'] +); +``` + +You can pass more than one context to load them in two places. + + +```php +$templateMgr->addStyleSheet( + 'tutorialExampleStyles', + $url, + ['contexts' => ['backend', 'frontend'] +); +``` + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/3.4/en/examples.md b/dev/plugin-guide/3.4/en/examples.md new file mode 100644 index 000000000000..cc2208c4aa7d --- /dev/null +++ b/dev/plugin-guide/3.4/en/examples.md @@ -0,0 +1,23 @@ +--- +title: Examples - Plugin Guide for OJS, OMP and OPS +book: dev-plugin-guide +version: 3.4 +--- + +# Examples + +Plugins can be put to a wide variety of uses. The examples below show you how to do things that many plugins may need to do. + +- [Plugin Template](https://github.com/pkp/pluginTemplate){:target="_blank"} - A working starter plugin that you can use as a base for your own plugins. +- [Add Styles and Scripts](./examples-styles-scripts) - How to add a CSS stylesheet or JavaScript file to the backend. +- [Create Vue.js components](https://github.com/jardakotesovec/backend-ui-example-plugin/tree/main) - Example plugin illustrating how to extend backend UI with own Vue.js components. +- [Context and Site](./examples-context-site) - How to distinguish between one journal and the admin area in your plugin. +- [Get Data](./examples-get-data) - How to get some data to display in your plugin. +- [Get Data from the Template Manager](./examples-get-data-template) - How to read data that has been assigned to a template when you hook into the template. +- [Database Tables](https://github.com/pkp/staticPages) - How to add a database table that is managed by the plugin. +- [Add Custom Fields](./examples-custom-field) - How to modify an existing settings form to add a new setting. +- [Add Custom Page](./examples-custom-page) - How to create a custom page with it's own URL and load it in the backend. +- [Events and Listeners](./example-events) - How to use listeners to subscribe to events in the core application. +- [Add Editorial Decision](./examples-decision) - How to add, edit or remove an editorial decision to customize the workflow. +- [Mailgun](https://github.com/Vitaliy-1/mailgun/) - An example plugin to send outgoing email through the Mailgun service. +- [Extend a Map](./examples-extend-map) - How to extend a map, for example to modify the data returned in a REST API response. diff --git a/dev/plugin-guide/3.4/en/getting-started.md b/dev/plugin-guide/3.4/en/getting-started.md new file mode 100644 index 000000000000..a9255d4b5079 --- /dev/null +++ b/dev/plugin-guide/3.4/en/getting-started.md @@ -0,0 +1,98 @@ +--- +title: Getting Started - Plugin Guide for OJS, OMP and OPS +description: A step-by-step tutorial to create your first plugin for Open Journal System (OJS), Open Monograph Press (OMP), or Open Preprint Systems (OPS). +book: dev-plugin-guide +version: 3.4 +--- + +# Getting Started + +> View the [example plugin](https://github.com/pkp/tutorialExample) that will be built in this tutorial. +{:.notice} + +This step-by-step tutorial will describe how to create a generic plugin called the "Tutorial Example Plugin". This plugin will be created for OJS, but the same steps can be followed to create a plugin for OMP or OPS. + +Create a directory for the plugin at `plugins/generic/tutorialExample` and create the following files in that directory. + +``` +ojs +│ +├─┬ plugins +│ │ +│ └─┬ generic +│ │ +│ └─┬ tutorialExample +│ ├── TutorialExamplePlugin.php +│ └── version.xml +``` + +The directory name must be letters and numbers. No spaces, `-`, or `_` characters are allowed. + +## TutorialExamplePlugin.php + +Every plugin must have a class which registers and runs the plugin. The file and class name must match the directory name, capitalize the first letter, and add the phrase `Plugin` at the end. So the directory `tutorialExample` would require a class named `TutorialExamplePlugin` in a file named `TutorialExamplePlugin.php`. + +```php +getEnabled()) { + // Do something when the plugin is enabled + } + + return $success; + } + + /** + * Provide a name for this plugin + * + * The name will appear in the Plugin Gallery where editors can + * install, enable and disable plugins. + */ + public function getDisplayName() + { + return 'Tutorial Example'; + } + + /** + * Provide a description for this plugin + * + * The description will appear in the Plugin Gallery where editors can + * install, enable and disable plugins. + */ + public function getDescription() + { + return 'This plugin is an example created for a tutorial on how to create a plugin.'; + } +} +``` + +## version.xml + +The `version.xml` provides information required to load the plugin. The `` must match the directory name. The `` must be the plugin's [category](./categories). + +```xml + + + + tutorialExample + plugins.generic + 1.0.0.0 + 2023-05-15 + +``` + +Go to Settings > Website > Plugins and try to enable and disable your plugin. If there is an error when enabling it, check your plugin against the [working example](https://github.com/pkp/tutorialExample). + +--- + +Learn how to choose the right [plugin category](./categories) for your plugin. diff --git a/dev/plugin-guide/3.4/en/index.md b/dev/plugin-guide/3.4/en/index.md new file mode 100644 index 000000000000..d034bfe6c6ac --- /dev/null +++ b/dev/plugin-guide/3.4/en/index.md @@ -0,0 +1,25 @@ +--- +title: Plugin Guide for OJS, OMP and OPS +description: How to create a plugin for Open Journal Systems, Open Monograph Press or Open Preprint Systems, to customize almost anything about the application to suit your needs. +book: dev-plugin-guide +version: 3.4 +--- + +# Introduction + +> If you want to make a new design for your journal, press or preprint server, read the [Theming Guide](/pkp-theming-guide/en). +{:.notice} + +This document describes how to create your own plugins for [OJS](https://pkp.sfu.ca/software/ojs), [OMP](https://pkp.sfu.ca/software/omp), and [OPS](https://pkp.sfu.ca/software/ops). It is written for software developers who want to extend the publishing platform to suit their needs. + +## What are plugins? + +Plugins add features by hooking in and modifying how the application responds to requests. Plugins can add fields to forms, change templates, save new information, modify authentication procedures, create new pages or change existing screens in the application. + +Plugins keep your customizations isolated from the main application code. When you update the application, your plugin's code will not be modified. This makes it easier to maintain customizations over time. + +We **strongly recommend** you use plugins to customize the software and never modify the main application code. + +--- + +Learn how to [start building your plugin](getting-started). diff --git a/dev/plugin-guide/3.4/en/release.md b/dev/plugin-guide/3.4/en/release.md new file mode 100644 index 000000000000..1670a09a5042 --- /dev/null +++ b/dev/plugin-guide/3.4/en/release.md @@ -0,0 +1,122 @@ +--- +title: Release a Plugin - Plugin Guide for OJS, OMP and OPS +description: How to package and release a plugin for OJS, OMP or OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Release a Plugin + +Plugins that you write can be made available through the Plugin Gallery in the application. We encourage our community to release their plugins under a GPL-compatible license so that they can be used for everyone's benefit. + +By releasing a plugin, you will get community support to identify bugs and translate the plugin. In some cases you may receive code contributions. + +Plugin releases are one way that the Public Knowledge Project recognizes the contributions of community partners. We are more likely to take the time to assist you in maintaining your plugins if they are used widely in our community. + +## Make Your Plugin Public + +Each release of your plugin must be made available for download publicly. We prefer that your code is available from a public code repository, such as [GitHub](https://github.com/) or [GitLab](https://about.gitlab.com/). + +The plugin must be made available under a GPL-compatible license so that our community can retain ownership over their publishing software. This licensing must be explicit in the code, usually by including a `LICENSE` file in the root directory of the plugin. + +See an [example](https://github.com/pkp/pluginTemplate/blob/main/LICENSE) of a license file. + +## Write Tests for Your plugin + +Plugins can take advantage of the [testing tools](/dev/testing/en) to run their plugin against different PHP versions and databases. Plugins with tests are more likely to be accepted in the plugin gallery and make it easier for you to test compatibility with each new release of OJS or OMP. + +Learn how to [write tests for your plugin](/dev/testing/en/plugins-themes). + +## Build and Package Your Plugin + +> Any non-essential files provided by your dependency manager (eg - composer, npm) should not be included with the package. These often include demos and examples that can be security risks when uploaded to the plugins directory. +{:.warning} + +Your release package should be a `.tar.gz` file that contains a single directory with all of the files necessary to run the plugin. The directory name should match the `product` name in the release XML. + +We provide a [CLI tool](https://github.com/pkp/pkp-plugin-cli/) that can help you build and package your plugin. Install it with the following command. + +``` +npm install -g pkp-plugin-cli +``` + +Use the following to build a release package and upload it as a release to your repository on GitHub, replacing the plugin name and version number with your own. + +``` +pkp-plugin release pluginName --newversion 1.0.0.0 +``` + +## Get the Plugin into the Plugin Gallery + +When you have prepared your release package and made it publicly available, open a pull request on our [plugin gallery repository](https://github.com/pkp/plugin-gallery/) that adds your plugin to the XML file. + +Your plugin's XML must provide a title, description, contact details, and information on each release package. + +```xml + + + Tutorial Example + https://github.com/pkp/tutorialExample + + + This plugin is an example created for a tutorial on how to create a plugin. + + + This plugin is an example created for a tutorial on how to create a plugin. It is intended for learning purposes and should not be used on a live journal website.

You can learn more about how to create a plugin at the plugin guide.

]]>
+ + + + Alec Smecher + Public Knowledge Project + pkp.contact@gmail.com + + + + + https://github.com/pkp/tutorialExample/releases/download/1.1.0.0/tutorialexample-1.1.0.0.tar.gz + + + + 3.1.2.0 + + + + + + + Update to be compatible with OJS 3.1.2. + + + + + https://github.com/pkp/tutorialExample/releases/download/1.0.0.0/tutorialexample-1.0.0.0.tar.gz + + 3.1.1.4 + + + 3.1.1.3 + + + Initial release. + +
+``` + +When you have opened the pull request, tests will run against your XML snippet and we will be able to merge your plugin into the list. + +In addition, each plugin must pass a code review. Your plugin will be given a `reviewed` or `partner` certification. We may not include your plugin in the gallery if it does not pass review. + +## Update Releases + +> Once your plugin has been added to the Plugin Gallery, you can not remove or modify the release package. If you modify the release package, the md5sum will change and the plugin will no longer be downloaded from the Plugin Gallery. +{:.warning} + +Your plugin will only appear in the plugin gallery for software versions with the appropriate `` statements. When a new version of PKP software is released, please test your plugin. + +If it is compatible, open a pull request with the additional `` tags in the `` statement. If changes are required to make it compatible, release a new version of your plugin and follow the instructions above to build a new release and add it to the plugin gallery. + +--- + +When you're ready, explore our [plugin examples](./examples) to learn more about what you can do with plugins. diff --git a/dev/plugin-guide/3.4/en/settings.md b/dev/plugin-guide/3.4/en/settings.md new file mode 100644 index 000000000000..9d4f1ac8a27a --- /dev/null +++ b/dev/plugin-guide/3.4/en/settings.md @@ -0,0 +1,21 @@ +--- +title: Plugin Settings - Plugin Guide for OJS, OMP and OPS +description: How to add a settings form for a plugin for OJS, OMP or OPS. +book: dev-plugin-guide +version: 3.4 +--- + +# Plugin Settings + +> This section describes how to create a special settings form for a plugin. You can also add fields to existing forms. See the [custom field example](./examples-custom-field). +{:.tip} + +Plugins can add a settings form so that an editor or admin can configure the plugin. Settings are accessed through the plugins list in the Website Settings area. + +![screenshot showing the settings action for the Citation Style Language plugin](../plugin-settings-action.png) + +View the [Plugin Template](https://github.com/pkp/pluginTemplate) for an example of a plugin with a settings form. + +--- + +When you're ready, learn how to [release your plugin](./release) to the public. diff --git a/dev/plugin-guide/3.4/en/templates.md b/dev/plugin-guide/3.4/en/templates.md new file mode 100644 index 000000000000..6f8365612a43 --- /dev/null +++ b/dev/plugin-guide/3.4/en/templates.md @@ -0,0 +1,136 @@ +--- +title: Templates - Plugin Guide for OJS, OMP and OPS +description: How to use and override templates in your OJS, OMP or OPS plugin. +book: dev-plugin-guide +version: 3.4 +--- + +# Templates + +Plugins have access to the same template system as the main application. Use this whenever you need to display or render HTML code in your plugin. + +All plugin templates are located in a `templates` subdirectory. + +``` +ojs +│ +├─┬ plugins +│ │ +│ └─┬ generic +│ │ +│ └─┬ tutorialExample +│ │ +│ ├─┬ templates +│ │ ├── example.tpl +│ │ └── settings.tpl +│ ├── index.php +│ └── TutorialExamplePlugin.php +│ └── version.xml +``` + +The `getTemplateResource()` method is available to every plugin. Use it to load a template in the plugin's template directory. + +```php +use APP\template\TemplateManager; + +$templateMgr = TemplateManager::getManager($request); +$templateMgr->display($this->getTemplateResource('example.tpl')); +``` + +Templates can be nested in subdirectories. + +``` +ojs +│ +├─┬ plugins +│ │ +│ └─┬ generic +│ │ +│ └─┬ tutorialExample +│ │ +│ ├─┬ templates +│ │ ├── example.tpl +│ │ └─┬ settings +│ │ └── index.tpl +│ ├── index.php +│ ├── TutorialExamplePlugin.php +│ └── version.xml +``` +```php +use APP\template\TemplateManager; + +$templateMgr = TemplateManager::getManager($request); +$templateMgr->display($this->getTemplateResource('settings/index.tpl')); +``` + +## Override templates + +> Learn more about [template overrides](/pkp-theming-guide/en/html-smarty). +{:.notice} + +By default, a template file in a theme plugin that matches the path of a template file in the application will override it. You can grant this ability to any plugin. + +Add a hook during registration to allow a plugin's templates to override templates in the application. + +```php +getEnabled()) { + Hook::add('TemplateResource::getFilename', [$this, '_overridePluginTemplates']); + } + + return $success; + } +``` + +Any template that matches the path and filename of an application's template will override it. In the example below, the plugin overrides the application's `common/footer.tpl` template. + +``` +ojs +│ +├─┬ plugins +│ └─┬ generic +│ └─┬ templateOverrideExample +│ ├─┬ templates +│ │ └─┬ common +│ │ └── footer.tpl +│ └── ... +├─┬ templates +│ └─┬ common +│ └── footer.tpl +``` + +Any plugin that can override application templates can also override the templates of another plugin. In the example below, the plugin overrides the block template of the `makeSubmission` block plugin. + +``` +ojs +│ +├─┬ plugins +│ ├─┬ blocks +│ │ └─┬ makeSubmission +│ │ ├─┬ templates +│ │ │ └── block.tpl +│ │ └── .. +│ └─┬ generic +│ └─┬ templateOverrideExample +│ ├─┬ templates +│ │ └─┬ plugins +│ │ └─┬ blocks +│ │ └─┬ makeSubmission +│ │ └─┬ templates +│ │ └── block.tpl +│ └── .. +``` + +--- + +Learn how to [add settings](./settings) to your plugin. \ No newline at end of file diff --git a/dev/plugin-guide/3.4/en/translation.md b/dev/plugin-guide/3.4/en/translation.md new file mode 100644 index 000000000000..e9b05aac83e2 --- /dev/null +++ b/dev/plugin-guide/3.4/en/translation.md @@ -0,0 +1,73 @@ +--- +title: Translation - Plugin Guide for OJS, OMP and OPS +description: How to support multiple languages in your OJS, OMP or OPS plugin. +book: dev-plugin-guide +version: 3.4 +--- + +# Translation + +Plugins should support more than one language. This allows them to work if the application is run in more than one language, or when the plugin is published for others to use. + +A plugin should include locale files for every language that it supports.The example below shows where the English and French (Canada) locale files are stored. + +``` +ojs +│ +├─┬ plugins +│ │ +│ └─┬ generic +│ │ +│ └─┬ tutorialExample +│ │ +│ ├─┬ locale +│ │ ├─┬ en +│ │ │ └── locale.po +│ │ └─┬ fr_CA +│ │ └── locale.po +│ └── TutorialExamplePlugin.php +│ └── version.xml +``` + +The locale file defines the language and any messages that should be translated. + +``` +msgid "plugins.generic.tutorialExample.name" +msgstr "Tutorial Example" + +msgid "plugins.generic.tutorialExample.description" +msgstr "This plugin is an example created for a tutorial on how to create a plugin." +``` + +Call the `__()` function to use a message in your plugin. + +```php +namespace APP\plugins\generic\tutorialExample; + +use PKP\plugins\GenericPlugin; + +class TutorialExamplePlugin extends GenericPlugin +{ + public function getDisplayName() + { + return __('plugins.generic.tutorialExample.name'); + } + + public function getDescription() + { + return __('plugins.generic.tutorialExample.description'); + } +} +``` + +In templates, call the `{translate ...}` function to use localized messages. + +```php +

{translate key="plugins.generic.tutorialExample.name"}

+``` + +Any plugin that is added to the Plugin Gallery must support translation, even if it only includes a locale file for one language. Our active community of translators will often provide a translation after you have released the plugin. + +--- + +Learn how to [use and override templates](./templates) in your plugin. diff --git a/dev/plugin-guide/3.4/index.md b/dev/plugin-guide/3.4/index.md new file mode 100644 index 000000000000..6b69f3fdbce6 --- /dev/null +++ b/dev/plugin-guide/3.4/index.md @@ -0,0 +1,9 @@ +--- +isBookIndex: true +title: Plugin Guide - Language Selection +--- + +# Plugin Guide + +- [English](en/) +- [Português](pt/) (Tradução incompleta) diff --git a/dev/plugin-guide/pt/SUMMARY.md b/dev/plugin-guide/3.4/pt/SUMMARY.md similarity index 100% rename from dev/plugin-guide/pt/SUMMARY.md rename to dev/plugin-guide/3.4/pt/SUMMARY.md diff --git a/dev/plugin-guide/pt/examples-context-site.md b/dev/plugin-guide/3.4/pt/examples-context-site.md similarity index 100% rename from dev/plugin-guide/pt/examples-context-site.md rename to dev/plugin-guide/3.4/pt/examples-context-site.md diff --git a/dev/plugin-guide/pt/examples-custom-field.md b/dev/plugin-guide/3.4/pt/examples-custom-field.md similarity index 100% rename from dev/plugin-guide/pt/examples-custom-field.md rename to dev/plugin-guide/3.4/pt/examples-custom-field.md diff --git a/dev/plugin-guide/pt/examples-custom-page.md b/dev/plugin-guide/3.4/pt/examples-custom-page.md similarity index 100% rename from dev/plugin-guide/pt/examples-custom-page.md rename to dev/plugin-guide/3.4/pt/examples-custom-page.md diff --git a/dev/plugin-guide/pt/examples-get-data-template.md b/dev/plugin-guide/3.4/pt/examples-get-data-template.md similarity index 100% rename from dev/plugin-guide/pt/examples-get-data-template.md rename to dev/plugin-guide/3.4/pt/examples-get-data-template.md diff --git a/dev/plugin-guide/pt/examples-get-data.md b/dev/plugin-guide/3.4/pt/examples-get-data.md similarity index 100% rename from dev/plugin-guide/pt/examples-get-data.md rename to dev/plugin-guide/3.4/pt/examples-get-data.md diff --git a/dev/plugin-guide/pt/examples-styles-scripts.md b/dev/plugin-guide/3.4/pt/examples-styles-scripts.md similarity index 100% rename from dev/plugin-guide/pt/examples-styles-scripts.md rename to dev/plugin-guide/3.4/pt/examples-styles-scripts.md diff --git a/dev/plugin-guide/pt/examples.md b/dev/plugin-guide/3.4/pt/examples.md similarity index 100% rename from dev/plugin-guide/pt/examples.md rename to dev/plugin-guide/3.4/pt/examples.md diff --git a/dev/plugin-guide/pt/index.md b/dev/plugin-guide/3.4/pt/index.md similarity index 100% rename from dev/plugin-guide/pt/index.md rename to dev/plugin-guide/3.4/pt/index.md diff --git a/dev/plugin-guide/pt/release.md b/dev/plugin-guide/3.4/pt/release.md similarity index 100% rename from dev/plugin-guide/pt/release.md rename to dev/plugin-guide/3.4/pt/release.md diff --git a/dev/plugin-guide/pt/settings.md b/dev/plugin-guide/3.4/pt/settings.md similarity index 100% rename from dev/plugin-guide/pt/settings.md rename to dev/plugin-guide/3.4/pt/settings.md diff --git a/dev/plugin-guide/en/SUMMARY.md b/dev/plugin-guide/en/SUMMARY.md index 7cf67fdeb85f..f500bb579f1b 100644 --- a/dev/plugin-guide/en/SUMMARY.md +++ b/dev/plugin-guide/en/SUMMARY.md @@ -15,6 +15,7 @@ * [Release a Plugin](./release) * [Examples](./examples) * [Plugin Template](https://github.com/pkp/pluginTemplate){:target="_blank"} + * [Extending Backend UI with Vue.js](./examples-backend-ui) * [Add Styles and Scripts](./examples-styles-scripts) * [Context and Site](./examples-context-site) * [Get Data](./examples-get-data) diff --git a/dev/plugin-guide/en/categories.md b/dev/plugin-guide/en/categories.md index cddec1e2053b..40ae749cabbc 100644 --- a/dev/plugin-guide/en/categories.md +++ b/dev/plugin-guide/en/categories.md @@ -1,7 +1,7 @@ --- title: Plugin Categories - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Plugin Categories diff --git a/dev/plugin-guide/en/example-decision.md b/dev/plugin-guide/en/example-decision.md index c6fcdf970d28..31bdc3adc30d 100644 --- a/dev/plugin-guide/en/example-decision.md +++ b/dev/plugin-guide/en/example-decision.md @@ -2,7 +2,7 @@ title: Example - Add an Editorial Decision - Plugin Guide for OJS, OMP and OPS description: How to add a custom editorial decision in a plugin for OJS, OMP or OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Add an Editorial Decision diff --git a/dev/plugin-guide/en/example-events.md b/dev/plugin-guide/en/example-events.md index d4bd27cc0e91..ab54b0a63b2d 100644 --- a/dev/plugin-guide/en/example-events.md +++ b/dev/plugin-guide/en/example-events.md @@ -2,7 +2,7 @@ title: Example - Events and Listeners - Plugin Guide for OJS, OMP and OPS description: How to use events and listeners in a plugin for OJS, OMP or OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Events and Listeners diff --git a/dev/plugin-guide/en/examples-backend-ui.md b/dev/plugin-guide/en/examples-backend-ui.md new file mode 100644 index 000000000000..ce1e27268e5a --- /dev/null +++ b/dev/plugin-guide/en/examples-backend-ui.md @@ -0,0 +1,22 @@ +--- +title: Example - Extending Backend UI with Vue.js - Plugin Guide for OJS, OMP and OPS +description: How to extend the editorial backend UI using Vue.js components in OJS, OMP and OPS plugins. +book: dev-plugin-guide +version: 3.5 +--- + +# Extending Backend UI with Vue.js + +As of version 3.5, the recommended approach to extend the editorial backend UI is to create Vue.js components and inject them via available hooks. This provides a flexible, modern way to customize the interface with full access to the ui-library components, composables, and styling system. + +## Resources + +- **[Storybook Plugin Guide](https://stable-3_5_0--6555d3db80418bb1681b8b17.chromatic.com/?path=/docs/guide-plugins--docs)** - Complete guide covering build setup, creating components, using ui-library components and composables, registering components, working with Pinia stores, translations, and styling. + +- **[Backend UI Example Plugin](https://github.com/jardakotesovec/backend-ui-example-plugin)** - Working examples demonstrating how to extend FileManager, Dashboard, Workflow, and other backend pages with custom Vue.js components. + +The Storybook guide also documents available JavaScript hooks for injecting components into specific pages and managers (Dashboard, Workflow, FileManager, ReviewerManager, ParticipantManager, GalleyManager). + +--- + +View more [examples](./examples). diff --git a/dev/plugin-guide/en/examples-context-site.md b/dev/plugin-guide/en/examples-context-site.md index de1544adb3c5..60d7eb4ef3f8 100644 --- a/dev/plugin-guide/en/examples-context-site.md +++ b/dev/plugin-guide/en/examples-context-site.md @@ -1,7 +1,7 @@ --- title: Example - Managing Context and Site - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Managing Context and Site diff --git a/dev/plugin-guide/en/examples-custom-field.md b/dev/plugin-guide/en/examples-custom-field.md index 6f9084cb114b..a60b20617054 100644 --- a/dev/plugin-guide/en/examples-custom-field.md +++ b/dev/plugin-guide/en/examples-custom-field.md @@ -2,7 +2,7 @@ title: Example - Add Custom Fields - Plugin Guide for OJS, OMP and OPS description: How to add data and form fields to extend the data model in OJS, OMP and OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Add Custom Fields diff --git a/dev/plugin-guide/en/examples-custom-page.md b/dev/plugin-guide/en/examples-custom-page.md index 6f7f23c4b29f..dba0563b3607 100644 --- a/dev/plugin-guide/en/examples-custom-page.md +++ b/dev/plugin-guide/en/examples-custom-page.md @@ -1,7 +1,7 @@ --- title: Example - Add Custom Page - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Add Custom Page @@ -9,6 +9,9 @@ version: 3.4 > You should understand the [Request Lifecycle](/dev/documentation/en/architecture-request) and be familiar with the application [architecture](/dev/documentation/en/architecture), especially [Handlers](/dev/documentation/en/architecture-handlers), before proceeding. {:.notice} +> **New in 3.5:** For adding custom UI to the editorial backend, consider using Vue.js components with the new JS hooks approach. See [Extending Backend UI with Vue.js](./examples-backend-ui) for the recommended method. +{:.tip} + You may want your plugin to add a new page to the application. This may be a separate settings page or editorial dashboard on the backend, or a new public page on the reader-facing website. A generic plugin can do this by hooking into the request lifecycle and loading its own [PageHandler](/dev/documentation/en/architecture-handlers). Consider a request to the following page. diff --git a/dev/plugin-guide/en/examples-extend-map.md b/dev/plugin-guide/en/examples-extend-map.md index 127912d79a49..07019da0ea1a 100644 --- a/dev/plugin-guide/en/examples-extend-map.md +++ b/dev/plugin-guide/en/examples-extend-map.md @@ -2,7 +2,7 @@ title: Example - Extend a Map - Plugin Guide for OJS, OMP and OPS description: Example code showing how to extend a map in a plugin for OJS, OMP and OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Extend a Map diff --git a/dev/plugin-guide/en/examples-get-data-template.md b/dev/plugin-guide/en/examples-get-data-template.md index bdaa2961986b..6c56335bfd6a 100644 --- a/dev/plugin-guide/en/examples-get-data-template.md +++ b/dev/plugin-guide/en/examples-get-data-template.md @@ -1,7 +1,7 @@ --- title: Example - Get Data from the Template - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Get Data from the Template diff --git a/dev/plugin-guide/en/examples-get-data.md b/dev/plugin-guide/en/examples-get-data.md index 6fd42474a6ea..38a668e0aaae 100644 --- a/dev/plugin-guide/en/examples-get-data.md +++ b/dev/plugin-guide/en/examples-get-data.md @@ -2,7 +2,7 @@ title: Example - Get Data - Plugin Guide for OJS, OMP and OPS description: Example code showing how to get some data in a plugin for OJS, OMP and OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Get Data diff --git a/dev/plugin-guide/en/examples-styles-scripts.md b/dev/plugin-guide/en/examples-styles-scripts.md index b57b2cd8ac9d..08d3f4421f74 100644 --- a/dev/plugin-guide/en/examples-styles-scripts.md +++ b/dev/plugin-guide/en/examples-styles-scripts.md @@ -1,7 +1,7 @@ --- title: Example - Add Styles and Scripts - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Add Styles and Scripts diff --git a/dev/plugin-guide/en/examples.md b/dev/plugin-guide/en/examples.md index cc2208c4aa7d..a29a75116353 100644 --- a/dev/plugin-guide/en/examples.md +++ b/dev/plugin-guide/en/examples.md @@ -1,7 +1,7 @@ --- title: Examples - Plugin Guide for OJS, OMP and OPS book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Examples @@ -9,8 +9,8 @@ version: 3.4 Plugins can be put to a wide variety of uses. The examples below show you how to do things that many plugins may need to do. - [Plugin Template](https://github.com/pkp/pluginTemplate){:target="_blank"} - A working starter plugin that you can use as a base for your own plugins. +- [Extending Backend UI with Vue.js](./examples-backend-ui) - How to extend the editorial backend with custom Vue.js components using the new recommended approach in 3.5+. - [Add Styles and Scripts](./examples-styles-scripts) - How to add a CSS stylesheet or JavaScript file to the backend. -- [Create Vue.js components](https://github.com/jardakotesovec/backend-ui-example-plugin/tree/main) - Example plugin illustrating how to extend backend UI with own Vue.js components. - [Context and Site](./examples-context-site) - How to distinguish between one journal and the admin area in your plugin. - [Get Data](./examples-get-data) - How to get some data to display in your plugin. - [Get Data from the Template Manager](./examples-get-data-template) - How to read data that has been assigned to a template when you hook into the template. diff --git a/dev/plugin-guide/en/getting-started.md b/dev/plugin-guide/en/getting-started.md index a9255d4b5079..3fe9d414690c 100644 --- a/dev/plugin-guide/en/getting-started.md +++ b/dev/plugin-guide/en/getting-started.md @@ -2,7 +2,7 @@ title: Getting Started - Plugin Guide for OJS, OMP and OPS description: A step-by-step tutorial to create your first plugin for Open Journal System (OJS), Open Monograph Press (OMP), or Open Preprint Systems (OPS). book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Getting Started @@ -10,6 +10,9 @@ version: 3.4 > View the [example plugin](https://github.com/pkp/tutorialExample) that will be built in this tutorial. {:.notice} +> **New in 3.5:** If you want to extend the editorial backend UI with custom components, see [Extending Backend UI with Vue.js](./examples-backend-ui) for the recommended approach. +{:.tip} + This step-by-step tutorial will describe how to create a generic plugin called the "Tutorial Example Plugin". This plugin will be created for OJS, but the same steps can be followed to create a plugin for OMP or OPS. Create a directory for the plugin at `plugins/generic/tutorialExample` and create the following files in that directory. diff --git a/dev/plugin-guide/en/index.md b/dev/plugin-guide/en/index.md index d034bfe6c6ac..fc8dbfbcc111 100644 --- a/dev/plugin-guide/en/index.md +++ b/dev/plugin-guide/en/index.md @@ -2,7 +2,7 @@ title: Plugin Guide for OJS, OMP and OPS description: How to create a plugin for Open Journal Systems, Open Monograph Press or Open Preprint Systems, to customize almost anything about the application to suit your needs. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Introduction diff --git a/dev/plugin-guide/en/release.md b/dev/plugin-guide/en/release.md index 1670a09a5042..0417cdb842d8 100644 --- a/dev/plugin-guide/en/release.md +++ b/dev/plugin-guide/en/release.md @@ -2,7 +2,7 @@ title: Release a Plugin - Plugin Guide for OJS, OMP and OPS description: How to package and release a plugin for OJS, OMP or OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Release a Plugin diff --git a/dev/plugin-guide/en/settings.md b/dev/plugin-guide/en/settings.md index 9d4f1ac8a27a..eeab9354a79d 100644 --- a/dev/plugin-guide/en/settings.md +++ b/dev/plugin-guide/en/settings.md @@ -2,7 +2,7 @@ title: Plugin Settings - Plugin Guide for OJS, OMP and OPS description: How to add a settings form for a plugin for OJS, OMP or OPS. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Plugin Settings diff --git a/dev/plugin-guide/en/templates.md b/dev/plugin-guide/en/templates.md index 6f8365612a43..5d977936f147 100644 --- a/dev/plugin-guide/en/templates.md +++ b/dev/plugin-guide/en/templates.md @@ -2,7 +2,7 @@ title: Templates - Plugin Guide for OJS, OMP and OPS description: How to use and override templates in your OJS, OMP or OPS plugin. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Templates diff --git a/dev/plugin-guide/en/translation.md b/dev/plugin-guide/en/translation.md index e9b05aac83e2..5b210c2afc65 100644 --- a/dev/plugin-guide/en/translation.md +++ b/dev/plugin-guide/en/translation.md @@ -2,7 +2,7 @@ title: Translation - Plugin Guide for OJS, OMP and OPS description: How to support multiple languages in your OJS, OMP or OPS plugin. book: dev-plugin-guide -version: 3.4 +version: 3.5 --- # Translation diff --git a/dev/plugin-guide/index.md b/dev/plugin-guide/index.md index c54cc26179a5..f195b4316332 100644 --- a/dev/plugin-guide/index.md +++ b/dev/plugin-guide/index.md @@ -5,5 +5,4 @@ title: Plugin Guide - Language Selection # Plugin Guide -* [English](en/) -* [Português](pt/) (Tradução incompleta) +- [English](en/) diff --git a/dev/ui-library/index.md b/dev/ui-library/index.md index 73e54bdd1206..1eed874dc68f 100644 --- a/dev/ui-library/index.md +++ b/dev/ui-library/index.md @@ -10,7 +10,8 @@ The UI libraries below contain UI components for the Public Knowledge Project's | Version | Description | | --- | --- | -| [dev](./dev/) | A recent snapshot of the `main` branch of the [UI Library](https://github.com/pkp/ui-library/). | +| [dev](https://main--6555d3db80418bb1681b8b17.chromatic.com/?path=/docs/guide-page-architecture--docs){:target="_blank"} | A recent snapshot of the `main` branch of the [UI Library](https://github.com/pkp/ui-library/). | +| [3.5](https://stable-3_5_0--6555d3db80418bb1681b8b17.chromatic.com){:target="_blank"} | The UI Library available in version 3.5 of each application. | | [3.4](./3.4/) | The UI Library available in version 3.4 of each application. | | [3.3](./3.3/) | The UI Library available in version 3.3 of each application. |