Array values discrepancy between backend and frontend

I’ve been working on a WordPress plugin to retrieve a directory structure from my server and display it in a webpage. I have a working demo, but there is an issue that I don’t understand.

I construct the data structure in the backend as an array, each item containing its name, relative path, type (file or folder) and children if it is a folder. The items in the base folder appear with incorrect paths when I print them in the console, but their children items are correct.

The project is much larger, but I tried to simplify and show the relevant code. This is the backend function that retreives the data structure:

    function list_items($base, $current_path = '') {
        $base_path = PWR_FILE_REPOSITORY_PATH . $base;   /** Absolute path to base folder */
        $full_path = $base_path . $current_path;         /** Absolute path to current folder */

        $items = [];

        if (is_dir($full_path)) {
            $files = scandir($full_path);
            foreach ($files as $file) {
                if ($file === '.' || $file === '..') continue;

                $full_item_path = trailingslashit($full_path) . $file;                  /** Absolute path to file*/
                $item_path = ltrim(str_replace($base_path, '', $full_item_path), '/\');/** Relative path to file from the base path */

                $item = [
                    'name' => $file,
                    'path' => $item_path,
                    'type' => is_dir($full_item_path) ? 'folder' : 'file',
                ];

                if ($item['type'] === 'folder') {
                    $item['children'] = self::list_items($base, $item_path . '/');
                }
                $items[] = $item;
            }
        }

        return $items;
    }

$base is the base folder for each user and should not appear in the item’s path. $current_path is initially empty because I’m retreiving the base folder. There is an intermediate function that acts as request handler, and I made sure that all paths are correct:

        $directories = self::list_items($base_path, $path);
        foreach ($directories as $item) {
            error_log(print_r($item, true));
        }
        wp_send_json_success(['directories' => $directories]);

Example log (this is correct):

Array
(
    [name] => folder1
    [path] => folder1
    [type] => folder
    [children] => Array
        (
            [0] => Array
                (
                    [name] => subfolder1_1
                    [path] => folder1/subfolder1_1
                    [type] => folder
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => file.txt
                                    [path] => folder1/subfolder1_1/file.txt
                                    [type] => file
                                )
                        )
                )
        )
)

Console (this is not correct):

