Infinite scroll

”Infinite scroll can sometimes be a good feature, but it also comes with drawbacks, especially in an eCommerce setting.”

Fördelar

  1. Kan uppmuntra engagemang & utforskning
    • Besökare tenderar att fortsätta skrolla istället för att klicka på ”Nästa sida”. (minskar interaktionskostnader)
    • Kan fungera bra för inspirationsshopping (mode, inredning, osv.), där besökaren inte har en specifik produkt i åtanke. Annars sociala media, några former av nyhetsflöden (tids- eller popularitetsstyrda objekt) och/eller sajter med stora mängder användargenererat innehåll där användarna förväntar sig en kontinuerlig ström av uppdateringar.
  2. Kan kännas snabbare (upplevd prestandaförbättring)
    • Färre hela sidladdningar, vilket håller besökaren i samma flöde.
    • Med korrekt lazy loading laddas endast synligt innehåll, vilket kan förbättra laddningstider (men detta är inte spcifikt för infinite skroll).
  3. Kan funka bättre för mobilanvändare
    • Mobilanvändare skrollar naturligt, skroll är mer intuitivt än tryck. Även på stationär dator/laptop.
    • Paginering med små knappar kan vara irriterande att trycka på.
  4. Efterliknar sociala medieflöden (vana vid UX)
    • Besökare är vana vid oändlig skroll från vissa sociala medieplattformar.
    • Kan fungera bra för mycket visuella webbplatser (t.ex. Pinterest-liknande listor).
  5. Kan minska avvisningsfrekvensen
    • Om besökare slipper klicka för att se fler objekt kan de stanna längre på sidan.
    • Dock fungerar detta bara om innehållet är engagerande.

Nackdelar

  1. Dålig användarupplevelse & brist på kontroll
    • Svårt att navigera: besökare kan inte enkelt hoppa tillbaka till en tidigare produkt eller hitta var de slutade efter att ha klickat på en produkt.
    • Ingen känsla av framsteg: med paginering vet man att man är på ”sida 3 av 10”. Med oändlig skroll finns ingen tydlig slutpunkt.
    • Ökad distraktion: besökare som vet vad de letar efter får skrolla genom irrelevant innehåll. När en målinriktad användare besöker en webbplats och vet exakt vad de söker, men det de vill ha är placerat långt ner i en oändlig skroll av innehåll, tvingas de ändå att bläddra igenom en mängd irrelevant material först. Det finns inget sätt att snabbt hoppa direkt till deras önskade destination.
    • Förvirrande upplevelse: stör besökarens förväntningar, särskilt på nyhetssidor.
    • Känsla av desorientering: besökare kan känna sig vilse när de navigerar på en sida som aldrig tar slut, vilket kan påverka deras förmåga att hitta specifik information.
    • Kontext är viktigt: generellt sett: oändlig skroll passar sociala medier, men inte strukturerade sidor.
    • Svårt att hitta slutet – Eftersom allt ligger i ett långt flöde kan det vara svårt att uppskatta hur mycket det är kvar (hur länge till ska jag behöva skrolla innan jag hittar min produkt/innehållet jag söker – eller se om det ens finns). Hur tar jag mig till de sista produkterna eller hur tar jag mig förbi allt detta som inte är intressant. Kan det jag söker finnas längre fram (har jag tid/ork att utforska det)?
  2. Prestandaproblem & långsam laddning
    • Hög minnesanvändning: webbläsaren fyller på DOM:en med mer innehåll (ex. fler produkter), vilket kan orsaka fördröjningar och lagg – särskilt på svagare enheter.
    • Ökad serverbelastning: om det implementeras dåligt kan det skapa onödiga databasförfrågningar och göra sidan långsammare för alla.
  3. SEO & indexeringsproblem
    • Svårt för sökmotorer att indexera: Google föredrar paginerat innehåll med tydliga URL:er. Oändlig skroll kan dölja produkter från sökmotorer.
    • Svårt att skapa unika URL:er: besökare kan inte enkelt dela en länk till ”sida 5” av en produktkategori.
  4. Tillgänglighetsproblem
    • Fungerar dåligt med tangentbordsnavigation: många besökare (särskilt de som använder skärmläsare) föredrar paginerat innehåll. Det är väldigt svårt att hantera en lösning för de som inte har pekdon eller surfar med touchskärm. En variant kan vara att erbjuda besökaren att byta till traditionell paginering. För en nybörjare eller en användare som bara navigerar med tangentbordet kan det vara oerhört frustrerande att försöka nå innehåll som är otillgängligt eftersom mer och mer laddas in längs vägen.
    • Kan orsaka obehag för känsliga besökare: konstant innehållsladdning kan vara desorienterande.
  5. Sidfoten blir otillgänglig/allt innehåll under blir otillgängligt
    • Viktiga länkar blir svåra att nå oändlig skroll skjuter sidfoten längre ner, vilket gör det svårt att komma åt saker som ofta finns där, som: Kontakt, Integritetspolicy, FAQ, Villkor osv.
    • ”Don’t Place Content After the Infinite Scroll – sticking a website footer beneath a sea of endlessly loading content saddles users with a Sisyphean task. They’ll get annoyed, and rightly so.”
  6. Problem med filtrering & sortering
    • Skrollpositionen nollställs: om en besökare ändrar ett filter kan oändlig skroll ofta återställa allt, vilket gör att de förlorar sin plats.
    • Sortering fungerar dåligt: att ändra sorteringsordning (t.ex. ”pris: Lågt till Högt”) kan tvinga en fullständig omladdning, vilket bryter flödet.
  7. Avsevärt svårare att koda (=tidskrävande)
    • Speciellt för att försöka addresera mycket av ovanstående punkter.

