Less Annoying CRM API – Add contact to group

I’m creating a plugin that creates a contact in Less Annoying CRM when a user account on a WordPress site is approved. I’ve looked through the API documentation and can’t find anything on how to add a user to a group.

So far I’ve managed to create contact’s and add them to a company if the company already exists, if not it creates a new one and assigns them to it.

I’ve tried adding the user to a buying group but that’s not the same thing, if you manually do it, you have to attach an item to the contact then select group.

Here is my code:

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

class Davora_Lacrm_API {

    private $api_url = "https://api.lessannoyingcrm.com/v2/";
    private $api_key = "**********************************";

    // Standard call for all LACRM API functions
    public function call_lacrm_api( string $function, array $parameters = [] ) {
        $curl_handle = curl_init( $this->api_url );
        $headers = [
            "Content-Type: application/json",
            "Authorization: $this->api_key"
        ];
        $body = [
            "Function" => $function,
            "Parameters" => $parameters
        ];

        curl_setopt( $curl_handle, CURLOPT_POSTFIELDS, json_encode( $body ) );
        curl_setopt( $curl_handle, CURLOPT_HTTPHEADER, $headers );
        curl_setopt( $curl_handle, CURLOPT_RETURNTRANSFER, true );

        $curl_result = curl_exec( $curl_handle );
        $return_value = false;

        // Handle errors
        if ( curl_errno( $curl_handle ) ) {
            error_log( "cURL Error: " . curl_error( $curl_handle ) );
        } else {
            $result_object = json_decode($curl_result, true);
            $http_code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE);
            if ($http_code === 400) {
                error_log( "LACRM API Error: " . $result_object['ErrorDescription'] );
            } else {
                $return_value = $result_object;
            }
        }

        curl_close( $curl_handle );
        return $return_value;
    }

    // Check if a company exists in LACRM by searching contacts
    public function check_if_company_exists( $company_name ) {
        // Use GetContacts to search for the company (which is a contact with IsCompany = true)
        $response = $this->call_lacrm_api( 'GetContacts', [
            "SearchTerms" => $company_name, // Search by company name
            "AdvancedFilters" => [
                [
                    "Name" => "CompanyName",  // Search in the "Company" field
                    "Operation" => "IsExactly", // Use "IsExactly" to match the exact company name
                    "Value" => $company_name
                ]
            ]
        ]);

        // Log the response for debugging
        if ( $response ) {
            error_log("Check Company Response: " . print_r( $response, true ));
        } else {
            error_log("No response from LACRM API while checking for company: $company_name");
        }

        // Check if we have a valid response with matching contacts
        if ( $response && isset( $response['Results'] ) && count( $response['Results'] ) > 0 ) {
            foreach ( $response['Results'] as $contact ) {
                // Manually check if this contact is a company
                if ( isset( $contact['IsCompany'] ) && $contact['IsCompany'] == 1 ) {
                    return $contact; // Return the first company (contact) found
                }
            }
        }

        return false; // Company does not exist
    }

    // Create a new company as a contact in LACRM
    public function create_company( $company_name ) {
        $parameters = [
            "IsCompany" => true, // Set IsCompany to true to create a company
            "Company Name" => $company_name, // Correctly reference the "Company Name" field
            "AssignedTo" => 555885 // Ensure assigned to a user
        ];

        // Create the company
        $response = $this->call_lacrm_api( "CreateContact", $parameters );

        // Log the response for debugging
        if ( $response ) {
            error_log("Company created successfully: " . print_r( $response, true ));
            return $response; // Return the response which will contain the ContactId
        } else {
            error_log("Failed to create company: $company_name");
            return false;
        }
    }

    public function prepare_lacrm_parameters( $user_id ) {
        $user = get_userdata( $user_id );
    
        // Get the company name from user meta
        $company_name = get_user_meta( $user_id, 'billing_company', true );
        if ( ! $company_name ) {
            $company_name = "New Company"; // Default company name if none exists
        }
    
        // Check if the company exists in LACRM
        $existing_company = $this->check_if_company_exists( $company_name );
    
        // If the company doesn't exist, create it as a contact with IsCompany = true
        if ( ! $existing_company ) {
            $existing_company = $this->create_company( $company_name ); // Create the new company
        }
    
        // Check if a valid ContactId is available for the company
        if ( $existing_company && isset( $existing_company['ContactId'] ) ) {
            $company_id = $existing_company['ContactId']; // Use the ContactId for company association
        } else {
            error_log("No valid ContactId found for the company: $company_name");
            $company_id = null; // Set company_id to null if no ContactId is found
        }
    
        // Prepare the buying group and ref
        $buying_group = ["Davora Marketing"]; // Default group
        $buying_group_ref = null;
    
        // Check if the user is a Cardgains member
        $cardgains_ref = get_user_meta( $user_id, 'afreg_additional_19125', true );
        if ( ! empty( $cardgains_ref ) ) {
            $buying_group[] = "Cardgains"; // Add "Cardgains" group
            $buying_group_ref = $cardgains_ref; // Set the Buying Group Ref
        }
    
        // Prepare address
        $address = [
            "Street" => get_user_meta( $user_id, 'billing_address_1', true ),
            "City" => get_user_meta( $user_id, 'billing_city', true ),
            "State" => get_user_meta( $user_id, 'billing_state', true ),
            "Zip" => get_user_meta( $user_id, 'billing_postcode', true ),
            "Country" => get_user_meta( $user_id, 'billing_country', true )
        ];
    
        // Return the parameters for creating/updating the contact
        return [
            "IsCompany" => false,  // User is a contact, not a company
            "AssignedTo" => 555885,
            "Name" => $user->first_name . ' ' . $user->last_name,
            "Email" => [
                [ "Text" => $user->user_email, "Type" => "Work" ]
            ],
            "Phone" => [
                [ "Text" => get_user_meta( $user_id, 'billing_phone', true ), "Type" => "Work" ]
            ],
            "Company Name" => $company_name, // Associate with the found or newly created company
            "Accounts Ref" => get_user_meta( $user_id, 'sageref', true ),
            "Address" => [$address],
            "Buying Group" => $buying_group, // Add to one or more buying groups
            "Buying Group Ref" => $buying_group_ref // Add the Cardgains membership ID, if applicable
        ];
    }    

    // Create a contact in LACRM
    public function create_contact( $user_id ) {
        if ( get_user_meta( $user_id, 'afreg_new_user_status', true ) === 'approved' ) {
            $parameters = $this->prepare_lacrm_parameters( $user_id );
            $response = $this->call_lacrm_api( "CreateContact", $parameters );

            if ( $response ) {
                error_log( "LACRM API Response: " . print_r( $response, true ) );
            } else {
                error_log( "LACRM API: Failed to create contact." );
            }
        }
    }
}

http.get fails most of the time while file_get_contents doesnt fail. What am I doing wrong with my node script?

I am using node to download data from overpass. I am constantly receiving errors (see far below), however, if I use file_get_contents in codeigniter I never receive any errors and data downloaded smoothly. I am wondering what am I doing wrong with my node script? I have even added more attempts to download and increased the timeout but no luck.

This is my node script:

const fs = require('fs');
const path = require('path');
const http = require('http');

// Overpass API URL and query
const overpassUrl = 'http://overpass-api.de/api/interpreter';
const query = '[out:xml][timeout:25];area(3600382313)->.searchArea;(nwr["landuse"="winter_sports"](area.searchArea););out geom;';

// Path for the temporary XML file
const xmlFilePath = path.join(__dirname, '../writable/ski/sknowed_calcs/aa_winter_sports_polygons.xml');

// Maximum number of retries
const maxRetries = 3;

// Function to fetch data
function fetchData(retries = 0) {
  console.log(`Fetching data from Overpass API... (Attempt ${retries + 1}/${maxRetries})`);

  // Encode query for URL
  const url = `${overpassUrl}?data=${encodeURIComponent(query)}`;

  // Send HTTP GET request with increased timeout
  http.get(url, { timeout: 220000 }, (res) => {
    res.setEncoding('utf8'); // Ensure response is treated as UTF-8

    if (res.statusCode !== 200) {
      console.error(`Failed to fetch data: ${res.statusCode} ${res.statusMessage}`);
      res.resume(); // Consume response to free up memory
      return;
    }

    let xmlData = '';

    // Collect data chunks
    res.on('data', (chunk) => {
      xmlData += chunk;
    });

    // When the entire response is received
    res.on('end', () => {
      console.log('Data fetched successfully.');

      // Ensure directory exists
      fs.mkdirSync(path.dirname(xmlFilePath), { recursive: true });

      // Save the XML response to a file with UTF-8 encoding
      fs.writeFileSync(xmlFilePath, xmlData, 'utf8');
      console.log(`XML file saved at ${xmlFilePath}`);
    });
  }).on('error', (error) => {
    if (error.code === 'ETIMEDOUT' && retries < maxRetries - 1) {
      console.error(`Request timed out. Retrying... (${retries + 1}/${maxRetries})`);
      setTimeout(() => fetchData(retries + 1), 5000); // Retry after 5 seconds
    } else {
      console.error('Request failed:', error);
    }
  });
}

// Run the function
fetchData();

This is the error I am receiving:

Fetching data from Overpass API... (Attempt 1/3)
Fetching data from Overpass API... (Attempt 2/3)
Fetching data from Overpass API... (Attempt 3/3)

Request timed out. Retrying... (1/3)
Request timed out. Retrying... (2/3)
Request failed: AggregateError [ETIMEDOUT]: 
    at internalConnectMultiple (node:net:1139:18)
    at internalConnectMultiple (node:net:1215:5)
    at Timeout.internalConnectMultipleTimeout (node:net:1739:5)
    at listOnTimeout (node:internal/timers:616:11)
    at process.processTimers (node:internal/timers:549:7) {
  code: 'ETIMEDOUT',
  [errors]: [
    Error: connect ETIMEDOUT 162.55.144.139:80
        at createConnectionError (node:net:1675:14)
        at Timeout.internalConnectMultipleTimeout (node:net:1734:38)
        at listOnTimeout (node:internal/timers:616:11)
        at process.processTimers (node:internal/timers:549:7) {
      errno: -110,
      code: 'ETIMEDOUT',
      syscall: 'connect',
      address: '162.55.144.139',
      port: 80
    },
    Error: connect ENETUNREACH 2a01:4f8:261:3c4f::2:80 - Local (:::0)
        at internalConnectMultiple (node:net:1211:16)
        at Timeout.internalConnectMultipleTimeout (node:net:1739:5)
        at listOnTimeout (node:internal/timers:616:11)
        at process.processTimers (node:internal/timers:549:7) {
      errno: -101,
      code: 'ENETUNREACH',
      syscall: 'connect',
      address: '2a01:4f8:261:3c4f::2',
      port: 80
    },
    Error: connect ETIMEDOUT 65.109.112.52:80
        at createConnectionError (node:net:1675:14)
        at Timeout.internalConnectMultipleTimeout (node:net:1734:38)
        at listOnTimeout (node:internal/timers:616:11)
        at process.processTimers (node:internal/timers:549:7) {
      errno: -110,
      code: 'ETIMEDOUT',
      syscall: 'connect',
      address: '65.109.112.52',
      port: 80
    },
    Error: connect ENETUNREACH 2a01:4f9:3051:3e48::2:80 - Local (:::0)
        at internalConnectMultiple (node:net:1211:16)
        at Timeout.internalConnectMultipleTimeout (node:net:1739:5)
        at listOnTimeout (node:internal/timers:616:11)
        at process.processTimers (node:internal/timers:549:7) {
      errno: -101,
      code: 'ENETUNREACH',
      syscall: 'connect',
      address: '2a01:4f9:3051:3e48::2',
      port: 80
    }
  ]
}

And this is my codeigniter4 script that never fails:

    public function osm_get_aa_winter_sports_polygons(){
        
        helper('filesystem');
        $query_get_skiArea = urlencode('[out:xml][timeout:25];area(3600382313)->.searchArea;(nwr["landuse"="winter_sports"](area.searchArea););out geom;');
        $overpass = 'http://overpass-api.de/api/interpreter?data='.$query_get_skiArea;
        $html = file_get_contents($overpass);
        if (write_file(WRITEPATH. "/ski/osm_data/temp_aa_winter_sports_polygons.xml", $html)){
                if (file_exists(WRITEPATH.'/ski/osm_data/aa_winter_sports_polygons.geojson')) {
                    $fileD = "aa_winter_sports_polygons.geojson.".date('m-d-Y-H-i-s'); //A e
                    @rename(WRITEPATH.'ski/osm_data/aa_winter_sports_polygons.geojson', WRITEPATH.'ski/'.$fileD);
                }
                exec('osmtogeojson '.WRITEPATH.'/ski/osm_data/temp_aa_winter_sports_polygons.xml > '.WRITEPATH.'/ski/osm_data/aa_winter_sports_polygons.geojson');
                return redirect()->to('/admin/ski/osm_data')
                                 ->with('info', 'Success - aa_winter_sports_polygons data');
        } else {
            // dd($TaskResult);
            $error = "something happened Harry";
            return redirect()->back()
                             ->with('errors', $error)
                             ->with('warning', 'Invalid Data')
                             ->withInput();
        }
    }

How attach a throttle middleware to a route?

I am having problems with attaching a middleware to my existing routes.
Problem is with extra parameters, which cause syntax errors.

My current route:

Route::get('summary/{topicid}/{issueid}', [AppHttpControllersSummaryController::class, 'show']);

Adding the needed middleware is described in the package I am trying to use:
https://github.com/GrahamCampbell/Laravel-Throttle

I fail when I am trying to do this:

Route::get('add-to-cart/{topicid}/{issueid}', [AppHttpControllersSummaryController::class, 'show'])->middleware([GrahamCampbellThrottleHttpMiddlewareThrottleMiddleware:10,30]);

The middleware does not cause issues if I omit the :10,30 part.

TODO:
Attach the middleware with all the parameters.

How do i fix Stripe Payment for Connected Accounts

Hello i am having a problem with Stripe Payment from buyer. I want the buyer to be able to buy digital content from my platform and we keep a fee from the buyer. I want the purchase to be done in the backend. I use stripe in another occasion and it is implemented ok.

  1. The seller account is created just fine and seller is verified from Stripe platform.
  2. The payment from buyer is succeed at first and then failed. As i show you from my stripe test dashboard in the photo i shared i think the payment intent returns incomplete.
    [![enter image description here][1]][1]

THE CODE
FRONT END

async initializePaymentElement() {
    try {
      // Collect payment details
      const paymentDetails = {
        amount: this.fractionsToPurchase * this.pricePerFraction * 100, // Amount in smallest currency unit (e.g., cents)
        currency: 'eur',
        email: this.user.email, // User email
        seller_connected_account_id: this.seller_connected_account_id, // Seller's Stripe account ID
      };
  
      console.log('Payment details being sent to backend:', paymentDetails);
  
      // Call backend to create Payment Intent
      const response: any = await this.userData.createPaymentIntent(paymentDetails).toPromise();
  
      console.log('Response from backend (createPaymentIntent):', response);
  
      if (response && response.clientSecret) {
        this.clientSecret = response.clientSecret;
        this.buyer_customer_id = response.customerId;
  
        console.log('Client secret received:', this.clientSecret);
        console.log('Buyer customer ID:', this.buyer_customer_id);
  
        // Load Stripe.js and initialize payment element
        if (!this.stripe) {
          this.stripe = await loadStripe('your-publishable-key');
        }
  
        if (!this.stripe) {
          console.error('Stripe.js failed to load');
          return;
        }
  
        this.elements = this.stripe.elements({ clientSecret: this.clientSecret });
        this.paymentElement = this.elements.create('payment');
  
        // Mount Payment Element
        const paymentElementContainer = document.getElementById('payment-element');
        if (paymentElementContainer) {
          this.paymentElement.mount(paymentElementContainer);
        }
  
        // Handle changes in the Payment Element
        this.paymentElement.on('change', (event: any) => {
          this.isPaymentElementFilled = event.complete;
          this.paymentError = event.error ? event.error.message : null;
        });
      } else {
        console.error('Failed to retrieve client secret or initialize Stripe.');
        this.paymentError = 'Failed to retrieve payment details.';
      }
    } catch (error) {
      console.error('Error during initializePaymentElement:', error);
      this.paymentError = 'Failed to initialize payment. Please try again.';
    }
  }
  
  async purchaseMediaFractions() {
    console.log("Starting purchaseMediaFractions...");
  
    // Validate `fractionsToPurchase`
    if (
      this.fractionsToPurchase > this.priceDetails?.fractions ||
      this.fractionsToPurchase < 1
    ) {
      console.error("Invalid fractions:", this.fractionsToPurchase);
      const toast = await this.toastController.create({
        message: "Please enter a valid number of fractions to purchase.",
        duration: 2000,
        color: "danger",
      });
      await toast.present();
      return;
    }
  
    const totalPrice = this.fractionsToPurchase * this.pricePerFraction;
    const platformFee = totalPrice * 0.1; // 10% platform fee
    const sellerEarnings = totalPrice - platformFee;
  
    if (!this.stripe) {
      console.error("Stripe instance is not initialized.");
      return;
    }
  
    const elements = this.elements;
    if (!elements) {
      console.error("Stripe Elements are not initialized.");
      return;
    }
  
    const paymentElement = this.paymentElement;
    if (!paymentElement) {
      console.error("Payment element is not mounted.");
      return;
    }
  
    try {
      // Confirm the payment with Stripe
      const { error, paymentIntent } = await this.stripe.confirmPayment({
        elements: this.elements,
        confirmParams: {
          payment_method_data: {
            billing_details: {
              email: this.user?.email, // Provide the buyer's email
            },
          },
        },
        redirect: "if_required", // Handle the redirect manually
      });
  
      if (error) {
        console.error("Payment confirmation error:", error.message);
        const toast = await this.toastController.create({
          message: `Payment failed: ${error.message}`,
          duration: 3000,
          color: "danger",
        });
        await toast.present();
        return;
      }
  
      if (paymentIntent?.status === "succeeded") {
        console.log("Payment successful:", paymentIntent);
  
        const toast = await this.toastController.create({
          message: "Payment successful!",
          duration: 3000,
          color: "success",
        });
        await toast.present();
  
        // Prepare purchase details for backend
        const purchaseDetails = {
          userId: this.user?.uid,
          mediaId: this.media?.msg_id,
          fractionsToPurchase: this.fractionsToPurchase,
          pricePerFraction: this.pricePerFraction,
          totalPrice,
          platformFee,
          sellerEarnings,
          sellerAccountId: this.seller_connected_account_id,
          buyerCustomerId: this.buyer_customer_id,
        };
  
        console.log("Purchase details:", purchaseDetails);
  
        // Call the backend
        this.userData.purchaseMediaFractions(purchaseDetails).subscribe(
          async (response: any) => {
            console.log("Backend response for purchaseMediaFractions:", response);
  
            const toast = await this.toastController.create({
              message: response.success ? response.message : response.error,
              duration: 2000,
              color: response.success ? "success" : "danger",
            });
            await toast.present();
  
            if (response.success) {
              console.log("Purchase completed successfully.");
              this.router.navigate(["/success"]);
            } else {
              console.error("Purchase failed:", response.error);
            }
          },
          async (error) => {
            console.error("HTTP error in purchaseMediaFractions:", error);
            const toast = await this.toastController.create({
              message: "An error occurred. Please try again later.",
              duration: 2000,
              color: "danger",
            });
            await toast.present();
          }
        );
      } else {
        console.error("Payment not completed:", paymentIntent?.status);
        const toast = await this.toastController.create({
          message: "Payment not completed.",
          duration: 3000,
          color: "warning",
        });
        await toast.present();
      }
    } catch (error) {
      console.error("Error during payment process:", error);
      const toast = await this.toastController.create({
        message: "An error occurred during payment. Please try again later.",
        duration: 3000,
        color: "danger",
      });
      await toast.present();
    }
  }

Services userData

createPaymentIntent(paymentDetails: any) {
        const url = this.appData.getApiUrl() + 'createPaymentIntent';
        const data = this.jsonToURLEncoded({
          api_signature: this.api_signature,
          ...paymentDetails, // Spread payment details into the request body
        });
      
        console.log('Calling createPaymentIntent API:', url);
        console.log('Request data:', data);
      
        return this.http.post(url, data, { headers: this.options }).pipe(
          tap((response: any) => {
            console.log('createPaymentIntent API response:', response);
          }),
          catchError((error) => {
            console.error('Error calling createPaymentIntent API:', error);
            throw error;
          })
        );
      }
      

    purchaseMediaFractions(purchaseDetails: any) {
        const url = this.appData.getApiUrl() + 'insertMediaPurchaseDetails';
        const data = {
            api_signature: this.api_signature,
            purchaseDetails: purchaseDetails // Send as a plain object
        };
    
        return this.http.post(url, JSON.stringify(data), {
            headers: this.options.set('Content-Type', 'application/json'),
        });
    } 

AND PHP FUNCTIONS

function createPaymentIntent() {
    $request = SlimSlim::getInstance()->request();
    $response = ['success' => false];
  
    // Extract parameters sent from the frontend
    $apiSignature = $request->post('api_signature');
    $amount = intval($request->post('amount')); // Ensure amount is an integer
    $currency = $request->post('currency');
    $email = $request->post('email');
    $sellerAccountId = $request->post('seller_connected_account_id'); // Seller's connected account ID

    error_log("Received API Signature: $apiSignature, Amount: $amount, Currency: $currency, Email: $email, Seller Account ID: $sellerAccountId");

    try {
        // Validate parameters
        if (!$amount || $amount <= 0) {
            throw new Exception("Invalid amount: Amount must be greater than 0.");
        }

        if (empty($email)) {
            throw new Exception("Invalid email: Email address is required.");
        }

        if (empty($sellerAccountId)) {
            throw new Exception("Invalid seller account ID: This is required.");
        }

        // Create a new Stripe Customer
        $customer = StripeCustomer::create([
            'email' => $email,
            'description' => 'One-time customer for purchase',
        ]);
        error_log("Stripe Customer Created: " . json_encode($customer));

        $buyerCustomerId = $customer->id;
        if (empty($buyerCustomerId)) {
            throw new Exception("Failed to create a Stripe customer.");
        }

        // Calculate Platform Fee (e.g., 10%)
        $applicationFeeAmount = intval($amount * 0.10); // Platform fee

        // Create the PaymentIntent
        $paymentIntentParams = [
            'amount' => $amount, // Amount in smallest currency unit (e.g., cents)
            'currency' => $currency,
            'customer' => $buyerCustomerId,
            'description' => 'Purchase',
            'transfer_data' => [
                'destination' => $sellerAccountId, // Connected seller account
            ],
            'application_fee_amount' => $applicationFeeAmount,
        ];
        error_log("PaymentIntent Parameters: " . json_encode($paymentIntentParams));

        $paymentIntent = StripePaymentIntent::create($paymentIntentParams);
        error_log("PaymentIntent Created: " . json_encode($paymentIntent));

        // Build the response with PaymentIntent details
        $response = [
            'success' => true,
            'paymentIntentId' => $paymentIntent->id,
            'clientSecret' => $paymentIntent->client_secret,
            'customerId' => $buyerCustomerId,
            'amount' => $amount,
            'currency' => $currency,
        ];
    } catch (StripeExceptionApiErrorException $e) {
        // Stripe-specific error
        error_log("Stripe API Error: " . $e->getMessage());
        $response = [
            'success' => false,
            'error' => $e->getMessage(),
        ];
    } catch (Exception $e) {
        // General error
        error_log("General Error: " . $e->getMessage());
        $response = [
            'success' => false,
            'error' => $e->getMessage(),
        ];
    }

    // Return response as JSON
    echo json_encode($response);
}

  
function insertMediaPurchaseDetails() {
    error_log('Function Called: insertMediaPurchaseDetails');

    $request = SlimSlim::getInstance()->request();
    $data = json_decode($request->getBody(), true);
    $purchaseDetails = $data['purchaseDetails'] ?? null;

    error_log('Request Body: ' . $request->getBody());

    // Validate purchase details
    if (!$purchaseDetails || !isset($purchaseDetails['userId'], $purchaseDetails['mediaId'], $purchaseDetails['fractionsToPurchase'], $purchaseDetails['pricePerFraction'], $purchaseDetails['totalPrice'], $purchaseDetails['platformFee'], $purchaseDetails['sellerEarnings'])) {
        error_log('Invalid Purchase Details: ' . print_r($purchaseDetails, true));
        echo json_encode(['error' => 'Invalid purchase details provided']);
        return;
    }

    // Log extracted values
    error_log('Extracted Purchase Details: ' . print_r($purchaseDetails, true));

    // Set Stripe API key
    Stripe::setApiKey('sk_test_51HiSUoGozbMWFnurBqY9URXX7pEVd0Rwnm9kyyyXuOr9pKNluCdpNp522HiGN65djoplcuJcCKjiXqtFBgZoM4f000XfvRgSgi');

    try {
        // Create the PaymentIntent
        $paymentIntent = StripePaymentIntent::create([
            'amount' => intval($purchaseDetails['totalPrice'] * 100), // Amount in cents
            'currency' => 'eur',
            'payment_method_types' => ['card'],
            'transfer_data' => [
                'destination' => $purchaseDetails['sellerAccountId'],
            ],
        ]);
        error_log('PaymentIntent Created: ' . json_encode($paymentIntent));

        // Log PaymentIntent status
        if ($paymentIntent->status !== 'succeeded') {
            error_log('PaymentIntent Status: ' . $paymentIntent->status);
            error_log('PaymentIntent Full Response: ' . print_r($paymentIntent, true));
            echo json_encode(['error' => 'Payment failed']);
            return;
        }

        // Proceed with database operations
        $db = getDB();
        $db->beginTransaction();
        error_log('Database Transaction Started');

        // Insert purchase details
        $insertSql = "INSERT INTO media_purchases (msg_id_fk, buyer_uid_fk, fraction_count, purchase_price, total_price, platform_fee, seller_earnings, purchase_date) 
                      VALUES (?, ?, ?, ?, ?, ?, ?, NOW())";
        $stmt = $db->prepare($insertSql);
        if (!$stmt->execute([$purchaseDetails['mediaId'], $purchaseDetails['userId'], $purchaseDetails['fractionsToPurchase'], $purchaseDetails['pricePerFraction'], $purchaseDetails['totalPrice'], $purchaseDetails['platformFee'], $purchaseDetails['sellerEarnings']])) {
            error_log('Failed to Insert Media Purchase: ' . json_encode($stmt->errorInfo()));
            throw new Exception('Failed to insert purchase details');
        }
        error_log('Media Purchase Inserted Successfully');

        // Commit transaction
        $db->commit();
        error_log('Database Transaction Committed Successfully');

        echo json_encode(['success' => true, 'message' => 'Purchase completed successfully']);
    } catch (StripeExceptionApiErrorException $e) {
        error_log('Stripe API Error: ' . $e->getMessage());
        echo json_encode(['error' => 'Transaction failed']);
    } catch (Exception $e) {
        error_log('General Error: ' . $e->getMessage());
        if (isset($db) && $db->inTransaction()) {
            $db->rollBack();
            error_log('Database Transaction Rolled Back');
        }
        echo json_encode(['error' => 'Transaction failed']);
    }
} 

THE CONSOLE RETURNS THESE

Payment details being sent to backend: {amount: 300, currency: 'eur', email: '[email protected]', seller_connected_account_id: 'acct_1Qevz92eCjM0J1d3'}
user-data.ts:541 Calling createPaymentIntent API: https://project.com/api/api/createPaymentIntent
user-data.ts:542 Request data: api_signature=bcbf2fd292fa27b76d509742cdc007e2&amount=300&currency=eur&email=pellapost%40outlook.com&seller_connected_account_id=acct_1Qevz92eCjM0J1d3
user-data.ts:546 createPaymentIntent API response: {success: true, paymentIntentId: 'pi_3QgjzmGozbMWFnur0N7W0dLL', clientSecret: 'pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A', customerId: 'cus_RZtnd76eH7eIVl', amount: 300, …}
sell-details.page.ts:610 Response from backend (createPaymentIntent): {success: true, paymentIntentId: 'pi_3QgjzmGozbMWFnur0N7W0dLL', clientSecret: 'pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A', customerId: 'cus_RZtnd76eH7eIVl', amount: 300, …}
sell-details.page.ts:616 Client secret received: pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A
sell-details.page.ts:617 Buyer customer ID: cus_RZtnd76eH7eIVl

sell-details.page.ts:654 Starting purchaseMediaFractions...
sell-details.page.ts:718 Payment successful: {id: 'pi_3QgjzmGozbMWFnur0N7W0dLL', object: 'payment_intent', amount: 300, amount_details: {…}, automatic_payment_methods: {…}, …}

sell-details.page.ts:740 Purchase details: {userId: '1122', mediaId: '815', fractionsToPurchase: 3, pricePerFraction: '1.00', totalPrice: 3, …}
sell-details.page.ts:745 Backend response for purchaseMediaFractions: {error: 'Payment failed'}
sell-details.page.ts:758 Purchase failed: Payment failed

and the error logs from php functions are these

