Ett WordPress-plugin är en PHP-fil med en kommentar högst upp. Allt annat är vanlig PHP och WordPress egna funktioner. I den här guiden bygger jag Driftnotis, ett plugin som visar en meddelanderad överst på sajten, till exempel ”Vi har stängt för inventering 24 december”. Administratören styr text, på/av och bakgrundsfärg från en sida under Inställningar. Hela pluginet ryms i en fil på 171 rader, och den kompletta filen finns längst ner i inlägget.
Jag valde just det här pluginet för att det tvingar fram allt som ett riktigt plugin behöver. Det har ett pluginhuvud och en inställningssida byggd med Settings API. Det sanerar det som sparas och escapar det som skrivs ut. Det har lite CSS, översättningsbara strängar och städar efter sig vid avinstallation. Samma mönster använder jag i betydligt större plugin åt kunder. Skillnaden är antalet rader, inte principerna.
Det här bygger vi
Resultatet är två saker. På sajten: en rad högst upp på varje sida med vit text på en färg du valt, och ingen rad alls när den är avstängd. I adminpanelen: sidan Inställningar > Driftnotis med ett textfält, en kryssruta, en färgväljare och en Spara-knapp.
Filstrukturen är den enklast möjliga.
wp-content/plugins/driftnotis/
└── driftnotis.php
Skapa mappen driftnotis i wp-content/plugins/, lägg filen driftnotis.php där och aktivera pluginet under Tillägg i adminpanelen. Mappen behövs även om det bara är en fil, eftersom WordPress identifierar pluginet som driftnotis/driftnotis.php och avinstallationen letar i mappen. Efter aktiveringen finns Driftnotis under Inställningar.
Jag bygger filen i fem steg. Varje steg är en del av samma fil, och kodblocket i slutet är hela filen i ett stycke.
Steg 1: pluginhuvudet och grundskyddet
Kommentaren högst upp är det som gör filen till ett plugin. WordPress läser den med get_plugin_data() när pluginlistan visas, och bara Plugin Name är obligatorisk. Jag tar med några till. Requires at least och Requires PHP hindrar aktivering på för gamla miljöer. Text Domain kopplar strängarna till översättningar, och License behövs om pluginet någon gång ska upp på wordpress.org.
Raden defined( 'ABSPATH' ) || exit; avbryter om någon anropar filen direkt i webbläsaren i stället för genom WordPress. Utan WordPress finns inte add_action(), och filen skulle annars dö med ett felmeddelande som avslöjar sökvägar på servern.
De två hjälpfunktionerna är hela datamodellen. Allt sparas i en enda option, driftnotis, som en array med tre nycklar. driftnotis_settings() slår ihop det sparade med standardvärdena, så resten av koden slipper kontrollera om en nyckel finns.
<?php
/**
* Plugin Name: Driftnotis
* Description: Visar en meddelanderad högst upp på sajten. Text, på/av och färg styrs under Inställningar > Driftnotis.
* Version: 1.0.0
* Requires at least: 6.7
* Requires PHP: 8.2
* Author: Kristoffer Pihlfelt
* License: GPL-2.0-or-later
* Text Domain: driftnotis
*/
defined( 'ABSPATH' ) || exit;
/**
* Default values. Used by register_setting() and as a fallback on output.
*/
function driftnotis_defaults() {
return array(
'text' => '',
'aktiv' => 0,
'farg' => '#1e3a5f',
);
}
/**
* Saved settings merged with the defaults, so every key always exists.
*/
function driftnotis_settings() {
return wp_parse_args( (array) get_option( 'driftnotis', array() ), driftnotis_defaults() );
}
Prefixet driftnotis_ på varje funktion är inte kosmetik. Alla aktiva plugin och temat delar samma namnrymd i PHP, och två funktioner med samma namn ger ett fatalt fel som tar ner hela sajten. Välj ett prefix som ingen annan rimligen använder.
Steg 2: inställningssidan
Sidan registreras på hooken admin_menu med add_options_page(). Funktionen tar sidtitel, menytext, rättigheten som krävs, en slug och funktionen som ritar sidan. Rättigheten manage_options har bara administratörer, och WordPress döljer menyvalet för alla andra. Jag kontrollerar rättigheten en gång till inuti ritfunktionen, eftersom en funktion som bara nås via menyn i dag kan få en annan väg in i morgon.
Själva sidan är ett formulär som postar till options.php. De tre anropen inuti formuläret är hela Settings API sett från sidan. settings_fields() skriver ut tre dolda fält: en nonce, vilken inställningsgrupp som sparas och åtgärden update. do_settings_sections() ritar alla sektioner och fält som registrerats för sidan i steg 3. submit_button() ger en knapp med rätt klasser.
<?php
// Part 2 of driftnotis.php: the settings page under Settings > Driftnotis.
add_action( 'admin_menu', 'driftnotis_add_page' );
function driftnotis_add_page() {
add_options_page(
__( 'Driftnotis', 'driftnotis' ),
__( 'Driftnotis', 'driftnotis' ),
'manage_options',
'driftnotis',
'driftnotis_render_page'
);
}
function driftnotis_render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields( 'driftnotis' );
do_settings_sections( 'driftnotis' );
submit_button( __( 'Spara', 'driftnotis' ) );
?>
</form>
</div>
<?php
}
Det som är lätt att missa är hur mycket options.php gör åt dig. Den kontrollerar att användaren har manage_options, att noncen från settings_fields() stämmer och att inställningsgruppen finns bland de registrerade. Först då sparar den värdet, via saneringsfunktionen i nästa steg, och skickar tillbaka till inställningssidan med meddelandet ”Inställningar sparade”. Jag skriver ingen egen formulärhantering alls.
Det är också därför jag inte läser $_POST själv. Gör du det måste du skapa noncen med wp_nonce_field(), kontrollera den med check_admin_referer(), kontrollera rättigheten och anropa update_option() på egen hand. Det är fem ställen att göra fel på, och jag har sett alla fem i plugin som kommit in för granskning. Settings API gör samma sak på tre rader.
Steg 3: registrera inställningen
På admin_init registrerar jag inställningen, en sektion och tre fält. register_setting() tar gruppen, optionens namn och en array med argument. type är array eftersom hela inställningen sparas som en. sanitize_callback pekar på funktionen som körs varje gång värdet sparas. default returneras av get_option() så länge inget sparats, och är anledningen till att sidan visar en färg redan första gången.
Sektionen behövs för att fälten ska ha någonstans att ritas. Varje add_settings_field() anger id, rubrik, funktionen som ritar fältet, sidan, sektionen och argumentet label_for. Det sista gör att WordPress omger rubriken med <label for="...">, så att ett klick på rubriken sätter fokus i fältet och skärmläsare får en riktig etikett.
<?php
// Part 3 of driftnotis.php: the setting, its fields and the sanitizer.
add_action( 'admin_init', 'driftnotis_register_settings' );
function driftnotis_register_settings() {
register_setting(
'driftnotis',
'driftnotis',
array(
'type' => 'array',
'sanitize_callback' => 'driftnotis_sanitize',
'default' => driftnotis_defaults(),
)
);
add_settings_section( 'driftnotis_main', '', 'driftnotis_section_intro', 'driftnotis' );
add_settings_field( 'driftnotis_text', __( 'Meddelande', 'driftnotis' ), 'driftnotis_field_text', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_text' ) );
add_settings_field( 'driftnotis_aktiv', __( 'Visa raden', 'driftnotis' ), 'driftnotis_field_aktiv', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_aktiv' ) );
add_settings_field( 'driftnotis_farg', __( 'Bakgrundsfärg', 'driftnotis' ), 'driftnotis_field_farg', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_farg' ) );
}
function driftnotis_section_intro() {
echo '<p>' . esc_html__( 'Raden visas högst upp på alla sidor så länge den är påslagen och har en text.', 'driftnotis' ) . '</p>';
}
function driftnotis_field_text() {
$s = driftnotis_settings();
printf(
'<input type="text" id="driftnotis_text" name="driftnotis[text]" value="%s" class="regular-text" maxlength="200">',
esc_attr( $s['text'] )
);
}
function driftnotis_field_aktiv() {
$s = driftnotis_settings();
printf(
'<input type="checkbox" id="driftnotis_aktiv" name="driftnotis[aktiv]" value="1" %s>',
checked( 1, $s['aktiv'], false )
);
}
function driftnotis_field_farg() {
$s = driftnotis_settings();
printf(
'<input type="color" id="driftnotis_farg" name="driftnotis[farg]" value="%s">',
esc_attr( $s['farg'] )
);
}
/**
* Runs on every save, before anything reaches the database.
* An unchecked checkbox is simply missing from $input, hence empty().
*/
function driftnotis_sanitize( $input ) {
$input = (array) $input;
$defaults = driftnotis_defaults();
$farg = sanitize_hex_color( $input['farg'] ?? '' );
return array(
'text' => sanitize_text_field( $input['text'] ?? '' ),
'aktiv' => empty( $input['aktiv'] ) ? 0 : 1,
'farg' => $farg ? $farg : $defaults['farg'],
);
}
Saneringen är den viktigaste funktionen i pluginet. Den får det råa formulärvärdet och returnerar exakt det som får hamna i databasen. sanitize_text_field() tar bort taggar, radbrytningar och överflödiga blanksteg. Kryssrutan är ett specialfall: är den avbockad skickar webbläsaren inget alls, så nyckeln saknas i $input, och empty() fångar både det och värdet 0. sanitize_hex_color() returnerar färgen om den har formen #rgb eller #rrggbb, tom sträng vid tom inmatning och null vid allt annat. De två senare faller tillbaka på standardfärgen.
Argumentet show_in_rest lämnar jag utanför. Det gör inställningen läsbar och skrivbar via REST API, vilket blockredigeraren behöver om ett block ska kunna ändra den. För en inställning av typen array kräver WordPress då ett schema som beskriver varje nyckel, annars loggas ett _doing_it_wrong. Det här pluginet har ingen anledning att gå den vägen.
Fältfunktionerna läser det sparade värdet och skriver ut ett inputfält var. Namnet driftnotis[text] gör att PHP samlar alla tre fält i en array, vilket är vad saneringen får. Värdena escapas med esc_attr() eftersom de hamnar i ett HTML-attribut. Färgfältet är <input type="color">, webbläsarens egen färgväljare. Den ger alltid ett värde på formen #rrggbb och kräver ingen JavaScript.
Steg 4: visa raden på sajten
Utskriften hänger på wp_body_open, hooken som avfyras direkt efter <body>. Blocktemans mall template-canvas.php anropar den alltid. Klassiska teman måste ha <?php wp_body_open(); ?> i header.php, vilket alla teman byggda efter WordPress 5.2 bör ha. Texten går genom esc_html() trots att den sanerades när den sparades. Sanering och escaping är två olika saker: sanering avgör vad som får sparas, escaping ser till att det som skrivs ut inte tolkas som HTML. Jag gör alltid båda, eftersom databasen kan ha fått värdet på andra vägar än det här formuläret.
CSS:en läggs på en handle utan källfil. wp_register_style() med false som src registrerar ett namn utan något att ladda, och wp_add_inline_style() hänger CSS:en på det namnet. WordPress skriver då ut en <style id="driftnotis-inline-css"> i <head>, bara på sidor där raden visas, och pluginet förblir en enda fil. Bakgrundsfärgen hamnar i CSS:en, och eftersom det inte finns någon escapingfunktion för CSS validerar jag värdet med sanitize_hex_color() en gång till vid utskriften. Ett värde som inte är en hexfärg kommer inte med.
<?php
// Part 4 of driftnotis.php: the bar itself and its CSS.
add_action( 'wp_body_open', 'driftnotis_print' );
function driftnotis_print() {
$s = driftnotis_settings();
if ( ! $s['aktiv'] || '' === $s['text'] ) {
return;
}
echo '<div class="driftnotis" role="status">' . esc_html( $s['text'] ) . '</div>';
}
add_action( 'wp_enqueue_scripts', 'driftnotis_styles' );
function driftnotis_styles() {
$s = driftnotis_settings();
if ( ! $s['aktiv'] || '' === $s['text'] ) {
return;
}
// CSS has no escaping function, so only a value that passes sanitize_hex_color() again goes in.
$farg = sanitize_hex_color( $s['farg'] );
$farg = $farg ? $farg : driftnotis_defaults()['farg'];
// A handle without src prints nothing by itself; the inline CSS is the whole stylesheet.
wp_register_style( 'driftnotis', false, array(), '1.0.0' );
wp_enqueue_style( 'driftnotis' );
wp_add_inline_style( 'driftnotis', '.driftnotis{background:' . $farg . ';color:#fff;padding:.6em 1em;text-align:center;font-size:.95em}' );
}
En rad CSS i en inline-tagg är rätt storlek för inline. Hade det handlat om flera hundra rader hade jag lagt dem i en egen fil och skickat sökvägen till wp_enqueue_style() i stället, så att webbläsaren kan cacha den. Vit text på en mörk bakgrund är standard här. Väljer du en ljus färg får du byta textfärgen i CSS:en, annars blir kontrasten för dålig.
Steg 5: översättning och avinstallation
Alla strängar som användaren ser går genom __() eller esc_html__() med textdomänen driftnotis. Sedan WordPress 4.6 laddas översättningar från wordpress.org automatiskt för plugin som finns där, ur mappen wp-content/languages/plugins/. Anropet till load_plugin_textdomain() behövs bara om du skickar med egna .mo-filer i pluginmappen. Jag tar med det ändå, på hooken init, så att det ligger rätt den dag mappen languages dyker upp. Sedan WordPress 6.7 varnar kärnan om översättningar laddas före init, så anropet får inte ligga löst i filen.
För avinstallationen finns två vägar: filen uninstall.php i pluginmappen eller register_uninstall_hook(). Jag väljer hooken, för att pluginet ska förbli en fil. Den har en egenhet som få guider nämner. Den läser optionen uninstall_plugins vid varje anrop och skriver till den om återanropet ändrats. Därför anropar jag den inuti aktiveringshooken, som körs en gång när pluginet aktiveras, och inte löst i filen där den skulle köras vid varje sidvisning.
Återanropet måste vara ett funktionsnamn eller en statisk metod, inte en anonym funktion, eftersom det sparas i databasen. När användaren klickar på Ta bort inkluderar WordPress pluginfilen, anropar funktionen och vår funktion raderar optionen. Inaktivering lämnar inställningarna kvar, vilket är rätt: den som stänger av pluginet tillfälligt ska inte behöva skriva om texten.
<?php
// Part 5 of driftnotis.php: translations and clean-up on uninstall.
add_action( 'init', 'driftnotis_textdomain' );
function driftnotis_textdomain() {
// Only needed for .mo files shipped inside the plugin folder. Translations from
// wordpress.org are picked up automatically since WordPress 4.6.
load_plugin_textdomain( 'driftnotis', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
register_activation_hook( __FILE__, 'driftnotis_activate' );
function driftnotis_activate() {
// register_uninstall_hook() writes to an option, so it belongs here and not at file load.
register_uninstall_hook( __FILE__, 'driftnotis_uninstall' );
}
function driftnotis_uninstall() {
delete_option( 'driftnotis' );
}
Hade pluginet haft egna tabeller eller mycket att städa hade jag valt uninstall.php. Den körs utan att pluginfilen laddas, och WordPress letar efter den först: finns filen används hooken inte alls.
Hela pluginfilen
Det här är allt. Lägg filen som wp-content/plugins/driftnotis/driftnotis.php, aktivera och gå till Inställningar > Driftnotis.
<?php
/**
* Plugin Name: Driftnotis
* Description: Visar en meddelanderad högst upp på sajten. Text, på/av och färg styrs under Inställningar > Driftnotis.
* Version: 1.0.0
* Requires at least: 6.7
* Requires PHP: 8.2
* Author: Kristoffer Pihlfelt
* License: GPL-2.0-or-later
* Text Domain: driftnotis
*/
defined( 'ABSPATH' ) || exit;
/**
* Default values. Used by register_setting() and as a fallback on output.
*/
function driftnotis_defaults() {
return array(
'text' => '',
'aktiv' => 0,
'farg' => '#1e3a5f',
);
}
/**
* Saved settings merged with the defaults, so every key always exists.
*/
function driftnotis_settings() {
return wp_parse_args( (array) get_option( 'driftnotis', array() ), driftnotis_defaults() );
}
/* ---- Settings page --------------------------------------------------- */
add_action( 'admin_menu', 'driftnotis_add_page' );
function driftnotis_add_page() {
add_options_page(
__( 'Driftnotis', 'driftnotis' ),
__( 'Driftnotis', 'driftnotis' ),
'manage_options',
'driftnotis',
'driftnotis_render_page'
);
}
function driftnotis_render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields( 'driftnotis' );
do_settings_sections( 'driftnotis' );
submit_button( __( 'Spara', 'driftnotis' ) );
?>
</form>
</div>
<?php
}
add_action( 'admin_init', 'driftnotis_register_settings' );
function driftnotis_register_settings() {
register_setting(
'driftnotis',
'driftnotis',
array(
'type' => 'array',
'sanitize_callback' => 'driftnotis_sanitize',
'default' => driftnotis_defaults(),
)
);
add_settings_section( 'driftnotis_main', '', 'driftnotis_section_intro', 'driftnotis' );
add_settings_field( 'driftnotis_text', __( 'Meddelande', 'driftnotis' ), 'driftnotis_field_text', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_text' ) );
add_settings_field( 'driftnotis_aktiv', __( 'Visa raden', 'driftnotis' ), 'driftnotis_field_aktiv', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_aktiv' ) );
add_settings_field( 'driftnotis_farg', __( 'Bakgrundsfärg', 'driftnotis' ), 'driftnotis_field_farg', 'driftnotis', 'driftnotis_main', array( 'label_for' => 'driftnotis_farg' ) );
}
function driftnotis_section_intro() {
echo '<p>' . esc_html__( 'Raden visas högst upp på alla sidor så länge den är påslagen och har en text.', 'driftnotis' ) . '</p>';
}
function driftnotis_field_text() {
$s = driftnotis_settings();
printf(
'<input type="text" id="driftnotis_text" name="driftnotis[text]" value="%s" class="regular-text" maxlength="200">',
esc_attr( $s['text'] )
);
}
function driftnotis_field_aktiv() {
$s = driftnotis_settings();
printf(
'<input type="checkbox" id="driftnotis_aktiv" name="driftnotis[aktiv]" value="1" %s>',
checked( 1, $s['aktiv'], false )
);
}
function driftnotis_field_farg() {
$s = driftnotis_settings();
printf(
'<input type="color" id="driftnotis_farg" name="driftnotis[farg]" value="%s">',
esc_attr( $s['farg'] )
);
}
/**
* Runs on every save, before anything reaches the database.
* An unchecked checkbox is simply missing from $input, hence empty().
*/
function driftnotis_sanitize( $input ) {
$input = (array) $input;
$defaults = driftnotis_defaults();
$farg = sanitize_hex_color( $input['farg'] ?? '' );
return array(
'text' => sanitize_text_field( $input['text'] ?? '' ),
'aktiv' => empty( $input['aktiv'] ) ? 0 : 1,
'farg' => $farg ? $farg : $defaults['farg'],
);
}
/* ---- Front end ------------------------------------------------------- */
add_action( 'wp_body_open', 'driftnotis_print' );
function driftnotis_print() {
$s = driftnotis_settings();
if ( ! $s['aktiv'] || '' === $s['text'] ) {
return;
}
echo '<div class="driftnotis" role="status">' . esc_html( $s['text'] ) . '</div>';
}
add_action( 'wp_enqueue_scripts', 'driftnotis_styles' );
function driftnotis_styles() {
$s = driftnotis_settings();
if ( ! $s['aktiv'] || '' === $s['text'] ) {
return;
}
// CSS has no escaping function, so only a value that passes sanitize_hex_color() again goes in.
$farg = sanitize_hex_color( $s['farg'] );
$farg = $farg ? $farg : driftnotis_defaults()['farg'];
// A handle without src prints nothing by itself; the inline CSS is the whole stylesheet.
wp_register_style( 'driftnotis', false, array(), '1.0.0' );
wp_enqueue_style( 'driftnotis' );
wp_add_inline_style( 'driftnotis', '.driftnotis{background:' . $farg . ';color:#fff;padding:.6em 1em;text-align:center;font-size:.95em}' );
}
/* ---- Translations and uninstall ------------------------------------- */
add_action( 'init', 'driftnotis_textdomain' );
function driftnotis_textdomain() {
// Only needed for .mo files shipped inside the plugin folder. Translations from
// wordpress.org are picked up automatically since WordPress 4.6.
load_plugin_textdomain( 'driftnotis', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
register_activation_hook( __FILE__, 'driftnotis_activate' );
function driftnotis_activate() {
// register_uninstall_hook() writes to an option, so it belongs here and not at file load.
register_uninstall_hook( __FILE__, 'driftnotis_uninstall' );
}
function driftnotis_uninstall() {
delete_option( 'driftnotis' );
}
Fallgropar
Det vanligaste felet jag får rätta i plugin som andra byggt är kod som litar på sin egen inmatning. Formuläret har en färgväljare, så värdet är alltid en hexfärg, resonerar man, och skriver ut det rakt in i CSS:en. Men optionen kan sättas via WP-CLI, via ett migreringsskript eller av ett annat plugin, och då är färgväljaren ingen garanti. Sanera vid sparande, escapa eller validera vid utskrift, varje gång.
Saneringsfunktionen körs två gånger första gången du sparar. update_option() sanerar värdet, upptäcker att optionen inte finns och anropar add_option(), som sanerar igen. Med funktionen ovan spelar det ingen roll, eftersom samma indata ger samma utdata. Lägger du in något med sidoeffekter i saneringen, som en räknare eller ett meddelande med add_settings_error(), dubbleras det den första gången.
Gruppnamnet i settings_fields() och det första argumentet till register_setting() måste vara samma sträng. Skiljer de sig stoppar options.php sparandet med felet ”not in the allowed options list”, och det ser ut som ett rättighetsfel fast det är ett stavfel.
En option utan autoload-argument laddas med alla andra autoladdade options på varje sidvisning. För tre små värden som behövs på varje sida är det precis rätt. Sparar du stora mängder data, som en logg eller cache, hör det inte hemma i en autoladdad option alls.
Byter du namn på en funktion sent i utvecklingen, kom ihåg att återanropen till add_settings_field() och hookarna är strängar. PHP klagar inte förrän funktionen anropas, och då med ett fatalt fel på sajten. Jag söker igenom filen efter det gamla namnet innan jag laddar upp.
Innan du lämnar över pluginet till någon annan, testa det med fientlig inmatning en gång. Spara en text som innehåller <script> och ", byt färgen till något ogiltigt med WP-CLI (wp option patch update driftnotis farg red) och ladda om sajten. Texten ska visas som text, färgen ska falla tillbaka på standard och inget ska gå sönder.
Nästa steg
Det första de flesta vill ha är ett datumintervall, så att raden visar sig själv den 24 december och försvinner den 26. Det är två datumfält till i samma sektion, sanering med strtotime() eller wp_date(), och en jämförelse mot current_time( 'timestamp' ) inuti driftnotis_print(). Lägg jämförelsen även i driftnotis_styles(), annars skrivs CSS:en ut i onödan.
Fler platser är nästa naturliga steg. En kortkod ger redaktören möjlighet att lägga texten i en sida, och en butik kan vilja ha raden bara i butiksdelen genom att byta wp_body_open mot woocommerce_before_main_content. Vill du ha en länk i texten behöver du ett fält till, sanerat med esc_url_raw() vid sparande och escapat med esc_url() vid utskrift.
Läs vidare i Plugin Handbook på developer.wordpress.org, särskilt avsnitten om Settings API och om säkerhet, där sanering och escaping förklaras funktion för funktion. Hela pluginfilen finns i det här inlägget, och som färdig zip i rutan under koden.
Vanliga frågor
Måste pluginet ligga i en egen mapp när det bara är en fil?
Det fungerar att lägga filen direkt i wp-content/plugins, men gör det inte. WordPress identifierar pluginet med sökvägen mapp/fil.php, avinstallationen letar efter uninstall.php i mappen, och översättningar väntas i en undermapp. En mapp från början sparar en flytt senare.
Varför syns inte raden på min sajt?
Kontrollera i den ordningen: att kryssrutan Visa raden är ibockad och texten inte är tom, att temat anropar wp_body_open() direkt efter body-taggen (klassiska teman från före WordPress 5.2 saknar den ibland), och att sidcachen är tömd. Blockteman anropar hooken alltid.
Kan jag lägga koden i temats functions.php i stället?
Det mesta, ja, men inte aktiverings- och avinstallationshooken, och raden försvinner den dag sajten byter tema. Funktioner som ska överleva ett temabyte hör hemma i ett plugin. Det är hela poängen med att bygga ett.
Jag får felet ”The driftnotis options page is not in the allowed options list” när jag sparar. Varför?
Gruppnamnet i settings_fields() stämmer inte med det första argumentet till register_setting(), eller så körs register_setting() inte på admin_init. options.php släpper bara igenom grupper som registrerats under samma förfrågan.