Hybridlösning

Paginering + ladda mer/fler-knapp (istället för klassisk indelning i 1, 2, 3 osv)

  • Ger användaren kontroll: De kan gå till en specifik sida och hoppa tillbaka utan att förlora sin plats.
  • Förbättrar prestanda: Laddar bara in det som behövs, vilket minskar minnesanvändningen.
  • SEO-vänligt: Google kan indexera varje sida korrekt.
  • Fungerar med filtrering & sortering: Ingen risk att besökarens val nollställs.
  • Sidfoten är alltid tillgänglig: viktiga länkar förblir enkla att nå.

Andra nackdelar med oändlig skroll kvarstår: Det är fortfarande svårt att återgå till sin tidigare plats efter att ha klickat på en länk, och användare får fortfarande ingen känsla av framsteg genom att ”bli klar” med en sida.


Överväger fördelarna nackdelarna?

För en e-handelswebbplats med 200+ produkter per kategori, troligtvis inte.

Oändlig skroll passar bättre för innehållsdrivna, inspirationsbaserade sajter (som Pinterest), men för en strukturerad produktvisning skapar det fler problem än fördelar.

Man behöver ha med sig att oändlig skroll snabbt blir blir tröttsamt och svårnavigerat om man inte hittar det man letar efter. I ett fall så som en klassisk ehandel så är en gedigen kategorisering och bra, relevanta, filterval effektivt. Är det riktigt bra så ska man inte ens behöva en sökfunktion (även om vissa gärna föredrar den ingången så klart). Då lär man sällan ha särskilt många produkter att visa ändå när besökaren väl ha trattat sig ner.

En oändlig skroll måste vara ruggigt välkodad om det ska fungera och det tar tid. Det är kanske sällan dessa pengar är motiverade att lägga om man ser till föregående stycke. Ett exempel på ett brutalt frustrerande scenario som tyvärr är ganska vanligt är när besökaren skrollat i tio minuter för att hitta en intressant produkt, väljer att klicka in på produkten för att sedan gå tillbaka och fortsätta men då får starta om från början.

Om någon insisterar på oändlig skroll kan en hybridlösning kanske vara ett alternativ som balanserar prestanda, SEO och användbarhet. Men här kan man säga att brytgränsen går någonstans kring 75-100 produkter, sen är vinsterna med traditionell paginering tydliga. Samtidigt som en god produtksajt har både kategorisering, filtrering och sök på plats vilket snabbt bör avgränsa antalet produkter till ett mindre antal, där alla kan visas direkt – således är oändlig skroll onödigt/oanvänt.

”Testa oändlig skroll först”. Innan du lägger tid på att koda, ta reda på vilket problem du försöker lösa”. Om målet är att öka tid på sidan och minska avvisningsfrekvensen, bör du skapa en prototyp och testa den på en bred grupp användare, inklusive personer med funktionsnedsättningar, för att se om designen faktiskt uppfyller målet. Oavsett om du väljer oändlig skroll, ”ladda mer”-knappar, paginering eller bara en sökruta, är det viktigaste steget att testa lösningen och samla in användarfeedback.

(summering:)

Annars, en traditionell paginering med 24-48 produkter per sida ger tydligare navigering och bättre prestanda. Man får en förutsägbarhet som kan vara mycket värdefull; besökare vet ungefär hur mycket innehåll som finns och hur långt de behöver gå igenom, vilket skapar en mer logisk och förutsägbar upplevelse.

Bättre struktur och navigering ger besökaren bättre kontroll över innehållet och kan enkelt hoppa mellan sidor för att hitta specifik information. Detta är särskilt användbart i t.ex. e-handel eller sökresultat.

Bättre prestanda och bättre SEO eftersom enbart en begränsad mängd innehåll laddas per sida vilket minskar belastningen på både webbläsaren och servern, vilket leder till snabbare laddningstider. Varje sida kan indexeras individuellt, vilket gör det lättare för sökmotorer att hitta och ranka innehållet.

Etsy-exemplet

