ProcessWire - Module schreiben: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
(Die Seite wurde neu angelegt: „== Beispiele == == ProcessWire Hello World Modul == Beispiel Modul mit Hooks (liegt immer in der Standardinstallation) <syntaxhighlight lang="php"> <?php names…“)
 
 
(52 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
 +
== Links ==
 +
'''Wichtigste Resourcen'''
 +
https://processwire.com/docs/modules/development/ - Guter Ausgangspunkt
 +
https://processwire.com/api/ref/module/ - Module API
 +
https://processwire.com/docs/modules/types/ - Welche Modultypen gibt es ?
 +
https://processwire.com/talk/topic/778-module-dependencies/ - Module die andere Module benötigen
 +
https://processwire.com/talk/forum/19-moduleplugin-development/ - Forum zum Thema Module entwickeln
 +
http://somatonic.github.io/Captain-Hook/ - Hook Cheatsheet
 +
https://processwire.com/blog/posts/new-module-configuration-options/ - Neuere Konfigurationsmöglichkeiten
 +
https://processwire.com/docs/start/api-access/ Zugriff auf ProcessWire API Variablen (wire Objekt)
 +
 +
''' Tutorials und Beispiele zum Einstieg '''
 +
https://processwire.com/docs/modules/ - Introduction, Development, Hooks, Types, Pro Modules, Third Party
 +
https://github.com/ryancramerdesign/ProcessHello - Modul Skelett Beispiel für eigene Backend (Process) Module
 +
https://github.com/ryancramerdesign/FieldtypeMapMarker - Beispiel für ein Fieldtype und Inputfield Modul
 +
https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/ - super Tutorial zu Admin Modulen
 +
https://webdesign.tutsplus.com/tutorials/a-beginners-introduction-to-writing-modules-in-processwire--cms-26862
 +
 +
'''Wikiseiten'''
 +
[[ProcessWire - Module Snippets]]
 +
[[ProcessWire - Fieldtype erstellen (Module)]]
 +
[[Process Module (ProcessWire)]]
 +
 +
'''Weitere Links'''
 +
http://modules.pw/ (Module Creator)
 +
 +
== Wo - Was ? ==
 +
=== Welche Typen von Modulen gibt es ? ===
 +
https://processwire.com/docs/modules/types/
 +
* '''Fieldtype''': Repräsentiert einen Datentyp. Meistens keine Public API, Handelt Daten / Datenbank - [[ProcessWire - Fieldtype erstellen (Module)]]
 +
* '''Inputfield''': Sammelt User Eingaben über ein Formular im Admin Bereich. Im Gegensatz zum Fieldtype geht es hier um das UI im Backend. Das Handling der Daten liegt beim Fieldtype.
 +
* [[Process Module (ProcessWire)|'''Process''' for creating admin processes/applications.]]
 +
* '''Textformatter''' for formatting text.
 +
* '''AdminTheme''' for creating themes in the admin.
 +
* '''WireMail''' for modules that send email and extend the WireMail class.
 +
* '''Tfa''' for implementing a specific kind of two-factor authentication.
 +
* '''ImageSizerEngine''' for modules that extend ImageSizerEngine for resizing images.
 +
* '''FileCompiler''' for modules that extend FileCompilerModule for compilation of files.
 +
* '''FileValidator''' for modules that extend FileValidatorModule for validation of files.
 +
 +
=== Wie werden Felder in der Datenbank angelegt ? ===
 +
Felder die in der Datenbank speichern sind vom Typ "Fieldtype", meistens in Kombination mit einem Inputfield das dann die Frontendausgabe übernimmt.
 +
Module können auch für ihre eigene Konfiguration Daten in der Datenbank speichern. Das geht z.B. über eine ModulnameConfig Datei (s.u.).
 +
 +
=== Namenskonventionen ===
 +
Ein Modulname sollte mit dem Typ den das Modul hat beginnen. Also etwa ProcessMeinAdminModul oder FieldtypeTable. ProcessWire listet das Modul dann in der Entsprechenden Kategorie auf. Wenn man etwas anderes nimmt z.B. LoginRegister dann kommt das Modul unter der neuen Rubrik "Login"
 +
 +
=== Konfigurierbare Module ===
 +
Das geht seit 2.5.5 mittlerweile sehr einfach mit den neuen configuration-options.
 +
https://processwire.com/blog/posts/new-module-configuration-options/
 +
[[ProcessWire - Konfigurierbare Module]]
 +
 +
== Spezielle Funktionen in Modulen ==
 +
 +
=== Modulinformation ===
 +
Das Modul stellt Infos zur Verfügung und nutzt dafür eine von 3 Methoden:
 +
    getModuleInfo() static method in your module class that returns an array.
 +
    YourModuleClass.info.php file that populates an $info array.
 +
    YourModuleClass.info.json file that contains an info object.
 +
 +
Hier werden title, version, summary und weitere Daten hinterlegt. Hier kann man auch angeben ob ein Modul ein Autoload Modul ist.
 +
 +
https://processwire.com/api/ref/module/
 +
 +
=== init ===
 +
public function init(){}
 +
Initialisiert das Modul. ProcessWire ruft diese Funktion auf, wenn das Modul geladen ist. Bei Autoload Modulen wird es aufgerufen wenn die ProcessWire API bereit ist. Daher ist es ein guter Ort um Hooks einzubinden.
 +
 +
=== ready ===
 +
<syntaxhighlight lang="php">
 +
  public function ready() {
 +
    if($this->page->template == 'admin') {
 +
      $this->message($this->hi());
 +
    }
 +
  }
 +
</syntaxhighlight>
 +
Wird in autoload Modulen aufgerufen wenn die API bereit ist. Nützlich für Hooks:
 +
 +
=== execute ===
 +
<syntaxhighlight lang="php">
 +
/**
 +
* This function is executed when a page with your Process assigned is accessed.
 +
*
 +
* This can be seen as your main or index function. You'll probably want to replace
 +
* everything in this function.
 +
*
 +
*/
 +
public function ___execute() {
 +
// greetingType and greeting are automatically populated to this module
 +
// and they were defined in ProcessHello.config.php
 +
if($this->greetingType == 'message') {
 +
$this->message($this->greeting);
 +
} else if($this->greetingType == 'warning') {
 +
$this->warning($this->greeting);
 +
} else {
 +
$this->error($this->greeting);
 +
}
 +
// generate some navigation
 +
$out = "
 +
<h2>$this->greeting</h2>
 +
<dl class='nav'>
 +
<dt><a href='./something/'>Do Something</a></dt>
 +
<dd>Runs the executeSomething() function.</dd>
 +
</dl>
 +
";
 +
return $out;
 +
}
 +
</syntaxhighlight>
 +
 +
=== executeSomething ===
 +
Ruft man eine Unterseite des Moduls auf kann man eine eigene execute Funktion nutzen
 +
___executeSlug wobei Slug für den Url Slug steht. Heißt der slug /field/ dann wird executeField() aufgerufen, falls diese Funktion existiert.
 +
<syntaxhighlight lang="php">
 +
/**
 +
* Called when the URL is this module's page URL + "/something/"
 +
*
 +
*/
 +
public function ___executeSomething() {
 +
// set a new headline, replacing the one used by our page
 +
// this is optional as PW will auto-generate a headline
 +
$this->headline('This is something!');
 +
// add a breadcrumb that returns to our main page
 +
// this is optional as PW will auto-generate breadcrumbs
 +
$this->breadcrumb('../', 'Hello');
 +
$out = "
 +
<h2>Not much to to see here</h2>
 +
<p><a href='../'>Go Back</a></p>
 +
";
 +
return $out;
 +
}
 +
</syntaxhighlight>
 +
* executeMySecondPage translates to my-second-page in the URL
 +
* executeMysecondpage translates to mysecondpage in the URL
 +
 +
=== install & uninstall ===
 +
<syntaxhighlight lang="php">
 +
 +
/**
 +
* Called only when your module is installed
 +
*
 +
* If you don't need anything here, you can simply remove this method.
 +
*
 +
*/
 +
public function ___install() {
 +
parent::___install(); // always remember to call parent method
 +
}
 +
/**
 +
* Called only when your module is uninstalled
 +
*
 +
* This should return the site to the same state it was in before the module was installed.
 +
*
 +
* If you don't need anything here, you can simply remove this method.
 +
*
 +
*/
 +
public function ___uninstall() {
 +
parent::___uninstall(); // always remember to call parent method
 +
}
 +
</syntaxhighlight>
 +
 +
== Vererbung und Eigenschaften in Modulen ==
 +
Modules are not different from PHP classes.
 +
 +
To change the properties of MyModule class from within MyOtherModule class, you can either:
 +
 +
#Make MyOtherModule class extend MyModule. It will thus inherit MyModule's public and protected properties and methods
 +
#Make the properties firstName and lastName configurable by passing them as parameters/arguments in MyModule's constructor method.
 +
 +
== Autoload Module und Hooks ==
 +
Autoload werden automatisch geladen müssen also nicht in einem Template o.ä. gestartet werden. Daher bieten sie sich an um die Funktionalität von ProcessWire zu erweitern. Als Werkzeug dafür dienen Hooks. Mit Hooks kann man an vielen Stellen den Rendering Process der Seiten beeinflussen. Außerdem kann man für eigene Module ebenfalls Hooks bereitstellen.
 +
 +
=== Hooks ===
 +
https://processwire.com/docs/modules/hooks/
 +
Hooks sind ein mächtiges Werkzeug. Sie geben einem die Möglichkeit an vielen Stellen "einzuhaken" und Funktionalität einzubauen.
 +
<syntaxhighlight lang="php">
 +
public function ready() {
 +
  $this->addHookAfter('Page::render', function($event) {
 +
    $event->return .= '<p>Hallo</p>';
 +
  });
 +
}
 +
</syntaxhighlight>
 +
Schreibt am Ende Jeder Seite ein "Hallo". Das $event Objekt ist ein [https://processwire.com/api/ref/hook-event/ HookEvent]. Im Beispiel fügen wir einfach etwas Markup hinzu. Über $event->arguments() kann man aber auf ale Argumente zugreifen.
 +
 +
Das gleiche aber nicht mit anonymer Funktion:
 +
<syntaxhighlight lang="php">
 +
public function ready() {
 +
  // add hook after Page::render() and make it call the "test" method of $this module
 +
  $this->addHookAfter('Page::render', $this, 'test');
 +
}
 +
 +
public function test($event) {
 +
  // modify the return value of Page::render() to include the following:
 +
  $event->return .= '<p>Hallo</p>';
 +
}
 +
</syntaxhighlight>
 +
 +
=== Hooks zum erweitern von existierenden Klassen nutzen ===
 +
Mit Hooks kann man in vorhandene Klassen einhaken. Z.B. läßt sich das Page Objekt erweitern.
 +
<syntaxhighlight lang="php">
 +
public function ready() {
 +
  $this->addHook('Page::summarize', $this, 'summarize');
 +
}
 +
public function summarize($event) {
 +
  $page = $event->object; // the $event->object represents the object hooked (Page)
 +
  $maxlen = $event->arguments(0); // first argument is the optional max length
 +
  if(!$maxlen) $maxlen = 200; // if no $maxlen was present, we'll use a default of 200
 +
  $summary = $this->sanitizer->truncate($page->body, $maxlen); // use sanitizer truncate method to create a summary
 +
  $event->return = $summary; // populate $summary to $event->return, the return value
 +
}
 +
</syntaxhighlight>
 +
So kann man ganz einfach eine Zusammenfassung aller Kindseiten in einem Template rendern:
 +
<syntaxhighlight lang="php">
 +
foreach($page->children as $item) {
 +
  $summary = $item->summarize(150);
 +
  echo "<li><a href='$item->url'>$item->title</a><br>$summary";
 +
}
 +
</syntaxhighlight>
 +
 +
=== Autoload Module nur im Admin Bereich laden ===
 +
 +
'autoload' => 'template=admin'
 +
statt
 +
'autoload' => true
 +
 +
=== Hook Beispiele ===
 +
Hook am Ende des Renderings
 +
$this->addHookAfter('Page::render', function($event) {...});
 +
Füge eine Funktion "summarize" zum Page Objekt hinzu.
 +
$this->addHook('Page::summarize', $this, 'summarize');
 +
Hook auf eine einzelne Instanz (hier pages Objekt)
 +
$this->pages->addHookAfter('saved', function($event){...});
 +
$this->addHook('Page::method', ...) // Spricht ALLE Instanzen an - Regelfall
 +
$page->addHook('method', ...) // Spricht nur die EINE Instanz der Seite an.
 +
 +
== Module Dependencies - voneinander abhängige Module ==
 +
 +
https://processwire.com/talk/topic/778-module-dependencies/
 +
Wenn ein Modul nicht ohne ein anderes funktioniert spricht man von Module Dependency. In solchen Fällen kann man in der Modul Info angeben In vielen Fällen besteht ein Modul unter der Haube aus mehreren Einzelmodulen.
 +
 +
Um ProcessWire mitzuteilen dass ein Modul benötigt wird gibt man es in der Moduldefinition an:
 +
'requires' => "LazyCron"  // added this line
 +
oder auch mehrere als Array
 +
'requires' => array("LazyCron", "AdminBar")  // added this line
 +
 +
ProcessWire kann auch dafür sorgen, dass ein Modul andere "Kindmodule" gleich mitinstalliert bzw. deinstalliert wenn es nicht mehr benötigt wird. Dazu gibt man dem Modul noch die 'install' dependency mit:
 +
 +
<syntaxhighlight lang="php">
 +
<?php
 +
public static function getModuleInfo() {
 +
  return array(
 +
      'title' => 'Log Master',
 +
      'version' => 101,
 +
      'author' => 'Lumberjack Bob',
 +
      'summary' => 'Log all actions on your site',
 +
      'requires' => 'LazyCron',
 +
      'installs' => 'ProcessLogMaster'  // added this line
 +
      );
 +
);
 +
</syntaxhighlight>
 +
 +
ProcessWire sieht dann eine Kindabhängigkeit zu diesem Modul und handelt auch das Deinstallieren, wenn das Elternmodul deinstalliert wird.
 +
 +
== CSS und JavaScript in Modulen ==
 +
Bei Process Modulen möchte man für das Styling etc. im Backend oft CSS und JS Dateien hinzufügen.
 +
 +
Einfach Eine Datei MeinModul.css und / oder MeinModul.js hinzufügen. JQuery ist im Admin bereits geladen daher kann eine MeinModul.js Starter Datei so aussehen:
 +
 +
<pre>
 +
 +
/**
 +
* This JS file is only loaded when the ProcessHello module is run
 +
*
 +
* You should delete it if you have no javascript to add.
 +
*
 +
*/
 +
 +
$(document).ready(function() {
 +
// do something
 +
});
 +
</pre>
 +
 +
== Flexibler Umgang mit Templates - TemplateFile ==
 +
TemplateFile sind eine Möglichkeit Templates zu Managen. Nützlich in Modulen oder in größeren Teams wo man sehr granuliert die Zugriffe über den Code steuern möchte.
 +
https://processwire.com/talk/topic/291-simple-tutorial-on-how-to-use-templatefile-class/
 +
https://processwire.com/api/ref/template-file/
 +
<syntaxhighlight lang="php">
 +
<?php
 +
 +
$out = new TemplateFile("./includes/my_markup.html");
 +
$out->set('headline', $page->get("headline|title"));
 +
$out->set('body', $page->body);
 +
$out->set('sidebar', $page->sidebar);
 +
$out->set('navigation', $pages->find("parent=/"));
 +
echo $out->render();
 +
</syntaxhighlight>
 +
 +
In my_markup.html kann man headline, body... einfach als Variablen setzen. Beim Rendern werden diese dann ersetzt.
 +
 
== Beispiele ==
 
== Beispiele ==
== ProcessWire Hello World Modul ==
+
=== Felder und Templates erstellen ===
 +
In einem Modul kann man Felder und Templates automatisch erstellen.
 +
https://processwire.com/talk/topic/20533-solved-fields-templates-and-fieldgroups-how-do-they-interact/
 +
 
 +
For everyone not deeply into obscure PW vodoo magic, creating a template with fields is always:
 +
 
 +
    Create a template
 +
    Create a fieldgroup with the same name as the template
 +
    Create your fields, save them and add them to the fieldgroup
 +
    Save the fieldgroup
 +
    Set the fieldgroup in the template
 +
    Save the template
 +
 
 +
* Are Fieldgroups required for adding fields to templates? What are these for?
 +
** Yes, they are. As written above, they are just a necessary glue.
 +
*What if I'd like to add fields after the template was saved and how can I add them to the same fieldgroup?
 +
** Just add them to the fieldgroup
 +
<pre>
 +
$fg = $template->fieldgroup;
 +
$fg->add($field);
 +
$fg->save();
 +
</pre>
 +
 
 +
=== Frontend Rendering Module ===
 +
A bit old but working
 +
 
 +
<syntaxhighlight lang="php">
 +
<?php
 +
 
 +
/**
 +
* FrontEndRender
 +
*
 +
* Author: Ben Byford
 +
*
 +
* ProcessWire 2.x
 +
* Copyright (C) 2010 by Ryan Cramer
 +
* Licensed under GNU/GPL v2, see LICENSE.TXT
 +
*
 +
* http://www.processwire.com
 +
* http://www.ryancramer.com
 +
*
 +
*/
 +
 
 +
class FrontEndRender extends WireData implements Module {
 +
 
 +
public static function getModuleInfo() {
 +
return array(
 +
'title' => 'FrontEndRender',
 +
'version' => 0.1,
 +
'summary' => "Outputs html and static variables to frontend",
 +
'author' => 'Ben Byford',
 +
'singular' => true,
 +
'href' => 'https://github.com/benbyford/PW-starter-modules'
 +
);
 +
}
 +
 
 +
// protected variable only accessable within module
 +
protected $name = 'Ben';
 +
 
 +
/*
 +
* render function to be called in PW template like this:
 +
* $FrontEndRender = $modules->getModule('FrontEndRender');
 +
* echo '<h1>' . $FrontEndRender->render() . '</h1>';
 +
*
 +
*/
 +
public function render(){
 +
return "Hello " . $this->name;
 +
}
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 
 +
=== ProcessWire Hello World Modul ===
 
Beispiel Modul mit Hooks (liegt immer in der Standardinstallation)
 
Beispiel Modul mit Hooks (liegt immer in der Standardinstallation)
 
<syntaxhighlight lang="php">
 
<syntaxhighlight lang="php">
Zeile 137: Zeile 506:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
=== ProcessWire Admin Bereich mit eigener Funktionalität erweitern (Modul)===
 +
https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
 +
<syntaxhighlight lang="php">
 +
 +
<?php
 +
/**
 +
* ProcessSimpleAdminPage
 +
*
 +
* @author Ben Byford
 +
* http://www.benbyford.com
 +
*
 +
* @see http://www.processwire.com
 +
*/
 +
 +
class ProcessSimpleAdminPage extends Process {
 +
 +
    public static function getModuleInfo() {
 +
        return array(
 +
            'title' => 'Process Simple Admin Page',
 +
            'summary' => 'Simple Process module that adds new admin page with',
 +
            'version' => 001,
 +
 +
            // Modules that extend Process may specify a 'page' attribute in the
 +
            // getModuleInfo(), this page will automatically be given the module
 +
            // process when added to teh pagetree.
 +
 +
            // I have exampled but commented out the 'page' settings below
 +
            // so that I can show how one might add a page to install() and
 +
            // uninstall() in this and other modules (that might not extend
 +
            // Process)
 +
 +
 +
        // 'page' => array(
 +
        // 'name' => 'site-config',
 +
        // 'parent' => 'admin',
 +
        // 'title' => 'Site Config'
 +
        //   )
 +
        );
 +
    }
 +
 +
    public function execute() {
 +
        return '
 +
            <h2>Edit the text here in the module</h2>
 +
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mattis eros vitae metus sodales eget suscipit purus rhoncus. Proin ultrices gravida dolor, non porttitor enim interdum vitae. Integer feugiat lacinia tincidunt. Nulla laoreet tristique tristique. Sed elementum justo a nisl elementum sit amet accumsan nisi tempor. Nulla quis eros et massa dignissim imperdiet a vitae purus.</p>
 +
 +
            <p>Donec scelerisque pulvinar sem eu lobortis. Maecenas turpis ipsum, tempus dictum pharetra eu, consectetur vitae arcu. Fusce orci mauris, semper at tempus quis, volutpat molestie tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed quam tortor, tincidunt sed semper lacinia, scelerisque dapibus quam. Morbi at nisi luctus lacus auctor ultrices eu eu leo.</p>
 +
 +
            <p>Praesent faucibus purus id felis tincidunt dignissim. Sed sit amet ligula mi, eget semper dui. Proin consectetur gravida massa, nec luctus purus hendrerit in. Etiam volutpat, elit non venenatis suscipit, libero neque consectetur diam, id rutrum magna odio ac ligula. Maecenas sollicitudin congue neque fermentum vestibulum. Morbi nec leo nisi. Donec at nisl odio, et porta ligula.</p>
 +
 +
            <p>Sed quis arcu nisi, ac tempor augue. Praesent non elit libero, a ullamcorper lorem. Curabitur porta odio eu nunc ultricies interdum id nec risus. Donec nibh nibh, porta eget vehicula ac, aliquet eget ante. Phasellus eget lorem eu eros eleifend ultrices. Cras sit amet neque sit amet nibh fringilla cursus ut id mauris. Praesent quis nunc justo, sed suscipit lectus. Phasellus eget ultrices risus. Curabitur eu semper est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut suscipit, nisl ut imperdiet eleifend, turpis arcu placerat tortor, nec laoreet lacus neque ac tellus. Aenean ac lacus justo, quis ultricies nisi.</p>
 +
            ';
 +
    }
 +
    public function install(){
 +
 +
        // create new page to add to CMS
 +
$page = new Page();
 +
 +
        // add page attributes
 +
        $page->template = "admin";
 +
        $page->name = "cms-faq";
 +
        $page->title = "CMS FAQ";
 +
        $page->save();
 +
 +
        // set this module as the page process, this allows us to display the above
 +
        $page->process = 'ProcessSimpleAdminPage';
 +
 +
        // get admin page and set as page parent
 +
        $admin = $this->pages->get("id=2");
 +
        $page->parent = $admin;
 +
 +
        // save page
 +
        $page->save();
 +
}
 +
 +
public function uninstall(){
 +
 +
        // delete created page
 +
        $page = $this->pages->get("name=cms-faq");
 +
        if(count($page)) $this->pages->delete($page, true);
 +
}
 +
}
 +
</syntaxhighlight>
 +
 +
=== ProcessWire Textformatter Modul ===
 +
https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
 +
<syntaxhighlight lang="php">
 +
<?php
 +
 +
/**
 +
* TextformatterFindReplace
 +
*
 +
* Author: Ben Byford
 +
*
 +
* ProcessWire 2.x
 +
* Copyright (C) 2010 by Ryan Cramer
 +
* Licensed under GNU/GPL v2, see LICENSE.TXT
 +
*
 +
* http://www.processwire.com
 +
* http://www.ryancramer.com
 +
*
 +
*/
 +
 +
class TextformatterFindReplace extends Textformatter implements Module {
 +
 +
public static function getModuleInfo() {
 +
return array(
 +
'title' => 'TextformatterFindReplace',
 +
'version' => 0.1,
 +
'summary' => "Finds and replaces any instance of config input to config output",
 +
'author' => 'Ben Byford',
 +
'singular' => true,
 +
'href' => 'https://github.com/benbyford/PW-starter-modules'
 +
);
 +
}
 +
 +
 +
/**
 +
    * Find and Replace the input string
 +
    *
 +
    * @param string $str The block of text to parse
 +
    *
 +
    * The incoming string is replaced with the formatted version of itself.
 +
**/
 +
 +
public function format(&$str) {
 +
$find = $this->findStr;
 +
$str = preg_replace_callback($find, array($this,"replace"), $str);
 +
 +
}
 +
 +
// adding three underscores to a function allows other modules to hook it
 +
public function ___replace($match) {
 +
return $this->replaceStr;
 +
}
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 +
=== Admin Funktionalität: Countdown zum Seitenveröffentlichen ===
 +
https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
 +
 +
The module PageDeferredPublish, on clicking one of the '''Publish Later buttons''', sets LazyCron to check that page’s countdown every minute and publishes the page when its countdown reaches 0. This means I can publish a page approximately 24 hours in advance (obviously the checking interval and delay time can be changed to your requirements).
 +
 +
I did this by:
 +
 +
'''Creating two fields''' within my install() function: a checkbox field to set to true when a button is checked to indicate the page should countdown, and a countdown field to store the count in seconds for that specific page.
 +
 +
'''Adding hooks''' to both ''ProcessPageEdit::buildForm'' and ''ProcessPageListActions::getExtraActions'' enabling me to add the two buttons.
 +
 +
Checking to see if one of the buttons was clicked with my '''ready() function''' then setting the corresponding page’s checkbox to true.
 +
 +
Using a '''LazyCron hook function''' to check all pages that are unpublished for true checkboxes and then comparing the seconds field to see if the page needs publishing. If not then deduct the elapsed time in seconds.
 +
 +
The result is a '''module that has settings''' (the time interval to check and publish at a later time), '''hooks into the admin''' using buttons and fields, and enables us to '''use other modules installe'''d in PW (i.e. LazyCron).
 +
 +
<syntaxhighlight lang="php">
 +
<?php
 +
/**
 +
* DeferredPublish (0.0.1)
 +
* DeferredPublish publishes a page after a defined amount of time has elapsed using lazycron.
 +
*
 +
* @author Ben Byford
 +
* http://www.benbyford.com
 +
*
 +
* ProcessWire 2.x
 +
* Copyright (C) 2011 by Ryan Cramer
 +
* Licensed under GNU/GPL v2, see LICENSE.TXT
 +
*
 +
* http://www.processwire.com
 +
* http://www.ryancramer.com
 +
*
 +
*/
 +
 +
class PageDeferredPublish extends WireData implements Module {
 +
 +
public static function getModuleInfo() {
 +
return array(
 +
'title' => "PageDeferredPublish",
 +
'version' => "0.0.1",
 +
'summary' => "PageDeferredPublish",
 +
'author' => "Ben Byford",
 +
'href' => "https://github.com/benbyford/PW-intermediate-modules",
 +
'icon' => "clock-o",
 +
'autoload' => true,
 +
'singular' => true,
 +
'requires' => "LazyCron",
 +
);
 +
}
 +
 +
private $postPubLater = null;
 +
private $pageID = null;
 +
private $pagePubLater = null;
 +
private $currentPage = null;
 +
 +
private $submitName = 'pdp_pub_later_submit';
 +
private $listPageName = 'pdp_pub_later_list';
 +
private $fieldName = 'pdp_pub_later';
 +
private $checkboxName = 'pdp_pub_later_check';
 +
 +
private $defaultInterval = 'everyMinute';
 +
private $defaultTime = 86400;
 +
 +
 +
public function install(){
 +
// add new fields needed for module
 +
$this->installFields();
 +
}
 +
public function uninstall(){
 +
// uninstall fields
 +
$this->uninstallFields();
 +
}
 +
 +
// initialize the hook in your AutoLoad module
 +
public function init() {
 +
 +
// get defaults from module setting in CMS
 +
$this->defaultTime = $this->pub_after;
 +
$this->defaultInterval = $this->cron_check;
 +
 +
// get admin URL
 +
$this->adminUrl = $this->wire('config')->urls->admin;
 +
 +
// add hooks to CRON, PageList, PageEdit
 +
    $this->addHookAfter("LazyCron::{$this->defaultInterval}", $this, 'publishDefferedPages');
 +
$this->addHook("ProcessPageListActions::getExtraActions", $this, 'hookPageListActions');
 +
$this->addHookAfter("ProcessPageEdit::buildForm", $this, "editForm");
 +
}
 +
 +
public function ready() {
 +
 +
// if list button clicked then grab id
 +
$this->pagePubLater = $this->input->get($this->listPageName);
 +
$this->pageID = $this->input->id;
 +
 +
// get page
 +
$this->currentPage = $this->pages->get($this->pageID);
 +
 +
// if pagelist pub later submit button clicked
 +
if($this->pagePubLater){
 +
$this->message("Page {$this->currentPage->name} deffered for publish.");
 +
}
 +
 +
// if page edit submit button clicked
 +
$this->postPubLater = $this->input->post($this->submitName);
 +
if($this->postPubLater){
 +
$this->message("Page {$this->currentPage->name} deffered for publish.");
 +
}
 +
 +
// if either deffered publish sumbit found then publish page later
 +
if($this->pagePubLater || $this->postPubLater){
 +
$this->publishLater();
 +
}
 +
}
 +
 +
/*
 +
* Hook: ProcessPageEdit::buildForm
 +
*
 +
* add Publish Later button to edit page if not yet published
 +
*/
 +
public function ___editForm(HookEvent $form) {
 +
 +
// get the InputFieldForm object from the event (return value of buildForm())
 +
$form = $form->return;
 +
 +
$page = $this->pages->get($this->input->get->id);
 +
$check = $page->get($this->checkboxName);
 +
 +
// check if publish button available and therfore unpublished
 +
$target = $form->get('submit_publish');
 +
 +
if($target && $check == false){
 +
 +
// get InputfieldText module
 +
$submit2 = $this->modules->get('InputfieldSubmit');
 +
$submit2->attr('name', $this->submitName);
 +
$submit2->attr('id', 'publish_later');
 +
$submit2->attr('class', 'ui-button ui-widget ui-corner-all head_button_clone ui-state-default ui-priority-secondary');
 +
$submit2->attr('value', 'Publish Later'); // Button: save unpublished
 +
 +
// get form element save and place before
 +
$target = $form->get('submit_save');
 +
$form->insertBefore($submit2, $target);
 +
 +
$form->return = $form;
 +
}
 +
}
 +
 +
public function ___hookPageListActions(HookEvent $event) {
 +
 +
// get current page
 +
$page = $event->arguments[0];
 +
 +
// check to see if page is published
 +
$pagePub = $page->is(Page::statusUnpublished);
 +
 +
// check to see if page template has deffered field
 +
$pageHasDefferField = $page->get($this->fieldName);
 +
 +
$actions = array();
 +
 +
// don't get homepage or pages that are already published or are being deffered for publish
 +
if($page->id > 1 && $pagePub == "published" && !is_null($pageHasDefferField) && $page->get($this->checkboxName) == false) {
 +
 +
$actions['publish_later'] = array(
 +
'cn'  => 'PublishLater',
 +
'name' => 'pub later',
 +
'url'  => "{$this->adminUrl}?{$this->listPageName}=1&id={$page->id}",
 +
);
 +
}
 +
if(count($actions)) $event->return = $actions + $event->return;
 +
}
 +
 +
/*
 +
* Publish Page on cron job
 +
*
 +
* Gets deffered page and publishes
 +
*/
 +
public function ___publish($page) {
 +
$page->removeStatus(Page::statusUnpublished);
 +
$page->removeStatus(Page::statusHidden);
 +
$page->Save();
 +
}
 +
 +
/*
 +
* Main publish later function
 +
*
 +
* Gets deffered page and sets fields to be checked be lazy cron
 +
*/
 +
private function ___publishLater() {
 +
 +
// get page
 +
$page = $this->pages->get($this->pageID);
 +
 +
// set output formatting to false
 +
$page->of(false);
 +
 +
//  set field time to settings default time and checkbox to true
 +
$page->set($this->checkboxName, true);
 +
$page->set($this->fieldName, $this->defaultTime);
 +
 +
// save page
 +
$page->save();
 +
$page->of(true);
 +
}
 +
 +
/*
 +
* Lazy Cron hook function
 +
*
 +
* Triggers every [set time interval] and checks pages
 +
* Publishes page if time runout
 +
*
 +
* Adds publish page log
 +
*/
 +
public function ___publishDefferedPages(HookEvent $e){
 +
 +
// seconds since last lazycron
 +
$seconds = $e->arguments[0];
 +
 +
// find all pages with deffered field
 +
$defferedPages = $this->pages->find("{$this->checkboxName}=1,include=unpublished");
 +
 +
// for each page decrease time for deffered field
 +
foreach ($defferedPages as $page) {
 +
 +
// get current page time
 +
$timeTillPublish = $page->get($this->fieldName);
 +
 +
// set time to time minus time past
 +
$timeLeft = $timeTillPublish - $seconds;
 +
 +
// if time passed 0 or less then publish page
 +
$page->of(false);
 +
if($timeLeft <= 0){
 +
// remove flags and save
 +
$this->publish($page);
 +
 +
$page->set($this->fieldName, 0);
 +
$page->set($this->checkboxName, 0);
 +
 +
// log a page has been published
 +
$log = wire('log');
 +
$log->message('CRON:'. $seconds .' Pages: '. $page->name .' published');
 +
}else{
 +
$page->set($this->fieldName, $timeLeft);
 +
}
 +
// save page time
 +
$page->Save();
 +
$page->of(true);
 +
}
 +
}
 +
 +
/*
 +
* Install new module specific fields
 +
*/
 +
private function installFields(){
 +
 +
// install pub later checkbox field
 +
// find installed fields
 +
$f = $this->fields->get($this->fieldName);
 +
if($f){
 +
 +
// if field already found then don't try and make it
 +
$this->message($this->fieldName . ' field found');
 +
 +
}else{
 +
 +
// create new field object to store crontime
 +
$f = new Field();
 +
// get a field type
 +
$f->type = $this->modules->get("FieldtypeInteger");
 +
$f->name = $this->fieldName;
 +
$f->label = 'Publish Time Left';
 +
$f->defaultValue = $this->defaultTime;
 +
$f->icon = 'clock-o';
 +
$f->columnWidth = 50;
 +
$f->collapsed = 1;
 +
$f->flags = 4;
 +
 +
$f->save(); // save the field
 +
 +
// create new field object to store whether to publish or not
 +
$f = new Field();
 +
// get a field type
 +
$f->type = $this->modules->get("FieldtypeCheckbox");
 +
$f->name = $this->checkboxName;
 +
$f->label = 'Publish Page later';
 +
$f->defaultValue = false;
 +
$f->columnWidth = 50;
 +
$f->icon = 'clock-o';
 +
$f->collapsed = 1;
 +
$f->flags = 4;
 +
 +
$f->save(); // save the field
 +
}
 +
}
 +
 +
/*
 +
* Uninstall module specific fields
 +
*/
 +
private function uninstallFields(){
 +
 +
// find installed fields
 +
$fInt = $this->fields->get($this->fieldName);
 +
$fCheck = $this->fields->get($this->checkboxName);
 +
 +
if($fInt && $fCheck){
 +
$fieldIntUsed = $fInt->numFieldgroups();
 +
$fieldCheckUsed = $fCheck->numFieldgroups();
 +
 +
if($fieldIntUsed){
 +
// field in use by template
 +
$this->message('Unable to uninstall field '.$fInt->name);
 +
}else{
 +
// delete installed fields
 +
$this->fields->delete($fInt);
 +
$this->fields->save;
 +
}
 +
 +
if($fieldCheckUsed){
 +
// field in use by template
 +
$this->message('Unable to uninstall field '.$fCheck->name);
 +
}else{
 +
// delete installed fields
 +
$this->fields->delete($fCheck);
 +
$this->fields->save;
 +
}
 +
}
 +
}
 +
}
 +
</syntaxhighlight>
 +
 +
=== Inputfield / Fieldtype ===
 +
[[ProcessWire - Fieldtype erstellen (Module)]]
 +
 +
=== Konfigurationsdatei statt getModuleInfo ===
 +
Bei größeren Konfigruation (z.B. Admin Page Navigation) sinnvoll.
 +
<syntaxhighlight lang="php">
 +
<?php
 +
/**
 +
* ProcessHello.info.php
 +
*
 +
* Return information about this module.
 +
*
 +
* If preferred, you can use a getModuleInfo() method in your module file,
 +
* or you can use a ModuleName.info.json file (if you prefer JSON definition).
 +
*
 +
*/
 +
$info = array(
 +
// Your module's title
 +
'title' => 'Hello: Process Module Example',
 +
// A 1 sentence description of what your module does
 +
'summary' => 'A starting point module skeleton from which to build your own Process module.',
 +
// Module version number: use 1 for 0.0.1 or 100 for 1.0.0, and so on
 +
'version' => 1,
 +
// Name of person who created this module (change to your name)
 +
'author' => 'Ryan Cramer',
 +
// Icon to accompany this module (optional), uses font-awesome icon names, minus the "fa-" part
 +
'icon' => 'thumbs-up',
 +
// URL to more info: change to your full modules.processwire.com URL (if available), or something else if you prefer
 +
'href' => 'http://modules.processwire.com/',
 +
// name of permission required of users to execute this Process (optional)
 +
'permission' => 'helloworld',
 +
// permissions that you want automatically installed/uninstalled with this module (name => description)
 +
'permissions' => array(
 +
'helloworld' => 'Run the HelloWorld module'
 +
),
 +
 +
// page that you want created to execute this module
 +
'page' => array(
 +
'name' => 'helloworld',
 +
'parent' => 'setup',
 +
'title' => 'Hello World'
 +
),
 +
// optional extra navigation that appears in admin
 +
// if you change this, you'll need to a Modules > Refresh to see changes
 +
'nav' => array(
 +
array(
 +
'url' => '',
 +
'label' => 'Hello',
 +
'icon' => 'smile-o',
 +
),
 +
array(
 +
'url' => 'something/',
 +
'label' => 'Something',
 +
'icon' => 'beer',
 +
),
 +
)
 +
// for more options that you may specify here, see the file: /wire/core/Process.php
 +
// and the file: /wire/core/Module.php
 +
);
 +
</syntaxhighlight>
 +
 +
== Interessantes - Read On ==
 +
https://processwire.com/talk/topic/24067-solved-settings-fields-that-dont-save-to-database/
 +
https://processwire.com/talk/topic/24075-solved-create-field-and-add-to-template/
 +
[[ProcessWire Modules - common functions]]

Aktuelle Version vom 9. September 2022, 17:49 Uhr

Links[Bearbeiten]

Wichtigste Resourcen

https://processwire.com/docs/modules/development/ - Guter Ausgangspunkt
https://processwire.com/api/ref/module/ - Module API
https://processwire.com/docs/modules/types/ - Welche Modultypen gibt es ?
https://processwire.com/talk/topic/778-module-dependencies/ - Module die andere Module benötigen
https://processwire.com/talk/forum/19-moduleplugin-development/ - Forum zum Thema Module entwickeln
http://somatonic.github.io/Captain-Hook/ - Hook Cheatsheet
https://processwire.com/blog/posts/new-module-configuration-options/ - Neuere Konfigurationsmöglichkeiten
https://processwire.com/docs/start/api-access/ Zugriff auf ProcessWire API Variablen (wire Objekt)

Tutorials und Beispiele zum Einstieg

https://processwire.com/docs/modules/ - Introduction, Development, Hooks, Types, Pro Modules, Third Party
https://github.com/ryancramerdesign/ProcessHello - Modul Skelett Beispiel für eigene Backend (Process) Module
https://github.com/ryancramerdesign/FieldtypeMapMarker - Beispiel für ein Fieldtype und Inputfield Modul
https://processwire.com/blog/posts/building-custom-admin-pages-with-process-modules/ - super Tutorial zu Admin Modulen
https://webdesign.tutsplus.com/tutorials/a-beginners-introduction-to-writing-modules-in-processwire--cms-26862

Wikiseiten

ProcessWire - Module Snippets
ProcessWire - Fieldtype erstellen (Module)
Process Module (ProcessWire)

Weitere Links

http://modules.pw/ (Module Creator)

Wo - Was ?[Bearbeiten]

Welche Typen von Modulen gibt es ?[Bearbeiten]

https://processwire.com/docs/modules/types/
  • Fieldtype: Repräsentiert einen Datentyp. Meistens keine Public API, Handelt Daten / Datenbank - ProcessWire - Fieldtype erstellen (Module)
  • Inputfield: Sammelt User Eingaben über ein Formular im Admin Bereich. Im Gegensatz zum Fieldtype geht es hier um das UI im Backend. Das Handling der Daten liegt beim Fieldtype.
  • Process for creating admin processes/applications.
  • Textformatter for formatting text.
  • AdminTheme for creating themes in the admin.
  • WireMail for modules that send email and extend the WireMail class.
  • Tfa for implementing a specific kind of two-factor authentication.
  • ImageSizerEngine for modules that extend ImageSizerEngine for resizing images.
  • FileCompiler for modules that extend FileCompilerModule for compilation of files.
  • FileValidator for modules that extend FileValidatorModule for validation of files.

Wie werden Felder in der Datenbank angelegt ?[Bearbeiten]

Felder die in der Datenbank speichern sind vom Typ "Fieldtype", meistens in Kombination mit einem Inputfield das dann die Frontendausgabe übernimmt. Module können auch für ihre eigene Konfiguration Daten in der Datenbank speichern. Das geht z.B. über eine ModulnameConfig Datei (s.u.).

Namenskonventionen[Bearbeiten]

Ein Modulname sollte mit dem Typ den das Modul hat beginnen. Also etwa ProcessMeinAdminModul oder FieldtypeTable. ProcessWire listet das Modul dann in der Entsprechenden Kategorie auf. Wenn man etwas anderes nimmt z.B. LoginRegister dann kommt das Modul unter der neuen Rubrik "Login"

Konfigurierbare Module[Bearbeiten]

Das geht seit 2.5.5 mittlerweile sehr einfach mit den neuen configuration-options.

https://processwire.com/blog/posts/new-module-configuration-options/
ProcessWire - Konfigurierbare Module

Spezielle Funktionen in Modulen[Bearbeiten]

Modulinformation[Bearbeiten]

Das Modul stellt Infos zur Verfügung und nutzt dafür eine von 3 Methoden:

   getModuleInfo() static method in your module class that returns an array.
   YourModuleClass.info.php file that populates an $info array.
   YourModuleClass.info.json file that contains an info object.

Hier werden title, version, summary und weitere Daten hinterlegt. Hier kann man auch angeben ob ein Modul ein Autoload Modul ist.

https://processwire.com/api/ref/module/

init[Bearbeiten]

public function init(){}

Initialisiert das Modul. ProcessWire ruft diese Funktion auf, wenn das Modul geladen ist. Bei Autoload Modulen wird es aufgerufen wenn die ProcessWire API bereit ist. Daher ist es ein guter Ort um Hooks einzubinden.

ready[Bearbeiten]

  public function ready() {
    if($this->page->template == 'admin') {
      $this->message($this->hi());
    }
  }

Wird in autoload Modulen aufgerufen wenn die API bereit ist. Nützlich für Hooks:

execute[Bearbeiten]

	/**
	 * This function is executed when a page with your Process assigned is accessed. 
 	 *
	 * This can be seen as your main or index function. You'll probably want to replace
	 * everything in this function. 
	 *
	 */
	public function ___execute() {
		// greetingType and greeting are automatically populated to this module
		// and they were defined in ProcessHello.config.php
		if($this->greetingType == 'message') {
			$this->message($this->greeting); 
		} else if($this->greetingType == 'warning') {
			$this->warning($this->greeting); 
		} else {
			$this->error($this->greeting); 
		}
		// generate some navigation
		$out = 	"
			<h2>$this->greeting</h2>
			<dl class='nav'>
				<dt><a href='./something/'>Do Something</a></dt>
				<dd>Runs the executeSomething() function.</dd>
			</dl>
			";
		return $out;
	}

executeSomething[Bearbeiten]

Ruft man eine Unterseite des Moduls auf kann man eine eigene execute Funktion nutzen ___executeSlug wobei Slug für den Url Slug steht. Heißt der slug /field/ dann wird executeField() aufgerufen, falls diese Funktion existiert.

	/**
	 * Called when the URL is this module's page URL + "/something/"
	 *
	 */
	public function ___executeSomething() {
		// set a new headline, replacing the one used by our page
		// this is optional as PW will auto-generate a headline 
		$this->headline('This is something!'); 
		// add a breadcrumb that returns to our main page 
		// this is optional as PW will auto-generate breadcrumbs
		$this->breadcrumb('../', 'Hello'); 
		$out = 	"
			<h2>Not much to to see here</h2>
			<p><a href='../'>Go Back</a></p>
			";
		return $out; 
	}
  • executeMySecondPage translates to my-second-page in the URL
  • executeMysecondpage translates to mysecondpage in the URL

install & uninstall[Bearbeiten]

/**
	 * Called only when your module is installed
	 *
	 * If you don't need anything here, you can simply remove this method. 
	 *
	 */
	public function ___install() {
		parent::___install(); // always remember to call parent method
	}
	/**
	 * Called only when your module is uninstalled
	 *
	 * This should return the site to the same state it was in before the module was installed. 
	 *
	 * If you don't need anything here, you can simply remove this method. 
	 *
	 */
	public function ___uninstall() {
		parent::___uninstall(); // always remember to call parent method
	}

Vererbung und Eigenschaften in Modulen[Bearbeiten]

Modules are not different from PHP classes.

To change the properties of MyModule class from within MyOtherModule class, you can either:

  1. Make MyOtherModule class extend MyModule. It will thus inherit MyModule's public and protected properties and methods
  2. Make the properties firstName and lastName configurable by passing them as parameters/arguments in MyModule's constructor method.

Autoload Module und Hooks[Bearbeiten]

Autoload werden automatisch geladen müssen also nicht in einem Template o.ä. gestartet werden. Daher bieten sie sich an um die Funktionalität von ProcessWire zu erweitern. Als Werkzeug dafür dienen Hooks. Mit Hooks kann man an vielen Stellen den Rendering Process der Seiten beeinflussen. Außerdem kann man für eigene Module ebenfalls Hooks bereitstellen.

Hooks[Bearbeiten]

https://processwire.com/docs/modules/hooks/

Hooks sind ein mächtiges Werkzeug. Sie geben einem die Möglichkeit an vielen Stellen "einzuhaken" und Funktionalität einzubauen.

public function ready() {
  $this->addHookAfter('Page::render', function($event) {
    $event->return .= '<p>Hallo</p>';
  });
}

Schreibt am Ende Jeder Seite ein "Hallo". Das $event Objekt ist ein HookEvent. Im Beispiel fügen wir einfach etwas Markup hinzu. Über $event->arguments() kann man aber auf ale Argumente zugreifen.

Das gleiche aber nicht mit anonymer Funktion:

public function ready() {
  // add hook after Page::render() and make it call the "test" method of $this module
  $this->addHookAfter('Page::render', $this, 'test');
}

public function test($event) {
  // modify the return value of Page::render() to include the following:
  $event->return .= '<p>Hallo</p>';
}

Hooks zum erweitern von existierenden Klassen nutzen[Bearbeiten]

Mit Hooks kann man in vorhandene Klassen einhaken. Z.B. läßt sich das Page Objekt erweitern.

public function ready() {
  $this->addHook('Page::summarize', $this, 'summarize');
}
public function summarize($event) {
  $page = $event->object; // the $event->object represents the object hooked (Page)
  $maxlen = $event->arguments(0); // first argument is the optional max length
  if(!$maxlen) $maxlen = 200; // if no $maxlen was present, we'll use a default of 200
  $summary = $this->sanitizer->truncate($page->body, $maxlen); // use sanitizer truncate method to create a summary
  $event->return = $summary; // populate $summary to $event->return, the return value
}

So kann man ganz einfach eine Zusammenfassung aller Kindseiten in einem Template rendern:

foreach($page->children as $item) {
  $summary = $item->summarize(150);
  echo "<li><a href='$item->url'>$item->title</a><br>$summary";
}

Autoload Module nur im Admin Bereich laden[Bearbeiten]

'autoload' => 'template=admin' 

statt

'autoload' => true

Hook Beispiele[Bearbeiten]

Hook am Ende des Renderings

$this->addHookAfter('Page::render', function($event) {...});

Füge eine Funktion "summarize" zum Page Objekt hinzu.

$this->addHook('Page::summarize', $this, 'summarize');

Hook auf eine einzelne Instanz (hier pages Objekt)

$this->pages->addHookAfter('saved', function($event){...});
$this->addHook('Page::method', ...) // Spricht ALLE Instanzen an - Regelfall
$page->addHook('method', ...) // Spricht nur die EINE Instanz der Seite an.

Module Dependencies - voneinander abhängige Module[Bearbeiten]

https://processwire.com/talk/topic/778-module-dependencies/

Wenn ein Modul nicht ohne ein anderes funktioniert spricht man von Module Dependency. In solchen Fällen kann man in der Modul Info angeben In vielen Fällen besteht ein Modul unter der Haube aus mehreren Einzelmodulen.

Um ProcessWire mitzuteilen dass ein Modul benötigt wird gibt man es in der Moduldefinition an:

'requires' => "LazyCron"  // added this line

oder auch mehrere als Array

'requires' => array("LazyCron", "AdminBar")  // added this line

ProcessWire kann auch dafür sorgen, dass ein Modul andere "Kindmodule" gleich mitinstalliert bzw. deinstalliert wenn es nicht mehr benötigt wird. Dazu gibt man dem Modul noch die 'install' dependency mit:

<?php
public static function getModuleInfo() {
   return array(
       'title' => 'Log Master',
       'version' => 101,
       'author' => 'Lumberjack Bob',
       'summary' => 'Log all actions on your site',
       'requires' => 'LazyCron', 
       'installs' => 'ProcessLogMaster'  // added this line
       ); 
);

ProcessWire sieht dann eine Kindabhängigkeit zu diesem Modul und handelt auch das Deinstallieren, wenn das Elternmodul deinstalliert wird.

CSS und JavaScript in Modulen[Bearbeiten]

Bei Process Modulen möchte man für das Styling etc. im Backend oft CSS und JS Dateien hinzufügen.

Einfach Eine Datei MeinModul.css und / oder MeinModul.js hinzufügen. JQuery ist im Admin bereits geladen daher kann eine MeinModul.js Starter Datei so aussehen:


/**
 * This JS file is only loaded when the ProcessHello module is run
 *
 * You should delete it if you have no javascript to add.
 *
 */

$(document).ready(function() {
	// do something
}); 

Flexibler Umgang mit Templates - TemplateFile[Bearbeiten]

TemplateFile sind eine Möglichkeit Templates zu Managen. Nützlich in Modulen oder in größeren Teams wo man sehr granuliert die Zugriffe über den Code steuern möchte.

https://processwire.com/talk/topic/291-simple-tutorial-on-how-to-use-templatefile-class/
https://processwire.com/api/ref/template-file/
<?php

$out = new TemplateFile("./includes/my_markup.html"); 
$out->set('headline', $page->get("headline|title")); 
$out->set('body', $page->body);
$out->set('sidebar', $page->sidebar); 
$out->set('navigation', $pages->find("parent=/"));
echo $out->render();

In my_markup.html kann man headline, body... einfach als Variablen setzen. Beim Rendern werden diese dann ersetzt.

Beispiele[Bearbeiten]

Felder und Templates erstellen[Bearbeiten]

In einem Modul kann man Felder und Templates automatisch erstellen.

https://processwire.com/talk/topic/20533-solved-fields-templates-and-fieldgroups-how-do-they-interact/
For everyone not deeply into obscure PW vodoo magic, creating a template with fields is always:
   Create a template
   Create a fieldgroup with the same name as the template
   Create your fields, save them and add them to the fieldgroup
   Save the fieldgroup
   Set the fieldgroup in the template
   Save the template
  • Are Fieldgroups required for adding fields to templates? What are these for?
    • Yes, they are. As written above, they are just a necessary glue.
  • What if I'd like to add fields after the template was saved and how can I add them to the same fieldgroup?
    • Just add them to the fieldgroup
$fg = $template->fieldgroup;
$fg->add($field);
$fg->save();

Frontend Rendering Module[Bearbeiten]

A bit old but working

<?php

/**
 * FrontEndRender
 *
 * Author: Ben Byford
 *
 * ProcessWire 2.x
 * Copyright (C) 2010 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class FrontEndRender extends WireData implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => 'FrontEndRender',
			'version' => 0.1,
			'summary' => "Outputs html and static variables to frontend",
			'author' => 'Ben Byford',
			'singular' => true,
			'href' => 'https://github.com/benbyford/PW-starter-modules'
		);
	}

	// protected variable only accessable within module
	protected $name = 'Ben';

	/*
	* render function to be called in PW template like this:
	* $FrontEndRender = $modules->getModule('FrontEndRender');
	* echo '<h1>' . $FrontEndRender->render() . '</h1>';
	*
	*/
	public function render(){
		return "Hello " . $this->name;
	}
}
?>

ProcessWire Hello World Modul[Bearbeiten]

Beispiel Modul mit Hooks (liegt immer in der Standardinstallation)

<?php namespace ProcessWire;

/**
 * ProcessWire 'Hello world' demonstration module
 *
 * Demonstrates the Module interface and how to add hooks.
 * 
 * See README file for further links regarding module development.
 * 
 * This file is licensed under the MIT license
 * https://processwire.com/about/license/mit/
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 *
 */

class Helloworld extends WireData implements Module {

	/**
	 * getModuleInfo is a module required by all modules to tell ProcessWire about them
	 *
	 * @return array
	 *
	 */
	public static function getModuleInfo() {

		return array(

			// The module's title, typically a little more descriptive than the class name
			'title' => 'Hello World', 

			// version number 
			'version' => 3, 

			// summary is brief description of what this module is
			'summary' => 'An example module used for demonstration purposes.',
			
			// Optional URL to more information about the module
			'href' => 'https://processwire.com',

			// singular=true: indicates that only one instance of the module is allowed.
			// This is usually what you want for modules that attach hooks. 
			'singular' => true, 

			// autoload=true: indicates the module should be started with ProcessWire.
			// This is necessary for any modules that attach runtime hooks, otherwise those
			// hooks won't get attached unless some other code calls the module on it's own.
			// Note that autoload modules are almost always also 'singular' (seen above).
			'autoload' => true, 
		
			// Optional font-awesome icon name, minus the 'fa-' part
			'icon' => 'smile-o', 
			);
	}

	/**
	 * Initialize the module
	 *
	 * ProcessWire calls this when the module is loaded. For 'autoload' modules, this will be called
	 * when ProcessWire's API is ready. As a result, this is a good place to attach hooks. 
	 *
	 */
	public function init() {

		// add a hook after the $pages->save, to issue a notice every time a page is saved
		$this->pages->addHookAfter('save', $this, 'example1'); 

		// add a hook after each page is rendered and modify the output
		$this->addHookAfter('Page::render', $this, 'example2'); 

		// add a 'hello' method to every page that returns "Hello World"
		// use "echo $page->hello();" in your template file to display output
		$this->addHook('Page::hello', $this, 'example3'); 

		// add a 'hello_world' property to every page that returns "Hello [user]"
		// use "echo $page->hello_world;" in your template file to display output
		$this->addHookProperty('Page::hello_world', $this, 'example4'); 
	}

	/**
	 * Example1 hooks into the pages->save method and displays a notice every time a page is saved
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example1($event) {
		/** @var Page $page */
		$page = $event->arguments[0]; 
		$this->message("Hello World! You saved {$page->path}."); 
	}


	/**
	 * Example2 hooks into every page after it's rendered and adds "Hello World" text at the bottom
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example2($event) {

		/** @var Page $page */
		$page = $event->object; 

		// don't add this to the admin pages
		if($page->template == 'admin') return;

		// add a "Hello World" paragraph right before the closing body tag
		$event->return = str_replace("</body>", "<p>Hello World!</p></body>", $event->return); 
	}

	/**
	 * Example3 adds a 'hello' method (not property) to every page that simply returns "Hello World"
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example3($event) {
		$event->return = "Hello World";
	}

	/**
	 * Example 4 adds a 'hello_world' property (not method) to every page that returns "Hello [user]"
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function example4($event) {
		$event->return = "Hello " . $this->user->name; 
	}
	
}

ProcessWire Admin Bereich mit eigener Funktionalität erweitern (Modul)[Bearbeiten]

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
<?php
/**
* ProcessSimpleAdminPage
*
* @author Ben Byford
* http://www.benbyford.com
*
* @see http://www.processwire.com
*/

class ProcessSimpleAdminPage extends Process {

    public static function getModuleInfo() {
        return array(
            'title' => 'Process Simple Admin Page',
            'summary' => 'Simple Process module that adds new admin page with',
            'version' => 001,

            // Modules that extend Process may specify a 'page' attribute in the
            // getModuleInfo(), this page will automatically be given the module
            // process when added to teh pagetree.

            // I have exampled but commented out the 'page' settings below
            // so that I can show how one might add a page to install() and
            // uninstall() in this and other modules (that might not extend
            // Process)


        // 	'page' => array(
        // 		'name' => 'site-config',
        // 		'parent' => 'admin',
        // 		'title' => 'Site Config'
        // 	   )
        );
    }

    public function execute() {
        return '
            <h2>Edit the text here in the module</h2>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mattis eros vitae metus sodales eget suscipit purus rhoncus. Proin ultrices gravida dolor, non porttitor enim interdum vitae. Integer feugiat lacinia tincidunt. Nulla laoreet tristique tristique. Sed elementum justo a nisl elementum sit amet accumsan nisi tempor. Nulla quis eros et massa dignissim imperdiet a vitae purus.</p>

            <p>Donec scelerisque pulvinar sem eu lobortis. Maecenas turpis ipsum, tempus dictum pharetra eu, consectetur vitae arcu. Fusce orci mauris, semper at tempus quis, volutpat molestie tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed quam tortor, tincidunt sed semper lacinia, scelerisque dapibus quam. Morbi at nisi luctus lacus auctor ultrices eu eu leo.</p>

            <p>Praesent faucibus purus id felis tincidunt dignissim. Sed sit amet ligula mi, eget semper dui. Proin consectetur gravida massa, nec luctus purus hendrerit in. Etiam volutpat, elit non venenatis suscipit, libero neque consectetur diam, id rutrum magna odio ac ligula. Maecenas sollicitudin congue neque fermentum vestibulum. Morbi nec leo nisi. Donec at nisl odio, et porta ligula.</p>

            <p>Sed quis arcu nisi, ac tempor augue. Praesent non elit libero, a ullamcorper lorem. Curabitur porta odio eu nunc ultricies interdum id nec risus. Donec nibh nibh, porta eget vehicula ac, aliquet eget ante. Phasellus eget lorem eu eros eleifend ultrices. Cras sit amet neque sit amet nibh fringilla cursus ut id mauris. Praesent quis nunc justo, sed suscipit lectus. Phasellus eget ultrices risus. Curabitur eu semper est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut suscipit, nisl ut imperdiet eleifend, turpis arcu placerat tortor, nec laoreet lacus neque ac tellus. Aenean ac lacus justo, quis ultricies nisi.</p>
            ';
    }
    public function install(){

        // create new page to add to CMS
		$page = new Page();

        // add page attributes
        $page->template = "admin";
        $page->name = "cms-faq";
        $page->title = "CMS FAQ";
        $page->save();

        // set this module as the page process, this allows us to display the above
        $page->process = 'ProcessSimpleAdminPage';

        // get admin page and set as page parent
        $admin = $this->pages->get("id=2");
        $page->parent = $admin;

        // save page
        $page->save();
	}

	public function uninstall(){

        // delete created page
        $page = $this->pages->get("name=cms-faq");
        if(count($page)) $this->pages->delete($page, true);
	}
}

ProcessWire Textformatter Modul[Bearbeiten]

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863
<?php

/**
 * TextformatterFindReplace
 *
 * Author: Ben Byford
 *
 * ProcessWire 2.x
 * Copyright (C) 2010 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class TextformatterFindReplace extends Textformatter implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => 'TextformatterFindReplace',
			'version' => 0.1,
			'summary' => "Finds and replaces any instance of config input to config output",
			'author' => 'Ben Byford',
			'singular' => true,
			'href' => 'https://github.com/benbyford/PW-starter-modules'
		);
	}


	/**
     * Find and Replace the input string
     *
     * @param string $str The block of text to parse
     *
     * The incoming string is replaced with the formatted version of itself.
	 **/

	public function format(&$str) {
		$find = $this->findStr;
		$str = preg_replace_callback($find, array($this,"replace"), $str);

	}

	// adding three underscores to a function allows other modules to hook it
	public function ___replace($match) {
		return $this->replaceStr;
	}
}
?>

Admin Funktionalität: Countdown zum Seitenveröffentlichen[Bearbeiten]

https://webdesign.tutsplus.com/tutorials/extending-the-processwire-admin-using-custom-modules--cms-26863

The module PageDeferredPublish, on clicking one of the Publish Later buttons, sets LazyCron to check that page’s countdown every minute and publishes the page when its countdown reaches 0. This means I can publish a page approximately 24 hours in advance (obviously the checking interval and delay time can be changed to your requirements).

I did this by:

Creating two fields within my install() function: a checkbox field to set to true when a button is checked to indicate the page should countdown, and a countdown field to store the count in seconds for that specific page.

Adding hooks to both ProcessPageEdit::buildForm and ProcessPageListActions::getExtraActions enabling me to add the two buttons.

Checking to see if one of the buttons was clicked with my ready() function then setting the corresponding page’s checkbox to true.

Using a LazyCron hook function to check all pages that are unpublished for true checkboxes and then comparing the seconds field to see if the page needs publishing. If not then deduct the elapsed time in seconds.

The result is a module that has settings (the time interval to check and publish at a later time), hooks into the admin using buttons and fields, and enables us to use other modules installed in PW (i.e. LazyCron).

<?php
/**
 * DeferredPublish (0.0.1)
 * DeferredPublish publishes a page after a defined amount of time has elapsed using lazycron.
 *
 * @author Ben Byford
 * http://www.benbyford.com
 *
 * ProcessWire 2.x
 * Copyright (C) 2011 by Ryan Cramer
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 *
 * http://www.processwire.com
 * http://www.ryancramer.com
 *
 */

class PageDeferredPublish extends WireData implements Module {

	public static function getModuleInfo() {
		return array(
			'title' => "PageDeferredPublish",
			'version' => "0.0.1",
			'summary' => "PageDeferredPublish",
			'author' => "Ben Byford",
			'href' => "https://github.com/benbyford/PW-intermediate-modules",
			'icon' => "clock-o",
			'autoload' => true,
			'singular' => true,
			'requires' => "LazyCron",
		);
	}

	private $postPubLater 	= null;
	private $pageID 		= null;
	private $pagePubLater 	= null;
	private $currentPage	= null;

	private $submitName 	= 'pdp_pub_later_submit';
	private $listPageName	= 'pdp_pub_later_list';
	private $fieldName 		= 'pdp_pub_later';
	private $checkboxName	= 'pdp_pub_later_check';

	private $defaultInterval = 'everyMinute';
	private $defaultTime 	= 86400;


	public function install(){
		// add new fields needed for module
		$this->installFields();
	}
	public function uninstall(){
		// uninstall fields
		$this->uninstallFields();
	}

	// initialize the hook in your AutoLoad module
	public function init() {

		// get defaults from module setting in CMS
		$this->defaultTime = $this->pub_after;
		$this->defaultInterval = $this->cron_check;

		// get admin URL
		$this->adminUrl = $this->wire('config')->urls->admin;

		// add hooks to CRON, PageList, PageEdit
	    $this->addHookAfter("LazyCron::{$this->defaultInterval}", $this, 'publishDefferedPages');
		$this->addHook("ProcessPageListActions::getExtraActions", $this, 'hookPageListActions');
		$this->addHookAfter("ProcessPageEdit::buildForm", $this, "editForm");
	}

	public function ready() {

		// if list button clicked then grab id
		$this->pagePubLater = $this->input->get($this->listPageName);
		$this->pageID = $this->input->id;

		// get page
		$this->currentPage = $this->pages->get($this->pageID);

		// if pagelist pub later submit button clicked
		if($this->pagePubLater){
			$this->message("Page {$this->currentPage->name} deffered for publish.");
		}

		// if page edit submit button clicked
		$this->postPubLater = $this->input->post($this->submitName);
		if($this->postPubLater){
			$this->message("Page {$this->currentPage->name} deffered for publish.");
		}

		// if either deffered publish sumbit found then publish page later
		if($this->pagePubLater || $this->postPubLater){
			$this->publishLater();
		}
	}

	/*
	*	Hook: ProcessPageEdit::buildForm
	*
	*	add Publish Later button to edit page if not yet published
	*/
	public function ___editForm(HookEvent $form) {

		// get the InputFieldForm object from the event (return value of buildForm())
		$form = $form->return;

		$page = $this->pages->get($this->input->get->id);
		$check = $page->get($this->checkboxName);

		// check if publish button available and therfore unpublished
		$target = $form->get('submit_publish');

		if($target && $check == false){

			// get InputfieldText module
			$submit2 = $this->modules->get('InputfieldSubmit');
			$submit2->attr('name', $this->submitName);
			$submit2->attr('id', 'publish_later');
			$submit2->attr('class', 'ui-button ui-widget ui-corner-all head_button_clone ui-state-default ui-priority-secondary');
			$submit2->attr('value', 'Publish Later'); // Button: save unpublished

			// get form element save and place before
			$target = $form->get('submit_save');
			$form->insertBefore($submit2, $target);

			$form->return = $form;
		}
	}

	public function ___hookPageListActions(HookEvent $event) {

		// get current page
		$page = $event->arguments[0];

		// check to see if page is published
		$pagePub = $page->is(Page::statusUnpublished);

		// check to see if page template has deffered field
		$pageHasDefferField = $page->get($this->fieldName);

		$actions = array();

		// don't get homepage or pages that are already published or are being deffered for publish
		if($page->id > 1 && $pagePub == "published" && !is_null($pageHasDefferField) && $page->get($this->checkboxName) == false) {

			$actions['publish_later'] = array(
				'cn'   => 'PublishLater',
				'name' => 'pub later',
				'url'  => "{$this->adminUrl}?{$this->listPageName}=1&id={$page->id}",
			);
		}
		if(count($actions)) $event->return = $actions + $event->return;
	}

	/*
	* Publish Page on cron job
	*
	* Gets deffered page and publishes
	*/
	public function ___publish($page) {
		$page->removeStatus(Page::statusUnpublished);
		$page->removeStatus(Page::statusHidden);
		$page->Save();
	}

	/*
	* Main publish later function
	*
	* Gets deffered page and sets fields to be checked be lazy cron
	*/
	private function ___publishLater() {

		// get page
		$page = $this->pages->get($this->pageID);

		// set output formatting to false
		$page->of(false);

		//  set field time to settings default time and checkbox to true
		$page->set($this->checkboxName, true);
		$page->set($this->fieldName, $this->defaultTime);

		// save page
		$page->save();
		$page->of(true);
	}

	/*
	* Lazy Cron hook function
	*
	* Triggers every [set time interval] and checks pages
	* Publishes page if time runout
	*
	* Adds publish page log
	*/
	public function ___publishDefferedPages(HookEvent $e){

		// seconds since last lazycron
		$seconds = $e->arguments[0];

		// find all pages with deffered field
		$defferedPages = $this->pages->find("{$this->checkboxName}=1,include=unpublished");

		// for each page decrease time for deffered field
		foreach ($defferedPages as $page) {

			// get current page time
			$timeTillPublish = $page->get($this->fieldName);

			// set time to time minus time past
			$timeLeft = $timeTillPublish - $seconds;

			// if time passed 0 or less then publish page
			$page->of(false);
			if($timeLeft <= 0){
				// remove flags and save
				$this->publish($page);

				$page->set($this->fieldName, 0);
				$page->set($this->checkboxName, 0);

				// log a page has been published
				$log = wire('log');
				$log->message('CRON:'. $seconds .' Pages: '. $page->name .' published');
			}else{
				$page->set($this->fieldName, $timeLeft);
			}
			// save page time
			$page->Save();
			$page->of(true);
		}
	}

	/*
	* Install new module specific fields
	*/
	private function installFields(){

		// install pub later checkbox field
		// find installed fields
		$f = $this->fields->get($this->fieldName);
		if($f){

			// if field already found then don't try and make it
			$this->message($this->fieldName . ' field found');

		}else{

			// create new field object to store crontime
			$f = new Field();
			// get a field type
			$f->type = $this->modules->get("FieldtypeInteger");
			$f->name = $this->fieldName;
			$f->label = 'Publish Time Left';
			$f->defaultValue = $this->defaultTime;
			$f->icon = 'clock-o';
			$f->columnWidth = 50;
			$f->collapsed = 1;
			$f->flags = 4;

			$f->save(); // save the field

			// create new field object to store whether to publish or not
			$f = new Field();
			// get a field type
			$f->type = $this->modules->get("FieldtypeCheckbox");
			$f->name = $this->checkboxName;
			$f->label = 'Publish Page later';
			$f->defaultValue = false;
			$f->columnWidth = 50;
			$f->icon = 'clock-o';
			$f->collapsed = 1;
			$f->flags = 4;

			$f->save(); // save the field
		}
	}

	/*
	* Uninstall module specific fields
	*/
	private function uninstallFields(){

		// find installed fields
		$fInt = $this->fields->get($this->fieldName);
		$fCheck = $this->fields->get($this->checkboxName);

		if($fInt && $fCheck){
			$fieldIntUsed = $fInt->numFieldgroups();
			$fieldCheckUsed = $fCheck->numFieldgroups();

			if($fieldIntUsed){
				// field in use by template
				$this->message('Unable to uninstall field '.$fInt->name);
			}else{
				// delete installed fields
				$this->fields->delete($fInt);
				$this->fields->save;
			}

			if($fieldCheckUsed){
				// field in use by template
				$this->message('Unable to uninstall field '.$fCheck->name);
			}else{
				// delete installed fields
				$this->fields->delete($fCheck);
				$this->fields->save;
			}
		}
	}
}

Inputfield / Fieldtype[Bearbeiten]

ProcessWire - Fieldtype erstellen (Module)

Konfigurationsdatei statt getModuleInfo[Bearbeiten]

Bei größeren Konfigruation (z.B. Admin Page Navigation) sinnvoll.

<?php
/**
 * ProcessHello.info.php
 * 
 * Return information about this module.
 *
 * If preferred, you can use a getModuleInfo() method in your module file, 
 * or you can use a ModuleName.info.json file (if you prefer JSON definition). 
 *
 */
$info = array(
	// Your module's title
	'title' => 'Hello: Process Module Example', 
	// A 1 sentence description of what your module does
	'summary' => 'A starting point module skeleton from which to build your own Process module.', 
	// Module version number: use 1 for 0.0.1 or 100 for 1.0.0, and so on
	'version' => 1, 
	// Name of person who created this module (change to your name)
	'author' => 'Ryan Cramer', 
	// Icon to accompany this module (optional), uses font-awesome icon names, minus the "fa-" part
	'icon' => 'thumbs-up', 
	// URL to more info: change to your full modules.processwire.com URL (if available), or something else if you prefer
	'href' => 'http://modules.processwire.com/', 
	// name of permission required of users to execute this Process (optional)
	'permission' => 'helloworld', 
	// permissions that you want automatically installed/uninstalled with this module (name => description)
	'permissions' => array(
		'helloworld' => 'Run the HelloWorld module'
	), 
	
	// page that you want created to execute this module
	'page' => array(
		'name' => 'helloworld',
		'parent' => 'setup', 
		'title' => 'Hello World'
	),
	// optional extra navigation that appears in admin
	// if you change this, you'll need to a Modules > Refresh to see changes
	'nav' => array(
		array(
			'url' => '', 
			'label' => 'Hello', 
			'icon' => 'smile-o', 
		), 
		array(
			'url' => 'something/', 
			'label' => 'Something', 
			'icon' => 'beer', 
		),
	)
	// for more options that you may specify here, see the file: /wire/core/Process.php
	// and the file: /wire/core/Module.php
);

Interessantes - Read On[Bearbeiten]

https://processwire.com/talk/topic/24067-solved-settings-fields-that-dont-save-to-database/
https://processwire.com/talk/topic/24075-solved-create-field-and-add-to-template/
ProcessWire Modules - common functions