PHPackages                             remp/campaign-module - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. remp/campaign-module

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

remp/campaign-module
====================

REMP Campaign Laravel package

4.3.1(5mo ago)024MITPHPPHP ^8.2

Since Aug 16Pushed 5mo ago6 watchersCompare

[ Source](https://github.com/remp2020/campaign-module)[ Packagist](https://packagist.org/packages/remp/campaign-module)[ RSS](/packages/remp-campaign-module/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (34)Versions (20)Used By (0)

Campaign
========

[](#campaign)

Campaign admin allows you to create banner campaigns without knowledge of HTML/CSS/JS, A/B test the banners and in combination with REMP Beam (for providing statistics) display and evaluate performance of your campaigns.

### Integration with CMS/CRM

[](#integration-with-cmscrm)

#### Javascript snippet

[](#javascript-snippet)

Include following snippet into the page to process campaigns and display banners. Update `rempConfig` object as needed.

Note: To automatically track banner events to BEAM Tracker, add also `tracker` property to `rempConfig` object. See [BEAM README](../Beam/README.md) for details. The two snippets complement each other and can be combined into one big JS snippet including Campaign and Beam functionality.

```
(function(win, doc) {
    function mock(fn) {
        return function() {
            this._.push([fn, arguments])
        }
    }
    function load(url) {
        var script = doc.createElement("script");
        script.type = "text/javascript";
        script.async = true;
        script.src = url;
        doc.getElementsByTagName("head")[0].appendChild(script);
    }
    win.remplib = win.remplib || {};
    var mockFuncs = {
        "campaign": "init",
        "tracker": "init trackEvent trackPageview trackCommerce",
        "iota": "init"
    };

    Object.keys(mockFuncs).forEach(function (key) {
        if (!win.remplib[key]) {
            var fn, i, funcs = mockFuncs[key].split(" ");
            win.remplib[key] = {_: []};

            for (i = 0; i  Properties)
    // required if you're using REMP segments
    token: String,

    // optional, identification of logged user
    userId: String,

    // optional, flag whether user is currently subscribed to the displayed content
    userSubscribed: Boolean,

    // optional, this is by default generated by remplib.js library and you don't need to override it
    browserId: String,

    // optional, controls where cookies (RTM - REMP's UTM parameters of visit) are stored
    cookieDomain: ".remp.press",

    // optional, controls which type of storage should be used by default (`local_storage` or `cookie`)
    // default is `local_storage`
    storage: "local_storage",

    // optional, specifies how long to store specific keys in storage (in minutes)
    storageExpiration: {
        // default value (in minutes) for all storage keys if not overriden in `keys`
        "default": 15,
        // specific pairs of key name and life length in minutes
        "keys": {
            "browser_id": 1051200, // 2 years in minutes
            "campaigns": 1051200
        }
    },

    // required, Campaign specific options
    campaign: {
        // required, URL host of REMP Campaign
        url: "http://campaign.remp.press",

        // Additional params that will be appended links within displayed banner
        //
        // Key represents variable name, value should be defined as callback returning string response.
        // Following example will be appended as "&foo=bar&baz=XXX".
        // If the value is not function, remplib validation will throw an error and won't proceed further.
        bannerUrlParams:  {
            "foo": function() { return "bar" },
            "baz": function() { return "XXX" }
        },

        variables: {
            // variables replace template placeholders in banners,
            // e.g. {{ email }} -> foo@example.com
            //
            // the callback doesn't pass any parameters, it's required for convenience and just-in-time evaluation
            //
            // missing variable is translated to empty string
            email: {
                value: function() {
                    return "foo@example.com"
                }
            },
        },

        // Optional. Pageview attributes are used to provide to Campaign additional information about the pageview.
        // You can configure your campaigns to be displayed based on these attributes - see "Pageview attributes"
        // section when editing the campaign.
        //
        // The value can be:
        //   - string: Campaign is displayed when the string matches configured value for given attribute name.
        //   - array of strings: Campaign is displayed, when one of the provided strings matches the configured value.
        //
        // Any other kind of value is ignored and triggers JS console warning.
        //
        // All of provided attributes which are not configured in the campaign are ignored during processing.
        pageviewAttributes: {
            "attribute_1": "value_1",
            "attribute_2": ["value_1", "value_2"]
        }
    }

    // if you want to automatically track banner events to BEAM Tracker,
    // add also rempConfig.tracker property
    //
    // see REMP BEAM README.md

};
remplib.campaign.init(rempConfig);
```

##### Single-page applications

[](#single-page-applications)

If you use single-page application and need to reinitialize JS library after it's been loaded:

1. Update the `rempConfig` variable to reflect the navigation changes.
2. Call `remplib.campaign.init(rempConfig)` again to reinitialize the JS tracking state. All existing banners will hide and campaigns will be evaluated again.

#### Segment integration

[](#segment-integration)

To determine who to display a campaign to, Campaign is dependent on user segments - effectively lists of user/browser IDs which should see a banner. You can register as many segment providers as you want, the only condition is that the providers should work with the same user-base (one user ID has to always point to the) same user.

The implementation is required to implement [`App\Contracts\SegmentContract`](app/Contracts/SegmentContract.php)interface.

All registered implementations are hidden behind facade of `SegmentAggregator`. This facade is then used to display available segments in configuration listings and to evaluate actual members of segments during campaign runtime.

If you want to link the Campaign to your own system, these are the methods to implement:

- `provider(): string`: Uniquely identifies segment provider among other segment providers. This is internally required to namespace segment names in case of same segment name being used in multiple segment sources.

    ```
    return "my-provider";
    ```
- `list(): Collection`: Returns collection of all segments available for this provider. The structure of response is:

    ```
    return [
        [
            'name' => String, // user friendly label
            'provider' => String, // should be same as result of provider()
            'code' => String, // machine friendly name, slug
            'group' => [
                'id' => Integer, // ID of segment group
                'name' => String, // user friendly label of group
                'sorting' => Integer // sorting index; lower the number, sooner the group appears in the list
            ]
        ],
    ];
    ```
- `users(CampaignSegment $segment): Collection`: Returns list of user IDs belonging to the segment.

    - `$segment`: Instance of `CampaignSegment` holding the reference to *segment* used in a *campaign*.

    The response is than expected to be collection of integers/strings representing user IDs:

    ```
    return collect([
        String,
        String,
        // ...
    ])
    ```
- `checkUser(CampaignSegment $campaignSegment, string $userId): bool`: Checks whether given `userId` belongs to the provided `campaignSegment`. This can either be done against external API (if it's prepared for realtime usage) or against cached list of user IDs - it's internal to the implementation. If the implementation doesn't support user-based segments, return `false`.
- `checkBrowser(CampaignSegment $campaignSegment, string $browserId): bool`: Checks whether given `browserId`belongs to the provided `campaignSegment`. This can either be done against external API (if it's prepared for realtime usage) or against cached list of browser IDs - it's internal to the implementation. If the implementation doesn't support browser-based segments, return `false`.
- `cacheEnabled(): bool`: Flag whether the segments of this provider used in active campaigns should be cached by the application or not. If `true`, Campaign will every hour request `users()` for each active Campaign and stores the collection of user IDs to the application cache.
- `getProviderData()`: Provider can pass arbitrary data back to the user's browser. This can be leveraged to cache browser/user related information without affecting the backend system. This data can be then altered by JS in the frontend application (if needed). All provided data is passed back to the server on each `campaign/showtime`request - on each banner display attempt.

    *Note: We use this kind of caching to count number of trackable events directly in browser. Segment rules based on these events are then evaluated based on the data provided in cache instead of hitting database. This method dramatically improved performance of REMP Beam event database (Elasticsearch).*
- `setProviderData($providerData): void`: Complementary *providerData* method to store back the data provided by frontend application in incoming request.

When implemented, create a segment provider providing the instance of your implementation. The provider should set `\App\Contracts\SegmentAggregator::TAG` to the class so your implementation would get also registered to the `SegmentAggregator`.

#### Banner custom styles

[](#banner-custom-styles)

Banners use default system fonts. If you want to use your own fonts you can add styles for `.remp-banner .serif` and `.remp-banner .sans-serif` to custom css field, or directly to web page using campaign showtime functionality.

Example:

```
.remp-banner .serif {
    font-family: 'Lumin Serif', serif;
}

.remp-banner .sans-serif {
    font-family: 'Lumin Sans', sans-serif;
}
```

### Settings and features

[](#settings-and-features)

##### Campaign position collision resolving (experimental)

[](#campaign-position-collision-resolving-experimental)

By default, Campaign displays all active and matched banners to the positions they're intended to be displayed at. If you want to ensure that there's always only one banner displayed at a single position, you can enable this feature with ENV variable:

```
PRIORITIZE_BANNERS_ON_SAME_POSITION=1

```

When this feature is enabled and the system resolves multiple banners at the same position, the rules are:

- Display the banner which is part of the campaign with the most variants.
- If there are more of them, display the banner with the most recently updated campaign.

##### Collapsible banner state storing

[](#collapsible-banner-state-storing)

Collapsible banner stores its collapse state and displays in the same state on the next display (expanded, collapsed). If user collapses campaign banner then it displays collapsed on the next display and vice versa.

In collapsible banner settings there is a toggle to override this behaviour and display banner always in initial state.

##### Snippets

[](#snippets)

Campaign supports use of snippets in your banner template contents, custom javascript and custom css. You can create snippets using `Add new snippet` in the `Snippets` main menu section.

You can use created snippets by adding `{{ snippet_name }}` to one of the fields used in banner content or in custom javascript &amp; css.

##### Campaign Debugger

[](#campaign-debugger)

To be able to debug live campaigns and troubleshoot problems, Campaign provides Campaign Debugger tool.

Set `CAMPAIGN_DEBUG_KEY` in Campaign `.env` file. It can be any (preferrably random) value - you will be asked for the key later, when working with debugger.

To start debugger, go to page that displays campaign(s) you want to debug and append `#campaignDebug` to the URL. In the debugger window, fill in required parameters `Debug key` and `Campaign public ID` and submit. After page reload, debugger should provide you with a reason why the particular campaign was shown/not shown.

### API Documentation

[](#api-documentation)

Campaign provides a simple API for several tasks described bellow.

All examples use `http://campaign.remp.press` as a base domain. Please change the host to the one you use before executing the examples.

All examples use `XXX` as a default value for authorization token, please replace it with the real token API token that can be acquired in the REMP SSO.

All requests should contain (and be compliant) with the follow HTTP headers.

```
Content-Type: application/json
Accept: application/json
Authorization: Bearer REMP_SSO_API_TOKEN

```

API responses can contain following HTTP codes:

ValueDescription200 OKSuccessful response, default value202 AcceptedSuccessful response, accepted for processing400 Bad RequestInvalid request (missing required parameters)403 ForbiddenThe authorization failed (provided token was not valid)404 Not foundReferenced resource wasn't foundIf possible, the response includes `application/json` encoded payload with message explaining the error further.

---

##### POST `/api/banners/BANNER_ID/one-time-display`

[](#post-apibannersbanner_idone-time-display)

Campaign supports one-time display of banners to specific users. Such banner does not need associated campaign to be displayed. This can be used e.g. as a way of reminding users about required actions (instead of notifying them via email).

When calling this endpoint, please replace `BANNER_ID` in URL with an actual ID of the banner. Each one-time banner has to specify its expiration date and a user it shows to (targeted either via user ID or via browser ID).

##### *Body:*

[](#body)

```
{
  "user_id": "29953", // String; Required (if browser_id is not present); ID of targeted user
  "browser_id": "aaaaaa-bbbb-cccc-dddd-1111111", // String; Required (if user_id is not present); ID of targeted browser
  "expires_at": "2019-12-29T10:05:00" // Date; Required; RFC3339 formatted datetime, specifying when the one-time banner expires (it won't show after the date)
}
```

##### *Examples*:

[](#examples)

curl```
curl -X POST \
  http://campaign.remp.press/api/banners/[BANNER_ID]/one-time-display \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/json' \
  -d '{
        "user_id": "29953",
        "expires_at": "2019-12-29T10:05:00"
}'
```

raw PHP```
$payload = [
    "user_id" => "29953",
    "expires_at" => "2019-12-29T10:05:00"
];
$jsonPayload = json_encode($payload);
$context = stream_context_create([
        'http' => [
            'method' => 'POST',
            'header' => "Content-Type: type=application/json\r\n"
                . "Accept: application/json\r\n"
                . "Content-Length: " . strlen($jsonPayload) . "\r\n"
                . "Authorization: Bearer XXX",
            'content' => $jsonPayload,
        ]
    ]
);
$bannerId = 1;
$response = file_get_contents("http://campaign.remp.press/api/banners/{$bannerId}/one-time-display", false, $context);
// process response (raw JSON string)
```

##### *Response:*

[](#response)

Valid response with 202 HTTP code:

```
{
  "status": "ok"
}
```

##### Override user's presence in segment's cache

[](#override-users-presence-in-segments-cache)

- Add user: `POST /api/segment-cache/provider/{segment_provider}/code/{segment_code}/add-user`
- Remove user: `POST /api/segment-cache/provider/{segment_provider}/code/{segment_code}/remove-user`

If segment provider supports it, Campaign will cache members of segment for faster access. If segment is dynamic *(changed often)*, it can lead to incorrect campaign targeting *(old data)*.

> Note: Interval for cache invalidation is set to one hour *(check `App\Console\Kernel->schedule()`)*.

These two API endpoints allow overriding membership of one user in cached segment from outside of Campaign *(eg. from CRM)*.

Both endpoints return response `404 Not found` if segment is not actively used by active or scheduled campaign.

##### *API path parameters:*

[](#api-path-parameters)

ParameterTypeRequiredDescription`segment_provider`stringyesSegment's provider returned by `SegmentContract->provider()`.
 *Eg. out of box contracts: `crm_segment/remp_segment`.*`segment_code`stringyesCode which identifies segment *(stored in `campaign_segments.code`)*.##### *Body:*

[](#body-1)

```
{
  "user_id": "29953", // String; Required; ID of targeted user
}
```

##### *Examples*:

[](#examples-1)

curlPath parameters:

- segment\_provider - `crm_segment`
- segment\_code - `example_testing_segment`

```
curl -X POST \
  http://campaign.remp.press/api/segment-cache/provider/crm_segment/code/example_testing_segment/add-user \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer XXX' \
  -H 'Content-Type: application/json' \
  -d '{
        "user_id": "29953"
}'
```

raw PHP```
$payload = [
    "user_id" => "29953",
];
$jsonPayload = json_encode($payload);
$context = stream_context_create([
        'http' => [
            'method' => 'POST',
            'header' => "Content-Type: type=application/json\r\n"
                . "Accept: application/json\r\n"
                . "Content-Length: " . strlen($jsonPayload) . "\r\n"
                . "Authorization: Bearer XXX",
            'content' => $jsonPayload,
        ]
    ]
);
$segmentProvider = "crm_segment";
$segmentCode = "example_testing_segment";
$response = file_get_contents("http://campaign.remp.press/api/segment-cache/provider/{$segmentProvider}/code/{$segmentCode}/add-user", false, $context);
// process response (raw JSON string)
```

##### *Response:*

[](#response-1)

Valid response with 202 HTTP code:

```
{
  "status": "ok"
}
```

Response with 404 HTTP code:

```
{
  "status": "error",
  "message": "Segment with code [example_testing_segment] from provider [crm_segment] is not actively used in the campaign."
}
```

### Newsletter Banner

[](#newsletter-banner)

This banner type allows you to directly collect newsletter subscribers.

In most cases you are going to need your own API proxy, since most newsletter APIs (including REMP CRM) make use of private tokens that should not be exposed to client. In case your newsletter API does not require such, you might not need a proxy.

##### Setup

[](#setup)

All configuration options are set via `.env`, refer to [`.env.example`](.env.example) for full list of available options.

##### API requirements

[](#api-requirements)

Newsletter API is required to respond with proper HTTP response code. On both success and failure, there is and custom event emitted. See next section for details.

##### Custom Events

[](#custom-events)

Custom events `rempNewsletterSubscribeFailure` or `rempNewsletterSubscribeSuccess` are available if requests are configured with XHR. Events are fired on `form` element. Event contains following items in `detail` prop:

fieldtypedescription`type`*String*`response` - if there is a valid XHR response
`exception` - for general error, e.g. connection error`response`*Object*`fetch` response object, applicable for `response` type`message`*String*error message, applicable for `exception` type##### Error handling example

[](#error-handling-example)

```
window.addEventListener("rempNewsletterSubscribeFailure", function(event){
    if (event.detail && event.detail.type === 'response'){
        const response = event.detail.response;
        console.warn('HTTP Status Code:', response.status);

        // to retrieve body use `json()` or `text()` depending on your API implemetation
        response.json().then(function(data) {
            console.warn(data);
        });
    }

    if (event.detail && event.detail.type === 'exception') {
        console.warn(event.detail.message);
    }
});
```

### Healthcheck

[](#healthcheck)

Route `http://campaign.remp.press/health` provides health check for database, Redis, storage and logging.

Returns:

- **200 OK** and JSON with list of services *(with status "OK")*.
- **500 Internal Server Error** and JSON with description of problem. E.g.:

    ```
    {
      "status":"PROBLEM",
      "log":{
        "status":"PROBLEM",
        "message":"Could not write to log file",
        "context": // error or thrown exception...
      //...
    }

    ```

### MaxMind - GeoIP2 Lite

[](#maxmind---geoip2-lite)

This product includes GeoLite2 data created by MaxMind, available from . Due to the licence changes, you need to update the database manually. If you do, provide path to the updated database via `MAXMIND_DATABASE` environment variable.

### RTM tracking

[](#rtm-tracking)

Following params are added to every request or link within banners. They serve for campaign performance tracking.

- rtm\_source: `"remp_campaign"` =&gt; always same string
- rtm\_medium: `displayType` =&gt; overlay or inline
- rtm\_campaign: `campaignUuid` =&gt; campaign id
- rtm\_content: `uuid` =&gt; banner id
- banner\_variant: `variantUuid` =&gt; banner variant id (in campaign)

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance72

Regular maintenance activity

Popularity6

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 57.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~26 days

Recently: every ~12 days

Total

19

Last Release

159d ago

Major Versions

3.11.0 → 4.0.02025-04-29

PHP version history (2 changes)3.9.0PHP ^8.1

4.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c733f9bd683c3814197d8a532b7da1ba1f631bb1efe1cde5f064feab1e24877?d=identicon)[rootpd](/maintainers/rootpd)

---

Top Contributors

[![rootpd](https://avatars.githubusercontent.com/u/812909?v=4)](https://github.com/rootpd "rootpd (32 commits)")[![lubos-michalik](https://avatars.githubusercontent.com/u/63700066?v=4)](https://github.com/lubos-michalik "lubos-michalik (8 commits)")[![zoldia](https://avatars.githubusercontent.com/u/1526070?v=4)](https://github.com/zoldia "zoldia (6 commits)")[![markoph](https://avatars.githubusercontent.com/u/6843562?v=4)](https://github.com/markoph "markoph (4 commits)")[![miroc](https://avatars.githubusercontent.com/u/1230714?v=4)](https://github.com/miroc "miroc (2 commits)")[![burithetech](https://avatars.githubusercontent.com/u/3502143?v=4)](https://github.com/burithetech "burithetech (2 commits)")[![davidkvasnovsky](https://avatars.githubusercontent.com/u/12381721?v=4)](https://github.com/davidkvasnovsky "davidkvasnovsky (2 commits)")

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/remp-campaign-module/health.svg)

```
[![Health](https://phpackages.com/badges/remp-campaign-module/health.svg)](https://phpackages.com/packages/remp-campaign-module)
```

###  Alternatives

[grumpydictator/firefly-iii

Firefly III: a personal finances manager.

22.8k69.3k](/packages/grumpydictator-firefly-iii)[blair2004/nexopos

The Free Modern Point Of Sale System build with Laravel, TailwindCSS and Vue.js.

1.2k2.3k](/packages/blair2004-nexopos)[shlinkio/shlink

A self-hosted and PHP-based URL shortener application with CLI and REST interfaces

4.8k4.3k](/packages/shlinkio-shlink)[ronasit/laravel-helpers

Provided helpers function and some helper class.

1475.7k13](/packages/ronasit-laravel-helpers)[concrete5/core

Concrete core subtree split

19159.3k48](/packages/concrete5-core)[eveseat/web

SeAT Web Interface

2723.2k135](/packages/eveseat-web)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
