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.

    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

    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

    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/

    Add capability for editors to edit menus

    /**
     * -------------------------------------------------------------------------
     * Add capability for editors to edit menus
     * -------------------------------------------------------------------------
     */
    /*function add_theme_caps(){
    	global $pagenow;
    	// gets the author role
    	$role = get_role('editor');
    
    	if ('themes.php' == $pagenow && isset($_GET['activated'])) {
    		// Test if theme is activated
    		// Theme is activated
    		// This only works, because it accesses the class instance.
    		// would allow the editor to edit the theme options
    		$role->add_cap('edit_theme_options');
    	} else {
    		// Theme is deactivated
    		// Remove the capability when theme is deactivated
    		$role->remove_cap('edit_theme_options');
    	}
    }
    add_action( 'load-themes.php', 'add_theme_caps' );
    */
    
    function hide_menu() {
    
    	if (current_user_can('editor')) {
    		$role = get_role('editor');
    		if (!$role->has_cap( 'edit_theme_options' ) ) {
    			$role->add_cap('edit_theme_options');
    		}
    
    		remove_submenu_page( 'themes.php', 'themes.php' ); // hide the theme selection submenu
    		remove_submenu_page( 'themes.php', 'widgets.php' ); // hide the widgets submenu
    		remove_submenu_page( 'themes.php', 'customize.php?return=%2Fwp-admin%2Fthemes.php' ); // hide the customizer submenu
    		remove_submenu_page( 'themes.php', 'customize.php?return=%2Fwp-admin%2Fprofile.php' );
    		remove_submenu_page( 'themes.php', 'customize.php?return=%2Fwp-admin%2Fnav-menus.php' );
    	}
    }
    
    add_action('admin_head', 'hide_menu');

    Varför man bör tänka till några gånger extra vid önskemål om “Sticky header”/”Sticky menu”

    This fancy pattern hurts UX far more than it improves it.”

    Adam Silver 

    (Skrivit för Smashing Magazine, A List Apart och CSS Tricks. Jobbat med ex. GOV.UK, Just Eat, Tesco och BBC – vilket, om någon inte känner till ovanstående, är ett sjukt CV)

    Problem #1: tar alltid upp plats

    Är alltid i vägen för content. Än värre på mindre skärmar/upplösningar där ytan är än mer begränsad.

    Problem #2: de skymmer innehåll

    Hänger ihop med #1. De lägger sig ovanför övrigt innehåll.

    Problem #3: kan gå sönder vid inzoomning

    När man zoomar kan storleken på header/meny öka och på så vis tränga bort övrigt content. Eller så försvinner dom, förvrängs/”felalignar” eller kapas. 

    Kan ev undvikas på vissa sätt men till kostnad av ganska mycket programmeringstid.

    Problem #4: de verkar närmare än de är

    Sticky menyer/headers är alltid visuellt tillgängliga men i verkligheten är de ofta väldigt långt bort för användare av tangentbord som navigerar med tabtangenten; eller de som på annat sätt navigerar webben.

    Problem #5: de skymmer länkar och andra fokuserbara element

    Besökare som navigerar tillbaka upp på sidan med tangentbord kan hamna i ett läge där fokus är skymt bakom sticky meny/header.

    Problem #6: svåra att komma åt

    Om sticky header/meny är högre än innehållet och viewport så kan man inte komma åt det nedersta på menyn i vissa webbläsare. Även om innehållet är högre än header/menyn så måste man fortfarande scrolla ner till botten på content för att se menyn.

    Man kan addera en inre scrollbar till menyn men multipla scroll är svåra att hantera (https://baymard.com/blog/inline-scroll-areas). 

    Problem #7: interna sidankare känns trasiga när de klickas på

    Vissa sticky innehåller länkar som tar besökaren längre ner (eller upp) på sidan, sk. ankarlänkar.

    Med en stickyfunktionalitet så är det inte säkert att man förflyttar sig vid klick och därför kan interfacet känns trasigt.

    Vad ska du göra istället?

    1. Håll sidor korta: Sticky menyer/headers är ett symptom av (väldigt) långa sidor, åtgärda grundorsaken. Har du inte långa sidor så behövs inte heller sticky header/meny.
    2. Låt besökare skrolla(!): Det är en myt att skrollning är ett problem (och helt seriöst så började dom diskussionerna ebba ut redan för +10 år sedan). Även på telefoner så är toppen av sidan oftast en “flick” eller två ifrån (normalt sett kan man alltid trycka på “top bar:en” för att bli skickad tillbaka till toppen, i de flesta webbläsare).
    3. Lägg in relevanta länkar, i kontext: Exempelvis kan du lägga in länkar i löptext (det är trots allt själva definitionen av World Wide Web), lägga till formulär i slutet av en artikel eller kasta ut en CTA (länkad banner/knapp) här och var.

    Var försiktig med att disable:a submit-knappar

    Många gånger är man frestad att inaktivera en submitknapp för att man tänker sig att man då eliminerar fel eller åtminstone inte visar fel längs vägen (“error prevention is better than a well-designed error”). Men här är några saker man kan tänka på om man tänker så.

    #1: Vi får ingen feedback

    När fel görs i ett formulär utan feedback så behöver användaren scanna av varje fält, hitta ett eventuellt fel och sen komma på hur de ska lösa det. Kontra en validering på submit som tydligt markerar ut vart felet är och varför det blir fel.

    #2: De kan få interfacet att kännas br0ken

    Om användaren tycker att deras svar är okej så känns UI söndrigt “Hallå, jag har ju fyllt i allt?!”. Och om det finns flera fel och ett av dessa åtgärdas så fortsätter knappen att vara inaktiverad.

    #3: Svåra att se

    Inaktiverade knappar har generellt sett låg kontrast för att signalera att de är inaktiva. Detta gör dom svåra att läsa/se, speciellt för användare med synnedsättningar.

    #4: Är inte fokuserbara (focusable)

    Användare som navigerar med tangentbordet kan inte tabba till knappen. Och användare med dålig syn kan inte se knappen. En tydlig väg framåt saknas därför för dessa användare.

    #5: De är missvisande

    Inaktiverade knappar har generellt sett låg kontrast för att signalera att de är inaktiva. Men detta är inte alltid tydligt. Så vissa användare kommer ändå att försöka klicka på dom.

    #6: Användare kanske inte märker att knappen blir aktiverad

    Detta beror på att knappen kanske är “utanför skärmen” pga upplösning/skärmstorlek eller storlek på formuläret. Och om knappen är synlig så är användaren snarare fokuserad på att fylla i fälten, inte på att knappen byter status.

    Att inaktivera submitknappen hindrar inte användare att göra fel. Men den kan hindra dom från att förstå att de gjort ett och hur de löser det.

    Alternativ för betalning på Internet (med WordPress)

    Priser hämtade nov 2023

    Förr i tiden kunde man enkelt hänga på en köpknapp från Paypal, a la 

    https://www.paypal.com/buttons/ men jag vet inte om det är någon höjdare numera.

    Om man vill utgå från ett formulär så har två av de största premium-formulärbyggarna för WordPress (något begränsade) betalningsalternativ man kan hänga på: 

    Gravity Forms

    Basic $59 / år

    Elite $259 / år

    Pro $159 / år

    2Checkout (Elite), Stripe (Pro), Paypal Checkout (Pro)

    Ninja Forms

    Plus $99/år

    Paypal ingår

    Pro $199/år

    Stripe, Authorize.net och Elavon ingår

    Notera att Stripe erbjuder ex Klarna Checkout(vet ej hur det fungerar)

    Här finns även en uppstickare som verkar intressant:

    WP Simple Pay

    Klarna, Stripe, Google Pay, Apple Pay etc.

    Från $49.50/år men kräver troligtvis $199.50 för att få ex Klarna

    Easy Digital Downloads

    Ett annat alternativ som påminner om WooCommerce (och är en otroligt stor lösning om man inte har fysiska produkter, tror det är den näst största i WP-sfären efter Woo) är Easy Digital Downloads.https://easydigitaldownloads.com/

    Inkluderar Stripe och PayPal

    Från €95/år – antagligen krävs dyrare nivå (€192 el. €288)

    Precis som med WooCommerce bygger man ut med extra tillägg beroende på önskad funktionalitet. Woo har dock väldigt många fler (och fler svenskanpassade) lösningar enligt min uppfattning.

    WooCommercebaserade lösningar

    WooCommerce, världens största e-handelsverktyg och byggt som ett tillägg till WordPress. Man kan lägga upp en tjänst som en produkt. Det behöver inte vara fysiska produkter man säljer men det blir dock en skillnad i köpförfarandet. Man lägger produkt i varukorg kontra ett formulär med köpinfo (jämför Gravity, Ninja, WPSP ovan).

    Betaltjänster (payment gateways) för WooCommerce

    Paypal ingår (tror det är https://woo.com/products/woocommerce-paypal-payments/)

    Manuella alternativ så som Gratis, Betala fysiskt (alltså en form av bokning), faktura/bankgiro ingår.

    Klarna, Walley, Qliro, Worldline (tidigare Bambora), Svea m.fl. erbjuder färdiga gratis tillägg för att koppla ihop med WooCommerce. Jag tror att alla (osäker på Svea) innehåller bl.a. Swish som betalmetod.

    Swish (för) handel

    Redlight Media i Malmö erbjuder ett tillägg till WooCommerce för enbart Swishbetalning (960 kr/år). Notera att precis som med övriga betaltjänstleverantörer så tillkommer Swish egna kostnad. Kostnaderna skiljer sig åt beroende på ett antal faktorer som ex bank. Men ett prisexempel (lånat från Angry Creative så vet inte hur aktuellt det är 2023/2024) kan se ut så här, för att få en uppfattning:

    WooCommerce: gratis

    Swishtiullägg till WC: 960 kr/år

    Uppläggningsavgift Swish: 1000 kr

    Månads- eller årsavgift: ca 50 kr/mån alt. 500-1500 kr/år beroende på bank

    Transaktionsavgift: 1-3 kr

    Ungefärliga prisjämförelser

    TjästKostnad
    WooCommerce + Klarna, Walley, Qliro, Worldline etcGratis tillägg, betala för respektive tjänst (gissningsvis månadsavgift + transaktionsavgift)
    WooCommerce + Swish Handel960 kr / år för swishtillägget + avtal med Swish (månadsavgift + transaktionsavgift)
    Easy Digigtal DownloadsGissningsvis €192 / år + avtal med ex Klarna (månadsavgift + transaktionsavgift)
    WP Simple PayGissningsvis $199,50 / år + avtal med ex Klarna (månadsavgift + transaktionsavgift)
    Ninja FormsFrån $99/år (om Paypal funkar) men antagligen $199 / år + avtal med ex Stripe (transaktionsavgift + procentuell avgift – helt beroende på Stripelösning och om man ex har Klarna därigenom)
    Gravity Forms$159 / år, då ingår PayPal Checkout eller Stripe. Tillkommer transaktionsavgift + procentuell avgift – helt beroende på Stripelösning och om man ex har Klarna därigenom

    Jag tror att man tänka att det kostar som minst ~ 2 000 kr / år oavsett tjänst man väljer. Exklusive att jag rekommenderar att hålla sajt och tillägg ajour (gärna 3-4 gånger per år) för att undvika säkerhetshål, att något går sönder eller för den delen få nya features. Samt att det totalt tenderar att gå mindre tid vid mindre insatser ofta, än om man skulle göra en stor uppdatering vid färre tillfällen (1 gång/år eller mer sällan).

    Prisexempel från Klarna är ruggigt svårt att hitta men jag har exempelpriser nedan men osäker hur gamla dessa är:

    Fast avgift per transaktion: 3,50 SEK

    Rörlig avgift på transaktionsvärde: 2.79%

    Säljer man en produkt för 1 000 kr på ett år skulle det då innebära minst 2 000 kr för att använda tjänsterna samt strax över 30 kr i transaktionsavgifter (Klarna).