Etsy övergick från oändlig skroll till traditionell paginering eftersom det minskade engagemanget. Besökare tappade bort sig i sökresultaten, klickade mindre och sparade färre favoriter. Otydlig navigering och frånvaron av en sidfot gjorde upplevelsen sämre. Paginering visade sig vara mer effektivt helt enkelt.

Den har några år på nacken men essensen i berättelsen är fortfarande aktuell https://danwin.com/2013/01/infinite-scroll-fail-etsy/


Slutsats

Valet mellan oändlig skroll och paginering beror på vilken typ av innehåll och användarupplevelse som önskas.

  • Oändlig skroll fungerar bäst för sociala medier, bildflöden och nyhetssajter där besökaren vill konsumera nytt innehåll utan att leta specifikt.
  • Paginering är bättre för e-handel, sökresultat och informationssidor där det är viktigt att kunna navigera enkelt och hitta specifikt innehåll.

📌 Om en sida innehåller många produkter eller artiklar som behöver vara lätta att hitta senare, är paginering allt som oftast det bättre alternativet!


Om du ska oändlig skroll ha tänk på detta:

  • Erbjud möjligheten att hoppa framåt. Tvinga inte användarna att slösa tid på att skrolla igenom innehåll de vet att de inte behöver för att komma till det de letar efter. Inkludera knappar som låter dem hoppa till en viss sektion, bokstav eller siffra.
  • Bygg hela tiden om url:en dynamiskt så att den speglar det subset av objekt besökaren är på. På så vis har man hela tiden en ”delbar länk”.
  • Bygg in stöd för att spara exakt vart i flödet besökaren står och vid bakåtklick/återgång så bör besökaren landa på exakt samma plats.
  • Bygg in möjlighet att växla till vanlig paginering för de som inte surfar med pekdon eller touch.
  • Bygg in någon form av publicerat paginerat sidsystem som sökmotorer kan indexera – dessa bör dock skicka besökarna till din riktiga sida. Det finns många sätt att lösa detta på tror jag och svårt att ge något konkret lösning. Men indexeringen är viktig och bör ej förbises.
  • All viktig information under en oändlig skroll behöver finnas någon annanstans. Lättillgängligt.
  • Det blir dyrt.

Exempel:

Ser man till hur ex. H&M gör så behöver vi välja kön först, därefter en huvudkategori i form av plaggtyp (jackor, byxor). Man kan inte gå direkt på jackor, de tvingar besökaren till ett smalare val innan resultat visas. Det jag tänker är alltså att man har ett antal taxonomier som kommer att valla besökaren ganska straight on mot rätt utbud direkt.

Blåkläder ger besökaren möjlighet att göra 1,2,3 kategorival direkt (på ett och samma klick så att säga) när de i menyn länkar till ex Herr – Jackor. Men jag kan även nå Alla produkter (https://www.blaklader.se/sv/alla-produkter). Men med 1 180 produkter framme så är det ändå effektivt gjort.

  • Paginering med 50 produkter per sida.
  • Sorterat på Nyheter men med möjlighet att ändra till: artikelnummer, popularitet, namn, pris
  • Lätttillgängliga kategori och filterval. Med 2-3 klick bland filterna är jag nere på 1-2 produkter som matchar det jag söker.
  • Dvs, ett väldigt snabb flöde att hitta rätt
  • På Blåkläder tycks det vara plaggtyperna och kön som är egentliga kategorier, resten är attribut. Varje kategori har sin egna ”sida”, indexerbar och till synes snarlik alla andra produktsidor. Det man gör visuellt är dock att hantera både kategorier och attribut som filter i ett ett enda stort filterbibliotek. Men för varje kategorisida så är vissa specifika filter utvalda och ses direkt i kontakt med produktflödet. Övriga filter kan fällas ut.

Källor:

https://www.nngroup.com/articles/infinite-scrolling-tips/

https://uxplanet.org/ux-infinite-scrolling-vs-pagination-1030d29376f1

https://builtin.com/articles/infinite-scroll

https://www.smashingmagazine.com/2013/05/infinite-scrolling-lets-get-to-the-bottom-of-this

https://medium.com/design-bootcamp/the-pros-and-cons-of-infinite-scroll-vs-pagination-which-one-to-choose-eba97109ed38

Skapa posts på mailchimp campaigns via api

<?php
// Mailchimp API credentials
const MAILCHIMP_API_KEY = '';  // Replace with your Mailchimp API Key
const MAILCHIMP_DATA_CENTER = '';  // Replace with your Mailchimp data center (e.g., 'us2')

// Function to fetch and create posts for sent Mailchimp campaigns
function create_campaign_posts_from_mailchimp() {
	if (empty(MAILCHIMP_API_KEY)) {
		error_log("Mailchimp API key is not set.");
		return;
	}

	// Mailchimp API URL to get sent campaigns
	$url = "https://" . MAILCHIMP_DATA_CENTER . ".api.mailchimp.com/3.0/campaigns?status=sent";

	// Initialize cURL
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_HTTPHEADER, [
		"Authorization: Bearer " . MAILCHIMP_API_KEY,
		"Content-Type: application/json"
	]);

	// Execute cURL and get response
	$response = curl_exec($ch);
	curl_close($ch);

	// Decode JSON response
	$data = json_decode($response, true);

	// Check if campaigns exist
	if (!isset($data['campaigns']) || empty($data['campaigns'])) {
		error_log("No campaigns found.");
		return;
	}

	// Loop through campaigns and create posts
	foreach ($data['campaigns'] as $campaign) {
		create_campaign_post($campaign);
	}
}

