I have created a custom plugin in Craft CMS 4 that has to decode data and store it in it's corresponding entries. However, while developing I fired the method in my init function so on every pageload, it would be triggered which is not necessary. So I want to fire the method when a button in my twig template is clicked. I managed to puzzle my way through the fundamentals but for some reason won't it fire the actual function. Does anybody knows what I'doing wrong..? My code:
Edited Plugin.php
public function init(): void
{
parent::init();
// Render the template for my plugin.
Event::on(
View::class,
View::EVENT_REGISTER_SITE_TEMPLATE_ROOTS,
function(RegisterTemplateRootsEvent $event) {
$event->roots['_jsonify'] = __DIR__ . '/src/templates';
}
);
// Register the event that should be triggered on this url.
Event::on(
UrlManager::class,
UrlManager::EVENT_REGISTER_CP_URL_RULES,
function(RegisterUrlRulesEvent $event) {
$event->rules['_jsonify/import/test'] = '_jsonify/import/test';
}
);
Craft::$app->onInit(function() {
$this->getJsonFile();
$this->decodeJsonFile();
});
}
PluginController.php
<?php
namespace plugins\jsonify\controllers;
use Craft;
use craft\web\Controller;
use craft\elements\Entry;
use yii\web\Response;
class JsonifySettingsController extends Controller
{
// Test function for test purposes
public function test(): Response
{
dd('test');
return $this->asJson(['message' => true]);
}
private function actionHandleJsonRead(): Response
{
$jsonFile = $this->decodeJsonFile();
$section = Craft::$app->sections->getSectionByHandle('test');
foreach ($jsonFile as $key => $data) {
$existingEntry = Entry::find()
->sectionId($section->id)
->andWhere(['title' => $data['Trial_name']])
->one();
if ($existingEntry) {
Craft::$app->getSession()->setNotice('Entry already exists with the same unique identifier. Skipping.');
continue;
}
$entry = new Entry();
$entry->sectionId = $section->id;
$entry->title = $data['Trial_name'];
$entry->testId = $data['testID'];
$entry->entryName = $data['Name'];
$entry->contractorSampleAnalysis = $data['Contractor_sample_analysis'];
$entry->country = $data['Country'];
$entry->crops = $data['Crops'];
$entry->deltaYield = $data['Delta_yield'];
$entry->lat = $data['lat'];
$entry->location = $data['Location'];
$entry->locationXy = $data['location_XY'];
$entry->lon = $data['lon'];
$entry->mapExport = $data['Map_export'];
$entry->primaryCompany = $data['Primary_company'];
$entry->primaryContact = $data['Primary_contact'];
$entry->sizeHa = $data['Size_ha'];
$entry->specsTrial = $data['Specs_trial'];
$entry->entryStatus = $data['Status'];
$entry->summaryResults = $data['Summary_results'];
$entry->testType = $data['Test_type'];
$entry->variety = $data['Variety'];
$entry->year = $data['Year'];
$entry->entryId = $data['ID'];
$entry->internalSpecsTrial = $data['Internal_specs_trial'];
$entry->prio = $data['Prio'];
$entry->trialName = $data['Trial_name'];
$entry->xy = $data['XY'];
$entry->internalStatusTodo = $data['Internal_status_todo'];
$entry->samplingAnalysis = $data['Sampling_Analysis'];
$entry->statusObservations = $data['Status_Observations'];
$entry->subsidyProject = $data['Subsidy_project'];
$entry->xyRandomiser = $data['XY_randomiser'];
if (Craft::$app->elements->saveElement($entry)) {
Craft::$app->getSession()->setNotice('Entry saved.');
} else {
Craft::$app->getSession()->setError('Couldn’t save the entry: ' . implode(', ', $entry->getErrorSummary(true)));
continue;
}
}
$data = ['message' => 'JSON data read successfully'];
return $this->asJson($data);
}
}
Edited Twig template
{% extends "_layouts/cp.twig" %}
{% set title = "jsonify"|t('_jsonify') %}
{% set fullPageForm = true %}
{% block content %}
{% set folderId = 1 %}
{% set assets = craft.assets.folderId(folderId).kind('json').all() %}
{% if assets|length %}
<ul class="testing">
{% for asset in assets %}
<li style="width: auto; display: flex; justify-content: space-between; align-items: center">
{% if asset.extension == 'json' %}
{{ asset.filename }}
<form method="post">
{{ csrfInput() }}
{{ actionInput('jsonify/import/test') }}
<button
style="margin-left: 30px; background: #d3d3d3; padding: 3px 12px; border-radius: 4px"
class="json-button"
width="120px"
>
Read JSON
</button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No assets found.</p>
{% endif %}
{% endblock %}
Inside the button is the action to trigger the method.
At this moment the template is loaded inside the settings. I rather have my plugin in a screen which is accessible from crafts dashboard sidebar, but I have no clue how to make that happen..
Before we dive in… you might try the first-party Feed Me plugin, which can import and synchronize data from JSON files, without any coding required.
I think you've got all the pieces you need—and then some!
You don't need to configure a route to your controller—plugins automatically have their controllers exposed via action routes.
Solution: Remove
UrlManager::EVENT_REGISTER_SITE_URL_RULESevent listener.As you noted,
Craft::$app->onInit()is triggered on every request.Solution: Remove this block, and allow the plugin methods to be called from your dedicated controller!
Your controller is not autoloadable. PHP classes must be named the same as their file.
Solution: Rename the class from
JsonifySettingsControllertoImportController, and rename the file toImportController.php. This file must be in theyour-plugin-directory/controllers/directory—or, in acontrollers/directory next to your primary plugin class.Only public controller methods can be routed automatically. Currently, your action method is
private!Solution: Use
public function actionHandleJsonRead()for the method signature.Your settings template includes markup that conflicts with the built-in form element. Craft wraps the output from
getSettingsHtml()in a<form>that automatically POSTs values to itsplugins/save-plugin-settingsaction.Solution: Use a different template. Add
templates/import.twigto your plugin’s folder, and it will be automatically accessible at/admin/jsonify/import!Don't worry about Javascript and Ajax right now. Use the
actionInput()function inside a basic HTML form, like this:This will submit a normal HTTP request to Craft, which will route it to your controller.
Note: You are already loading the asset in the back-end during import (with
Asset::find()). There is no need to send its URL as part of the request, nor download the asset file from its URL. Instead, the browser will make a request to the back-end, which reads the file and performs the import.Setting a flash message has no effect for JSON requests—and repeatedly setting the same flash level multiple times ("notice," in this case) will only display the last message (the others are overwritten).
Solution: Track failures by setting a flag, and use the
asSuccess()method to send a response. The body of your action method might look like this:Note the different
returnstatement. This method automatically sets flash messages, or formats them appropriately for Ajax requests (if you want to switch back). Our message includes the number of entries that didn't import due to duplication.ImportController::actionIndex()is not currently used.Solution: Remove it!
This is not guaranteed to be a complete solution, but hopefully it helps clean up what you have so it's easier to tell what's going on!