```
fetchDirectoryStructure().then(data => {
    console.log(data);

    /** More code here */
});

Array(n) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ]
​
0: Object { name: "folder1", path: "base_folder1/folder1", type: "folder", … }
​​
  children: Array(m) [ {…}, {…}, {…}, … ]
​​​
    0: Object { name: "subfolder1_1", path: "folder1/subfolder1_1", type: "folder", … }
​​​​
      children: Array [ {…} ]
​​​​​
        0: Object { name: "file.txt", path: "folder1/subfolder1_1/file.txt", type: "file" }
​​
...

As you can see, there’s a discrepancy between the path sent (‘folder1’) and the path received (‘base_folder1/folder1’) and I don’t understand why does it happen, because I don’t modify the structure at any point. I’ll write down the fetch too if its helpful.

    async function fetchDirectoryStructure(path = '') {
        const response = await fetch(pwrFileRepoAjax.ajaxurl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: `action=get_directory_structure&base_path=${pathManager.getBasePath()}&path=${path}&nonce=${encodeURIComponent(pwrFileRepoAjax.nonce)}`,
        });
        if (!response.ok) {
            console.error('AJAX request failed:', response.status, response.statusText);
            throw new Error('AJAX request failed');
        }
        const data = await response.json();
        if (!data.success) {
            console.error('Error in AJAX response:', data);
            throw new Error('Error in AJAX response');
        }
        return data.data.directories;
    }

weather api key not working? how to resolve this [closed]

I’m working with Open Weather API, but when i send an request for a city data, i get network error.

var apiKey = “b6278500dc4dc994511feaf7311bf025”;

function getWeather(city) {
    var url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=b6278500dc4dc994511feaf7311bf025&units=metric`;

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.responseType = "json";
    xhr.send(null);

    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        output_section.style.display = "block"; 
        if (xhr.status === 200) {
          var DATA = xhr.response;
          
          var iconAlt = DATA.weather[0].description;
          var iconImgSrc = `https://openweathermap.org/img/wn/${DATA.weather[0].icon}.png`;

How to load a js file after DOMContentLoaded [closed]

I’m trying to load a js file from the theme folder of WordPress – wp-content/themes/new/js/newfile.js – I need this to load after DOMContentLoaded, but I have no idea how to do this.

The JavaScript is currently not working, and I’ve been told it might work using DOMContentLoaded so it executes at the right time.

I’ve Googled this, but I couldn’t find a simple guide of how to do it.

Thanks in advance

Webpack Module Federation Bundle Remote Modules in Build

im using the ModuleFederationPlugin for Webpack to use module federation. its working as expected.

im working on a native mobile app version with Tauri (its basically a native wrapper with a webview). unlike the webapp, id like to have it so it doesnt use remote modules.

is there a simple way to achieve this like a config somewhere like:

{
...
bundle: true // maybe use some env var to set this?
...
}

Why Does Prettier Break `return foo(` onto a New Line Even with `printWidth = 200`?

When I format the file, Prettier breaks the line and moves return foo( to a new line.

Why is that? I’ve set printWidth = 200, and there’s plenty of space to keep it on one line.

How can I stop Prettier from breaking the line like that?
I’m not looking for a workaround like prettier-ignore.
Is there a configuration option to control this?
Sure! Here’s a more natural rephrasing:

function foo() { // I don't care whether it works or not — I'm only concerned about the formatting
  /*
  return foo('test', baz`
    some text here
  `);
  */

  return foo(
    'test',
    baz`
    some text here
  `
  );
}

.prettierrc:

{
  "singleQuote": true,
  "arrowParens": "always",
  "semi": true,
  "trailingComma": "es5",
  "bracketSameLine": true,
  "printWidth": 200
}

This is what I expect (and it’s fine that the text is on a new line since I pressed Enter there):

return foo('test', baz`
    some text here
`);

stackblitz demo

FB.login() instantly returns without waiting for WhatsApp signup modal (works in static HTML but not in EJS)

I’m implementing the WhatsApp Business embedded signup flow using Facebook’s SDK. When calling FB.login() the callback runs instantly, and when i finish the process with the modal nothing happen

– Note: The exact same code, when placed in a static .html file and
hosted over HTTPS, works perfectly the flow
proceeds as expected.

NestJS route (renders EJS):

async whatsappOnboardingStarter(@Query() query: { config: string }, @Res() res: Response) {
  const nonce = crypto.randomBytes(16).toString('hex');
  res.setHeader(
    'Content-Security-Policy',
    `script-src 'self' 'nonce-${nonce}' https://connect.facebook.net; frame-src https://www.facebook.com https://web.facebook.com;`
  );
  res.render('whatsapp-onboarding-starter', {
    appId: this.configService.get('WHATSAPP_APP_ID'),
    graphApiVersion: 'v22.0',
    configId: this.configService.get('WHATSAPP_CONFIG_ID'),
    host_url: this.configService.get('HOST_URL'),
    restaurantData: query.config,
    nonce,
  });
}

EJS template:

<!DOCTYPE html>
<html lang="en">
<head>…</head>
<body>
  <div id="fb-root"></div>
  <button id="signup-button">Connect WhatsApp Business</button>

  <script nonce="<%= nonce %>">
    window.fbAsyncInit = function () {
      FB.init({
        appId: '<%= appId %>',
        autoLogAppEvents: true,
        xfbml: true,
        version: '<%= graphApiVersion %>',
      });
    };

    const fbLoginCallback = (response) => {
      console.log('FB LOGIN CALLBACK', response);
      // Additional logic...
    };

    const launchWhatsAppSignup = () => {
      FB.login(fbLoginCallback, {
        config_id: '<%= configId %>',
        response_type: 'code',
        override_default_response_type: true,
        extras: { setup: {}, featureType: '', sessionInfoVersion: '3' },
      });
    };

    document
      .getElementById('signup-button')
      .addEventListener('click', launchWhatsAppSignup);
  </script>

  <script
    async defer crossorigin="anonymous"
    src="https://connect.facebook.net/en_US/sdk.js"
    nonce="<%= nonce %>"
  ></script>
</body>
</html>

Response that return immediately when use ejs :

FB LOGIN CALLBACK : {
  "authResponse": {
    "accessToken": "........",
    "userID": "........",
    "expiresIn": 3600,
    "signedRequest": "........",
    "graphDomain": "facebook",
    "data_access_expiration_time": 1750000000
  },
  "status": "connected"
}

Note: This response is solely related to the Facebook login process. It does not appear on the HTML side and is different from the response returned by the WhatsApp signup popup, which provides a separate set of data.

What I’ve tried :

  • Cleared cookies and local storage, tested in incognito mode, not
    logged in to Facebook
  • Confirmed CSP headers allow Facebook SDK
  • No JavaScript or network errors in the console

How can I fix the slot ng-repeat so it will display the areas for the selected block?

I am trying to display the areas for the selected block, the wizardStep is the parent controller and the optionSelector is the child controller. I am sending the selected name and value of the selected option to the parent controller so we can then display the content of that selected option. However, the slot ng-repeat in the wizard step html is not displaying the block. how can we fix this?

optionSelector.controller.js

angular.module("umbraco").controller("optionSelectorController", function ($scope, selectionService) {
    var vm = this;
    vm.propertyEditorForm = $scope.propertyForm;
    vm.blockEditorApi = $scope.blockEditorApi;
    vm.depth = $scope.depth || 0;
    vm.model = $scope.model;
    vm.block = vm.model.block;

    vm.select = function (block) {
        console.log("Selected Option:", block);

        selectionService.setSelected({
            fieldName: vm.block.data.fieldName,
            fieldValue: block.$block.data.fieldValue
        });
    };
});

optionSelector.html

<div class="option-selector" ng-controller="optionSelectorController as vm">
    <div class="options">
        <div ng-repeat="area in vm.block.layout.areas" style="display: flex;">
            <div ng-repeat="block in area.items">
                <div class="option-item">
                    <h5 ng-bind="block.$block.data.title"></h5>
                    <p ng-bind-html="block.$block.data.description.markup"></p>

                    <div class="option-footer">
                        <button class="btn btn-primary"
                                ng-click="vm.select(block)"
                                ng-class="{ 'selected': vm.selectedOptionKey === block.$block.key }">
                            <span ng-if="vm.selectedOptionKey === block.$block.key">Selected</span>
                            <span ng-if="vm.selectedOptionKey !== block.$block.key">Select</span>
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<style>
    .options {
        border-top: 3rem solid aqua;
        width: 100%;
    }

    .option-selector {
        display: flex;
        flex-direction: row;
    }

    .umb-block-grid__layout-container {
        display: flex !important;
    }
</style>

wizardStep.controller.js:

angular.module("umbraco").controller("wizardStepController", function ($scope, selectionService, blockEditorService) {
    var vm = this;
    vm.model = $scope.model;
    vm.block = vm.model.block;

    vm.title = vm.block.data.title;
    vm.description = vm.block.data.description;
    vm.additionalInfo = vm.block.data.additionalInfo;

    vm.propertyEditorForm = null;
    vm.blockEditorApi = null;
    vm.layoutColumns = 12;
    vm.depth = 0;

    function circularReplacer(obj) {
        let seen = new WeakSet();
        return function (key, value) {
            if (typeof value === "object" && value !== null) {
                if (seen.has(value)) return "[Circular]";
                seen.add(value);
            }
            return value;
        };
    }

    selectionService.onSelect(function (data) {
        console.log("Received selected option:", data);

        const { fieldName, fieldValue } = data;
        vm.selectedBlock = null;

        for (const outerArea of vm.block.layout.areas || []) {
            for (const parentItem of outerArea.items || []) {
                const parentBlock = parentItem.$block;

                if (!parentBlock?.data?.fieldName) continue;

                for (const innerArea of parentBlock.layout.areas || []) {
                    for (const childItem of innerArea.items || []) {
                        const childBlock = childItem.$block;
                        const childValue = childBlock?.data?.fieldValue;

                        if (parentBlock.data.fieldName === fieldName && childValue === fieldValue) {
                            vm.selectedBlock = childBlock;

                            console.log("Selected block layout:", vm.selectedBlock.layout);
                            $scope.$applyAsync();
                            vm.selectedTitle = childBlock.data.title;
                            vm.selectedDescription = childBlock.data.description;
                            vm.fieldValue = childValue;
                            break;
                        }
                    }
                    if (vm.selectedBlock) break;
                }

                if (vm.selectedBlock) break;
            }
            if (vm.selectedBlock) break;
        }

        console.log("Matched block:", JSON.stringify(vm.selectedBlock, circularReplacer(vm.selectedBlock)));
    });
});

wizardStep.html

<div ng-controller="wizardStepController as vm">
    <div class="wizard-step">
        <div class="wizard-step__header">
            <h2 ng-bind="vm.title"></h2>
            <p ng-bind-html="vm.description.markup"></p>
            <div ng-bind="vm.additionalInfo"></div>
        </div>

        <div class="wizard-step__content">
            <slot ng-repeat="area in vm.block.layout.areas"
                  name="{{area.$config.alias}}"
                  block-editor-api="vm.blockEditorApi"
                  property-editor-form="vm.propertyEditorForm">
            </slot>
        </div>
    </div>

    <div class="selected-info" ng-if="vm.selectedBlock.layout.areas.length">
        <slot ng-repeat="area in vm.selectedBlock.layout.areas track by area.key"
              name="{{area.$config.alias}}">
        </slot>
    </div>
</div>

<slot name="test"></slot>

<style>
    .wizard-step__content {
        box-sizing: border-box;
    }
</style>

How do I make the whole table scale down to the size of it’s parent’s div?

const container = document.querySelector('.container');
const tbody = document.querySelector('#myTable tbody');

const ROW_COUNT = 40;
for (let i = 1; i <= ROW_COUNT; i++) {
  const tr = document.createElement('tr');
  const td = document.createElement('td');
  td.textContent = 'Row ' + i;
  tr.appendChild(td);
  tbody.appendChild(tr);
}

function updateRowHeights() {
  const rows = tbody.querySelectorAll('tr');
  const totalHeight = container.clientHeight;
  const rowHeight = totalHeight / rows.length;
  rows.forEach(row => {
    row.style.height = rowHeight + 'px';
  });
}

updateRowHeights();
window.addEventListener('resize', updateRowHeights);
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.container {
  height: 50vh;
  width: 90vw;
  margin: 2rem auto;
  border: 2px solid #666;
  overflow: hidden;
}

table {
  width: 100%;
  border-collapse: collapse;
}

td {
  border: 1px solid #999;
  text-align: center;
}
<div class="container">
    <table id="myTable">
        <tbody></tbody>
    </table>
</div>

This is just a sample code of what I’m trying to do in my project(for reference of what I’m talking about).

I need a div to fit dynamically to the screen(the HTML need to be able to be opened in normal PC monitor, TV, tablets, and mobile), in here i make the container 50vh.

The problem is I have a table with decent rows(about 40-50). I need them to be displayed entirely, no matter the size of the font or rows(it doesn’t matter if its so small), inside the table without any scrolling or paging(my company needs to cut cycle time of production).

I have not found a way to do this, its either getting cut off vertically, hidden by overflow:hidden, or the scrollbar show-up. Any help would be nice !

Middleware is not triggering in next.js web application

I am using below code in my middleware.js file in next.js application. Ideal when I go http://localhost:3000/home it should redirect to http://localhost:3000/, but it seems like my middleware is not triggering.

Here is my file structure
GSPANN+Abodh.shah@NODE1363 MINGW64 /d/camdiumUI/application/frontend (main)
$ ls
README.md components/ middleware.js next.config.js node_modules/ package-lock.json package.json pages/ public/ redux/ styles/ utils/

export function middleware(request) {
  console.log(1)
  return NextResponse.redirect(new URL('/', request.url))
}

// See "Matching Paths" below to learn more
export const config = {
  matcher: ['/home'],
}

Questions about Conducting Conjoint Experiment through Qualtrics

I am currently conducting a conjoint experiment through Qualtrics. Specifically, in this experiment, first, in each block, the conjoint table includes three regular features: Gender (levels: Female and Male), Party (levels: Dem and Rep), and Ideology (levels: conservative, liberal, and independent) and one of two features (randomly picked): Abortion (levels: support, oppose, and neutral) and Gun (levels: support, oppose, and neutral). Second, in each block, features in the conjoint table are randomly reordered. The issue now is even though the above goals are satisfied, the exported data fails to capture the feature name of “Gun,” i.e., even though the feature Gun was selected and shown to the respondent, in the exported data, the name of this feature is still Abortion. Below is the Javascript I use. Any help is appreciated. In addition, I set up embedded data in survey flow as instructed as “F-1-1”, “F-1-1-1”, “F-1-2-1”, “F-1-2”, etc.

Javascript:

  // Code to randomly generate conjoint profiles in a Qualtrics survey
  
  // Terminology clarification: 
    // Task = Set of choices presented to respondent in a single screen (i.e. pair of candidates)
    // Profile = Single list of attributes in a given task (i.e. candidate)
    // Attribute = Category characterized by a set of levels (i.e. education level)
    // Level = Value that an attribute can take in a particular choice task (i.e. "no formal education")
    
    // Attributes and Levels stored in a 2-dimensional Array 
    
    /* Randomize array in-place using Durstenfeld shuffle algorithm */
      function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
          var j = Math.floor(Math.random() * (i + 1));
          var temp = array[i];
          array[i] = array[j];
          array[j] = temp;
        }
        return(array);
      }
    
    // Function to generate weighted random numbers
    function weighted_randomize(prob_array, at_key)
    {
      var prob_list = prob_array[at_key];
      
      // Create an array containing cutpoints for randomization
      var cumul_prob = new Array(prob_list.length);
      var cumulative = 0.0;
      for (var i=0;  i < prob_list.length; i++){
        cumul_prob[i] = cumulative;
        cumulative = cumulative + parseFloat(prob_list[i]);
      }
      
      // Generate a uniform random floating point value between 0.0 and 1.0
      var unif_rand = Math.random();
      
      // Figure out which integer should be returned
      var outInt = 0;
      for (var k = 0; k < cumul_prob.length; k++){
        if (cumul_prob[k] <= unif_rand){
          outInt = k + 1;
        }
      }
      
      return(outInt);
      
    }
    
    var alway = {
      "Gender": ["Female", "Male"],
      "Party": ["Dem", "Rep"],
      "Ideology": ["Liberal", "Conservative", "Independent"]
    };
    
    var opt = {
      "Abortion": ["Support", "Oppose", "Neutral"],
      "Gun": ["Support", "Oppose", "Neutral"]
    };
    
    var keys = Object.keys(opt);
    var randomKey = keys[Math.floor(Math.random() * keys.length)];
    var rpick = opt[randomKey];
    
    var featurearray = Object.assign({}, alway);
    featurearray[randomKey] = rpick;
    
    var restrictionarray = [];
    
    var probabilityarray = {};
    
    // Indicator for whether weighted randomization should be enabled or not
    var weighted = 0;
    
    // K = Number of tasks displayed to the respondent
    var K = 8;
    
    // N = Number of profiles displayed in each task
    var N = 2;
    
    // num_attributes = Number of Attributes in the Array
    var num_attributes = featurearray.length;
    
    // Should duplicate profiles be rejected?
      var noDuplicateProfiles = true;
    
    var attrconstraintarray = [];
    
    // Re-randomize the featurearray
    
    // Place the $featurearray keys into a new array
    var featureArrayKeys = Object.keys(featurearray);
    
    // If order randomization constraints exist, drop all of the non-free attributes
    if (attrconstraintarray.length != 0){
      for (const constraints of attrconstraintarray){
        if (constraints.length > 1){
          for (var p = 1; p < constraints.length; p++){
            if (featureArrayKeys.includes(constraints[p])){
              var remkey = featureArrayKeys.indexOf(constraints[p]);
              featureArrayKeys.splice(remkey, 1);
            }
          }
        }
      }
    } 
    
    // Re-randomize the featurearray keys
    featureArrayKeys = shuffleArray(featureArrayKeys);
    
    // Re-insert the non-free attributes constrained by $attrconstraintarray
    if (attrconstraintarray.length != 0){
      for (const constraints of attrconstraintarray){
        if (constraints.length > 1){
          var insertloc = constraints[0];
          if (featureArrayKeys.includes(insertloc)){
            var insert_block = [];
            for (var p = 1; p < constraints.length; p++){
              insert_block.push(constraints[p]);
            }
            var begin_index = featureArrayKeys.indexOf(insertloc);
            featureArrayKeys.splice(begin_index+1, 0, ...insert_block);
          }
        }
      }
    }
    
    
    // Re-generate the new $featurearray - label it $featureArrayNew
    var featureArrayNew = {};
    for (var h = 0; h < featureArrayKeys.length; h++){
      featureArrayNew[featureArrayKeys[h]] = featurearray[featureArrayKeys[h]];        
    }
    
    
    // Initialize the array returned to the user
    // Naming Convention
    // Level Name: F-[task number]-[profile number]-[attribute number]
    // Attribute Name: F-[task number]-[attribute number]
    // Example: F-1-3-2, Returns the level corresponding to Task 1, Profile 3, Attribute 2 
    // F-3-3, Returns the attribute name corresponding to Task 3, Attribute 3
    
    var returnarray = {};
    
    // For each task $p
    for(var p = 1; p <= K; p++){
      var featureArrayKeys = Object.keys(featurearray);
  featureArrayKeys = shuffleArray(featureArrayKeys);

  // ✅ Store attribute names with explicit keys
  for (var h = 0; h < featureArrayKeys.length; h++) {
    var attrLabelKey = "F-" + p + "-" + (h + 1) + "-name";
    Qualtrics.SurveyEngine.setEmbeddedData(attrLabelKey, featureArrayKeys[h]);
  }
      // For each profile $i
      for(var i = 1; i <= N; i++){
        
        // Repeat until non-restricted profile generated
        var complete = false;
        
        while (complete == false){
          
          // Create a count for $attributes to be incremented in the next loop
          var attr = 0;
          
          // Create a dictionary to hold profile's attributes
            var profile_dict = {};

            // For each attribute $attribute and level array $levels in task $p
            for(var q = 0; q < featureArrayKeys.length; q++){
                // Get Attribute name
                var attr_name = featureArrayKeys[q];
                    
                // Increment attribute count
                attr = attr + 1;
    
                // Create key for attribute name
                var attr_key = "F-" + p + "-" + attr;

    
                // Store attribute name in returnarray
                returnarray[attr_key] = attr_name;

                // Get length of levels array
                var num_levels = featureArrayNew[attr_name].length;

                // Randomly select one of the level indices
                if (weighted == 1){
                    var level_index = weighted_randomize(probabilityarray, attr_name) - 1;

                }else{
                    var level_index = Math.floor(Math.random() * num_levels);
                }   

                // Pull out the selected level
                var chosen_level = featureArrayNew[attr_name][level_index];
                
                // Store selected level in profileDict
                profile_dict[attr_name] = chosen_level;
    
                // Create key for level in $returnarray
                var level_key = "F-" + p + "-" + i + "-" + attr;
    
                // Store selected level in $returnarray
                returnarray[level_key] = chosen_level;

            }

            var clear = true;
            
            // Cycle through restrictions to confirm/reject profile
            if (restrictionarray.length != 0){
                for (var v = 0; v < restrictionarray.length; v++){
                    var falsevar = 1;
                    for (var mp = 0; mp < restrictionarray[v].length; mp++){
                        if (profile_dict[restrictionarray[v][mp][0]] == restrictionarray[v][mp][1]){
                            falsevar = falsevar*1;
                        }else{
                            falsevar = falsevar*0;
                        }                           
                    }
                    if (falsevar == 1){
                        clear = false;
                    }
                }
            }
                            
            // If we're throwing out duplicates
          if (noDuplicateProfiles == true){
            // Cycle through all previous profiles to confirm no identical profiles
            if (i > 1){    
              // For each previous profile
              for(var z = 1; z < i; z++){
                
                // Start by assuming it's the same
                        var identical = true;
                        
                        // Create a count for $attributes to be incremented in the next loop
                        var attrTemp = 0;
                        
                        // For each attribute $attribute and level array $levels in task $p
                        for(var qz = 0; qz < featureArrayKeys.length; qz++){
                            
                            // Increment attribute count
                            attrTemp = attrTemp + 1;
    
                            // Create keys 
                            var level_key_profile = "F-" + p + "-" + i + "-" + attrTemp;
                            var level_key_check = "F-" + p + "-" + z + "-" + attrTemp;
                            
                            // If attributes are different, declare not identical
                            if (returnarray[level_key_profile] != returnarray[level_key_check]){
                                identical = false;
                            }
                        }
                        // If we detect an identical profile, reject
                        if (identical == true){
                            clear = false;
                        }
                    }                
                }
            }
            complete = clear;
        }
    }
}
    