// Function to create a post from a Mailchimp campaign
function create_campaign_post($campaign) {
	// Check if post with campaign ID already exists
	$existing_post = get_posts([
		'post_type'   => 'post',
		'meta_key'    => 'campaign_id',
		'meta_value'  => $campaign['id']
	]);

	if (!empty($existing_post)) {
		return;  // Post already exists
	}

	// Prepare post data
	$post_title = "Nyhetsbrev: " . sanitize_text_field($campaign['settings']['title']);
	$post_content = "
        <p><a href='" . esc_url($campaign['archive_url']) . "'>" . esc_html($campaign['settings']['title']) . "</a></p>
        <p>Sent Time: " . date("Y-m-d H:i", strtotime($campaign['send_time'])) . "</p>
        <p>Subject Line: " . esc_html($campaign['settings']['subject_line']) . "</p>
        <p>Recipients: " . esc_html($campaign['report_summary']['opens']) . " opens, " . esc_html($campaign['report_summary']['clicks']) . " clicks</p>
    ";
	$post_date = current_time('mysql');  // Set post date to current time

	// Insert the post into WordPress
	$new_post = [
		'post_title'   => $post_title,
		'post_content' => $post_content,
		'post_status'  => 'publish',
		'post_date'    => $post_date,
		'post_author'  => 1,  // Default admin user
		'meta_input'   => [
			'campaign_id' => $campaign['id']
		]
	];

	wp_insert_post($new_post);
}

// Cron job function to run daily
function schedule_mailchimp_campaign_cron() {
	if (!wp_next_scheduled('sync_mailchimp_campaigns_event')) {
		wp_schedule_event(time(), 'daily', 'sync_mailchimp_campaigns_event');
	}
}
add_action('wp', 'schedule_mailchimp_campaign_cron');

// Hook the cron job to our function
add_action('sync_mailchimp_campaigns_event', 'create_campaign_posts_from_mailchimp');

Lista senaste kampanjer från mailchimp

<?php

// Mailchimp API credentials
$api_key = 'YOUR_API_KEY';
$dc = 'YOUR_DC'; // Replace with your data center (e.g., 'us2')

// Mailchimp API URL to get sent campaigns
$url = "https://$dc.api.mailchimp.com/3.0/campaigns?status=sent";

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer $api_key",
    "Content-Type: application/json"
]);

// Execute cURL and get response
$response = curl_exec($ch);
curl_close($ch);

// Decode JSON response
$data = json_decode($response, true);

// Check for campaigns
if (!empty($data['campaigns'])) {
    echo "<h2>Sent Mailchimp Campaigns</h2><ul>";

    // Reverse the order so latest campaigns appear first
    $campaigns = array_reverse($data['campaigns']);

    foreach ($campaigns as $campaign) {
        $sent = date("Y-m-d H:i", strtotime($campaign['send_time'])); // Format date
        $title = htmlspecialchars($campaign['settings']['title']);
        $archive_url = htmlspecialchars($campaign['archive_url']);

        echo '<li><a href="' . $archive_url . '" target="_blank">' . $title . ' (' . $sent . ')</a></li>';
    }
    echo "</ul>";
} else {
    echo "<p>No sent campaigns found.</p>";
}
?>

Med senaste 30 dagarna, filter och sök:

<?php
// Mailchimp API credentials
$api_key = 'YOUR_API_KEY';
$dc = 'YOUR_DC'; // Replace with your data center (e.g., 'us2')

// Mailchimp API URL to get sent campaigns
$url = "https://$dc.api.mailchimp.com/3.0/campaigns?status=sent";

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer $api_key",
    "Content-Type: application/json"
]);

// Execute cURL and get response
$response = curl_exec($ch);
curl_close($ch);

// Decode JSON response
$data = json_decode($response, true);

// Define filters
$cutoff_date = strtotime("-30 days"); // Only show last 30 days
$search_query = isset($_GET['search']) ? strtolower(trim($_GET['search'])) : ''; // Get search term

// Pagination settings
$items_per_page = 5;
$current_page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$offset = ($current_page - 1) * $items_per_page;

// Filter and sort campaigns
$campaigns = array_reverse($data['campaigns']); // Latest first
$filtered_campaigns = [];

