Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c803fcd
Removed dependency on ReactPHP
TheSyscall Mar 3, 2026
b04bf63
Install php-webdriver
TheSyscall Mar 4, 2026
529a1c7
Add types to printable html document
TheSyscall Mar 4, 2026
ec4097f
Use str_starts_with whereever possible
TheSyscall Mar 4, 2026
a890086
Add method to create parameters for printing with the webdriver
TheSyscall Mar 4, 2026
1607807
Add webdrivers for chrome and gecko (firefox)
TheSyscall Mar 4, 2026
42fb217
Use destructor instead of close method
TheSyscall Mar 4, 2026
380b66a
Rewrite Pdfexport hook
TheSyscall Mar 4, 2026
7a28a6a
Convert the HeadlessChrome class into a PdfPrintDriver
TheSyscall Mar 4, 2026
a634193
Move and rename HeadlessChrome to HeadlessChromeDriver
TheSyscall Mar 4, 2026
1e255ef
Rename $targetId -> $frameId
TheSyscall Mar 5, 2026
835e92b
Always waitForPageLoad after setting content
TheSyscall Mar 5, 2026
059642c
Implement basic driver selection
TheSyscall Mar 5, 2026
1168851
Fix form connection validation
TheSyscall Mar 5, 2026
c66980d
Use chrome Page.printToPDF
TheSyscall Mar 5, 2026
48d7829
Fix coverpage creation
TheSyscall Mar 5, 2026
4161126
FIXUP: remove comment
TheSyscall Mar 5, 2026
b863a7a
Use try catch
TheSyscall Mar 5, 2026
fb9f483
Format changes
TheSyscall Mar 5, 2026
df37ddb
Rename Driver to Backend
TheSyscall Mar 5, 2026
3457afc
Allow for filesystem based html transfer for HeadlesChromeBackend
TheSyscall Mar 5, 2026
3becc7f
Change ConfigController to CompatController
TheSyscall Mar 6, 2026
ea4ce28
Move BackendConfigForm away from ZendForms
TheSyscall Mar 6, 2026
751d564
Change element order to reflect precedence
TheSyscall Mar 6, 2026
f2fc458
Add headers and short descriptions
TheSyscall Mar 6, 2026
408175c
Require two underscores to divide section and key
TheSyscall Mar 6, 2026
fbdc9dc
Move initialization of the backend to the backend locator
TheSyscall Mar 6, 2026
299000b
Add license headers
TheSyscall Mar 6, 2026
0680aa4
Use outerHTML instead of innerHTML to preseve class names on the body…
TheSyscall Mar 9, 2026
5e61ca1
Remove unused methods
TheSyscall Mar 9, 2026
cafe3a7
Explicitly call close on the backend
TheSyscall Mar 9, 2026
bf2c076
Fixup: clear header & set transfer mode
TheSyscall Mar 10, 2026
f4d2fc3
Manually close filestorage
TheSyscall Mar 10, 2026
eb13c9b
Wait for chrome to close and force close it after a timeout
TheSyscall Mar 10, 2026
059f197
Use constants instead of magic numbers
TheSyscall Mar 10, 2026
93766d0
Relicense to GPL-3.0-only and add SPDX license headers
TheSyscall Mar 13, 2026
7df8e4b
Code style changes
TheSyscall Mar 13, 2026
0d7a86b
fixup! Code style changes
TheSyscall Mar 13, 2026
1b1e3e7
Use Callout instead of building something custom
TheSyscall Mar 13, 2026
ef93d16
Implement WebDriver Protocol with GuzzlePHP
TheSyscall Mar 18, 2026
e48d408
Use new WebDriver implementation
TheSyscall Mar 18, 2026
77ae387
Add support for layout plugins to ChromeWebdriver
TheSyscall Mar 19, 2026
40e04e1
Code style changes
TheSyscall Mar 19, 2026
55a5a44
Revert "Install php-webdriver"
TheSyscall Mar 19, 2026
aa7704c
Add missing license headers and remove unused files
TheSyscall Mar 20, 2026
c5ab073
Code review suggestions
TheSyscall Mar 20, 2026
92a0d2b
Use ConfigForm in IW2
TheSyscall Mar 23, 2026
085922b
Use 0/1 instead of n/y
TheSyscall Mar 23, 2026
eef4330
Move script blocks into separate JavaScript files
TheSyscall Mar 23, 2026
5854c67
Update config form to allow for an arbitrary number of backends
TheSyscall Mar 26, 2026
1dc3217
Document webdriver installation
TheSyscall Mar 26, 2026
322bbeb
Add docstrings
TheSyscall Apr 7, 2026
b08e5e2
Use ShellCommand abstraction
TheSyscall Apr 7, 2026
174faf2
Add a function to check if adding a cover page is supported
TheSyscall Apr 8, 2026
958467f
Fix a deprecation with unset member access
TheSyscall Apr 8, 2026
0a5dfa2
fixme! Docstrings for ShellCommand
TheSyscall Apr 8, 2026
907a1fd
Bring back htmlToPdf function
TheSyscall Apr 8, 2026
6861696
Store the last working backend instance
TheSyscall Apr 10, 2026
50f297c
Remove types to stay backward compatible
TheSyscall Apr 10, 2026
0cec004
Change `first` method to mimic the behaviour of the base class
TheSyscall Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 106 additions & 9 deletions application/controllers/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
namespace Icinga\Module\Pdfexport\Controllers;