// Write returnarray to Qualtrics

var returnarrayKeys = Object.keys(returnarray);
    
for (var pr = 0; pr < returnarrayKeys.length; pr++){
       Qualtrics.SurveyEngine.setEmbeddedData(returnarrayKeys[pr], returnarray[returnarrayKeys[pr]]); 
}

Laravel PDF (Spatie) + Puppeteer + Forge: “No usable sandbox” error in production

I’m using the spatie/laravel-pdf package (which wraps Browsershot + Puppeteer) to generate PDFs in my Laravel 12 project.

It works perfectly on my local machine, but when I deploy it to my Forge provisioned Ubuntu 24.04 server, I get this error when trying to generate a PDF:

Error: Failed to launch the browser process!
[xxxx/FATAL:zygote_host_impl_linux.cc(132)] No usable sandbox!

Best Way to Add a Lightweight Forum to WooCommerce Site Without Slowing It Down? [closed]

I’m running a WooCommerce site.

I want to integrate a lightweight discussion forum where users can:

  • Share reviews

  • Post product questions

  • Engage in shoe-related topics

I’ve considered:

  • bbPress (seems a bit heavy and old)
  • wpForo (too feature-rich for what
  • I need) Creating a custom CPT + comments + simple front-end template

My main concern is:

  • Keeping performance high (Core Web Vitals are key)
  • SEO indexing of forum threads
  • Clean integration with existing WordPress + WooCommerce setup