foreach ($campaigns as $campaign) {
    $sent_time = strtotime($campaign['send_time']);
    $title = htmlspecialchars($campaign['settings']['title']);
    $archive_url = htmlspecialchars($campaign['archive_url']);

    // Filter campaigns within the last 30 days and matching search
    if ($sent_time >= $cutoff_date && ($search_query === '' || stripos($title, $search_query) !== false)) {
        $filtered_campaigns[] = [
            'title' => $title,
            'archive_url' => $archive_url,
            'sent' => date("Y-m-d H:i", $sent_time) // Format date
        ];
    }
}

// Get total filtered items and slice for pagination
$total_campaigns = count($filtered_campaigns);
$paginated_campaigns = array_slice($filtered_campaigns, $offset, $items_per_page);

// Display search form
echo '<form method="GET" action="">
        <input type="text" name="search" value="' . htmlspecialchars($search_query) . '" placeholder="Search campaigns...">
        <button type="submit">Search</button>
      </form>';

if (!empty($paginated_campaigns)) {
    echo "<h2>Sent Mailchimp Campaigns (Last 30 Days)</h2><ul>";

    foreach ($paginated_campaigns as $campaign) {
        echo '<li><a href="' . $campaign['archive_url'] . '">' . $campaign['title'] . ' (' . $campaign['sent'] . ')</a></li>';
    }

    echo "</ul>";

    // Pagination controls
    $total_pages = ceil($total_campaigns / $items_per_page);
    if ($total_pages > 1) {
        echo "<div>";
        for ($i = 1; $i <= $total_pages; $i++) {
            $active = ($i == $current_page) ? 'style="font-weight:bold;"' : '';
            echo '<a ' . $active . ' href="?page=' . $i . '&search=' . urlencode($search_query) . '">Page ' . $i . '</a> ';
        }
        echo "</div>";
    }
} else {
    echo "<p>No campaigns found.</p>";
}
?>

Som shortcode

function get_mailchimp_campaigns($atts) {
    // Default shortcode parameters
    $atts = shortcode_atts(
        array(
            'api_key' => '', // API key as a parameter
            'dc' => '',      // Data center as a parameter
        ),
        $atts,
        'mailchimp_campaigns'
    );

    // Extract values from the shortcode attributes
    $api_key = $atts['api_key'];
    $dc = $atts['dc'];

    // Check if API key and data center are provided
    if (empty($api_key) || empty($dc)) {
        return '<p>Please provide the API key and data center in the shortcode.</p>';
    }

    // Mailchimp API URL to get sent campaigns
    $url = "https://$dc.api.mailchimp.com/3.0/campaigns?status=sent";

    // Initialize cURL
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Authorization: Bearer $api_key",
        "Content-Type: application/json"
    ]);

    // Execute cURL and get response
    $response = curl_exec($ch);
    curl_close($ch);

    // Decode JSON response
    $data = json_decode($response, true);

    // Check for campaigns and prepare output
    if (!empty($data['campaigns'])) {
        $output = "<h2>Sent Mailchimp Campaigns</h2><ul>";

        // Reverse the order so latest campaigns appear first
        $campaigns = array_reverse($data['campaigns']);

        foreach ($campaigns as $campaign) {
            $sent = date("Y-m-d H:i", strtotime($campaign['send_time'])); // Format date
            $title = htmlspecialchars($campaign['settings']['title']);
            $archive_url = htmlspecialchars($campaign['archive_url']);

            $output .= '<li><a href="' . $archive_url . '">' . $title . ' (' . $sent . ')</a></li>';
        }

        $output .= "</ul>";
    } else {
        $output = "<p>No sent campaigns found.</p>";
    }

    return $output;
}
add_shortcode('mailchimp_campaigns', 'get_mailchimp_campaigns');

shortcode:

[mailchimp_campaigns api_key="your-api-key" dc="us6"]

Oderland Email routing om domän ligger någon annanstans

Ex. fel: ”550 No Such User Here”

Om domänen har mailhantering någon annanstans måste Email routing (cPanel) stå på Extern/Remote.

Så länge mailhanteringen för domänen inte ligger på samma server som webbhotellkontot måste den vara remote, annars kommer servern inte ens kika på MX-pekarna och enbart försöka sig på lokala leveranser

Komplettera SPF-inlägg i DNS-servrar med: _vsp.oderland.com

v=spf1 include:spf.protection.outlook.com include:_vsp.oderland.com include:_spf.webhotel24.se include:all._spf.plma.se include:11223344.spf03.hubspotemail.net -all