use Icinga\Application\Config;
use Icinga\Module\Pdfexport\Forms\ChromeBinaryForm;
use Icinga\Web\Controller;
use Icinga\Application\Logger;
use Icinga\Module\Pdfexport\Forms\BackendConfigForm;
use Icinga\Web\Form\ConfigForm;
use Icinga\Web\Notification;
use ipl\Html\Attributes;
use ipl\Html\Form;
use ipl\Html\HtmlString;
use ipl\Html\Table;
use ipl\Web\Compat\CompatController;
use Icinga\Web\Widget\Tabs;
use ipl\Web\Widget\ButtonLink;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;

class ConfigController extends Controller
class ConfigController extends CompatController
{
public function init()
{
Expand All @@ -18,14 +29,100 @@ public function init()
parent::init();
}

public function chromeAction()
public function backendsAction(): void
{
$form = (new ChromeBinaryForm())
->setIniConfig(Config::module('pdfexport'));
$button = new ButtonLink(
$this->translate('Create a New Backend'),
'pdfexport/config/createbackend',
'plus',
['title' => $this->translate('Create a New Backend')],
);
$button->setBaseTarget('_next');
$this->addContent($button);

$form->handleRequest();
$table = new Table();
$table->setAttributes(Attributes::create([
'class' => 'table-row-selectable common-table',
'data-base-target' => '_next',
]));
$table->add(Table::tr([
Table::th($this->translate('Backend')),
Table::th($this->translate('Priority')),
]));

$this->view->tabs = $this->Module()->getConfigTabs()->activate('chrome');
$this->view->form = $form;
$config = Config::module('pdfexport');

$sections = [];
foreach ($config as $name => $data) {
$sections[] = [$name, $data, (int) $data->get('priority')];
}

usort($sections, function ($a, $b) {
return $a[2] <=> $b[2];
});

foreach ($sections as [$name, $data]) {
$table->add(Table::tr([
Table::td([
new Icon('print'),
new Link($name, 'pdfexport/config/backend?backend=' . $name),
]),
Table::td($data->get('priority')),
], [
'class' => 'clickable',
]));
}

$this->mergeTabs($this->Module()->getConfigTabs()->activate('backends'));
$this->addContent($table);
}

public function backendAction(): void
{
$name = $this->params->shiftRequired('backend');
$this->addTitleTab($this->translate(sprintf('Edit %s', $name)));

$form = new BackendConfigForm();
$form->setConfig(Config::module('pdfexport'));
$form->setSection($name);

$form->on(Form::ON_SUBMIT, function () use ($form) {
Notification::success($this->translate('Updated print backend'));
$this->redirectNow('__CLOSE__');
});

$form->on(ConfigForm::ON_DELETE, function () use ($form) {
Notification::success($this->translate('Print backend deleted'));
$this->redirectNow('__CLOSE__');
});

$form->handleRequest($this->getServerRequest());

$this->addContent(HtmlString::create($form->render()));
}

public function createbackendAction(): void
{
$this->addTitleTab($this->translate(sprintf('Create Print Backend')));

$form = new BackendConfigForm();
$form->setConfig(Config::module('pdfexport'));
$form->setIsCreateForm(true);

$form->on(Form::ON_SUBMIT, function () {
Notification::success($this->translate('Created new print backend'));
$this->redirectNow('__CLOSE__');
});

$form->handleRequest($this->getServerRequest());

$this->addContent($form);
}

protected function mergeTabs(Tabs $tabs): void
{
foreach ($tabs->getTabs() as $tab) {
$this->tabs->add($tab->getName(), $tab);
}
}
}
173 changes: 173 additions & 0 deletions application/forms/BackendConfigForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

// SPDX-FileCopyrightText: 2019 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\Pdfexport\Forms;