Has anyone built a minimal discussion area into a WooCommerce store?
Any modern solutions or lightweight plugins you’d recommend?
Even custom PHP/JS setups are welcome — just need something fast and simple.

PHP DOMDocument/XPath not extracting movie data from Paytm Movies page

I’m trying to scrape movie details (name, genre, etc.) from Paytm Movies Coimbatore using PHP’s DOMDocument and XPath. While my code fetches the HTML successfully, it fails to extract text content from movie cards.

{"@context":"http://schema.org","@type":"Movie","name":"Good Bad Ugly","url":"https://paytm.com/movies","genre":"action","image":"https://assetscdn1.paytm.com/images/cinema/Goof-e0906bf0-115c-11f0-ac8c-93d19273dc5b.jpg","inLanguage":"Tamil","duration":"PT140M","datePublished":"2025-04-10","releasedEvent":{"@context":"http://schema.org","@type":"PublicationEvent","startDate":"2025-04-10","location":{"@type":"Country","name":"IN"}},"aggregateRating":{"@context":"http://schema.org","@type":"AggregateRating","ratingValue":9.3,"bestRating":10,"ratingCount":2031}}93%Good Bad UglyUA16+TamilBook Ticket
<?php
header('Content-Type: text/html; charset=utf-8');

$url = "https://paytm.com/movies/coimbatore";