Warning! WP Super Cache caching was broken but has been fixed! The script advanced-cache.php could not load wp-cache-phase1.php

  1. Troligtvis är adressen fel, ofta kan detta uppstå när man deployar wp-config via deployverktyg och det är felkonfat. Pluginet lagar sig själv om man laddar om i admin, då kan man se vilken sökväg det ska vara. Det ska alltså vara sökvägen till pluginmappen och inte cache-mappen i wp-content.
  2. The constant is not defined until after the plugin loads. This error is possible if the line ”require_once(ABSPATH . ’wp-settings.php’);” is present in wp-config.php . WPCACHEHOME is probably being defined after this line, but needs to be defined above it.
define( 'WPCACHEHOME', '<site root>/wp-content/plugins/wp-super-cache/' );
require_once(ABSPATH . 'wp-settings.php');

Generera bild från pdf med Imagick

if (extension_loaded('imagick')) {
	$generated_thumbnail = generate_pdf_thumbnail($file['url'], $file['ID']);
}



/**
 * Generates a thumbnail for a PDF file using Imagick.
 *
 * This function extracts the first page of the given PDF and saves it as a JPEG image
 * in the WordPress uploads directory. If Imagick is not available or the generation fails,
 * it returns false.
 *
 * @param string $pdf_url The URL of the PDF file.
 * @param int $attachment_id The WordPress attachment ID of the PDF.
 *
 * @return string|false The URL of the generated thumbnail, or false on failure.
 */
function generate_pdf_thumbnail(string $pdf_url, int $attachment_id): false|string
{
	if (!extension_loaded('imagick')) {
		return false; // Imagick is required for PDF thumbnail generation.
	}

	$upload_dir = wp_upload_dir();
	$output_path = $upload_dir['path'] . '/pdf-thumbnail-' . $attachment_id . '.jpg';
	var_dump($output_path);
	try {
		$imagick = new Imagick();
		$imagick->setResolution(150, 150); // Set resolution for better quality.
		$imagick->readImage($pdf_url . '[0]'); // Extract the first page.
		//$imagick->readImage($upload_dir['basedir'] . '/2025/01/JU.pdf' . '[0]' );

		$imagick->setImageFormat('jpg');
		$imagick->writeImage($output_path);
		$imagick->clear();
		$imagick->destroy();

		return $upload_dir['url'] . '/pdf-thumbnail-' . $attachment_id . '.jpg';

	} catch (Exception $e) {
		error_log('Couldn\'t generate image from pdf: ' . $e->getMessage());
		return false;
	}
}

Redirect if not admin

add_action('template_redirect', function () {
    // Check if the current user is not an admin
    if (!current_user_can('administrator')) {
        // Get the current requested URL path
        $request_uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');

        // Define the paths to redirect
        $redirect_paths = [
            'kontakt',
            'om-oss',
            'mitt-konto',
        ];

        // Check if the requested path matches any of the defined paths or their subpaths
        foreach ($redirect_paths as $path) {
            if (strpos($request_uri, $path) === 0) {
                // Perform a temporary redirect (302)
                wp_redirect('https://mindoman.se/lank-till-ny-sida/', 302);
                exit;
            }
        }
    }
});

Ta ut alla bilder som används på en sajt (finns i post content)

SELECT p1.ID, p1.post_title FROM wp_posts p1 WHERE p1.post_type = 'attachment' AND p1.post_mime_type LIKE 'image%' AND NOT EXISTS ( SELECT 1 FROM wp_posts p2 WHERE p2.post_status = 'publish' AND p2.post_content LIKE CONCAT('%', p1.guid, '%') );

