ProcessWire - Module Snippets
Nützliche Snippets für das Schreiben von Modulen
Siehe auch
ProcessWire - Module schreiben https://github.com/Toutouwai Github von Robin Salis mit vielen Beispielen
Grundrezepte
Adminseite erstellen (Non Process Module)
Das benötigt man NICHT IN PROCESS MODULEN. Da funktioniert das auch ohne manuelles erstellen.
Here is a short example on how you could do it (of course there are a lot of different ways to archive this):
public function ___install() {
// Creates a new Page object
$page = new Page();
// use your own selector to choose the parent page
$parent = $this->pages->get("name=setup");
$page->parent = $parent;
// choose a template
$page->template = $this->templates->get('admin');
// name and title
$page->name = "your-page";
$page->title = "Your Page";
// .. and save the new page object as a real page
$page->save();
// success message
$this->message(sprintf($this->_("Installed to %s"), $page->path));
}
public function ___uninstall() {
$parent = $this->pages->get("name=setup");
// the name has to be the same as defined above
// you could use a constant variable for this
$page = $parent->child("name=your-page");
// only delete page if existing
if($page->id) {
$this->pages->delete($page);
$this->message(sprintf($this->_("Removed %s"), $page->path));
}
}
</syntaxhighlights>
== Diverses ==
=== ProcessWire Version testen ===
<syntaxhighlight lang="php">
public function ___install()
{
if (ProcessWire::versionMajor == 2 && ProcessWire::versionMinor < 1) {
throw new WireException("This module requires ProcessWire 2.1 or newer");
}
//...
Testen ob ein bestimmtes Modul installiert ist
if (!$this->modules->isInstalled('Tasker')) {
$this->message('Tasker module is missing. Install it before using Dataset module.');
return;
}
$this->tasker = $this->modules->get('Tasker');
Seiten erstellen
https://processwire.com/talk/topic/352-creating-pages-via-api/ https://processwire.com/blog/posts/processwire-2.6.21-upgrades-comments-more-on-pw-3.x/#more-updates-on-processwire-3.0 (in multiinstance modules)
<?php
include(./index.php) // bootstrap PW (only if pw is not ready)
$p = new Page(); // create new page object
$p->template = 'page'; // set template
$p->parent = wire('pages')->get('/about/'); // set the parent
$p->name = 'mynewpage_url'; // give it a name used in the url for the page otherwise it will be created from title
$p->title = 'My New Page'; // set page title (not neccessary but recommended)
// added by Ryan: save page in preparation for adding files (#1)
$p->save();
// populate fields
$p->image = 'path/to/image.jpg'; // populate a single image field (#2)
$p->images->add('path/to/image1.jpg'); // add multiple to images field
$p->save();
// testing
echo 'id: '.$p->id.'<br/>';
echo 'path: '.$p->path;
You may need to add a
$page->setOutputFormatting(false)
depending on the context and if PW gives you an error about needing to turn off output formatting.
You can override a page by using id:
$p = new Page();
$p->template = "basic-page";
$p->parent = 1;
$p->title = "Page Title";
$p->id = 4254;
$p->of(false);
$p->save();
With
adjustName=>true
and not using name PW will create a unique name. Just omitting name might work too.
Get Unique Pagename
/**
* Given a page name, check that it is unique and return it or a unique numbered variation of it
*
*/
protected function getUniquePageName($pageName) {
$n = 0;
do {
$testName = $pageName . "-" . (++$n);
$test = $this->parent->child("name=$testName, include=all");
if(!$test->id) break;
} while(1);
return $testName;
}
JavaScript und Styles injecten
https://processwire.com/talk/topic/18920-how-to-load-a-javascript-file-in-a-module/ https://processwire.com/talk/topic/4472-how-to-insert-scripts-and-stylesheets-from-your-module/
Admin themes all have lines like these in their template code to load the contents of the $config->scripts and $config->styles:
<?php foreach($config->styles as $file) echo "\n\t<link type='text/css' href='$file' rel='stylesheet' />"; ?>
<?php foreach($config->scripts as $file) echo "\n\t<script type='text/javascript' src='$file'></script>"; ?>
Another way if you would like to load arbitrary JavaScript file in your module is :
Load arbitrary JavaScript File in a module
Hinweis ready() funktioniert nur in autoload modulen.
protected $jsfiles = array(
'js/MyScript.js'
);
// Then in ready(), write :
foreach ($this->jsfiles as $jsfile) {
$this->config->scripts->add($this->config->urls->MyModule . $jsfile);
}
Beispiel aus dem Email Obfuscation Modul (Todo - bereinigen):
/**
* constants for js file loading method
*
*/
const loadManual = 0;
const loadScripts = 1;
const loadAuto = 2;
const loadInline = 3;
private $options = array(
'noscript' => 'Enable JavaScript to view protected content.',
'jsFile' => 'emo.min.js',
'jsLoad' => self::loadAuto,
'mode' => self::modeExclude,
'mailto' => false,
'debug' => false,
'useFixedKey' => false,
'fixedKey' => '',
'selectedTemplates' => array(),
'selectedPages' => array(),
);
/**
* set some defaults when module is ready
*
*/
public function ready()
{
// set user language id string
$this->lang = $this->wire('languages') ? $this->user->language->isDefault() ? '' : "__{$this->user->language->id}" : '';
// prepend path to emo.js and add to scripts array when set
if($this->jsLoad != self::loadManual) {
$root = $this->jsLoad == self::loadInline ? $this->config->paths->$this : $this->config->urls->$this;
if(strpos($this->jsFile, $root) === false) $this->jsFile = $root . $this->jsFile;
if($this->jsLoad == self::loadScripts) $this->config->scripts->add($this->jsFile);
}
}
// in render
public function render(HookEvent $event)
{
if($this->obfuscated) return;
if(!$this->isRenderAllowed($event)) return;
if($this->debug) $time = Debug::timer();
$info = $this->modules->getModuleInfo($this);
$page = $event->object->page;
$output = $event->return;
// auto obfuscate output
if($this->mode != self::modeManual && !$this->addrCount) {
$output = $this->obfuscate($output);
}
if($this->addrCount) {
// add emo.js to body
if(strpos($output, $this->jsFile) === false) {
$script = '';
if($this->jsLoad == self::loadAuto) {
$v = $info['version'];
$script .= "\n<script src=\"{$this->jsFile}?v={$v}\" async defer></script>\n";
}
if($this->jsLoad == self::loadInline) {
$script = "\n<script>\n" . file_get_contents($this->jsFile) . "</script>\n";
}
if(strlen($script)) {
$output = str_replace("</body>", "{$script}</body>", $output);
}
}
// body script data
$scriptRows = array(
"var emo = emo || {};",
"emo.key = '" . $this->getKey() . "';",
);
if($this->debug) { $scriptRows[] = "emo.debug = true;"; }
// append emo script to body
$script = "\n<script>" . implode(" ", $scriptRows) . "</script>\n";
$output = str_replace("</body>", "{$script}</body>", $output);
// append debug info to output
if($this->debug && $this->user->isLoggedin()) {
$data = array();
foreach($this->options as $key => $val) $data[$key] = $this->$key;
$data = array_merge($this->debugData, $data);
$this->debugTime += Debug::timer($time);
$output .= "\n<!-- \n\n\t" . $info['title'] . " v.{$info['version']}";
$output .= "\n\n\tCrypted {$this->addrCount} email address in {$this->debugTime} seconds\n\n";
$output .= json_encode($data, JSON_PRETTY_PRINT);
$output .= "\n\n-->";
}
$this->obfuscated = true;
}
$event->return = $output;
}
Script nachdem jQuery geladen ist
"Why my JS file are loaded before Jquery ? Should I call my script in init(), ready() or execute() ?
It just needs to be moved anywhere after the PW initialization (after init, after ready). JqueryCore is not an autoload module (since its only used by admin) so all the autoload modules init before it does. If your module has an execute() or render() or something like that, that's where you'd want to move your script loader. This is also important for an autoload module because some sites use $config->scripts or $config->styles for the front-end, and you wouldn't want your module loading into that context, because who knows if they are even using jQuery in their site.
Variablen an Script übergeben
How to pass value to a jquery document ready function ?
// your functions
function doSomething($) {
// @note: non-self-executing function
// 'config' is a global PW variable
var text = config.demo;
$("h2.tagline").addClass("color-red");
console.log(text);
}
// ready
jQuery(document).ready(function ($) {
doSomething($);// pass jQuery ($) if needed
});
Selektoren
Einfacher Check auf gültigen Selector
// check the page selector
if (strlen($selector)<2 || !strpos($selector, '=')) {
$this->error("ERROR: invalid page selector '{$selector}' found in the input.");
return false;
}
Admin Module
Admin Module sind Module die im Backend genutzt werden.
Skripte und Styles im Admin Bereich
ProcessWire - Skripte und Styles im Admin Bereich
Seite im Admin anlegen
Hinweis: Überholt. Geht heute viel einfacher (siehe Beispiel in Links von Bernhard Baumrock). Die Methoden install und uninstall sind trotzdem nützlich. getInstalledPage kann trotzdem nützlich sein. Man benötigt es aber nicht zwingend zum Anlegen der Seite, dies erledigt PW selbst wenn man es in der Konfiguration des Moduls angibt.
Wenn keine Seite angelegt ist wird sie angelegt. Nützlich z.B. für die ___install() und ___uninstall() Methoden
/**
* Return the page that this Process is installed on
*
*/
protected function getInstalledPage() {
$admin = $this->pages->get($this->config->adminRootPageID);
$parent = $admin->child("name=setup");
if(!$parent->id) $parent = $admin;
$page = $parent->child("name=" . self::adminPageName);
if(!$page->id) {
$page = new Page();
$page->parent = $parent;
$page->template = $this->templates->get('admin');
$page->name = self::adminPageName;
$page->title = "Import Pages From CSV";
$page->process = $this;
$page->sort = $parent->numChildren;
$page->save();
}
return $page;
}
/**
* Install the module and create the page where it lives
*
*/
public function ___install() {
if(ProcessWire::versionMajor == 2 && ProcessWire::versionMinor < 1) {
throw new WireException("This module requires ProcessWire 2.1 or newer");
}
$page = $this->getInstalledPage();
$this->message("Installed to {$page->path}");
if($page->parent->name == 'setup') $this->message("Click to your 'Setup' page to start using the CSV Importer");
}
/**
* Uninstall the module
*
*/
public function ___uninstall() {
$page = $this->getInstalledPage();
if($page->id) {
$this->message("Removed {$page->path}");
$this->pages->delete($page);
}
}
$filesPath = $page->filesManager->path;
User Interface im Admin Bereich - Helferlein
Buttons
Händisch Hier noch mit einer Abfrage ob man im Modal ist um Modals in Modals zu verhindern.
if(!$this->input->get('modal')) {
<a href="./link-to-page/" class="ui-button ui-state-default"> My Button </a>
}
Programmatisch Nützlich um ganze Sets zu erzeugen.
$out = '';
$buttons = [
'randomizeData' => 'Randomize Data',
'addDataset' => 'Add Dataset',
'addData' => 'Add Data',
'removeDataset' => 'Remove Dataset',
'removeData' => 'Remove Data',
];
$button = $this->modules->get('InputfieldButton');
$button->setSmall(true);
foreach($buttons as $id => $label) {
$button->id = $id;
$button->value = $label;
$out .= $button->render();
}
AdminTable
$table = $this->modules->get('MarkupAdminDataTable');
$table->headerRow(['A', 'B', 'C']);
$table->row([1, 2, 3]);
$table->row([4, 5, 6]);
return $table->render();
Seiten in Panel öffnen
Man kann Unterseiten in einem Panel aufmachen. Einfach die Klasse pw-panel zu Links hinzufügen.
<a href="./due-contracts/" class="ui-button ui-state-default pw-panel"> Fällige Verträge </a>
Mehr Infos im Quelltext (suche nach panel)
https://github.com/processwire/processwire/blob/master/wire/modules/Jquery/JqueryUI/panel.js
Skripte hinzufügen
public function ___executeChart() {
$this->config->scripts->add(
'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.min.js'
);
$this->config->scripts->add(
'http://www.chartjs.org/samples/latest/utils.js'
);
$this->config->scripts->add(
$this->config->urls->ProcessSimple . 'chart.js'
);
$out = '';
$buttons = [
'randomizeData' => 'Randomize Data',
'addDataset' => 'Add Dataset',
'addData' => 'Add Data',
'removeDataset' => 'Remove Dataset',
'removeData' => 'Remove Data',
];
$button = $this->modules->get('InputfieldButton');
$button->setSmall(true);
foreach($buttons as $id => $label) {
$button->id = $id;
$button->value = $label;
$out .= $button->render();
}
return $out;
}
Formulare im Backend/Admin
ProcessWire - Formulare https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/#handling-user-input-using-forms-amp-inputfields https://gitlab.com/baumrock/ProcessSkyDashboard/blob/master/ProcessSkyDashboard.module.php.v2
Beispiel:
public function ___execute() {
$form = $this->modules->get('InputfieldForm');
bd($this->input->post);
$field = $this->modules->get('InputfieldMarkup');
$field->label = 'Table';
$field->value = $this->renderTable();
$field->columnWidth = 50;
$form->add($field);
$field = $this->modules->get('InputfieldMarkup');
$field->label = 'Chart';
$field->value = $this->renderChart();
$field->columnWidth = 50;
$form->add($field);
$fieldset = $this->modules->get('InputfieldFieldset');
$fieldset->label = 'Add Skyscraper';
//$fieldset->collapsed = Inputfield::collapsedYes;
$field = $this->modules->get('InputfieldText');
$field->name = 'Name';
$field->columnWidth = 33;
$fieldset->add($field);
$field = $this->modules->get('InputfieldText');
$field->name = 'Height';
$field->columnWidth = 33;
$fieldset->add($field);
// see InputfieldPage.module for all available options
$field = $this->modules->get('InputfieldPage');
$field->inputfield = 'InputfieldSelect';
$field->findPagesSelector = 'template=city';
$field->labelFieldName = 'title';
$field->name = 'City';
$field->columnWidth = 34;
$fieldset->add($field);
$button = $this->modules->get('InputfieldSubmit');
$button->value = 'Save';
$button->icon = 'floppy-o';
$fieldset->add($button);
$form->add($fieldset);
return $form->render();
}
public function ___executeAddSkyscraper() {
// build the add-skyscraper form
$form = $this->buildAddSkyscraper();
// if form not submitted, display it instead
if(!$this->input->post('submit')) return $form->render();
// process the InputfieldForm
$form->processInput($this->input->post);
if(count($form->getErrors())) {
// form had errors, ask them to fix
$this->error('Please fix the errors');
return $form->render();
}
// add skyscraper from submitted form
$p = new Page();
$p->template = 'skyscraper';
$p->parent = $form->get('City')->value->first();
$p->title = $form->get('Name')->value;
$p->height = $form->get('Height')->value;
$p->save();
// show message
$this->message('Added a new skyscraper to the database');
// redirect to our dashboard
$this->session->redirect('./');
}
Tabs im Backend
https://processwire.com/talk/topic/24934-using-tabs-wiretabs-in-a-module-config/
Tabs kann man im Admin mit dem InputfieldWrapper erzeugen. Da es ein Inputfield Objekt ist benötigt man auch ein Formular. Das Ganze sieht am Ende so aus:
Formular - InputfieldForm - Tabwrapper - InputfieldWrapper -- Tab - InputfieldWrapper --- InputfieldMarkup (oder andere Inputfields)
Dazu muss man noch das JavaScript z.b. per jQuery Schnipsel einbinden.
Im Programm kann das dann so ausschauen wie in Beispiel 2 (hier in einer Variante für die Modulkonfiguration).
All in one
https://processwire.com/api/ref/jquery-wire-tabs/render/
Seit Version 3.0.127. gibt es auch eine all-in-one Lösung Example
echo $modules->get('JqueryWireTabs')->render([
'Foo' => 'Foo tab content',
'Bar' => 'Bar tab content',
'Baz' => 'Baz tab content',
]);
Manuell
Beispiel 1 (unvollständig)
public function ___execute(){
$form = $this->wire('modules')->get("InputfieldForm");
$form->name = 'select';
$form->method = 'post';
$tabsWrapper = new InputfieldWrapper();
$tabsWrapper->attr("id", "fulfillment-list");
//$tab = $modules->get('InputfieldWrapper'); // works
//$tab = new InputfieldWrapper(); // not recommended - could ressolve in issues All newly-created objects that are derived from Wire SHOULD have their dependencies injected. In order to do this, pass the new object through the wire() method.
$tab = $this->wire(new InputfieldWrapper()); // if you need compatibility for older pw
$tab->attr('id', 'tab_fulfillment-list'); // best way?
$tab->attr('title', 'Fulfillments');
$tab->attr('class', 'WireTab');
$f = $this->wire('modules')->get("InputfieldMarkup");
$f->attr('name', 'tab_content');
$out = '<div>Dies ist mein Inhalt</div>';
$f->attr('value', $out);
$tab->add($f);
// ... more fields...
$tabsWrapper->add($tab);
// next tab
$tab = new InputfieldWrapper();
$tab->id = 'tab2'; // alternative Schreibweise statt attr()
$tab->title = 'TAB 2';
$tab->attr('class', 'WireTab');
// ... more fields to add ...
$form->add($tabsWrapper);
return $form->render();
}
Beispiel 2 - Tabs für eine Modulkonfiguration
class ModuleConfigTabs extends WireData implements Module, ConfigurableModule {
/**
* Module information
*/
public static function getModuleInfo() {
return array(
'title' => 'Module Config Tabs',
'summary' => 'Demonstration of tabs within a module config.',
'version' => '0.1.0',
'author' => 'Robin Sallis',
'href' => 'https://github.com/Toutouwai/ModuleConfigTabs',
'requires' => 'ProcessWire>=3.0.0, PHP>=5.4.0',
);
}
/**
* Config inputfields
*
* @param InputfieldWrapper $inputfields
*/
public function getModuleConfigInputfields($inputfields) {
$modules = $this->wire()->modules;
// Get WireTabs
$modules->get('JqueryWireTabs');
// Load custom CSS and JS
$config = $this->wire()->config;
$info = $this->getModuleInfo();
$version = $info['version'];
$config->styles->add($config->urls->$this . "{$this}.css?v=$version");
$config->scripts->add($config->urls->$this . "{$this}.js?v=$version");
// Wrapper to hold the tabs
/** @var InputfieldWrapper $tabs_container */
$tabs_container = $modules->get('InputfieldWrapper');
$tabs_container->id = 'mct-tabs-container';
$inputfields->add($tabs_container);
// Tab 1
/** @var InputfieldWrapper $tab */
$tab1 = $modules->get('InputfieldWrapper');
$tab1->attr('title', 'Name');
$tab1->attr('class', 'WireTab');
$tabs_container->add($tab1);
/** @var InputfieldText $f */
$f = $modules->get('InputfieldText');
$f->name = 'first_name';
$f->label = $this->_('First name');
$f->value = $this->{$f->name};
$f->columnWidth = 50;
$tab1->add($f);
/** @var InputfieldText $f */
$f = $modules->get('InputfieldText');
$f->name = 'last_name';
$f->label = $this->_('Last name');
$f->value = $this->{$f->name};
$f->columnWidth = 50;
$tab1->add($f);
// Tab 2
/** @var InputfieldWrapper $tab */
$tab2 = $modules->get('InputfieldWrapper');
$tab2->attr('title', 'Contact');
$tab2->attr('class', 'WireTab');
$tabs_container->add($tab2);
/** @var InputfieldEmail $f */
$f = $modules->get('InputfieldEmail');
$f->name = 'email';
$f->label = $this->_('Email');
$f->value = $this->{$f->name};
$f->columnWidth = 50;
$tab2->add($f);
/** @var InputfieldText $f */
$f = $modules->get('InputfieldText');
$f->name = 'phone';
$f->label = $this->_('Phone');
$f->value = $this->{$f->name};
$f->columnWidth = 50;
$tab2->add($f);
/** @var InputfieldTextarea $f */
$f = $modules->get('InputfieldTextarea');
$f->name = 'address';
$f->label = $this->_('Address');
$f->value = $this->{$f->name};
$tab2->add($f);
}
}
$(document).ready(function() {
$('#mct-tabs-container').WireTabs({
items: $('.WireTab')
});
});
#mct-tabs-container { margin-top:20px; margin-bottom:20px; }
Schnipsel - Todo
Schnipsel...
// override css
$form->setClasses(array('form'=>'uk-form-horizontal InputfieldForm'));
Seiten
Einzigartiger Seitenname
Z.B. beim verschieben evtl. wichtig falls Seiten mit dem gleichen Namen vorhanden sind.
//In the context of the hook $p->name = $this->pages->names->uniquePageName($this->sanitizer->pageName($p->title));
oder
$p->name = $this->pages->names()->uniquePageName($p->name);
$p->parent = pages()->get('/news/');
$event->arguments(0, $p);
Action Buttons im Page Edit hinzufügeb / Save Button erweitern
Am Beispiel der Clone Funktion (funktioniert nur mit aktiviertem Clone Modul) zeigen Robin Salis und Bernhard Baumrock, wie sich Button-Funktionen in den Edit Dialog einer Seite erweitern lassen.
Bernhard erweitert den aufklappbaren Button unten auf der Seite (Save) mit einer Save + Clone Funktion. In der ready.php oder in einem Modul wird
$this->wire->addHookAfter('ProcessPageEdit::getSubmitActions', function($event) {
bd('hook getSubmitActions');
$page = $event->process->getPage();
if($page->template != "invoice") return;
$actions = $event->return;
unset($actions['next']);
$actions['clone'] = [
'value' => 'clone',
'icon' => 'clone',
'label' => 'Save + create copy',
];
$event->return = $actions;
});
$this->wire->addHookAfter('ProcessPageEdit::processSubmitAction', function($event) {
bd('hook processSubmitActions');
$action = $event->arguments(0); // action name, i.e. 'hello'
$page = $event->process->getPage(); // Page that was edited/saved
if($page->template != 'invoice') return;
if($action === 'clone') {
$copy = $this->wire->pages->clone($page);
$copy->title .= ' (copy ' . uniqid() . ')';
$copy->save();
$this->wire->session->redirect($copy->editUrl);
}
});
Robin geht einen anderen Weg und erstellt einen komplett eigenen Button im Settings Tab.
// Add a clone button to the Settings tab of Page Edit if this page is allowed to be cloned
$wire->addHookAfter('ProcessPageEdit::buildFormSettings', function(HookEvent $event) {
$wrapper = $event->return;
$modules = $event->wire()->modules;
$page = $event->process->getPage();
/** @var ProcessPageClone $ppc */
$ppc = $modules->get('ProcessPageClone');
if($page && $ppc->hasPermission($page)) {
/** @var InputfieldButton $f */
$f = $modules->get('InputfieldButton');
$f->value = 'Clone this page';
$f->href = $event->wire()->config->urls->admin . 'page/clone/?id=' . $page->id;
$wrapper->add($f);
}
});
// Edit the cloned page after it is created
$wire->addHookBefore('ProcessPageClone::execute', function(HookEvent $event) {
$event->wire()->addHookBefore('Session::redirect', function(HookEvent $event) {
$url = $event->arguments(0);
$id = (int) filter_var($url, FILTER_SANITIZE_NUMBER_INT);
$redirect_url = $event->wire()->config->urls->admin . 'page/edit/?id=' . $id;
$event->arguments(0, $redirect_url);
});
});
Felder
Textfelder auf read only (locked) setzen
$field->collapsed = Inputfield::collapsedLocked; // or $field->collapsed = Inputfield::locked;
Templates
Page Object an Template Datei übergeben
public function getInvoiceHtml($oid){
$html = '';
$order = $this->pages->get($oid);
$t = $this->getTemplate("padinvoice_pdf.php");
$t->set("order", $order);
$html = $t->render();
return $html;
}