// Initialize cURL with headers
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language: en-US,en;q=0.5',
    ]
]);

$html = curl_exec($ch);
if (curl_errno($ch)) {
    die('cURL error: ' . curl_error($ch));
}
curl_close($ch);

$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML('<?xml encoding="UTF-8">' . $html);
libxml_clear_errors();

$xpath = new DOMXPath($dom);
$movies = [];

// Updated selectors based on current Paytm structure
$movieCards = $xpath->query("//div[contains(@class, 'movie-card') or contains(@class, 'movie')]");




foreach ($movieCards as $card) {
    // Check if the card is valid
    echo '<pre>'; print_r($card->textContent); echo '</pre>';
}

?>

output data like

Array (
  [0] => [
    'title' => 'Kill bill',
    'genre' => 'Action',
    'url' => '/movies/kill bill'
  ]
)

FCM v1 Payload Mismatch: fcm_options.link vs click_action in SW

I’m using the Firebase Cloud Messaging (FCM) HTTP v1 API to send push notifications to a web app. The web app uses the Firebase JavaScript SDK (v11.2.0), and I have a firebase-messaging-sw.js file at the root of the domain to handle background notifications.

According to the official FCM v1 documentation, to set a URL that opens when a notification is clicked, the webpush.fcm_options.link field should be used in the message payload.