Tar alla posts från wp_posts där post_type är attachment och MIME type är en bild. Sedan kollar den om guid på attachments finns i post_content hos någon publicerad post. Denna måste justeras för att kolla efter bildanvänding i options, custom fields osv.

    Debug.log

    // Enable Debug logging to the /wp-content/debug.log file
    define( 'WP_DEBUG_LOG', true );
    function track_redirects_in_template() {
    	if ( did_action( 'wp_redirect' ) ) {
    		error_log( 'Redirect action triggered in template.' );
    	} else {
    		error_log( 'No redirect detected in template.' );
    	}
    }
    add_action( 'template_redirect', 'track_redirects_in_template' );
    function check_is_singular_essay() {
    	if ( is_singular( 'my-cpt' ) ) {
    		error_log( 'My CPT post is being accessed.' );
                    global $post;
                    error_log( 'We are on: ' . $post->ID );
    	}
    }
    add_action( 'wp_head', 'check_is_singular_essay' );
    error_log( 'Message | post type: ' . get_post_type() . ' | ' . get_the_title() );
    global $wp_query;
    error_log( 'Query Variables: ' . print_r( $wp_query->query_vars, true ) );
    add_action( 'init', function() {
        global $wp_rewrite;
        error_log( print_r( $wp_rewrite->wp_rewrite_rules(), true ) );
    } );
    add_filter( 'request', function ( $query_vars ) {
        error_log( 'Request Vars During Parse: ' . print_r( $query_vars, true ) );
        return $query_vars;
    } );
    add_action( 'init', function () {
        $query = new WP_Query([
            'post_type' => 'my-cpt',
            'posts_per_page' => -1,
        ]);
    
        if ( $query->have_posts() ) {
            error_log( 'CPT found: ' . $query->posts[0]->post_title );
        } else {
            error_log( 'No cpt posts found in query.' );
        }
    } );
    add_action( 'init', function () {
        $query = new WP_Query([
            'post_type' => 'my-cpt',
            'posts_per_page' => -1,
        ]);
    
        if ( $query->have_posts() ) {
            error_log( 'CPT found: ' . $query->posts[0]->post_title );
        } else {
            error_log( 'No cpt posts found in query.' );
        }
    } );
    add_action( 'init', function () {
        error_log( 'Registered Post Types: ' . print_r( get_post_types(), true ) );
    } );
    add_action( 'template_redirect', function() {
        error_log( 'Current User Caps: ' . print_r( wp_get_current_user()->allcaps, true ) );
        error_log( 'Current User Role: ' . implode( ', ', wp_get_current_user()->roles ) );
        error_log(print_r(get_role('subscriber')->capabilities, true));
    }

    Läsning november 2024

    https://accessiblenumbers.com

    https://neurodiversity.design

    https://uxdesign.cc/building-for-adhd-will-make-your-product-better-for-everyone-795eb5bc9afa

    https://stephaniewalter.design/blog/neurodiversity-and-ux-essential-resources-for-cognitive-accessibility/

    https://readabilityguidelines.co.uk

    https://www.smashingmagazine.com/2024/10/how-bottom-up-design-approach-enhances-site-accessibility

    https://www.smashingmagazine.com/2024/10/css-min-all-the-things

    https://www.joshwcomeau.com/css/custom-css-reset

    https://piccalil.li/blog/a-more-modern-css-reset

    Negera CSS-variabel

    :root {
      --margin: 20px;
    }
    
    /* Set margin-left of the container to -20px */
    .container {
      margin-left: calc(var(--margin) * -1);
    }

    Ta bort :root och :where styles från nyare WP-versioner

    Remove :root and :where inline css styles from newer WP versions. https://core.trac.wordpress.org/ticket/61660#:~:text=would%20be%20great.-,%2336,-%40butterflymedia%0A5

    add_action(
        'wp_head',
        function () {
        	global $wp_styles;
    
            if ( ! empty( $wp_styles->registered['global-styles']->extra['after'][0] ) ){
        		$wp_styles->registered['global-styles']->extra['after'][0] = str_replace(
                    [
                        ':root :where',
                        ':root',
                        ':where',
                    ],
                    '',
                    $wp_styles->registered['global-styles']->extra['after'][0]
                );
            }
        },
        1
    );

    Adams fyra designpriciper

    Principle #1: Good design works for everyone

    There are many reasons for this principle but my favourite is that designing for a minority makes things better for everyone.

    Examples:

    • Subtitles don’t just help deaf people; they allow people to watch a video in a loud cafe
    • Plain language isn’t just easier to read for people with low literacy; experts find it easier to read too
    • Large radio buttons don’t just help people with motor impairments; everyone finds them easier to click

    Principle #2: Good design makes things obvious

    Chris Pratley, founder of OneNote said “You know you have a good design when people say ‘oh yeah, of course’ like the solution was obvious”.

    Examples:

    • Instead of using a hamburger menu, just show the navigation items and let them wrap if necessary
    • Instead of using sticky menus, just let users scroll and put calls to action in context
    • Instead of hiding hint text in tooltips, just show the hint inline

    Principle #3: Good design puts users in control

    Design for real life. People prefer to interact in different ways. And we should design for both an idealised work flow as well as when things don’t go to plan.

    Examples:

    • Instead of expecting users to fill out a form in one go, expect them to get interrupted
    • Instead of showing a menu on hover, show it on click
    • Instead of infinite scrolling, let users paginate

    Principle #4: Good design is lightweight

    According to Google increasing the page load time from 1 to 3 seconds increases the chance of abandonment by 32%. At 6 seconds it increases to 106%. Slow interfaces are stressful and untrustworthy.

    Examples:

    • Kill the background video and prioritise the content and flow
    • Kill the tooltips and reduce the content to its irreducible core
    • Kill the carousel and show the content inline

    Töm Action Scheduler Woocommerce

    Töm logs-tabellen och därefter actions:

    DELETE FROM `toolwp_actionscheduler_actions` WHERE `status` = 'complete'
    DELETE FROM `toolwp_actionscheduler_actions` WHERE `status` = 'canceled'
    DELETE FROM `toolwp_actionscheduler_actions` WHERE `status` = 'failed'

    Länka utvald bild-blocket

    <?php
    /**
     * Modify featured image/post thumbnail block on products
     *
     * @param string|null $block_content The pre-rendered content. Default null.
     * @param WPBlock     $block         The block being rendered.
     * @return string modified Block HTML
     */
    function modify_featured_image_markup( $block_content, $block ) {
        global $post;
    
        if ( get_post_type( $post ) === 'product' && $block['blockName'] === 'core/post-featured-image' ) {
    
            $img_url = get_the_post_thumbnail_url( get_the_ID(), 'full' );
    
            $pattern = '/<figure(.*?)>(.*?)<img(.*?)src=["\']([^"\']+)["\'](.*?)>(.*?)<\/figure>/s';
            $replacement = '<figure$1>$2<a class="featured-image-link lightgallery-item" href="' . $img_url . '"><img$3src="$4"$5></a>$6</figure>';
    
            return preg_replace( $pattern, $replacement, $block_content );
    
        }
    
        return $block_content;
    }
    
    add_filter( 'render_block_core/post-featured-image', 'modify_featured_image_markup', 10, 2 );
    

    How web bloat impacts users with slow devices

    In 2017, we looked at how web bloat affects users with slow connections. Even in the U.S., many users didn’t have broadband speeds, making much of the web difficult to use. It’s still the case that many users don’t have broadband speeds, both inside and outside of the U.S. and that much of the modern web isn’t usable for people with slow internet, but the exponential increase in bandwidth (Nielsen suggests this is 50% per year for high-end connections) has outpaced web bloat for typical sites, making this less of a problem than it was in 2017, although it’s still a serious problem for people with poor connections.

    CPU performance for web apps hasn’t scaled nearly as quickly as bandwidth so, while more of the web is becoming accessible to people with low-end connections, more of the web is becoming inaccessible to people with low-end devices even if they have high-end connections. For example, if I try browsing a ”modern” Discourse-powered forum on a Tecno Spark 8C, it sometimes crashes the browser. Between crashes, on measuring the performance, the responsiveness is significantly worse than browsing a BBS with an 8 MHz 286 and a 1200 baud modem. On my 1Gbps home internet connection, the 2.6 MB compressed payload size ”necessary” to load message titles is relatively light. The over-the-wire payload size has ”only” increased by 1000x, which is dwarfed by the increase in internet speeds. But the opposite is true when it comes to CPU speeds — for web browsing and forum loading performance, the 8-core (2 1.6 GHz Cortex-A75 / 6 1.6 GHz Cortex-A55) CPU can’t handle Discourse. The CPU is something like 100000x faster than our 286. Perhaps a 1000000x faster device would be sufficient.

    https://danluu.com/slow-device/

    Hur man använder wp_nonce_field()

    Retrieve or display hidden nonce field for a form.

    The nonce field is used to validate that the contents of the form came from the location on the current site and not somewhere else. The nonce does not offer absolute protection, but should protect against most cases. It is very important to use nonce field in forms.

    The $action and $name are optional, but if you want to have better security, it is strongly suggested to set those two parameters. It is easier to just call the function without any parameters, because validation of the nonce doesn’t require any parameters, but since crackers know what the default is it won’t be difficult for them to find a way around your nonce and cause damage.

    The input name will be whatever $name value you gave. The input value will be the nonce creation value.

    Return

    String. Nonce field HTML markup.

    Usage

    wp_nonce_field( $action, $name, $referer, $echo );

    $action(int/string) Action name. The nonce token is generated based on it. Default: -1

    $name (string) The value of the name attribute of the input HTML tag. The value of the field can be taken from $_POST[ $name ] variable. Default: ’_wpnonce’

    $referer (true/false) Whether to set a referer field for validation. Hidden referer field can be added along with the nonce field. Also, such referer field can be added separately with wp_referer_field() function.
    Default: true

    $echo(true/false) false — don’t print, return the data to the variable for further handling. Default: true

    Form data verification

    <form method="post">
       <!-- some inputs here ... -->
       <?php wp_nonce_field( 'name_of_my_action', 'name_of_nonce_field' ); ?>
    </form>

    Output:

    output: <input type="hidden" id="_wpnonce" name="_wpnonce" value="5284708911" />
    <input type="hidden" name="_wp_http_referer" value="/permalink" />

    After the form data has been sent, when handling the data the nonce code must be verified with wp_verify_nonce() function. Like so:

    <?php
    if ( empty($_POST) || ! wp_verify_nonce( $_POST['name_of_nonce_field'], 'name_of_my_action') ){
       print 'Verification failed. Try again.';
       exit;
    }
    else {
       // Data handling
    }

    If request data is received in the admin panel, it can be verified with check_admin_referer() function. If the verification has not been passed, then check_admin_referer() automatically print the error message and stop the further PHP execution — so there’s no need to specify what to do if the verification is failed.

    function my_handler_function(){
    
    	// verification
    	// On failed verification, prints a error message and kills PHP execution.
    	check_admin_referer( 'name_of_my_action', 'name_of_nonce_field' );
    
    	// data handling
    }