Skip to content

Campaign::subscribe() fails when $isModel=true — Orm\Builder is not iterable #41

@ryu3k

Description

@ryu3k

Bug Description

Campaign::subscribe() silently returns 0 results when called with $isModel = true and a Builder object (as done by subscribeBySegment()). This is because FluentCrm\Framework\Database\Orm\Builder does not implement Traversable/IteratorAggregate, so foreach ($builder as $item) iterates over zero items instead of query results.

Steps to Reproduce

  1. Create a campaign via REST API: POST /fluent-crm/v2/campaigns
  2. Set content: PUT /fluent-crm/v2/campaigns/{id}
  3. Set recipients: POST /fluent-crm/v2/campaigns/{id}/draft-recipients with tag filter — returns correct count
  4. Process recipients: POST /fluent-crm/v2/campaigns/{id}/subscribe with same filter — returns "Sorry, No subscribers found based on your filters"

Root Cause

In Campaign::subscribeBySegment() (Campaign.php):

$model = $this->getSubscribersModel($settings); // Returns Orm\Builder
$totalCount = $model->count(); // Works: returns correct count
// ...
$result = $this->subscribe($model, [], true); // Fails silently

In Campaign::subscribe():

if ($isModel) {
    $subscribers = $subscriberIds; // This is an Orm\Builder, not a Collection
}

foreach ($subscribers as $subscriber) { // Zero iterations!
    // ...
}

VerificationOrm\Builder is not iterable:

$model = $campaign->getSubscribersModel($settings);
var_dump($model instanceof \Traversable); // false
var_dump(is_iterable($model));            // false
var_dump(class_implements(get_class($model))); // [] (no interfaces)

// foreach gives 0 results:
foreach ($model as $item) { /* never executes */ }

// But ->get() works:
$model->get()->count(); // Returns correct count

Suggested Fix

In Campaign::subscribe(), call ->get() when $isModel is true and the value is a Builder:

if ($isModel) {
    $subscribers = $subscriberIds;
    if ($subscribers instanceof \FluentCrm\Framework\Database\Orm\Builder) {
        $subscribers = $subscribers->get();
    }
}

Or alternatively, in subscribeBySegment():

// Before:
$result = $this->subscribe($model, [], true);

// After:
$result = $this->subscribe($model->get(), [], true);

Impact

  • The POST /campaigns/{id}/subscribe REST endpoint is completely non-functional
  • The admin UI is not affected because it uses the schedule endpoint which processes recipients via a background AJAX handler (fluentcrm-post-campaigns-emails-processing) instead of calling subscribe
  • Any third-party integration or API automation that calls the subscribe endpoint will silently fail with 0 recipients

Environment

  • FluentCRM (free) latest as of 2026-03-02
  • WordPress 6.x
  • PHP 8.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions