diff --git a/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Engine.php b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Engine.php
index 49af9787fe..4e15671f16 100644
--- a/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Engine.php
+++ b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Engine.php
@@ -10,8 +10,11 @@ class CRM_Goonjcustom_Engine {
/**
*
*/
- public static function processQueue($params) {
- $returnValues = [];
+ public static function processQueue(int $maxSeconds = 60): array {
+ $returnValues = [
+ 'processed' => 0,
+ 'results' => [],
+ ];
$queue = \CRM_Queue_Service::singleton()->create([
'name' => self::QUEUE_NAME,
@@ -24,7 +27,8 @@ public static function processQueue($params) {
'errorMode' => CRM_Queue_Runner::ERROR_CONTINUE,
]);
- $maxRunTime = time() + 30;
+ $maxSeconds = max(1, (int) $maxSeconds);
+ $maxRunTime = time() + $maxSeconds;
$continue = TRUE;
// Loop to process the queue items.
@@ -35,10 +39,18 @@ public static function processQueue($params) {
$continue = FALSE;
}
- $returnValues[] = $result;
+ if (isset($result['exception']) && $result['exception'] instanceof \Throwable) {
+ $result['exception'] = [
+ 'type' => get_class($result['exception']),
+ 'message' => $result['exception']->getMessage(),
+ ];
+ }
+
+ $returnValues['results'][] = $result;
+ $returnValues['processed']++;
}
- return civicrm_api3_create_success($returnValues, $params, 'SendAuthorizationQueue', 'Run');
+ return $returnValues;
}
}
diff --git a/wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php b/wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php
index 4c5aa40ba4..6dd4668a07 100644
--- a/wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php
+++ b/wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php
@@ -745,8 +745,9 @@ public static function aclCollectionCamp($entity, &$clauses, $userId, $condition
->addWhere('status', '=', 'Added')
->addWhere('group_id.Chapter_Contact_Group.Use_Case', '=', 'chapter-team')
->execute();
-
$teamGroupContact = $teamGroupContacts->first();
+ error_log("teamGroupContact: " . print_r($teamGroupContact, TRUE));
+
if (!$teamGroupContact) {
// @todo we should handle it in a better way.
@@ -763,8 +764,13 @@ public static function aclCollectionCamp($entity, &$clauses, $userId, $condition
->addWhere('id', '=', $groupId)
->execute();
+
$group = $chapterGroups->first();
+ error_log("group: " . print_r($group, TRUE));
+
$statesControlled = $group['Chapter_Contact_Group.States_controlled'];
+ error_log("statesControlled: " . print_r($statesControlled, TRUE));
+
if (empty($statesControlled)) {
// Handle the case when the group is not controlling any state.
diff --git a/wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php b/wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php
index 081d6a1e0a..4361897e28 100644
--- a/wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php
+++ b/wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php
@@ -1742,4 +1742,193 @@ public static function generateInvoiceNumber(string $op, string $objectName, int
}
}
+ /**
+ * Send camp outcome acknowledgement email after 5 days.
+ */
+ public static function sendCampOutcomeAckEmailAfter5Days($collectionCamp) {
+ $campId = $collectionCamp['id'];
+
+ // Fetch Event Volunteer
+ $volunteeringActivities = Activity::get(FALSE)
+ ->addSelect('activity_contact.contact_id')
+ ->addJoin('ActivityContact AS activity_contact', 'LEFT')
+ ->addWhere('activity_type_id:name', '=', 'Volunteering')
+ ->addWhere('Volunteering_Activity.Collection_Camp', '=', $campId)
+ ->addWhere('activity_contact.record_type_id', '=', 3)
+ ->execute();
+
+ // Collect volunteer contact IDs
+ $volunteerContactIds = [];
+ foreach ($volunteeringActivities as $volunteer) {
+ $volunteerContactIds[] = $volunteer['activity_contact.contact_id'];
+ }
+
+ // Fetch volunteer emails (if any volunteers found)
+ $eventVolunteerEmails = [];
+ if (!empty($volunteerContactIds)) {
+ $volunteerContacts = Contact::get(FALSE)
+ ->addSelect('email.email')
+ ->addJoin('Email AS email', 'LEFT')
+ ->addWhere('id', 'IN', $volunteerContactIds)
+ ->execute();
+
+ foreach ($volunteerContacts as $contact) {
+ if (!empty($contact['email.email'])) {
+ $eventVolunteerEmails[] = $contact['email.email'];
+ }
+ }
+ }
+
+ // Convert to comma-separated string for CC
+ $eventVolunteerCC = implode(',', $eventVolunteerEmails);
+
+ $collectionCamps = EckEntity::get('Collection_Camp', FALSE)
+ ->addSelect('Collection_Camp_Intent_Details.Location_Area_of_camp', 'Core_Contribution_Details.Number_of_unique_contributors', 'Camp_Outcome.Rate_the_camp', 'Camp_Outcome.Total_Fundraised_form_Activity', 'Collection_Camp_Intent_Details.Start_Date', 'title', 'Collection_Camp_Intent_Details.End_Date')
+ ->addWhere('id', '=', $campId)
+ ->execute()->single();
+
+ $contribution = Contribution::get(FALSE)
+ ->addSelect('total_amount')
+ ->addWhere('contribution_status_id:name', '=', 'Completed')
+ ->addWhere('Contribution_Details.Source', '=', $campId)
+ ->execute();
+
+ $totalAmount = 0;
+
+ foreach ($contribution as $c) {
+ $totalAmount += $c['total_amount'];
+ }
+
+ $collectionSourceVehicleDispatche = EckEntity::get('Collection_Source_Vehicle_Dispatch', FALSE)
+ ->addSelect('Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office')
+ ->addWhere('Camp_Vehicle_Dispatch.Collection_Camp', '=', $campId)
+ ->execute();
+
+ $materialGeneratedList = [];
+ foreach ($collectionSourceVehicleDispatche as $dispatch) {
+ $materialGeneratedList[] = $dispatch['Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office'];
+ }
+
+ $materialGeneratedHtml = '';
+ if (!empty($collectionSourceVehicleDispatche)) {
+ // Outer bullet
+ $materialGeneratedHtml .= "
Material generated:
";
+ // Inner numbered list with inline style for emails
+ $materialGeneratedHtml .= "";
+ foreach ($collectionSourceVehicleDispatche as $dispatch) {
+ $materialGeneratedHtml .= "- " . $dispatch['Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office'] . "
";
+ }
+ $materialGeneratedHtml .= "
";
+ }
+
+ $uniqueContributors = $collectionCamps['Core_Contribution_Details.Number_of_unique_contributors'];
+
+ $campRating = $collectionCamps['Camp_Outcome.Rate_the_camp'];
+ $fundsGenerated = $collectionCamps['Camp_Outcome.Total_Fundraised_form_Activity'];
+ $collectionCampTitle = $collectionCamps['title'];
+
+ $campAddress = $collectionCamps['Collection_Camp_Intent_Details.Location_Area_of_camp'];
+
+ $campStartDate = $collectionCamps['Collection_Camp_Intent_Details.Start_Date'];
+ $campEndDate = $collectionCamps['Collection_Camp_Intent_Details.End_Date'];
+
+ $campCompletionDate = $collectionCamp['Camp_Outcome.Camp_Status_Completion_Date'];
+ $campOrganiserId = $collectionCamp['Collection_Camp_Core_Details.Contact_Id'];
+
+ $campAttendedBy = Contact::get(FALSE)
+ ->addSelect('email.email', 'display_name')
+ ->addJoin('Email AS email', 'LEFT')
+ ->addWhere('id', '=', $campOrganiserId)
+ ->execute()->first();
+
+ $attendeeEmail = $campAttendedBy['email.email'];
+ $attendeeName = $campAttendedBy['display_name'];
+
+ if (!$attendeeEmail) {
+ throw new \Exception('Attendee email missing');
+ }
+
+ $mailParams = [
+ 'subject' => $attendeeName . ' thankyou for organizing the camp! A quick snapshot.',
+ 'from' => self::getFromAddress(),
+ 'toEmail' => $attendeeEmail,
+ 'replyTo' => self::getFromAddress(),
+ 'html' => self::getCampOutcomeAckEmailAfter5Days($attendeeName, $campAddress, $campStartDate, $totalAmount, $materialGeneratedHtml, $uniqueContributors, $campRating, $fundsGenerated, $campId, $campEndDate, $campOrganiserId),
+ 'cc' => $eventVolunteerCC,
+ ];
+
+ $emailSendResult = \CRM_Utils_Mail::send($mailParams);
+
+ if ($emailSendResult) {
+ \Civi::log()->info("Camp status email sent for collection camp: $campId");
+ try {
+ EckEntity::update('Collection_Camp', FALSE)
+ ->addValue('Camp_Outcome.Five_Day_Email_Sent', 1)
+ ->addWhere('id', '=', $campId)
+ ->execute();
+
+ }
+ catch (\CiviCRM_API4_Exception $ex) {
+ \Civi::log()->debug("Exception while creating update the email sent " . $ex->getMessage());
+ }
+ try {
+ $results = Activity::create(FALSE)
+ ->addValue('subject', $collectionCampTitle)
+ ->addValue('activity_type_id:name', 'Camp summary email')
+ ->addValue('status_id:name', 'Authorized')
+ ->addValue('activity_date_time', date('Y-m-d H:i:s'))
+ ->addValue('source_contact_id', $campOrganiserId)
+ ->addValue('target_contact_id', $campOrganiserId)
+ ->addValue('Collection_Camp_Data.Collection_Camp_ID', $campId)
+ ->execute();
+
+ }
+ catch (\CiviCRM_API4_Exception $ex) {
+ \Civi::log()->debug("Exception while creating Camp summary email activity: " . $ex->getMessage());
+ }
+
+ }
+
+ }
+
+ /**
+ *
+ */
+public static function getCampOutcomeAckEmailAfter5Days($attendeeName, $campAddress, $campStartDate, $totalAmount, $materialGeneratedHtml, $uniqueContributors, $campRating, $fundsGenerated, $campId, $campEndDate, $campOrganiserId) {
+ $homeUrl = \CRM_Utils_System::baseCMSURL();
+ $campVolunteerFeedback = $homeUrl . 'volunteer-camp-feedback/#?Collection_Source_Feedback.Collection_Camp_Code=' . $campId . '&Collection_Source_Feedback.Collection_Camp_Address=' . urlencode($campAddress) . '&Collection_Source_Feedback.Filled_By=' . $campOrganiserId;
+ // Conditionally include funds raised
+ $fundsGeneratedHtml = '';
+ if (!empty($fundsGenerated)) {
+ $fundsGeneratedHtml = "Funds raised through activities: $fundsGenerated";
+ }
+
+ $formattedCampStartDate = date('d-m-Y', strtotime($campStartDate));
+ $formattedCampEndDate = date('d-m-Y', strtotime($campEndDate));
+
+ // Conditional date text
+ if ($formattedCampStartDate === $formattedCampEndDate) {
+ $campDateText = "on $formattedCampStartDate";
+ } else {
+ $campDateText = "from $formattedCampStartDate to $formattedCampEndDate";
+ }
+
+ $html = "
+ Dear $attendeeName,
+ Thank you for organising the recent collection drive at $campAddress $campDateText! Your effort brought people together and added strength to this movement of mindful giving.
+ Here’s a quick snapshot of the camp:
+
+ $materialGeneratedHtml
+ - Footfall: $uniqueContributors
+ $fundsGeneratedHtml
+
+ If you haven’t filled the feedback form yet, you can share your thoughts here: Feedback Form
+ We would also love to hear about any highlights, challenges, or ideas you’d like us to know. Your reflections will help us make future drives even more impactful.
+ Looking forward to many more such collaborations ahead!
+ Warm regards,
Team Goonj
+ ";
+
+ return $html;
+ }
+
}
diff --git a/wp-content/civi-extensions/goonjcustom/api/v3/GoonjcustomAction.php b/wp-content/civi-extensions/goonjcustom/api/v3/GoonjcustomAction.php
index 44b1fb3419..d86126b02d 100644
--- a/wp-content/civi-extensions/goonjcustom/api/v3/GoonjcustomAction.php
+++ b/wp-content/civi-extensions/goonjcustom/api/v3/GoonjcustomAction.php
@@ -18,7 +18,17 @@
*
* @throws \CRM_Core_Exception
*/
+function _civicrm_api3_goonjcustom_action_process_queue_spec(&$spec) {
+ $spec['max_seconds'] = [
+ 'title' => 'Max Seconds',
+ 'description' => 'Maximum number of seconds to process queue items.',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'api.default' => 60,
+ ];
+}
+
function civicrm_api3_goonjcustom_action_process_queue($params) {
- $returnValues = CRM_Goonjcustom_Engine::processQueue(60);
+ $maxSeconds = isset($params['max_seconds']) ? (int) $params['max_seconds'] : 60;
+ $returnValues = CRM_Goonjcustom_Engine::processQueue($maxSeconds);
return civicrm_api3_create_success($returnValues, $params, 'GoonjcustomQueue', 'Process');
}
diff --git a/wp-content/civi-extensions/goonjcustom/cli/assign_contacts_to_group.php b/wp-content/civi-extensions/goonjcustom/cli/assign_contacts_to_group.php
new file mode 100644
index 0000000000..17fe0d144a
--- /dev/null
+++ b/wp-content/civi-extensions/goonjcustom/cli/assign_contacts_to_group.php
@@ -0,0 +1,155 @@
+ null,
+ 'source' => null,
+ 'email_domain' => null,
+ 'status' => 'Added',
+ 'dry_run' => false,
+ 'help' => false,
+ ];
+
+ foreach ($argv as $arg) {
+ if ($arg === '--help' || $arg === '-h') {
+ $args['help'] = true;
+ continue;
+ }
+ if ($arg === '--dry-run') {
+ $args['dry_run'] = true;
+ continue;
+ }
+ if (!str_starts_with($arg, '--') || !str_contains($arg, '=')) {
+ continue;
+ }
+
+ [$key, $value] = explode('=', substr($arg, 2), 2);
+ switch ($key) {
+ case 'group-id':
+ $args['group_id'] = (int) $value;
+ break;
+ case 'source':
+ $args['source'] = (string) $value;
+ break;
+ case 'email-domain':
+ $args['email_domain'] = (string) $value;
+ break;
+ case 'status':
+ $args['status'] = (string) $value;
+ break;
+ }
+ }
+
+ return $args;
+}
+
+function main(array $opts): void {
+ requireCiviBootstrap();
+
+ if (empty($opts['group_id'])) {
+ fwrite(STDERR, "Missing required --group-id.\n\n");
+ usage();
+ exit(2);
+ }
+
+ $status = $opts['status'] ?: 'Added';
+ if (!in_array($status, ['Added', 'Removed', 'Pending'], true)) {
+ fwrite(STDERR, "Invalid --status. Use Added, Removed, or Pending.\n");
+ exit(2);
+ }
+
+ $where = [];
+ $params = [
+ 1 => [(int) $opts['group_id'], 'Integer'],
+ 2 => [$status, 'String'],
+ ];
+
+ $from = 'civicrm_contact c';
+
+ if (!empty($opts['email_domain'])) {
+ $from .= ' INNER JOIN civicrm_email e ON e.contact_id = c.id';
+ $where[] = 'e.email LIKE %3';
+ $params[3] = ['%@' . $opts['email_domain'], 'String'];
+ }
+
+ if (!empty($opts['source'])) {
+ $where[] = 'c.source = %4';
+ $params[4] = [$opts['source'], 'String'];
+ }
+
+ if (empty($where)) {
+ fwrite(STDERR, "Refusing to run without a filter. Provide --source and/or --email-domain.\n");
+ exit(2);
+ }
+
+ $whereSql = implode(' AND ', $where);
+
+ // Use ON DUPLICATE KEY UPDATE so existing memberships get re-added.
+ $sql = "
+ INSERT INTO civicrm_group_contact (group_id, contact_id, status)
+ SELECT %1 AS group_id, c.id AS contact_id, %2 AS status
+ FROM {$from}
+ WHERE {$whereSql}
+ ON DUPLICATE KEY UPDATE status = VALUES(status)
+ ";
+
+ $sql = preg_replace('/\\s+/', ' ', trim($sql));
+
+ if (!empty($opts['dry_run'])) {
+ fwrite(STDOUT, $sql . "\n");
+ exit(0);
+ }
+
+ $dao = \CRM_Core_DAO::executeQuery($sql, $params);
+ $affected = method_exists($dao, 'affectedRows') ? (int) $dao->affectedRows() : 0;
+
+ fwrite(STDOUT, "Done. SQL affected rows: {$affected}\n");
+ fwrite(STDOUT, "If this is a Smart Group, run the 'Update Smart Groups Cache' scheduled job.\n");
+}
+
+$opts = parseArgs(array_slice($argv, 1));
+if (!empty($opts['help'])) {
+ usage();
+ exit(0);
+}
+
+main($opts);
+
diff --git a/wp-content/civi-extensions/goonjcustom/cli/seed_fake_contacts.php b/wp-content/civi-extensions/goonjcustom/cli/seed_fake_contacts.php
new file mode 100644
index 0000000000..05d7b59961
--- /dev/null
+++ b/wp-content/civi-extensions/goonjcustom/cli/seed_fake_contacts.php
@@ -0,0 +1,194 @@
+ 1000,
+ 'start' => 1,
+ 'batch' => 500,
+ 'email_domain' => 'example.invalid',
+ 'group_id' => null,
+ 'source' => 'Fake Load Test',
+ 'dry_run' => false,
+ 'help' => false,
+ ];
+
+ foreach ($argv as $arg) {
+ if ($arg === '--help' || $arg === '-h') {
+ $args['help'] = true;
+ continue;
+ }
+ if ($arg === '--dry-run') {
+ $args['dry_run'] = true;
+ continue;
+ }
+
+ if (!str_starts_with($arg, '--') || !str_contains($arg, '=')) {
+ continue;
+ }
+ [$key, $value] = explode('=', substr($arg, 2), 2);
+
+ switch ($key) {
+ case 'count':
+ case 'start':
+ case 'batch':
+ $args[$key] = (int) $value;
+ break;
+
+ case 'email-domain':
+ $args['email_domain'] = (string) $value;
+ break;
+
+ case 'group-id':
+ $args['group_id'] = $value === '' ? null : (int) $value;
+ break;
+
+ case 'source':
+ $args['source'] = (string) $value;
+ break;
+ }
+ }
+
+ $args['count'] = max(0, (int) $args['count']);
+ $args['start'] = max(1, (int) $args['start']);
+ $args['batch'] = max(1, (int) $args['batch']);
+
+ return $args;
+}
+
+function requireCiviBootstrap(): void {
+ if (!class_exists(\Civi::class) || !class_exists(Contact::class) || !function_exists('civicrm_api3')) {
+ fwrite(STDERR, "CiviCRM not bootstrapped. Run this via `cv php:script ...` (see --help).\n");
+ exit(2);
+ }
+}
+
+function makeEmail(int $i, string $domain): string {
+ // Keep it deterministic and unique.
+ return "fake{$i}@{$domain}";
+}
+
+function seedFakeContacts(array $opts): void {
+ requireCiviBootstrap();
+
+ $count = $opts['count'];
+ $start = $opts['start'];
+ $batchSize = $opts['batch'];
+ $emailDomain = $opts['email_domain'];
+ $groupId = $opts['group_id'];
+ $source = $opts['source'];
+ $dryRun = $opts['dry_run'];
+
+ fwrite(STDOUT, "Seeding {$count} contacts (start={$start}, batch={$batchSize}, domain={$emailDomain})\n");
+ if ($groupId) {
+ fwrite(STDOUT, "Adding to group_id={$groupId}\n");
+ }
+ if ($dryRun) {
+ fwrite(STDOUT, "Dry-run enabled (no writes).\n");
+ }
+
+ $createdTotal = 0;
+ $batchStart = $start;
+ $lastIndex = $start + $count - 1;
+
+ while ($batchStart <= $lastIndex) {
+ $batchEnd = min($lastIndex, $batchStart + $batchSize - 1);
+ $records = [];
+
+ for ($i = $batchStart; $i <= $batchEnd; $i++) {
+ $firstName = "Fake{$i}";
+ $lastName = "User";
+ $records[] = [
+ 'contact_type' => 'Individual',
+ 'first_name' => $firstName,
+ 'last_name' => $lastName,
+ 'display_name' => "{$firstName} {$lastName}",
+ 'source' => $source,
+ 'preferred_language' => 'en_US',
+ // Ensure they are emailable unless you explicitly want opt-outs.
+ 'do_not_email' => 0,
+ 'is_opt_out' => 0,
+ ];
+ }
+
+ if ($dryRun) {
+ $created = array_map(fn($r) => $r['display_name'], $records);
+ fwrite(STDOUT, "Would create " . count($created) . " contacts: {$created[0]} ... " . end($created) . "\n");
+ $createdTotal += count($records);
+ $batchStart = $batchEnd + 1;
+ continue;
+ }
+
+ $contactIds = [];
+ foreach ($records as $offset => $record) {
+ $i = $batchStart + $offset;
+
+ $contact = civicrm_api3('Contact', 'create', $record);
+ if (!empty($contact['id'])) {
+ $contactId = (int) $contact['id'];
+ $contactIds[] = $contactId;
+
+ civicrm_api3('Email', 'create', [
+ 'contact_id' => $contactId,
+ 'email' => makeEmail($i, $emailDomain),
+ 'is_primary' => 1,
+ // If "Home" doesn't exist, CiviCRM will pick default; keeping numeric avoids API4 syntax.
+ // 'location_type_id' => 1,
+ ]);
+
+ if ($groupId) {
+ civicrm_api3('GroupContact', 'create', [
+ 'group_id' => (int) $groupId,
+ 'contact_id' => $contactId,
+ 'status' => 'Added',
+ ]);
+ }
+ }
+ }
+
+ $createdTotal += count($contactIds);
+ fwrite(STDOUT, "Created {$createdTotal}/{$count} (last batch: {$batchStart}-{$batchEnd})\n");
+
+ $batchStart = $batchEnd + 1;
+ }
+
+ fwrite(STDOUT, "Done. Total created: {$createdTotal}\n");
+}
+
+$opts = parseArgs(array_slice($argv, 1));
+if ($opts['help']) {
+ usage();
+ exit(0);
+}
+
+seedFakeContacts($opts);
diff --git a/wp-content/civi-extensions/uk.co.vedaconsulting.mosaico/CRM/Mosaico/UrlFilter.php b/wp-content/civi-extensions/uk.co.vedaconsulting.mosaico/CRM/Mosaico/UrlFilter.php
index 8733e86dd1..8c527cea38 100644
--- a/wp-content/civi-extensions/uk.co.vedaconsulting.mosaico/CRM/Mosaico/UrlFilter.php
+++ b/wp-content/civi-extensions/uk.co.vedaconsulting.mosaico/CRM/Mosaico/UrlFilter.php
@@ -50,12 +50,25 @@ public function filterHtml($htmls) {
}
};
- $htmls = preg_replace_callback(';(\
]*src *= *")([^">]+)(");i', $callback, $htmls);
- $htmls = preg_replace_callback(';(\
]*src *= *\')([^">]+)(\');i', $callback, $htmls);
- $htmls = preg_replace_callback(';(\]*background *= *")([^">]+)(");i', $callback, $htmls);
- $htmls = preg_replace_callback(';(\]*background *= *")([^\'>]+)(\');i', $callback, $htmls);
- // WISHLIST: CSS backgrounds?
- return $htmls;
+ $filterOne = function ($html) use ($callback) {
+ $html = preg_replace_callback(';(\
]*src *= *")([^">]+)(");i', $callback, $html);
+ $html = preg_replace_callback(';(\
]*src *= *\')([^">]+)(\');i', $callback, $html);
+ $html = preg_replace_callback(';(\]*background *= *")([^">]+)(");i', $callback, $html);
+ $html = preg_replace_callback(';(\]*background *= *\')([^">]+)(\');i', $callback, $html);
+ // WISHLIST: CSS backgrounds?
+ return $html;
+ };
+
+ // preg_replace_callback() supports arrays, but processing a large batch as a single array
+ // may spike memory usage. Process items one-by-one to reduce peak memory.
+ if (is_array($htmls)) {
+ foreach ($htmls as $k => $v) {
+ $htmls[$k] = $filterOne($v);
+ }
+ return $htmls;
+ }
+
+ return $filterOne($htmls);
}
/**