use Exception;
use Icinga\Module\Pdfexport\Backend\Chromedriver;
use Icinga\Module\Pdfexport\Backend\Geckodriver;
use Icinga\Module\Pdfexport\Backend\HeadlessChromeBackend;
use Icinga\Web\Form\ConfigForm;
use ipl\Validator\CallbackValidator;

class BackendConfigForm extends ConfigForm
{
public function assemble(): void
{
$this->addSectionNameElement();

$this->addElement('number', 'priority', [
'label' => $this->translate('Priority'),
'required' => true,
'placeholder' => 100,
'min' => 0,
'description' => $this->translate('The priority of the backend. A lower priority will be used first.'),
]);

$this->addElement('select', 'type', [
'label' => $this->translate('Type'),
'multiOptions' => [
'' => sprintf(' - %s - ', t('Please choose')),
'chrome_webdriver' => t('Chrome WebDriver'),
'firefox_webdriver' => t('Firefox WebDriver'),
'remote_chrome' => t('Headless Chrome (Remote)'),
'local_chrome' => t('Headless Chrome (Local)'),
],
'required' => true,
'class' => 'autosubmit',
]);

$type = $this->getPopulatedValue('type') ?? $this->getConfigValue('type');

switch ($type) {
case 'remote_chrome':
$this->addElement('text', 'host', [
'label' => $this->translate('Host'),
'description' => $this->translate('Host address of the server with the running web browser.'),
'required' => true,
'validators' => [
new CallbackValidator(function ($value, CallbackValidator $validator) {
$port = $this->getValue('port') ?: 9222;

try {
$chrome = HeadlessChromeBackend::createRemote($value, $port);
$version = $chrome->getVersion();
} catch (Exception $e) {
$validator->addMessage($e->getMessage());
return false;
}

if ($version < HeadlessChromeBackend::MIN_SUPPORTED_CHROME_VERSION) {
$validator->addMessage(t(
'Chrome/Chromium supporting headless mode required'
. ' which is provided since version %s. Version detected: %s',
));
return false;
}

return true;
}),
],
]);

$this->addElement('number', 'port', [
'label' => $this->translate('Port'),
'description' => $this->translate('Port of the chrome developer tools. (Default: 9222)'),
'placeholder' => 9222,
'min' => 1,
'max' => 65535,
]);

break;

case 'local_chrome':
$this->addElement('text', 'binary', [
'label' => $this->translate('Binary'),
'placeholder' => '/usr/bin/google-chrome',
'description' => $this->translate('Path to the binary of the web browser.'),
'validators' => [
new CallbackValidator(function ($value, CallbackValidator $validator) {
if (empty($value)) {
return true;
}

try {
$chrome = (HeadlessChromeBackend::createLocal($value));
$version = $chrome->getVersion();
} catch (Exception $e) {
$validator->addMessage($e->getMessage());
return false;
}

if ($version < HeadlessChromeBackend::MIN_SUPPORTED_CHROME_VERSION) {
$validator->addMessage(t(
'Chrome/Chromium supporting headless mode required'
. ' which is provided since version %s. Version detected: %s',
));
}

return true;
}),
],
]);

$this->addElement('checkbox', 'force_temp_storage', [
'label' => $this->translate('Use temp storage'),
'description' => $this->translate(
'Use temp storage to transfer the html to the local chrome instance.'
),
'checkedValue' => '1',
'uncheckedValue' => '0',
]);

break;

case 'firefox_webdriver':
case 'chrome_webdriver':
$this->addElement('text', 'host', [
'label' => $this->translate('Host'),
'description' => $this->translate('Host address of the webdriver server'),
'required' => true,
'validators' => [
new CallbackValidator(function ($value, CallbackValidator $validator) use ($type) {
$port = $this->getValue('port') ?: 4444;

try {
$url = "$value:$port";
$backend = match ($type) {
'chrome_webdriver' => new Chromedriver($url),
'firefox_webdriver' => new Geckodriver($url),
default => throw new Exception("Invalid webdriver type $type"),
};

if (! $backend->isSupported()) {
$validator->addMessage(
t('The webdriver server reports that it is unable to generate PDFs'),
);
return false;
}
} catch (Exception $e) {
$validator->addMessage($e->getMessage());
return false;
}
return true;
}),
],
]);

$this->addElement('number', 'port', [
'label' => $this->translate('Port'),
'description' => $this->translate('Port of the webdriver instance. (Default: 4444)'),
'placeholder' => 4444,
'min' => 1,
'max' => 65535,
]);

break;
}

$this->addButtonElements();
}
}
Loading
Loading