[13-Jan-2025 11:31:42 Europe/Athens] Received API Signature: bcbf2fd292fa27b76d509742cdc007e2, Amount: 300, Currency: eur, Email: [email protected], Seller Account ID: acct_1Qevz92eCjM0J1d3
[13-Jan-2025 11:31:43 Europe/Athens] Stripe Customer Created: {"id":"cus_RZtnd76eH7eIVl","object":"customer","address":null,"balance":0,"created":1736760702,"currency":null,"default_source":null,"delinquent":false,"description":"One-time customer for purchase","discount":null,"email":"[email protected]","invoice_prefix":"2C322EAE","invoice_settings":{"custom_fields":null,"default_payment_method":null,"footer":null,"rendering_options":null},"livemode":false,"metadata":[],"name":null,"phone":null,"preferred_locales":[],"shipping":null,"tax_exempt":"none","test_clock":null}
[13-Jan-2025 11:31:43 Europe/Athens] PaymentIntent Parameters: {"amount":300,"currency":"eur","customer":"cus_RZtnd76eH7eIVl","description":"Purchase","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"application_fee_amount":30}
[13-Jan-2025 11:31:43 Europe/Athens] PaymentIntent Created: {"id":"pi_3QgjzmGozbMWFnur0N7W0dLL","object":"payment_intent","amount":300,"amount_capturable":0,"amount_details":{"tip":[]},"amount_received":0,"application":null,"application_fee_amount":30,"automatic_payment_methods":{"allow_redirects":"always","enabled":true},"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","client_secret":"pi_3QgjzmGozbMWFnur0N7W0dLL_secret_j4J2sGD89jO3gjFgETtktYe4A","confirmation_method":"automatic","created":1736760702,"currency":"eur","customer":"cus_RZtnd76eH7eIVl","description":"Purchase","invoice":null,"last_payment_error":null,"latest_charge":null,"livemode":false,"metadata":[],"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_configuration_details":{"id":"pmc_1PgT4IGozbMWFnurJXRBOp2V","parent":null},"payment_method_options":{"bancontact":{"preferred_language":"en"},"card":{"installments":null,"mandate_options":null,"network":null,"request_three_d_secure":"automatic"},"eps":[],"giropay":[],"ideal":[],"klarna":{"preferred_locale":null},"link":{"persistent_token":null}},"payment_method_types":["card","bancontact","eps","giropay","ideal","klarna","link"],"processing":null,"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"requires_payment_method","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"transfer_group":null}
[13-Jan-2025 11:32:14 Europe/Athens] Function Called: insertMediaPurchaseDetails
[13-Jan-2025 11:32:14 Europe/Athens] Request Body: {"api_signature":"bcbf2fd292fa27b76d509742cdc007e2","purchaseDetails":{"userId":"1122","mediaId":"815","fractionsToPurchase":3,"pricePerFraction":"1.00","totalPrice":3,"platformFee":0.30000000000000004,"sellerEarnings":2.7,"sellerAccountId":"acct_1Qevz92eCjM0J1d3","buyerCustomerId":"cus_RZtnd76eH7eIVl"}}
[13-Jan-2025 11:32:14 Europe/Athens] Extracted Purchase Details: Array
(
    [userId] => 1122
    [mediaId] => 815
    [fractionsToPurchase] => 3
    [pricePerFraction] => 1.00
    [totalPrice] => 3
    [platformFee] => 0.3
    [sellerEarnings] => 2.7
    [sellerAccountId] => acct_1Qevz92eCjM0J1d3
    [buyerCustomerId] => cus_RZtnd76eH7eIVl
)

[13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Created: {"id":"pi_3Qgk0HGozbMWFnur0JDICtzQ","object":"payment_intent","amount":300,"amount_capturable":0,"amount_details":{"tip":[]},"amount_received":0,"application":null,"application_fee_amount":null,"automatic_payment_methods":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","client_secret":"pi_3Qgk0HGozbMWFnur0JDICtzQ_secret_y4F4woUVuodAF2F4WANJl96Vm","confirmation_method":"automatic","created":1736760733,"currency":"eur","customer":null,"description":null,"invoice":null,"last_payment_error":null,"latest_charge":null,"livemode":false,"metadata":[],"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_configuration_details":null,"payment_method_options":{"card":{"installments":null,"mandate_options":null,"network":null,"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"processing":null,"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"requires_payment_method","transfer_data":{"destination":"acct_1Qevz92eCjM0J1d3"},"transfer_group":null}
[13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Status: requires_payment_method
[13-Jan-2025 11:32:14 Europe/Athens] PaymentIntent Full Response: StripePaymentIntent Object
(
    [id] => pi_3Qgk0HGozbMWFnur0JDICtzQ
    [object] => payment_intent
    [amount] => 300
    [amount_capturable] => 0
    [amount_details] => StripeStripeObject Object
        (
            [tip] => Array
                (
                )

        )

    [amount_received] => 0
    [application] => 
    [application_fee_amount] => 
    [automatic_payment_methods] => 
    [canceled_at] => 
    [cancellation_reason] => 
    [capture_method] => automatic
    [client_secret] => pi_3Qgk0HGozbMWFnur0JDICtzQ_secret_y4F4woUVuodAF2F4WANJl96Vm
    [confirmation_method] => automatic
    [created] => 1736760733
    [currency] => eur
    [customer] => 
    [description] => 
    [invoice] => 
    [last_payment_error] => 
    [latest_charge] => 
    [livemode] => 
    [metadata] => StripeStripeObject Object
        (
        )

    [next_action] => 
    [on_behalf_of] => 
    [payment_method] => 
    [payment_method_configuration_details] => 
    [payment_method_options] => StripeStripeObject Object
        (
            [card] => StripeStripeObject Object
                (
                    [installments] => 
                    [mandate_options] => 
                    [network] => 
                    [request_three_d_secure] => automatic
                )

        )

    [payment_method_types] => Array
        (
            [0] => card
        )

    [processing] => 
    [receipt_email] => 
    [review] => 
    [setup_future_usage] => 
    [shipping] => 
    [source] => 
    [statement_descriptor] => 
    [statement_descriptor_suffix] => 
    [status] => requires_payment_method
    [transfer_data] => StripeStripeObject Object
        (
            [destination] => acct_1Qevz92eCjM0J1d3
        )

    [transfer_group] => 
)

What am i doing wrong? Thanks in advance
[1]: https://i.sstatic.net/DdkrgZS4.png

Nginx returns ‘This site can’t be reached’ error when processing large datasets

I am managing a PHP 8.2 and Symfony 7-based school management platform hosted on a VPS with 8GB RAM from Hostinger, running Ubuntu and Nginx.

The Issue:
The problem arises when I attempt to generate report cards in bulk or compile global statistics for an entire class.

Specifically:

When generating report cards for a full class of 60 students, the page displays a “This site can’t be reached
enter image description here error after approximately 30 seconds.
This issue also occurs when sending mass notifications to over 1000 parents (e.g., during news announcements). Also, when register mass data in the database (1000+ notifications whend sending news), the same errors occur but in my database, all the data are saved but the “site unavailable” appear.

Observations:

  • When mass notifications are handled in the background using Symfony Messenger, the process works flawlessly, even for 1000+ notifications.
  • If I segment the report card generation (e.g., 10 students at a time), the process completes quickly without errors.
  • On my local development environment (Windows 10 with Apache, 8GB RAM), the same tasks complete successfully, regardless of the data size. Even if the process takes 1 to 10 minutes or more, it ultimately generates the expected results.

Actions Taken So Far:
I have explored potential solutions, including suggestions from ChatGPT, and implemented the following configurations:

  1. Upgrade php limit on php.ini from /etc/php/8.2/fpm/php.ini
  • -memory_limit=-1
  • maximum_time_execution = 3600
  • post_max_size = 100M
  • upload_max_filesize = 100M
  1. Upgrade nginx configuration on /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 1024;
        # multi_accept on;
}

http {
        # Configurations de timeout globales
        proxy_read_timeout 300s;  # Temps d'attente pour les proxys
        fastcgi_read_timeout 300s; # Temps d'attente pour FastCGI
        client_max_body_size 100M;
        client_body_timeout 300s;
        client_header_timeout 300s;
        keepalive_timeout 300s;
        send_timeout 300s;
        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}


#mail {
#       # See sample authentication script at:
#       # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#       # auth_http localhost/auth.php;
#       # pop3_capabilities "TOP" "USER";
#       # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#       server {
#               listen     localhost:110;
#               protocol   pop3;
#               proxy      on;
#       }
#
#       server {
#               listen     localhost:143;
#               protocol   imap;
#               proxy      on;
#       }
#}

  1. here is my mysql configuration
#
# The MySQL database server configuration file.
#
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

# Here is entries for some specific programs
# The following values assume you have at least 32M RAM

[mysqld]
#
# * Basic Settings
#
user            = mysql
# pid-file      = /var/run/mysqld/mysqld.pid
# socket        = /var/run/mysqld/mysqld.sock
# port          = 3306
# datadir       = /var/lib/mysql

# If MySQL is running as a replication slave, this should be
# changed. Ref https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_tmpdir
# tmpdir                = /tmp
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address            = 127.0.0.1
mysqlx-bind-address     = 127.0.0.1

#
# * Fine Tuning
#
#
innodb_buffer_pool_size = 6G
#innodb_log_file_size = 512M
innodb_redo_log_capacity = 1G
innodb_flush_log_at_trx_commit = 2
innodb_io_capacity = 1000

# Memory and performance tuning
key_buffer_size         = 64M
max_allowed_packet      = 128M
thread_stack            = 256K
thread_cache_size       = 8

# Table cache
table_open_cache        = 2000

# Connections
max_connections         = 500
#wait_timeout = 3600
#innodb_lock_wait_timeout = 3600

# Temp tables
tmp_table_size          = 64M
max_heap_table_size     = 64M
# Temp tables
tmp_table_size          = 64M
max_heap_table_size     = 64M

# MyISAM recover options
myisam-recover-options  = BACKUP

#
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
#
# Log all queries
# Be aware that this log type is a performance killer.
# general_log_file        = /var/log/mysql/query.log
# general_log             = 1

# Error log - should be very few entries.
log_error = /var/log/mysql/error.log

# Here you can see queries with especially long duration
# slow_query_log          = 1
# slow_query_log_file     = /var/log/mysql/mysql-slow.log
# long_query_time         = 2
# log-queries-not-using-indexes

# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
#       other settings you may need to change.
# server-id               = 1
# binlog_expire_logs_seconds = 2592000
max_binlog_size          = 100M
# binlog_do_db            = include_database_name
# binlog_ignore_db        = include_database_name
  1. I have also try this to my PHP-FPM Wokers on /etc/php/8.2/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10

Thank for any help

Add security group to folders in Active Directory with PHP / Symfony

I have a Symfony webapp, hosted inside the company on Ubuntu, running Apache Webserver. I can create a folder on a network share (using icewind/smb) and I can create the RO- and RW-Groups inside the Active Directory (using the LDAP-Component from Symfony).

My problem for now is, that I cant find a way to add these groups as security group to the new folder.

Here is my code so far:

public function createLdapFolder(array $data): bool
{

    $prefixMapping = [
        'OU=Section1,OU=_Company,DC=company,DC=local' => 'S1_',
        'OU=Section2,OU=_Company,DC=company,DC=local' => 'S2_',
        'OU=Section3,OU=_Company,DC=company,DC=local' => 'S3_',
        'OU=Section4,OU=_Company,DC=company,DC=local' => 'S4_',
        'general' => 'All_',
        'scan' => 'Scan_',
        'appstorage' => '',
    ];

    $selectPrefix = $data['selectPrefix'];
    $folderName = $data['folderName'];

    if (!preg_match('/^[a-zA-Z0-9_-]+$/', $folderName)) {
        throw new InvalidArgumentException('Invalid Foldername.');
    }

    if (!isset($prefixMapping[$selectPrefix])) {
        $this->requestStack->getCurrentRequest()->getSession()->getFlashBag()->add(
        'error',
        'Invalid Section.'
        );
        return false;
    }

    $prefix = $prefixMapping[$selectPrefix];
    $completeFolderName = $prefix . $folderName;

    $serverFactory = new ServerFactory();
    $auth = new BasicAuth($_ENV['LDAP_USERNAME'], 'company', $_ENV['LDAP_PASSWORD']);
    $server = $serverFactory->createServer($_ENV['LDAP_IP'], $auth);

    $shares = $server->listShares();

    foreach ($shares as $shareName) {
        $shareName->getName();
    }

    if ($selectPrefix != 'appstorage') {
        $shareName = $_ENV['LDAP_SHARE_1'];
    } else {
        $shareName = $_ENV['LDAP_SHARE_APPSTORAGE'];
    }

    $share = $server->getShare($shareName);
    $share->mkdir($completeFolderName);
    
    $this->createLdapFolderGroups($folderName);
    
    return true;
    
}

public function createLdapFolderGroups(string $folderName): bool
{
    
    $baseDn = 'OU=Folder,' . $_ENV['LDAP_GLOBAL_GROUPS_BASE_DN'];

    $entryRO = new Entry('cn=GG_Folder_' . $folderName . '-RO,' . $baseDn, [
        'sAMAccountName' => ['GG_Folder_' . $folderName . '-RO'],
        'objectClass' => ['top', 'group'],
        'groupType' => [-2147483646], 
    ]);

    $entryRW = new Entry('cn=GG_Folder_' . $folderName . '-RW,' . $baseDn, [
        'sAMAccountName' => ['GG_Folder_' . $folderName . '-RW'],
        'objectClass' => ['top', 'group'],
        'groupType' => [-2147483646], 
    ]);

    try {

        $this->ldap->getEntryManager()->add($entryRO);
        $this->ldap->getEntryManager()->add($entryRW);


        $this->logger->info("Group GG_Folder_{$folderName}-RO created.");
        $this->logger->info("Group GG_Folder_{$folderName}-RW created.");

        return true;
    } catch (Exception $e) {

        $this->logger->error("Error by creating group" . $e->getMessage());
        throw new Exception("Error by creating group" . $e->getMessage());
        return false;
    }

}

I also have an very old code from a very old test project, but this code only worked if web webserver is hosted under a Windows-system, not linux.

$globalGroupsBaseDN = 'OU=GlobalGroups,DC=testdc,DC=local';
$groupName = 'GG_' . $folderName;

$groupRO = $groupName . '-RO';
$groupRW = $groupName . '-RW';

$newGroupRODN = 'CN=' . $groupRO . ',' . $globalGroupsBaseDN;
$newGroupRWDN = 'CN=' . $groupRW . ',' . $globalGroupsBaseDN;

$newGroupROAttributes['objectClass'] = ['group', 'top'];
$newGroupROAttributes['cn'] = $groupRO;
$newGroupROAttributes['sAMAccountName'] = $groupRO;

$newGroupRWAttributes['objectClass'] = ['group', 'top'];
$newGroupRWAttributes['cn'] = $groupRW;
$newGroupRWAttributes['sAMAccountName'] = $groupRW;

ldap_add($ldapConnection, $newGroupRODN, $newGroupROAttributes);
ldap_add($ldapConnection, $newGroupRWDN, $newGroupRWAttributes);


// Add security group to folder
$groupAttributeRO = [
'member' => [$newGroupRWDN]
];

$groupAttributeRW = [
'member' => [$newGroupRODN]
];

ldap_mod_add($ldapConnection, $existingFolderPath, $groupAttributeRO);
ldap_mod_add($ldapConnection, $existingFolderPath, $groupAttributeRW);

var_dump($existingFolderPath).'<br>';
var_dump($groupAttributeRO); exit;

// Set security
$permissionsRO = [
    'read',
    'list',
    'read_property',
    'execute',
];

$permissionsRW = [
    'write',
    'read',
    'list',
    'read_property',
    'execute',
    'delete',
];

$securityDescriptor = 'D:P(' . implode(',', $permissionsRO) . ')';
ldap_mod_replace($ldapConnection, $existingFolderPath, ['ntSecurityDescriptor' => [$securityDescriptor]]);

$securityDescriptorRW = 'D:P(' . implode(',', $permissionsRW) . ')';
ldap_mod_replace($ldapConnection, $existingFolderPath, ['ntSecurityDescriptor' => [$securityDescriptorRW]]);

Could anyone please help me to make it work under a Linux Webserver?

Composer imported from repository fails to recognize some integrated PHP extensions (Docker)

I’m attempting to install project dependencies using Composer within a Docker container based on the php:8.3-fpm-alpine image. This image includes PHP with several extensions compiled and integrated into the PHP binary, as confirmed by the phpinfo():

Configuration File (php.ini) Path 
  => /usr/local/etc/php
________________________________________________________________

Configuration
Core

PHP Version => 8.3.15

extension_dir => /usr/local/lib/php/extensions/no-debug-non-zts-20230831
include_path => .:/usr/local/lib/php => .:/usr/local/lib/php

Tokenizer Support => enabled
Session Support   => enabled
fileinfo support  => enabled
SimpleXML support => enabled
DOM/XML           => enabled
ctype functions   => enabled
cURL support      => enabled
hash support      => enabled
iconv support     => enabled
json support      => enabled
Multibyte Support => enabled
OpenSSL support   => enabled
PCRE Support      => enabled
Phar              => enabled
sodium support    => enabled
XML Support       => active
libXML support    => active
XMLReader         => enabled
XMLWriter         => enabled

However, when I execute the composer install command, Composer fails to recognize the availability of some of these extensions. The composer check-platform-reqs command confirms this:

ext-dom          n/a    missing
ext-fileinfo     n/a    missing
ext-session      n/a    missing
ext-tokenizer    n/a    missing
ext-xml          n/a    missing
ext-xmlwriter    n/a    missing

The errors I get during composer install:

Problem 1..N
  - requires ext-session *
  - requires ext-fileinfo *
  - requires ext-tokenizer *
  - requires ext-dom *

* -> it is missing from your system. Install or enable PHP's session extension.

Your lock file does not contain a compatible set of packages.
Please run composer update.

To enable extensions, verify that they are enabled in your .ini files:
    - /etc/php83/php.ini
    - /etc/php83/conf.d/00_curl.ini
    - /etc/php83/conf.d/00_iconv.ini
    - /etc/php83/conf.d/00_mbstring.ini
    - /etc/php83/conf.d/00_openssl.ini
    - /etc/php83/conf.d/00_zip.ini
    - /etc/php83/conf.d/01_phar.ini

Results by composer diagnose:

Checking composer.json: OK
Checking composer.lock: OK
Checking platform settings: OK
Checking git settings: No git process found
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com rate limit: OK
Checking disk free space: OK

Checking pubkeys: FAIL
Missing pubkey for tags verification
Missing pubkey for dev verification

Run composer self-update --update-keys to set them up
Checking Composer version: OK
Checking Composer and its dependencies for vulnerabilities: OK
Composer version: 2.8.4
PHP version: 8.3.15
PHP binary path: /usr/bin/php83
OpenSSL version: OpenSSL 3.3.2 3 Sep 2024
curl version: 8.11.1 libz 1.3.1 ssl OpenSSL/3.3.2
zip: extension present, unzip present, 7-Zip present (7z)

My Dockerfile:

FROM php:8.3-fpm-alpine

WORKDIR /var/www/html
COPY --chown=www-data:www-data --chmod=755 composer.json composer.lock .

RUN apk update 
 && apk upgrade 
 && apk add --no-cache 
    libpq-dev 
    libzip-dev 
    libpng-dev 
    libjpeg-turbo-dev 
    freetype-dev 
    composer 
 && docker-php-ext-configure zip 
 && docker-php-ext-configure pdo_pgsql --with-pdo-pgsql=/usr/include/ 
 && docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ 
 && docker-php-ext-install -j$(nproc) gd pdo pgsql pdo_pgsql 
 && docker-php-ext-enable gd pgsql pdo pdo_pgsql 
 && rm -vrf /var/cache/apk/* 
 && composer install

USER www-data

EXPOSE 9000
CMD ["php-fpm"]

I’ve tried installing Composer within the Docker image using the following command:

COPY --from=composer/composer:latest-bin /composer /usr/bin/composer

However, this method also did not resolve the issue.

After investigating the issue further, I discovered that manually installing Composer within the Docker container resolved the discrepancy between phpinfo() and Composer’s extension detection. I used the following methods from the official Composer website:

Method 1:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

Method 2: Programmatic Installation

Verification.

After manually installing Composer, I executed composer check-platform-reqs again. This time, all required extensions were successfully detected:

ext-dom        20031129    success
ext-fileinfo     8.3.15    success
ext-session      8.3.15    success
ext-tokenizer    8.3.15    success
ext-xml          8.3.15    success
ext-xmlwriter    8.3.15    success

Results by composer diagnose:

Checking composer.json: OK
Checking composer.lock: OK
Checking platform settings: OK
Checking git settings: No git process found
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com rate limit: OK
Checking disk free space: OK

Checking pubkeys: 
Tags Public Key Fingerprint: 57815BA2 7E54DC31 7ECC7CC5 573090D0  87719BA6 8F3BB723 4E5D42D0 84A14642
Dev Public Key Fingerprint: 4AC45767 E5EC2265 2F0C1167 CBBB8A2B  0C708369 153E328C AD90147D AFE50952
OK

Checking Composer version: OK
Checking Composer and its dependencies for vulnerabilities: OK
Composer version: 2.8.4
PHP version: 8.3.15
PHP binary path: /usr/bin/php83
OpenSSL version: OpenSSL 3.3.2 3 Sep 2024
curl version: 8.11.1 libz 1.3.1 ssl OpenSSL/3.3.2
zip: extension present, unzip present, 7-Zip present (7z)

I tried to add SSH keys (Public Key, Dev Public Key) to see if authentication issues were affecting Composer’s behavior. However, this did not resolve the issue.

I can’t figure out who exactly is causing the error, the composer or the repository itself.

Since I am encountering unexpected behavior where Composer fails to recognize PHP extensions that are clearly enabled within the Docker container, and I am not very familiar with the intricacies of Composer’s behavior, I would greatly appreciate any insights or suggestions from the community.

Furthermore, I’d like to inquire about the safest method for installing Composer within a Docker image. Downloading the installer directly from an internet resource within the image might not be the most secure or reliable approach. Are there any recommended best practices for installing Composer within a Dockerized environment?

Match question mark and everything after

I’m trying to use preg_replace to create image links from urls.

Example:
https://www.google.com/image.png
becomes
<img src="https://www.google.com/image.png">

$patterns = array (
'~https?://S+?(?:png|gif|webp|jpe?g)~'
);
$replace = array (
'<div><img src="$0" style="width:100%;object-fit:contain;"></div>'
);
$string = preg_replace($patterns, $replace, $string);

I stripped out the irrelevant parts for brevity, but that’s an array for a reason.

This works great, unless the image has a question mark in it… ie,

https://www.google.com/image.png?123456

In this case, the question mark and everything after it will not match… so we wind up with:

<div><img src="https://www.google.com/image.png"></div>?123456

How can I get it to match the question mark as well?

Get Translation on Moodle using “get_string” for a specific language

Translate on Moodle using “get_string” for a specific language.I am trying to get a translation of my plugin block on Moodle in a specific language. but I cannot. here is a code example:

    public static function course_lesson_created(mod_lessoneventpage_created $event)
    {
        global $DB;

        $event_data = $event->get_data();
        // Get the course ID from the event data

        if (isset($event_data['target']) && $event_data['target'] == "page") {// if lesson page
            $objectid = $event_data['objectid'];

            // Query the database to get the lesson page
            $lessonPage = $DB->get_record('lesson_pages', ['id' => $objectid], '*', MUST_EXIST);
            $lesson = $DB->get_record('lesson', ['id' => $lessonPage->lessonid], '*', MUST_EXIST);
            $course_module = $DB->get_record('course_modules', ['id' => $event_data['contextinstanceid']], '*', MUST_EXIST);


            $response = self::create_MoodleContentDetail(
                $lessonPage->title,
                $lesson->intro,
                $event_data["action"],
                "lesson",
                "<h1>" . get_string('lessontitle', 'block_smartteacher', null, $course_module->lang) . ": {$lessonPage->title}</h1>" .
                "<h2>" . get_string('lessondescription', 'block_smartteacher', null, $course_module->lang) . ": {$lesson->intro}</h2>" .
                "<h3>" . get_string('lessoncontent', 'block_smartteacher', null, $course_module->lang) . ": {$lessonPage->contents}</h3>",
                $course_module->lang,
                $course_module->course, // courseId
                $course_module->module, // moduleTypeId
                $course_module->id, // moduleId
                $course_module->section  // sectionId
            );


            //  var_dump(json_encode($event_data));
            //  var_dump(json_encode($response));
            /*      var_dump(get_string('lessoncontent', 'block_smartteacher', null, $course_module->lang));
                 var_dump(get_string('lessoncontent', 'block_smartteacher', null, "ar"));
                 var_dump(get_string('lessoncontent', 'block_smartteacher', null, "en")); */
            // var_dump($course_module->lang == "ar");
            // var_dump($course_module->lang == "en");
            //  var_dump($course_module->lang == "ar" ? "ar" : "en");
            //  die();
        }

    }

Is my way wrong get_string('lessontitle', 'block_smartteacher', null, $course_module->lang) ?
I got that lang is correct its return "ar"

Keep getting error “Undefined array key” when I run pass session

I am developing a cab booking application in Laravel, and I am encountering an issue while trying to pass session data to a review page. The session data is stored in the controller methods, and the review page is supposed to display the stored data. However, when I redirect to the review page, I get the error “Undefined array key”, indicating that the session data is not being accessed correctly. Below is my HTML code for the review page, where I attempt to display the session data, and my controller code that saves and retrieves the session data. I have tried compacting the session with the redirect and merging arrays, but the error persists. Any insights into why this might be happening or how to resolve it would be appreciated! Thank you.

this is my review HTML page:

@extends('index')
@section('content')
    <!-- ====== Blog Start ====== -->
    <section class="ud-blog-grids">
        <div class="container">
          <div class="row">
            <div>
              <div class="ud-single-blog">
                <div class="ud-blog-image">
                  <a href="blog-details.html">
                    <img src="assets/images/blog/blog-01.jpg" alt="blog" />
                  </a>
                </div>
                <div class="ud-blog-content">
                  <span class="ud-blog-date">Dec 22, 2023</span>
                  <h3 class="ud-blog-title">
                    <a href="blog-details.html">
                      Meet AutoManage, the best AI management tools
                    </a>
                  </h3>
                  <p class="ud-blog-desc">
                    <p>Pickup: {{ $booking['pickup_location'] }}</p>
                    <p>Dropoff: {{ $booking['dropoff_location'] }}</p>
                    <p>Date: {{ $booking['date'] }}</p>
                    <p>Time: {{ $booking['time'] }}</p>
                    <p>Name: {{ $booking['name'] }}</p>
                    <p>Email: {{ $booking['email'] }}</p>
                    <p>Phone: {{ $booking['phone'] }}</p>
                    <p>Travelers: {{ $booking['nop'] }}</p>
                  </p>
                </div>
              </div>
            </div>
          </div>
        </div>
    </section>
    <!-- ====== Blog End ====== -->
@endsection

I have tried to store into var and compacting session with review page redirect in controller.

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use IlluminateSupportFacadesMail;
use IlluminateSupportFacadesSession;

class BookingController extends Controller
{
    //

    public function index(){
        return view('home');
    }

    public function saveBooking(Request $request){
        // Store field data to into Var
            $pickup_location = $request['pickupLocation'];
            $drpoff_location = $request['dropoffLocation'];
            $date = $request['pickupDate'];
            $time = $request['pickupTime'];

        // Validate the data
        // $request->validate([
        //     'pickupLocation' => 'required',
        //     'dropoffLocation' =>'required',
        //     'pickupDate' =>'required',
        //     'pickupTime' =>'required',
        // ]);

        // Store textfield value into session
        session(['booking' => $request->only(['pickup_location','drpoff_location','date','time']),]);

        return redirect()->route('booking.user');
    }

    public function userPage(){
        return view('users');
    }

    public function saveUsers(Request $request){
        // store field data into Var
        $name = $request['userName'];
        $phone = $request['userPhone'];
        $email = $request['userEmail'];
        $nop = $request['userNOP'];

        // Validate the data
        // $request->validate([
        //     'userName' => 'required',
        //     'userPhone' => 'required',
        //     'userEmail' => 'required',
        //     'userNOP' => 'required|max:4',
        // ]);

        // Store field value into session
        $booking = session('booking');
        session(['booking'=> array_merge($booking, $request->only(['name','phone','email','nop'])),]);

        return redirect()->route('booking.review');
    }

    public function reviewPage(){
        $booking = session('booking');
        return view('review',compact('booking'));
    }
}

This is error is showing on my browser.
enter image description here

About The function of ServiceListenerFactory in ZF2

I am reading the source code of Zend Framework 2’s (ZF2) ServiceManager to help me learn the Service Locator design pattern. However, when I came across the ServiceListenerFactory class, I couldn’t understand the role it plays in the overall design.
ServiceListenerFactory is responsible for loading configurations and creating ServiceListener instances, ensuring that services can be dynamically registered and created. The mapping relationship: ServiceListenerFactory maps configurations into the ServiceManager, enabling service factories to correctly create service instances based on the configurations.

<?php
namespace ZendMvcService;
use ZendModuleManagerListenerServiceListener;
use ZendModuleManagerListenerServiceListenerInterface;
use ZendMvcExceptionInvalidArgumentException;
use ZendMvcExceptionRuntimeException;
use ZendServiceManagerFactoryInterface;
use ZendServiceManagerServiceLocatorInterface;

class ServiceListenerFactory implements FactoryInterface
{
    const MISSING_KEY_ERROR = 'Invalid service listener options detected, %s array must contain %s key.';
    const VALUE_TYPE_ERROR = 'Invalid service listener options detected, %s must be a string, %s given.';

    protected $defaultServiceConfig = array(      /*Default mvc-related service configuration -- can be overridden by modules.
        'invokables' => array(
            'DispatchListener'     => 'ZendMvcDispatchListener',
            'RouteListener'        => 'ZendMvcRouteListener',
            'SendResponseListener' => 'ZendMvcSendResponseListener'
        ),
        'factories' => array(
            'Application'                    => 'ZendMvcServiceApplicationFactory',
            'Config'                         => 'ZendMvcServiceConfigFactory',
            'ControllerLoader'               => 'ZendMvcServiceControllerLoaderFactory',
            'ControllerPluginManager'        => 'ZendMvcServiceControllerPluginManagerFactory',
            'ConsoleAdapter'                 => 'ZendMvcServiceConsoleAdapterFactory',
            'ConsoleRouter'                  => 'ZendMvcServiceRouterFactory',
            'ConsoleViewManager'             => 'ZendMvcServiceConsoleViewManagerFactory',
            'DependencyInjector'             => 'ZendMvcServiceDiFactory',
            'DiAbstractServiceFactory'       => 'ZendMvcServiceDiAbstractServiceFactoryFactory',
            'DiServiceInitializer'           => 'ZendMvcServiceDiServiceInitializerFactory',
            'DiStrictAbstractServiceFactory' => 'ZendMvcServiceDiStrictAbstractServiceFactoryFactory',
            'FilterManager'                  => 'ZendMvcServiceFilterManagerFactory',
            'FormElementManager'             => 'ZendMvcServiceFormElementManagerFactory',
            'HttpRouter'                     => 'ZendMvcServiceRouterFactory',
            'HttpViewManager'                => 'ZendMvcServiceHttpViewManagerFactory',
            'HydratorManager'                => 'ZendMvcServiceHydratorManagerFactory',
            'InputFilterManager'             => 'ZendMvcServiceInputFilterManagerFactory',
            'MvcTranslator'                  => 'ZendMvcServiceTranslatorServiceFactory',
            'PaginatorPluginManager'         => 'ZendMvcServicePaginatorPluginManagerFactory',
            'Request'                        => 'ZendMvcServiceRequestFactory',
            'Response'                       => 'ZendMvcServiceResponseFactory',
            'Router'                         => 'ZendMvcServiceRouterFactory',
            'RoutePluginManager'             => 'ZendMvcServiceRoutePluginManagerFactory',
            'SerializerAdapterManager'       => 'ZendMvcServiceSerializerAdapterPluginManagerFactory',
            'ValidatorManager'               => 'ZendMvcServiceValidatorManagerFactory',
            'ViewHelperManager'              => 'ZendMvcServiceViewHelperManagerFactory',
            'ViewFeedRenderer'               => 'ZendMvcServiceViewFeedRendererFactory',
            'ViewFeedStrategy'               => 'ZendMvcServiceViewFeedStrategyFactory',
            'ViewJsonRenderer'               => 'ZendMvcServiceViewJsonRendererFactory',
            'ViewJsonStrategy'               => 'ZendMvcServiceViewJsonStrategyFactory',
            'ViewManager'                    => 'ZendMvcServiceViewManagerFactory',
            'ViewResolver'                   => 'ZendMvcServiceViewResolverFactory',
            'ViewTemplateMapResolver'        => 'ZendMvcServiceViewTemplateMapResolverFactory',
            'ViewTemplatePathStack'          => 'ZendMvcServiceViewTemplatePathStackFactory',
        ),
        'aliases' => array(
            'Configuration'                          => 'Config',
            'Console'                                => 'ConsoleAdapter',
            'Di'                                     => 'DependencyInjector',
            'ZendDiLocatorInterface'               => 'DependencyInjector',
            'ZendMvcControllerPluginManager'      => 'ControllerPluginManager',
            'ZendViewResolverTemplateMapResolver' => 'ViewTemplateMapResolver',
            'ZendViewResolverTemplatePathStack'   => 'ViewTemplatePathStack',
            'ZendViewResolverAggregateResolver'   => 'ViewResolver',
            'ZendViewResolverResolverInterface'   => 'ViewResolver',
        ),
        'abstract_factories' => array(
            'ZendFormFormAbstractServiceFactory',
        ),
    );
    /*Create the service listener service
     *Tries to get a service named ServiceListenerInterface from the service locator, otherwise creates a 
*ZendModuleManagerListenerServiceListener service, passing it the service locator instance and the default service
     * configuration, which can be overridden by modules.
     *It looks for the 'service_listener_options' key in the application config and tries to add service manager as configured. The value of
     * 'service_listener_options' must be a list (array) which contains the following keys:
     *   - service_manager: the name of the service manage to create as string
     *   - config_key: the name of the configuration key to search for as string
     *   - interface: the name of the interface that modules can implement as string
     *   - method: the name of the method that modules have to implement as string
     * @param  ServiceLocatorInterface  $serviceLocator
     * @return ServiceListener
     * @throws InvalidArgumentException For invalid configurations.   * @throws RuntimeException*/
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $configuration   = $serviceLocator->get('ApplicationConfig');

        if ($serviceLocator->has('ServiceListenerInterface')) {
            $serviceListener = $serviceLocator->get('ServiceListenerInterface');

            if (!$serviceListener instanceof ServiceListenerInterface) {
                throw new RuntimeException( 'The service named ServiceListenerInterface must implement ' .
                    'ZendModuleManagerListenerServiceListenerInterface'
                );
            }
            $serviceListener->setDefaultServiceConfig($this->defaultServiceConfig);
        } else {
            $serviceListener = new ServiceListener($serviceLocator, $this->defaultServiceConfig);
        }
        if (isset($configuration['service_listener_options'])) {
            if (!is_array($configuration['service_listener_options'])) {
                throw new InvalidArgumentException(sprintf( 'The value of service_listener_options must be an array, %s given.',
                    gettype($configuration['service_listener_options'])
                ));
            }

            foreach ($configuration['service_listener_options'] as $key => $newServiceManager) {
                if (!isset($newServiceManager['service_manager'])) {
                    throw new InvalidArgumentException(sprintf(self::MISSING_KEY_ERROR, $key, 'service_manager'));
                } elseif (!is_string($newServiceManager['service_manager'])) {
                    throw new InvalidArgumentException(sprintf(self::VALUE_TYPE_ERROR, 'service_manager',
                        gettype($newServiceManager['service_manager'])
                    ));
                }
                if (!isset($newServiceManager['config_key'])) {
                    throw new InvalidArgumentException(sprintf(self::MISSING_KEY_ERROR, $key, 'config_key'));
                } elseif (!is_string($newServiceManager['config_key'])) {
                    throw new InvalidArgumentException(sprintf(self::VALUE_TYPE_ERROR, 'config_key',
                        gettype($newServiceManager['config_key'])
                    ));
                }
                if (!isset($newServiceManager['interface'])) {
                    throw new InvalidArgumentException(sprintf(self::MISSING_KEY_ERROR, $key, 'interface'));
                } elseif (!is_string($newServiceManager['interface'])) {
                    throw new InvalidArgumentException(sprintf(self::VALUE_TYPE_ERROR, 'interface',
                        gettype($newServiceManager['interface'])
                    ));
                }
                if (!isset($newServiceManager['method'])) {
                    throw new InvalidArgumentException(sprintf(self::MISSING_KEY_ERROR, $key, 'method'));
                } elseif (!is_string($newServiceManager['method'])) {
                    throw new InvalidArgumentException(sprintf( self::VALUE_TYPE_ERROR, 'method',
                        gettype($newServiceManager['method'])
                    ));
                }

                $serviceListener->addServiceManager(
                    $newServiceManager['service_manager'],
                    $newServiceManager['config_key'],
                    $newServiceManager['interface'],
                    $newServiceManager['method']
                );
            }
        }
        return $serviceListener;
    }
}

Please tell me its role If you can also provide the relevant code in the ServiceManager, that would be even better!

Having issue implementing google recaptcha v3 in my php form

I am trying to use recaptcha to validate authenticity of my user before submitting the form but i am having issue that my page just refreshes when clicked on “book now” button rather than showing captcha.

 <script src="https://www.google.com/recaptcha/api.js"></script>

This is code snippet from my form ending-
 <div class="row mb-3">
                <!-- Number of Persons -->
                <div class="col-md-3">
                    <label for="persons" class="form-label">Number of Persons</label>
                    <input type="number" id="persons" name="persons" class="form-control" required min="1" placeholder="Enter number of persons">
                    <div class="invalid-feedback">Please enter the number of persons.</div>
                </div>

                <!-- Number of Rooms -->
                <div class="col-md-3">
                    <label for="rooms" class="form-label">Number of Rooms</label>
                    <input type="number" id="rooms" name="rooms" class="form-control" required min="1" placeholder="Enter number of rooms">
                    <div class="invalid-feedback">Please enter the number of rooms required.</div>
                </div>
            
                
                <div class="col-md-3">
                    <label for="check-in" class="form-label">Check-in Date</label>
                    <input type="date" id="check-in" name="check_in_date" class="form-control" required>
                    <div class="invalid-feedback">Please select a check-in date.</div>
                </div>
                <div class="col-md-3">
                    <label for="check-out" class="form-label">Check-out Date</label>
                    <input type="date" id="check-out" name="check_out_date" class="form-control" required>
                    <div class="invalid-feedback">Please select a check-out date.</div>
                </div>
                    
            </div>  

            <div class="mb-3">
                <label for="special-requests" class="form-label">Special Requests</label>
                <textarea id="special-requests" name="special_requests" class="form-control" rows="5" placeholder="Enter any special requests"></textarea>
            </div>
         
            <div class="text-center">
        <button 
            class="g-recaptcha btn btn-primary" 
            data-sitekey="my-site-key" 
            data-callback="onSubmit" 
            data-action="submit" 
            type="button">
            Book Now
        </button>
        <button type="submit" name="submit1" class="btn btn-primary" style="display: none;">Submit Hidden</button>
    </div>
        </form>

I handle form submission using php code on same page like

code snippet - 

  <?php
if (isset($_POST['submit1'])) {
    // Validate form inputs
    $errors = [];

    // Retrieve form data
    $title = $_POST['title'] ?? '-';
    $fullName = $_POST['name'] ?? '-';
    $employeeId = $_POST['Employee'] ?? '-';
    $designation = $_POST['Designation'] ?? '-';
    $posting = $_POST['posting'] ?? '-';
    $email = $_POST['email'] ?? '-';
    $phone = $_POST['phone'] ?? '-';
    $persons = $_POST['persons'] ?? '-';
    $rooms = $_POST['rooms'] ?? '-';
    $checkInDate = $_POST['check_in_date'] ?? '-';
    $checkOutDate = $_POST['check_out_date'] ?? '-';
    $specialRequests = $_POST['special_requests'] ?? '-';

    // Email content
    $subject ="Guest  House Booking";
    $body = "
    <h3>Booking Details</h3>
    <p><strong>Title:</strong> $title</p>
    <p><strong>Full Name:</strong> $fullName</p>
    <p><strong>Employee ID:</strong> $employeeId</p>
    <p><strong>Designation:</strong> $designation</p>
    <p><strong>Posting:</strong> $posting</p>
    <p><strong>Email:</strong> $email</p>
    <p><strong>Phone:</strong> $phone</p>
    <p><strong>Number of Persons:</strong> $persons</p>
    <p><strong>Number of Rooms:</strong> $rooms</p>
    <p><strong>Check-in Date:</strong> $checkInDate</p>
    <p><strong>Check-out Date:</strong> $checkOutDate</p>
    <p><strong>Special Requests:</strong> $specialRequests</p>
    ";

at end i have this js

<script>
   function onSubmit(token) {
     document.getElementById("first-form").submit();
   }
 </script>

first-form is id of my form
I need help on opening recaptcha when book now is clicked and only submitting the form on successful validation of recaptcha

I have tried the offical google documentation which refers to add js then add a button over my original hidden button and making a js button which does form submission. I have also tried to consult chatgpt which gave me some code

How to implement select2 on Laravel project with flowbite select input field inside a modal?

I’ve been trying to implement select2 on my Laravel project with flowbite select input field inside a modal to create a data. Somehow, the select2 doesn’t work if I’m using it inside a modal. Below is my modal and form inside it

<div class="w-full md:w-auto flex flex-col md:flex-row space-y-2 md:space-y-0 items-stretch md:items-center justify-end md:space-x-3 flex-shrink-0">
          <button
            id="addModalButton"
            data-modal-target="addModal" 
            data-modal-toggle="addModal"
            type="button" 
            class="flex items-center justify-center text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800">
            <svg 
              class="h-3.5 w-3.5 mr-2"
              fill="currentColor"
              viewbox="0 0 20 20" 
              xmlns="http://www.w3.org/2000/svg" 
              aria-hidden="true">
              <path clip-rule="evenodd" fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" />
            </svg>
            Add data
          </button> 
        </div>

<!-- Add data modal -->
      <div id="addModal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-modal md:h-full">
        <div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
          <!-- Modal content -->
          <div class="relative p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5">
            <!-- Modal header -->
            <div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
              <h3 class="text-lg font-semibold text-gray-900 dark:text-white">
                Add New House
              </h3>
              <button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="addModal">
                <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
                <span class="sr-only">Close</span>
              </button>
            </div>
            <!-- Modal body -->
            <form action="{{ route('dashboard.chickenEgg.store') }}" method="POST">
              @csrf
              <div class="grid gap-4 mb-4 sm:grid-cols-2">
                <div class="col-span-2">
                  <label for="house_id" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Chicken house</label>
                  <select id="house_id" name="house_id" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" required>

                  </select>
                @error('house_id')
                  <span class="text-red-600">{{ $message }}</span>
                @enderror
                </div>
                <div>
                  <label for="date" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date</label>
                  <input type="date" name="date" id="date" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" value="{{ old('date') }}" required>
                  @error('date')
                    <span class="text-red-600">{{ $message }}</span>
                  @enderror
                </div>
              </div>
              <button type="submit" class="text-white inline-flex items-center bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
                  <svg class="mr-1 -ml-1 w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd"></path></svg>
                  Add data
              </button>
            </form>
          </div>
        </div>
      </div>

I’ve try the solution from the select2 website where I just need to modify my code to something like this

<div id="myModal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
    ...
    <select id="mySelect2">
        ...
    </select>
    ...
</div>

...

<script>
    $('#mySelect2').select2({
        dropdownParent: $('#myModal')
    });
</script>

But that code is for Bootstrap, not for Tailwind CSS, particularly Flowbite.