However, when the notification is received in the service worker and I inspect the event.data.json() payload, I see that:

The URL from webpush.fcm_options.link appears under notification.click_action

The fcmOptions or webpush.fcm_options field is missing entirely

This behavior conflicts with what the documentation suggests, and similar inconsistencies have been noted in community posts and GitHub issues. It’s unclear whether this transformation from webpush.fcm_options.link to notification.click_action is intentional, reliable, or a compatibility fallback.

I want to understand the officially supported and stable method for specifying and retrieving the click-action URL in a service worker when using FCM HTTP v1 and Firebase JS SDK (compat version, 11.2.0).

What I sent (to FCM):
A POST request to:

https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send

Payload:

{ "message": { "token": "DEVICE_REGISTRATION_TOKEN", "notification": { "title": "Background Message Title", "body": "Background message body" }, "webpush": { "fcm_options": { "link": "https://your-target-url.com/some/path" }, "notification": { "icon": "/path/to/icon.png" } } } }

What I expected:
In the service worker (firebase-messaging-sw.js), I expected to access the URL via:

payload.fcmOptions.link

What actually happened:
The payload received in the push event looked like this:

{ "notification": { "title": "Background Message Title", "body": "Background message body", "icon": "/path/to/icon.png", "click_action": "https://your-target-url.com/some/path" }, "data": {}, "from": "FCM_SENDER_ID", "messageId": "MESSAGE_ID" }

So, click_action is present inside notification, but webpush or fcmOptions is missing entirely.

Service worker code (snippet):

`self.addEventListener(‘push’, (event) => {
if (event.data) {
const payload = event.data.json();
console.log(‘Link found via notification.click_action:’, payload?.notification?.click_action);

// Trying to access these fails:
// console.log(payload.webpush?.fcm_options); // undefined
// console.log(payload.fcmOptions); // undefined

}
});`

Questions

  • Why does the URL specified in webpush.fcm_options.link show up as notification.click_action in the service worker payload?
  • Is this transformation intentional and documented?
  • What is the recommended and reliable way to specify a click URL when sending push notifications via FCM HTTP v1?
  • Which field in the received event.data.json() payload should be used in the service worker to handle the click URL reliably?