├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── mkdocs.yml
├── phpspec.yml
├── phpunit.xml.dist
├── spec
├── Controller
│ └── WebhookControllerSpec.php
├── DependencyInjection
│ └── ConfigurationSpec.php
├── Event
│ ├── SubscriberEventSpec.php
│ ├── SubscriberListenerSpec.php
│ └── WebhookEventSpec.php
└── Subscriber
│ ├── ListRepositorySpec.php
│ ├── ListSynchronizerSpec.php
│ ├── SubscriberListSpec.php
│ └── SubscriberSpec.php
├── src
├── Command
│ ├── SynchronizeMergeFieldsCommand.php
│ ├── SynchronizeSubscribersCommand.php
│ └── WebhookCommand.php
├── Controller
│ └── WebhookController.php
├── DependencyInjection
│ ├── Configuration.php
│ └── WelpMailchimpExtension.php
├── Event
│ ├── SubscriberEvent.php
│ ├── SubscriberListener.php
│ └── WebhookEvent.php
├── Exception
│ └── MailchimpException.php
├── Provider
│ ├── ConfigListProvider.php
│ ├── DynamicProviderInterface.php
│ ├── ExampleSubscriberProvider.php
│ ├── FosSubscriberProvider.php
│ ├── ListProviderInterface.php
│ ├── ProviderFactory.php
│ └── ProviderInterface.php
├── Resources
│ ├── config
│ │ ├── routing.yml
│ │ └── services.yml
│ └── doc
│ │ ├── CONTRIBUTING.md
│ │ ├── configuration.md
│ │ ├── documentation.md
│ │ ├── index.md
│ │ ├── list-provider.md
│ │ ├── mailchimp-doc.md
│ │ ├── setup.md
│ │ ├── subscriber-language.md
│ │ ├── subscriber-provider.md
│ │ ├── tests.md
│ │ ├── usage.md
│ │ └── webhook.md
├── Subscriber
│ ├── ListRepository.php
│ ├── ListSynchronizer.php
│ ├── Subscriber.php
│ ├── SubscriberList.php
│ └── SubscriberListInterface.php
└── WelpMailchimpBundle.php
└── tests
├── Command
└── SynchronizeSubscribersCommandTest.php
├── Provider
└── configListProviderTest.php
└── Subscriber
├── ListRepositoryTest.php
├── ListSynchronizerTest.php
└── SubscriberTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | vendor
3 | docs
4 | composer.lock
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - '5.6'
4 | - '7.1'
5 |
6 | before_script:
7 | - php -d memory_limit=-1 composer install --dev -v --prefer-source
8 |
9 | script:
10 | - bin/phpspec run -fpretty --verbose
11 | #- bin/phpunit -c .
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | * [Contributing to Open Source guide](https://guides.github.com/activities/contributing-to-open-source/)
4 |
5 | ## Steps
6 |
7 | 1. Create an Issue
8 | 2. Pull Request
9 | 3. Merge \o/
10 |
11 | ## ToDo
12 |
13 | ☐ Moar unit tests!!
14 | ☐ Fix workaround to update User's email address (waiting for MailChimp API V3 update)
15 | ☐ Commands options for full sync, merge fields sync, webhook register (listId, ...)
16 | ☐ Spec documentation (http://phpdox.de/)
17 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Welpdev
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mailchimp-bundle
2 |
3 | [](https://travis-ci.org/welpdev/mailchimp-bundle)
4 | [](https://packagist.org/packages/welp/mailchimp-bundle)
5 | [](https://packagist.org/packages/welp/mailchimp-bundle)
6 | [](https://raw.githubusercontent.com/welpdev/mailchimp-bundle/master/LICENSE.md)
7 | [](https://welpdev.github.io/mailchimp-bundle/)
8 |
9 | This bundle will help you synchronise your project's newsletter subscribers into MailChimp throught MailChimp API V3.
10 |
11 | ## Features
12 |
13 | * [x] Use your own userProvider (basic `FosSubscriberProvider` included to interface with FosUserBundle)
14 | * [x] Use your own listProvider (`DoctrineListProvider` included to retrieve your list from a database)
15 | * [x] Synchronize Merge Fields with your config
16 | * [x] Synchronize your subscriber with a List
17 | * [x] Use lifecycle event to subscribe/unsubscribe/delete subscriber from a List
18 | * [x] Retrieve [MailChimp Object](https://github.com/drewm/mailchimp-api) to make custom MailChimp API V3 requests
19 | * [x] Register Webhooks
20 |
21 | ## Setup
22 |
23 | Add bundle to your project:
24 |
25 | ```bash
26 | composer require welp/mailchimp-bundle
27 | ```
28 |
29 | Add `Welp\MailchimpBundle\WelpMailchimpBundle` to your `AppKernel.php`:
30 |
31 | ```php
32 | $bundles = [
33 | // ...
34 | new Welp\MailchimpBundle\WelpMailchimpBundle(),
35 | ];
36 | ```
37 |
38 | ## Minimal Configuration
39 |
40 | In your `config.yml`:
41 |
42 | ```yaml
43 | welp_mailchimp:
44 | api_key: YOURMAILCHIMPAPIKEY
45 | ```
46 |
47 | More configuration on the [documentation](https://welpdev.github.io/mailchimp-bundle/configuration/).
48 |
49 | ## Full Documentation
50 |
51 | Look at the full documentation at
52 |
53 | * Setup
54 | * Configuration
55 | * Subscriber Provider
56 | * List Provider
57 | * Usage
58 | * Synchronize merge fields
59 | * Full synchronization with command
60 | * Unit synchronization with events
61 | * Subscribe new User
62 | * Unsubscribe a User
63 | * Update a User
64 | * Change User's email address (WORKAROUND)
65 | * Delete a User
66 | * Retrieve [MailChimp Object](https://github.com/drewm/mailchimp-api) to make custom MailChimp API V3 requests
67 | * Webhook
68 | * Update User when subscribe/unsubscribe
69 |
70 | ## Contributing
71 |
72 | If you want to contribute to this project, look at [over here](CONTRIBUTING.md)
73 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "welp/mailchimp-bundle",
3 | "description": "MailChimp API V3 Symfony Bundle",
4 | "type": "symfony-bundle",
5 | "homepage": "https://github.com/welpdev/mailchimp-bundle",
6 | "license": "MIT",
7 | "keywords": [
8 | "Symfony",
9 | "MailChimp",
10 | "API",
11 | "V3",
12 | "User",
13 | "Subscriber"
14 | ],
15 | "authors": [
16 | {
17 | "name": "Titouan BENOIT",
18 | "email": "titouan@welp.today"
19 | }
20 | ],
21 | "autoload": {
22 | "psr-4": {
23 | "Welp\\MailchimpBundle\\": "src/"
24 | }
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "Tests\\": "tests/"
29 | }
30 | },
31 | "require": {
32 | "php": ">=5.6",
33 | "drewm/mailchimp-api": "2.5.*"
34 | },
35 | "require-dev": {
36 | "phpspec/phpspec": "~2@dev",
37 | "phpunit/phpunit": "~5",
38 | "symfony/symfony": ">=2.7"
39 | },
40 | "config": {
41 | "bin-dir": "bin"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: MailChimp Bundle
2 | site_description: This bundle will help you synchronise your project's newsletter subscribers into MailChimp throught MailChimp API V3.
3 | site_author: Titouan BENOIT
4 | copyright: MIT
5 | repo_url: https://github.com/welpdev/mailchimp-bundle
6 | docs_dir: 'src/Resources/doc'
7 | site_dir: "docs"
8 | pages:
9 | - Home: index.md
10 | - Setup: setup.md
11 | - Configuration: configuration.md
12 | - Subscriber Provider: subscriber-provider.md
13 | - List Provider: list-provider.md
14 | - Usage: usage.md
15 | - Webhook: webhook.md
16 | - Tests: tests.md
17 | - Subscriber Languages: subscriber-language.md
18 | - MailChimp Doc: mailchimp-doc.md
19 | - Contributing: CONTRIBUTING.md
20 | theme: readthedocs
21 | markdown_extensions:
22 | - pymdownx.github
23 |
--------------------------------------------------------------------------------
/phpspec.yml:
--------------------------------------------------------------------------------
1 | suites:
2 | default:
3 | namespace: Welp\MailchimpBundle
4 | psr4_prefix: Welp\MailchimpBundle
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | ./tests/
9 |
10 |
11 |
12 |
13 | ./vendor
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/spec/Controller/WebhookControllerSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Welp\MailchimpBundle\Controller\WebhookController');
17 | $this->shouldHaveType('Symfony\Bundle\FrameworkBundle\Controller\Controller');
18 | }
19 |
20 | function it_access_denied_if_secret_is_false()
21 | {
22 | $request = new Request();
23 | $request->setMethod('POST');
24 |
25 | $this->shouldThrow(new \Exception('incorrect data format!'))
26 | ->duringIndexAction($request);
27 | }
28 |
29 | /*function it_pass_when_secret_is_true(){
30 | //$this->getParameter('welp_mailchimp.lists')->willReturn(['listid01' => ['webhook_secret' => 'mysecretTest']]);
31 |
32 | $request = new Request();
33 | $request->setMethod('POST');
34 | var_dump($request); die();
35 |
36 |
37 | }*/
38 | }
39 |
--------------------------------------------------------------------------------
/spec/DependencyInjection/ConfigurationSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Welp\MailchimpBundle\DependencyInjection\Configuration');
12 | }
13 |
14 | function it_is_symfony_configuration()
15 | {
16 | $this->shouldImplement('Symfony\Component\Config\Definition\ConfigurationInterface');
17 | }
18 |
19 | function it_gets_config_tree_builder()
20 | {
21 | $this
22 | ->getConfigTreeBuilder()
23 | ->shouldHaveType('Symfony\Component\Config\Definition\Builder\TreeBuilder');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spec/Event/SubscriberEventSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('1337', $subscriber);
14 | }
15 |
16 | function it_is_initializable()
17 | {
18 | $this->shouldHaveType('Welp\MailchimpBundle\Event\SubscriberEvent');
19 | $this->shouldHaveType('Symfony\Component\EventDispatcher\Event');
20 | }
21 |
22 | function it_has_a_listname()
23 | {
24 | $this->getListId()->shouldReturn('1337');
25 | }
26 |
27 | function it_has_a_subscriber($subscriber)
28 | {
29 | $this->getSubscriber()->shouldReturn($subscriber);
30 | }
31 |
32 | function it_has_old_email($subscriber){
33 | $this->beConstructedWith('1337', $subscriber, 'oldemail@free.fr');
34 | $this->getOldEmail('oldemail@free.fr');
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spec/Event/SubscriberListenerSpec.php:
--------------------------------------------------------------------------------
1 | findById('foo')->willReturn(['id' => 123]);
16 |
17 | $event->getListId()->willReturn('foo');
18 | $event->getSubscriber()->willReturn($subscriber);
19 |
20 | $this->beConstructedWith($listRepository);
21 | }
22 |
23 | function it_is_initializable()
24 | {
25 | $this->shouldHaveType('Welp\MailchimpBundle\Event\SubscriberListener');
26 | }
27 |
28 | function it_listen_to_subscribe_events($listRepository, $event, $subscriber)
29 | {
30 | $listRepository->subscribe('foo', $subscriber)->shouldBeCalled();
31 | $this->onSubscribe($event);
32 | }
33 |
34 | function it_listen_to_unsubscribe_events($listRepository, $event, $subscriber)
35 | {
36 | $listRepository->unsubscribe('foo', $subscriber)->shouldBeCalled();
37 | $this->onUnsubscribe($event);
38 | }
39 |
40 | function it_listen_to_pending_events($listRepository, $event, $subscriber)
41 | {
42 | $listRepository->pending('foo', $subscriber)->shouldBeCalled();
43 | $this->onPending($event);
44 | }
45 |
46 | function it_listen_to_clean_events($listRepository, $event, $subscriber)
47 | {
48 | $listRepository->clean('foo', $subscriber)->shouldBeCalled();
49 | $this->onClean($event);
50 | }
51 |
52 | function it_listen_to_delete_events($listRepository, $event, $subscriber)
53 | {
54 | $listRepository->delete('foo', $subscriber)->shouldBeCalled();
55 | $this->onDelete($event);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/spec/Event/WebhookEventSpec.php:
--------------------------------------------------------------------------------
1 | 0, 'data' => 154158])
11 | {
12 | $this->beConstructedWith($data);
13 | }
14 |
15 | function it_is_initializable()
16 | {
17 | $this->shouldHaveType('Welp\MailchimpBundle\Event\WebhookEvent');
18 | $this->shouldHaveType('Symfony\Component\EventDispatcher\Event');
19 | }
20 |
21 | function it_has_data($data)
22 | {
23 | $this->getData()->shouldReturn($data);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/spec/Subscriber/ListRepositorySpec.php:
--------------------------------------------------------------------------------
1 | prepareSubscriber($subscriber);
16 | $this->prepareMailchimpLists($mailchimp);
17 |
18 | $this->beConstructedWith($mailchimp);
19 | }
20 |
21 | public function it_is_initializable()
22 | {
23 | $this->shouldHaveType('Welp\MailchimpBundle\Subscriber\ListRepository');
24 | }
25 |
26 | public function it_can_get_the_mailchimp_object()
27 | {
28 | $this->getMailChimp()->shouldHaveType('\DrewM\MailChimp\MailChimp');
29 | }
30 |
31 | public function it_can_find_a_list_by_its_id(MailChimp $mailchimp)
32 | {
33 | $this->findById('ba039c6198')->shouldReturn(['id' => 'ba039c6198', 'name' => 'myList']);
34 | }
35 |
36 | public function it_can_not_find_a_list_by_its_id(MailChimp $mailchimp)
37 | {
38 | $mailchimp->success()->willReturn(false);
39 | $mailchimp->getLastResponse()->willReturn([
40 | "headers" => [],
41 | "body" => '{"type":"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/","title":"Invalid Resource","status":404,"detail":"The requested resource could not be found.","instance":""}'
42 | ]);
43 |
44 | $this->shouldThrow(new MailchimpException(404, 'The requested resource could not be found.', "http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/", "Invalid Resource", null, ''))
45 | ->duringFindById('notfound');
46 | }
47 |
48 | public function it_subscribe_a_subscriber(MailChimp $mailchimp, $subscriber)
49 | {
50 | $this->subscribe('ba039c6198', $subscriber)->shouldReturn([
51 | 'email_address' => 'charles@terrasse.fr',
52 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
53 | 'language' => 'fr',
54 | 'email_type' => 'html',
55 | 'status' => 'subscribed'
56 | ]);
57 | }
58 |
59 | public function it_unsubscribe_a_subscriber(MailChimp $mailchimp, $subscriber)
60 | {
61 | $this->unsubscribe('ba039c6198', $subscriber)->shouldReturn('unsubscribed');
62 | }
63 |
64 | public function it_pending_a_subscriber(MailChimp $mailchimp, $subscriber)
65 | {
66 | $this->pending('ba039c6198', $subscriber)->shouldReturn('pending');
67 | }
68 |
69 | public function it_clean_a_subscriber(MailChimp $mailchimp, $subscriber)
70 | {
71 | $this->clean('ba039c6198', $subscriber)->shouldReturn('cleaned');
72 | }
73 |
74 | public function it_delete_a_subscriber(MailChimp $mailchimp, $subscriber)
75 | {
76 | $mailchimp->delete("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691")->willReturn('deleted');
77 |
78 | $this->delete('ba039c6198', $subscriber)->shouldReturn('deleted');
79 | }
80 |
81 | public function it_update_a_subscriber(MailChimp $mailchimp, $subscriber)
82 | {
83 | $mailchimp->patch("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691", ["email_address" => "charles@terrasse.fr", "merge_fields" => ["FNAME" => "Charles", "LNAME" => "Terrasse"], "language" => "fr", "email_type" => "html"])->willReturn('update');
84 |
85 | $this->update('ba039c6198', $subscriber)->shouldReturn('update');
86 | }
87 |
88 | public function it_finds_merge_tags(MailChimp $mailchimp)
89 | {
90 | $mailchimp
91 | ->get("lists/123/merge-fields")
92 | ->willReturn(
93 | [
94 | 'merge_fields' => [
95 | ['tag' => 'EMAIL', 'name' => 'email'],
96 | ['tag' => 'FOO', 'name' => 'Bar'],
97 | ],
98 | 'total_items' => 2
99 | ]);
100 |
101 | $this->getMergeFields(123)->shouldReturn([['tag' => 'EMAIL', 'name' => 'email'], ['tag' => 'FOO', 'name' => 'Bar']]);
102 | }
103 |
104 | public function it_finds_merge_tags_more_than_10(MailChimp $mailchimp)
105 | {
106 | $mailchimp
107 | ->get("lists/123/merge-fields")
108 | ->willReturn(
109 | [
110 | 'merge_fields' => [
111 | ['tag' => 'FOO1', 'name' => 'Bar'],
112 | ['tag' => 'FOO2', 'name' => 'Bar'],
113 | ['tag' => 'FOO3', 'name' => 'Bar'],
114 | ['tag' => 'FOO4', 'name' => 'Bar'],
115 | ['tag' => 'FOO5', 'name' => 'Bar'],
116 | ['tag' => 'FOO6', 'name' => 'Bar'],
117 | ['tag' => 'FOO8', 'name' => 'Bar'],
118 | ['tag' => 'FOO9', 'name' => 'Bar'],
119 | ['tag' => 'FOO10', 'name' => 'Bar'],
120 | ],
121 | 'total_items' => 13
122 | ]);
123 |
124 | $mailchimp
125 | ->get("lists/123/merge-fields", array("count" => 13))
126 | ->willReturn(
127 | [
128 | 'merge_fields' => [
129 | ['tag' => 'FOO1', 'name' => 'Bar'],
130 | ['tag' => 'FOO2', 'name' => 'Bar'],
131 | ['tag' => 'FOO3', 'name' => 'Bar'],
132 | ['tag' => 'FOO4', 'name' => 'Bar'],
133 | ['tag' => 'FOO5', 'name' => 'Bar'],
134 | ['tag' => 'FOO6', 'name' => 'Bar'],
135 | ['tag' => 'FOO8', 'name' => 'Bar'],
136 | ['tag' => 'FOO9', 'name' => 'Bar'],
137 | ['tag' => 'FOO10', 'name' => 'Bar'],
138 | ['tag' => 'FOO12', 'name' => 'Bar'],
139 | ['tag' => 'FOO13', 'name' => 'Bar'],
140 | ],
141 | 'total_items' => 13
142 | ]);
143 |
144 | $this->getMergeFields(123)->shouldReturn([
145 | ['tag' => 'FOO1', 'name' => 'Bar'],
146 | ['tag' => 'FOO2', 'name' => 'Bar'],
147 | ['tag' => 'FOO3', 'name' => 'Bar'],
148 | ['tag' => 'FOO4', 'name' => 'Bar'],
149 | ['tag' => 'FOO5', 'name' => 'Bar'],
150 | ['tag' => 'FOO6', 'name' => 'Bar'],
151 | ['tag' => 'FOO8', 'name' => 'Bar'],
152 | ['tag' => 'FOO9', 'name' => 'Bar'],
153 | ['tag' => 'FOO10', 'name' => 'Bar'],
154 | ['tag' => 'FOO12', 'name' => 'Bar'],
155 | ['tag' => 'FOO13', 'name' => 'Bar'],
156 | ]);
157 | }
158 |
159 | public function it_deletes_a_merge_tag(MailChimp $mailchimp)
160 | {
161 | $mailchimp->delete("lists/123/merge-fields/foo")->shouldBeCalled();
162 |
163 | $this->deleteMergeField(123, 'foo');
164 | }
165 |
166 | public function it_adds_a_merge_tag(MailChimp $mailchimp)
167 | {
168 | $mailchimp->post("lists/123/merge-fields", $mergeData = [
169 | 'tag' => 'FOO',
170 | 'name' => 'Foo bar',
171 | 'options' => ['req' => true]
172 | ])->shouldBeCalled();
173 |
174 | $this->addMergeField(123, $mergeData);
175 | }
176 |
177 | public function it_updates_a_merge_tag(MailChimp $mailchimp)
178 | {
179 | $mailchimp->patch("lists/123/merge-fields/2", $mergeData = [
180 | 'tag' => 'FOO',
181 | 'name' => 'Foo bar',
182 | 'options' => ['req' => true]
183 | ])->shouldBeCalled();
184 |
185 | $this->updateMergeField(123, 2, $mergeData);
186 | }
187 |
188 | protected function prepareSubscriber(Subscriber $subscriber)
189 | {
190 | $subscriber->getEmail()->willReturn('charles@terrasse.fr');
191 | $subscriber->getMergeFields()->willReturn(['FNAME' => 'Charles', 'LNAME' => 'Terrasse']);
192 | $subscriber->getOptions()->willReturn(['language' => 'fr', 'email_type' => 'html']);
193 | $subscriber->formatMailChimp()->willReturn(['email_address' => 'charles@terrasse.fr', 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'], 'language' => 'fr', 'email_type' => 'html']);
194 | }
195 |
196 | protected function prepareMailchimpLists(MailChimp $mailchimp)
197 | {
198 | // success
199 | $mailchimp->success()->willReturn(true);
200 | // get the list
201 | $mailchimp->get("lists/ba039c6198")->willReturn(['id' => 'ba039c6198', 'name' => 'myList']);
202 | $mailchimp->get("lists/notfound")->willReturn(null);
203 | // subscribe member
204 | $mailchimp->put("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691", [
205 | 'email_address' => 'charles@terrasse.fr',
206 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
207 | 'language' => 'fr',
208 | 'email_type' => 'html',
209 | 'status' => 'subscribed'
210 | ])->willReturn([
211 | 'email_address' => 'charles@terrasse.fr',
212 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
213 | 'language' => 'fr',
214 | 'email_type' => 'html',
215 | 'status' => 'subscribed'
216 | ]);
217 |
218 | $mailchimp->put("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691", [
219 | 'email_address' => 'charles@terrasse.fr',
220 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
221 | 'language' => 'fr',
222 | 'email_type' => 'html',
223 | 'status' => 'unsubscribed'
224 | ])->willReturn('unsubscribed');
225 |
226 | $mailchimp->put("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691", [
227 | 'email_address' => 'charles@terrasse.fr',
228 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
229 | 'language' => 'fr',
230 | 'email_type' => 'html',
231 | 'status' => 'pending'
232 | ])->willReturn('pending');
233 |
234 | $mailchimp->put("lists/ba039c6198/members/b1a29fd58778c40c7f15f06a334dc691", [
235 | 'email_address' => 'charles@terrasse.fr',
236 | 'merge_fields' => ['FNAME' => 'Charles', 'LNAME' => 'Terrasse'],
237 | 'language' => 'fr',
238 | 'email_type' => 'html',
239 | 'status' => 'cleaned'
240 | ])->willReturn('cleaned');
241 | }
242 |
243 | protected function getSubscriberChunk($count, $offset)
244 | {
245 | $subscribers = [];
246 | for ($i = $offset; $i < $offset + $count; $i++) {
247 | $subscribers[] = new Subscriber(sprintf('email%s@example.org', $i));
248 | }
249 |
250 | return $subscribers;
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/spec/Subscriber/ListSynchronizerSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($listRepository);
15 | }
16 |
17 | function it_is_initializable()
18 | {
19 | $this->shouldHaveType('Welp\MailchimpBundle\Subscriber\ListSynchronizer');
20 | }
21 |
22 | function it_synchronize_merge_tags($listRepository, SubscriberList $list)
23 | {
24 | $listRepository->findById('foobar')->willReturn(['id' => 'foobar']);
25 | $listRepository->getMergeFields('foobar')->willReturn([
26 | ['merge_id' => 1, 'tag' => 'TAG1', 'name' => 'Tag 1'],
27 | ['merge_id' => 2, 'tag' => 'OBSOLETE', 'name' => 'This tag should not exist'],
28 | ]);
29 |
30 | $listRepository->deleteMergeField('foobar', 2)->shouldBeCalled();
31 | $listRepository->updateMergeField('foobar', 1, ['tag' => 'TAG1', 'name' => 'Tag 1', 'options' => ['req' => true]])->shouldBeCalled();
32 | $listRepository->addMergeField('foobar', ['tag' => 'TAG2', 'name' => 'Tag 2', 'options' => ['req' => false]])->shouldBeCalled();
33 |
34 | $this->synchronizeMergeFields('foobar', [
35 | ['tag' => 'TAG1', 'name' => 'Tag 1', 'options' => ['req' => true]],
36 | ['tag' => 'TAG2', 'name' => 'Tag 2', 'options' => ['req' => false]],
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/spec/Subscriber/SubscriberListSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('1337', $provider);
14 | }
15 |
16 | function it_is_initializable()
17 | {
18 | $this->shouldHaveType('Welp\MailchimpBundle\Subscriber\SubscriberList');
19 | }
20 |
21 | function it_has_a_name()
22 | {
23 | $this->getListId()->shouldReturn('1337');
24 | }
25 |
26 | function it_has_a_provider($provider)
27 | {
28 | $this->getProvider()->shouldReturn($provider);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/spec/Subscriber/SubscriberSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('charles@terrasse.fr');
13 | }
14 |
15 | public function it_is_initializable()
16 | {
17 | $this->shouldHaveType('Welp\MailchimpBundle\Subscriber\Subscriber');
18 | }
19 |
20 | public function it_has_an_email()
21 | {
22 | $this->getEmail()->shouldReturn('charles@terrasse.fr');
23 | }
24 |
25 | public function it_has_default_merge_fields()
26 | {
27 | $this->getMergeFields()->shouldReturn([]);
28 | }
29 |
30 | public function it_can_have_merge_fields()
31 | {
32 | $this->beConstructedWith('charles@terrasse.fr', $tags = ['FIRSTNAME' => 'Charles']);
33 | $this->getMergeFields()->shouldReturn($tags);
34 | }
35 |
36 | public function it_can_have_merge_fields_with_null_value()
37 | {
38 | $this->beConstructedWith('charles@terrasse.fr', $tags = ['FIRSTNAME' => 'Charles', 'BIRTHDATE' => null]);
39 | $this->getMergeFields()->shouldReturn(['FIRSTNAME' => 'Charles']);
40 | }
41 |
42 | public function it_can_have_options()
43 | {
44 | $this->beConstructedWith('charles@terrasse.fr', $tags = ['FIRSTNAME' => 'Charles'], $options = ['language' => "fr"]);
45 | $this->getOptions()->shouldReturn($options);
46 | }
47 |
48 | public function it_format_for_mailchimp()
49 | {
50 | $this->beConstructedWith('charles@terrasse.fr', $tags = ['FIRSTNAME' => 'Charles'], $options = ['language' => "fr"]);
51 | $this->formatMailChimp()->shouldReturn([
52 | 'email_address' => 'charles@terrasse.fr',
53 | 'merge_fields' => $tags,
54 | 'language' => "fr"
55 | ]);
56 | }
57 |
58 | public function it_format_for_mailchimp_without_mergetags()
59 | {
60 | $this->beConstructedWith('charles@terrasse.fr', [], $options = ['language' => "fr"]);
61 | $this->formatMailChimp()->shouldReturn([
62 | 'email_address' => 'charles@terrasse.fr',
63 | 'language' => "fr"
64 | ]);
65 | }
66 |
67 | public function it_format_for_mailchimp_without_options()
68 | {
69 | $this->beConstructedWith('charles@terrasse.fr', [], []);
70 | $this->formatMailChimp()->shouldReturn([
71 | 'email_address' => 'charles@terrasse.fr'
72 | ]);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Command/SynchronizeMergeFieldsCommand.php:
--------------------------------------------------------------------------------
1 | listSynchronizer = $listSynchronizer;
30 | $this->listProvider = $listProvider;
31 |
32 | parent::__construct();
33 | }
34 |
35 | protected function configure()
36 | {
37 | $this
38 | ->setDescription('Synchronizing merge fields in MailChimp')
39 | ->setName('welp:mailchimp:synchronize-merge-fields')
40 | // @TODO add params : listId
41 | ;
42 | }
43 |
44 | protected function execute(InputInterface $input, OutputInterface $output)
45 | {
46 | $output->writeln(sprintf('%s', $this->getDescription()));
47 |
48 | $lists = $this->listProvider->getLists();
49 |
50 | foreach ($lists as $list) {
51 | $this->listSynchronizer->synchronizeMergeFields($list->getListId(), $list->getMergeFields());
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Command/SynchronizeSubscribersCommand.php:
--------------------------------------------------------------------------------
1 | listSynchronizer = $listSynchronizer;
39 | $this->listProvider = $listProvider;
40 | $this->mailchimp = $mailchimp;
41 |
42 | parent::__construct();
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | protected function configure()
49 | {
50 | $this
51 | ->setDescription('Synchronizing subscribers in MailChimp')
52 | ->setName('welp:mailchimp:synchronize-subscribers')
53 | ->addOption(
54 | 'follow-sync',
55 | null,
56 | InputOption::VALUE_NONE,
57 | 'If you want to follow batches execution'
58 | )
59 | // @TODO add params : listId, providerServiceKey
60 | ;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | protected function execute(InputInterface $input, OutputInterface $output)
67 | {
68 | $output->writeln(sprintf('%s', $this->getDescription()));
69 |
70 | $lists = $this->listProvider->getLists();
71 |
72 | foreach ($lists as $list) {
73 | $output->writeln(sprintf('Synchronize list %s', $list->getListId()));
74 | $batchesResult = $this->listSynchronizer->synchronize($list);
75 | if ($input->getOption('follow-sync')) {
76 | while (!$this->batchesFinished($batchesResult)) {
77 | $batchesResult = $this->refreshBatchesResult($batchesResult);
78 | foreach ($batchesResult as $key => $batch) {
79 | $output->writeln($this->displayBatchInfo($batch));
80 | }
81 | sleep(2);
82 | }
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * Refresh all batch from MailChimp API.
89 | *
90 | * @param array $batchesResult
91 | *
92 | * @return array
93 | */
94 | private function refreshBatchesResult($batchesResult)
95 | {
96 | $refreshedBatchsResults = [];
97 |
98 | foreach ($batchesResult as $key => $batch) {
99 | $batch = $this->mailchimp->get('batches/'.$batch['id']);
100 | array_push($refreshedBatchsResults, $batch);
101 | }
102 |
103 | return $refreshedBatchsResults;
104 | }
105 |
106 | /**
107 | * Test if all batches are finished.
108 | *
109 | * @param array $batchesResult
110 | *
111 | * @return bool
112 | */
113 | private function batchesFinished($batchesResult)
114 | {
115 | $allfinished = true;
116 | foreach ($batchesResult as $key => $batch) {
117 | if ('finished' != $batch['status']) {
118 | $allfinished = false;
119 | }
120 | }
121 |
122 | return $allfinished;
123 | }
124 |
125 | /**
126 | * Pretty display of batch info.
127 | *
128 | * @param array $batch
129 | *
130 | * @return string
131 | */
132 | private function displayBatchInfo($batch)
133 | {
134 | if ('finished' == $batch['status']) {
135 | return sprintf('batch %s is finished, operations %d/%d with %d errors. http responses: %s', $batch['id'], $batch['finished_operations'], $batch['total_operations'], $batch['errored_operations'], $batch['response_body_url']);
136 | }
137 |
138 | return sprintf('batch %s, current status %s, operations %d/%d with %d errors', $batch['id'], $batch['status'], $batch['finished_operations'], $batch['total_operations'], $batch['errored_operations']);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/Command/WebhookCommand.php:
--------------------------------------------------------------------------------
1 | listProvider = $listProvider;
30 | $this->listRepository = $listRepository;
31 |
32 | parent::__construct();
33 | }
34 |
35 | protected function configure()
36 | {
37 | $this
38 | ->setDescription('Add main webhook to a MailChimp List')
39 | ->setName('welp:mailchimp:webhook')
40 | ;
41 | // @TODO add params : listId, webhookurl
42 | }
43 |
44 | protected function execute(InputInterface $input, OutputInterface $output)
45 | {
46 | $output->writeln(sprintf('%s', $this->getDescription()));
47 |
48 | $lists = $this->listProvider->getLists();
49 |
50 | foreach ($lists as $list) {
51 | $url = $list->getWebhookUrl().'?hooksecret='.$list->getWebhookSecret();
52 | $output->writeln('Add webhook to list: '.$list->getListId());
53 | $output->writeln('Webhook url: '.$url);
54 |
55 | $this->listRepository->registerMainWebhook($list->getListId(), $url);
56 | }
57 |
58 | $output->writeln('✔ done');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Controller/WebhookController.php:
--------------------------------------------------------------------------------
1 | isMethod('GET')) {
35 | return new JsonResponse([
36 | 'success' => true,
37 | 'ping' => 'pong',
38 | ]);
39 | }
40 |
41 | // Handle POST request of Mailchimp
42 | $type = $request->request->get('type');
43 | $data = $request->request->get('data');
44 | /* Response example:
45 | data[merges][FNAME]: Tztz
46 | data[merges][EMAIL]: tztz@gmail.com
47 | data[email_type]: html
48 | data[reason]: manual
49 | data[email]: tztz@gmail.com
50 | data[id]: 5c1b5a7c1e
51 | data[merges][LNAME]: TZST
52 | data[list_id]: ba039c6198
53 | data[web_id]: 3375995
54 | */
55 | $hooksecret = $request->query->get('hooksecret');
56 |
57 | if (empty($type) || empty($data) || empty($hooksecret) || !array_key_exists('list_id', $data)) {
58 | throw new AccessDeniedHttpException('incorrect data format!');
59 | }
60 |
61 | $listId = $data['list_id'];
62 |
63 | $listProviderKey = $this->getParameter('welp_mailchimp.list_provider');
64 | try {
65 | $listProvider = $this->get($listProviderKey);
66 | } catch (ServiceNotFoundException $e) {
67 | throw new \InvalidArgumentException(sprintf('List Provider "%s" should be defined as a service.', $listProviderKey), $e->getCode(), $e);
68 | }
69 |
70 | if (!$listProvider instanceof ListProviderInterface) {
71 | throw new \InvalidArgumentException(sprintf('List Provider "%s" should implement Welp\MailchimpBundle\Provider\ListProviderInterface.', $listProviderKey));
72 | }
73 |
74 | $list = $listProvider->getList($listId);
75 |
76 | // Check the webhook_secret
77 | $authorized = false;
78 | if($list != null && $list->getWebhookSecret() == $hooksecret) {
79 | $authorized = true;
80 | }
81 |
82 | if (!$authorized) {
83 | throw new AccessDeniedHttpException('Webhook secret mismatch!');
84 | }
85 |
86 | // Trigger the right event
87 | switch ($type) {
88 | case 'subscribe':
89 | $dispatcher = $this->get('event_dispatcher');
90 | $dispatcher->dispatch(WebhookEvent::EVENT_SUBSCRIBE, new WebhookEvent($data));
91 | break;
92 | case 'unsubscribe':
93 | $dispatcher = $this->get('event_dispatcher');
94 | $dispatcher->dispatch(WebhookEvent::EVENT_UNSUBSCRIBE, new WebhookEvent($data));
95 | break;
96 | case 'profile':
97 | $dispatcher = $this->get('event_dispatcher');
98 | $dispatcher->dispatch(WebhookEvent::EVENT_PROFILE, new WebhookEvent($data));
99 | break;
100 | case 'cleaned':
101 | $dispatcher = $this->get('event_dispatcher');
102 | $dispatcher->dispatch(WebhookEvent::EVENT_CLEANED, new WebhookEvent($data));
103 | break;
104 | case 'upemail':
105 | $dispatcher = $this->get('event_dispatcher');
106 | $dispatcher->dispatch(WebhookEvent::EVENT_UPEMAIL, new WebhookEvent($data));
107 | break;
108 | case 'campaign':
109 | $dispatcher = $this->get('event_dispatcher');
110 | $dispatcher->dispatch(WebhookEvent::EVENT_CAMPAIGN, new WebhookEvent($data));
111 | break;
112 | default:
113 | throw new AccessDeniedHttpException('type mismatch!');
114 | break;
115 | }
116 |
117 | return new JsonResponse([
118 | 'type' => $type,
119 | 'data' => $data,
120 | ]);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
24 | } else {
25 | $rootNode = $treeBuilder->root('welp_mailchimp');
26 | }
27 |
28 | $rootNode
29 | ->children()
30 | ->scalarNode('api_key')
31 | ->isRequired()
32 | ->end()
33 | ->scalarNode('list_provider')
34 | ->defaultValue('welp_mailchimp.list_provider')
35 | ->end()
36 | // lists
37 | ->arrayNode('lists')
38 | ->useAttributeAsKey('listId')
39 | ->prototype('array')
40 | ->children()
41 | ->scalarNode('subscriber_provider')->end()
42 | ->scalarNode('webhook_secret')->end()
43 | ->scalarNode('webhook_url')->end()
44 | // merge_fields
45 | ->arrayNode('merge_fields')
46 | ->prototype('array')
47 | ->children()
48 | ->scalarNode('tag')
49 | ->isRequired()
50 | ->end()
51 | ->scalarNode('name')
52 | ->isRequired()
53 | ->end()
54 | ->scalarNode('type')
55 | ->isRequired()
56 | ->end()
57 | ->booleanNode('required')
58 | ->end()
59 | ->scalarNode('default_value')
60 | ->end()
61 | ->booleanNode('public')
62 | ->end()
63 | ->integerNode('display_order')
64 | ->end()
65 | // tag options
66 | ->arrayNode('options')
67 | ->children()
68 | ->integerNode('default_country')
69 | ->end()
70 | ->scalarNode('phone_format')
71 | ->end()
72 | ->scalarNode('date_format')
73 | ->end()
74 | ->arrayNode('choices')
75 | ->prototype('scalar')->end()
76 | ->end()
77 | ->integerNode('size')
78 | ->end()
79 | ->end()
80 | ->end()
81 | ->scalarNode('help_text')
82 | ->end()
83 | ->end()
84 | ->end()
85 | ->end()
86 | ->end()
87 | ->end()
88 | ->end()
89 | ->end()
90 | ;
91 |
92 | return $treeBuilder;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/DependencyInjection/WelpMailchimpExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
17 | $container->setParameter('welp_mailchimp.lists', $config['lists']);
18 |
19 | $container->setParameter('welp_mailchimp.list_provider', $config['list_provider']);
20 | $container->setParameter('welp_mailchimp.api_key', isset($config['api_key']) ? $config['api_key'] : null);
21 |
22 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
23 | $loader->load('services.yml');
24 |
25 | // create an alias for the chosen list provider service
26 | $alias = $config['list_provider'];
27 | $container->setAlias('welp_mailchimp.list_provider.current', $alias);
28 |
29 | // Load all the used subscriber providers in the factory
30 | $this->loadSubscriberProviders($container, $config['lists']);
31 | }
32 |
33 | public function getAlias(): string
34 | {
35 | return 'welp_mailchimp';
36 | }
37 |
38 | public function loadSubscriberProviders(ContainerBuilder $container, $lists)
39 | {
40 | $providerFactory = $container->getDefinition('welp_mailchimp.provider.factory');
41 | foreach ($lists as $list) {
42 | $providerKey = $list['subscriber_provider'];
43 | $providerFactory->addMethodCall('addProvider', [$providerKey, new Reference($providerKey)]);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Event/SubscriberEvent.php:
--------------------------------------------------------------------------------
1 | listId = $listId;
82 | $this->subscriber = $subscriber;
83 | $this->oldEmail = $oldEmail;
84 | }
85 |
86 | /**
87 | * Get MailChimp listId
88 | * @return string
89 | */
90 | public function getListId()
91 | {
92 | return $this->listId;
93 | }
94 |
95 | /**
96 | * Get the User as Subscriber
97 | * @return Subscriber
98 | */
99 | public function getSubscriber()
100 | {
101 | return $this->subscriber;
102 | }
103 |
104 | /**
105 | * Get Subscriber's oldEmail (used for EVENT_CHANGE_EMAIL)
106 | * @return string
107 | */
108 | public function getOldEmail()
109 | {
110 | return $this->oldEmail;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Event/SubscriberListener.php:
--------------------------------------------------------------------------------
1 | listRepository = $listRepository;
24 | }
25 |
26 | /**
27 | * Action when a User subscribe
28 | * @param SubscriberEvent $event
29 | * @return void
30 | */
31 | public function onSubscribe(SubscriberEvent $event)
32 | {
33 | $this->listRepository->subscribe(
34 | $event->getListId(),
35 | $event->getSubscriber()
36 | );
37 | }
38 |
39 | /**
40 | * Action when a User unsubscribe
41 | * @param SubscriberEvent $event
42 | * @return void
43 | */
44 | public function onUnsubscribe(SubscriberEvent $event)
45 | {
46 | $this->listRepository->unsubscribe(
47 | $event->getListId(),
48 | $event->getSubscriber()
49 | );
50 | }
51 |
52 | /**
53 | * Action when a User is pending
54 | * @param SubscriberEvent $event
55 | * @return void
56 | */
57 | public function onPending(SubscriberEvent $event)
58 | {
59 | $this->listRepository->pending(
60 | $event->getListId(),
61 | $event->getSubscriber()
62 | );
63 | }
64 |
65 | /**
66 | * Action when a User is cleaned
67 | * @param SubscriberEvent $event
68 | * @return void
69 | */
70 | public function onClean(SubscriberEvent $event)
71 | {
72 | $this->listRepository->clean(
73 | $event->getListId(),
74 | $event->getSubscriber()
75 | );
76 | }
77 |
78 | /**
79 | * Action when a User update its info
80 | * @param SubscriberEvent $event
81 | * @return void
82 | */
83 | public function onUpdate(SubscriberEvent $event)
84 | {
85 | $this->listRepository->update(
86 | $event->getListId(),
87 | $event->getSubscriber()
88 | );
89 | }
90 |
91 | /**
92 | * Action when a User change its email address
93 | * @param SubscriberEvent $event
94 | * @return void
95 | */
96 | public function onChangeEmail(SubscriberEvent $event)
97 | {
98 | $this->listRepository->changeEmailAddress(
99 | $event->getListId(),
100 | $event->getSubscriber(),
101 | $event->getOldEmail()
102 | );
103 | }
104 |
105 | /**
106 | * Action when a User is deleted
107 | * @param SubscriberEvent $event
108 | * @return void
109 | */
110 | public function onDelete(SubscriberEvent $event)
111 | {
112 | $this->listRepository->delete(
113 | $event->getListId(),
114 | $event->getSubscriber()
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Event/WebhookEvent.php:
--------------------------------------------------------------------------------
1 | data = $data;
56 | }
57 |
58 | /**
59 | * Get data form webhook request
60 | * @return array
61 | */
62 | public function getData()
63 | {
64 | return $this->data;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Exception/MailchimpException.php:
--------------------------------------------------------------------------------
1 | status = $status;
66 | $this->detail = $detail;
67 | $this->type = $type;
68 | $this->title = $title;
69 | $this->errors = $errors;
70 | $this->instance = $instance;
71 | }
72 |
73 | /**
74 | * @param string $type
75 | *
76 | * @return static
77 | */
78 | public function setType($type)
79 | {
80 | $this->type = $type;
81 | return $this;
82 | }
83 |
84 | /**
85 | * @param string $title
86 | *
87 | * @return static
88 | */
89 | public function setTitle($title)
90 | {
91 | $this->title = $title;
92 | return $this;
93 | }
94 |
95 | /**
96 | * @param array $errors
97 | *
98 | * @return static
99 | */
100 | public function setErrors(array $errors)
101 | {
102 | $this->errors = $errors;
103 | return $this;
104 | }
105 |
106 | /**
107 | * @param string $instance
108 | *
109 | * @return static
110 | */
111 | public function setInstance($instance)
112 | {
113 | $this->instance = $instance;
114 | return $this;
115 | }
116 |
117 | /**
118 | * @return int
119 | */
120 | public function getStatus()
121 | {
122 | return $this->status;
123 | }
124 |
125 | /**
126 | * @return string
127 | */
128 | public function getDetail()
129 | {
130 | return $this->detail;
131 | }
132 |
133 | /**
134 | * @return string
135 | */
136 | public function getType()
137 | {
138 | return $this->type;
139 | }
140 |
141 | /**
142 | * @return string
143 | */
144 | public function getTitle()
145 | {
146 | return $this->title;
147 | }
148 |
149 | /**
150 | * @return string
151 | */
152 | public function getInstance()
153 | {
154 | return $this->instance;
155 | }
156 |
157 | /**
158 | * @return
159 | */
160 | public function getString()
161 | {
162 | return $this->string;
163 | }
164 |
165 | /**
166 | * @return array
167 | */
168 | public function getErrors()
169 | {
170 | return $this->errors;
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Provider/ConfigListProvider.php:
--------------------------------------------------------------------------------
1 | lists = $lists;
16 | $this->providerFactory = $providerFactory;
17 | }
18 |
19 | /**
20 | * Default list provider, retrieve the lists from the config file.
21 | * {@inheritdoc}
22 | */
23 | public function getLists()
24 | {
25 | if (sizeof($this->lists) == 0) {
26 | throw new \RuntimeException("No Mailchimp list has been defined. Check the your config.yml file based on MailchimpBundle's README.md");
27 | }
28 | $lists = array();
29 | foreach ($this->lists as $listId => $listParameters)
30 | {
31 | $lists[] = $this->createList($listId, $listParameters);
32 | }
33 | return $lists;
34 | }
35 |
36 | /**
37 | * Default list provider, retrieve one list from the config file.
38 | * {@inheritdoc}
39 | */
40 | public function getList($listId)
41 | {
42 | foreach ($this->lists as $id => $listParameters)
43 | {
44 | if($id == $listId)
45 | {
46 | return $this->createList($id, $listParameters);
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * create one SubscriberList
53 | * @param string $listId
54 | * @param string $listParameters
55 | * @return SubscriberList
56 | */
57 | private function createList($listId, $listParameters)
58 | {
59 | $providerServiceKey = $listParameters['subscriber_provider'];
60 | $provider = $this->providerFactory->create($providerServiceKey);
61 | $subscriberList = new SubscriberList($listId, $provider, $listParameters['merge_fields']);
62 | $subscriberList->setWebhookUrl($listParameters['webhook_url']);
63 | $subscriberList->setWebhookSecret($listParameters['webhook_secret']);
64 |
65 | return $subscriberList;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Provider/DynamicProviderInterface.php:
--------------------------------------------------------------------------------
1 | userRepository = $userRepository;
25 | }
26 |
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function getSubscribers()
31 | {
32 | $users = $this->userRepository->findSubscribers();
33 |
34 | $subscribers = array_map(function (User $user) {
35 | $subscriber = new Subscriber($user->getEmail(), [
36 | self::TAG_NICKNAME => $user->getNickname(),
37 | self::TAG_GENDER => $user->getGender(),
38 | self::TAG_BIRTHDATE => $user->getBirthdate() ? $user->getBirthdate()->format('Y-m-d') : null,
39 | self::TAG_CITY => $user->getCity(),
40 | self::TAG_LAST_ACTIVITY_DATE => $user->getLastActivityDate() ? $user->getLastActivityDate()->format('Y-m-d') : null,
41 | self::TAG_REGISTRATION_DATE => $user->getRegistrationDate() ? $user->getRegistrationDate()->format('Y-m-d') : null,
42 | ], [
43 | 'language' => 'fr',
44 | 'email_type' => 'html'
45 | ]);
46 |
47 | return $subscriber;
48 | }, $users);
49 |
50 | return $subscribers;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Provider/FosSubscriberProvider.php:
--------------------------------------------------------------------------------
1 | userManager = $userManager;
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function getSubscribers()
29 | {
30 | $users = $this->userManager->findUsers();
31 | // or find only enabled users :
32 | // $users = $this->userManager->findUserBy(array('enabled' => true));
33 |
34 | $subscribers = array_map(function (User $user) {
35 | $subscriber = new Subscriber($user->getEmail(), [
36 | self::TAG_USERNAME => $user->getUsername(),
37 | self::TAG_ENABLED => $user->isEnabled(),
38 | self::TAG_LAST_LOGIN => $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d') : null
39 | ]);
40 |
41 | return $subscriber;
42 | }, $users);
43 |
44 | return $subscribers;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Provider/ListProviderInterface.php:
--------------------------------------------------------------------------------
1 | providers[$providerKey])) {
20 | $this->providers[$providerKey] = $provider;
21 | }
22 | }
23 |
24 | /**
25 | * Get subscriber provider.
26 | *
27 | * @param string $providerKey
28 | *
29 | * @return ProviderInterface $provider
30 | */
31 | public function create($providerKey)
32 | {
33 | if (!isset($this->providers[$providerKey])) {
34 | throw new \InvalidArgumentException(sprintf('Provider "%s" should be defined as a service.', $providerKey));
35 | }
36 |
37 | return $this->providers[$providerKey];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Provider/ProviderInterface.php:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/Resources/doc/index.md:
--------------------------------------------------------------------------------
1 | # mailchimp-bundle
2 |
3 | [](https://travis-ci.org/welpdev/mailchimp-bundle)
4 | [](https://packagist.org/packages/welp/mailchimp-bundle)
5 | [](https://packagist.org/packages/welp/mailchimp-bundle)
6 | [](https://raw.githubusercontent.com/welpdev/mailchimp-bundle/master/LICENSE.md)
7 |
8 | This bundle will help you synchronise your project's newsletter subscribers into MailChimp through MailChimp API V3.
9 |
10 | ## Features
11 |
12 | * [x] Use your own userProvider (basic `FosSubscriberProvider` included to interface with FosUserBundle)
13 | * [x] Use your own listProvider (`DoctrineListProvider` included to retrieve your list from a database)
14 | * [x] Synchronize Merge Fields with your config
15 | * [x] Synchronize your subscriber with a List
16 | * [x] Use lifecycle event to subscribe/unsubscribe/delete subscriber from a List
17 | * [x] Retrieve [MailChimp Object](https://github.com/drewm/mailchimp-api) to make custom MailChimp API V3 requests
18 | * [x] Register Webhooks
19 |
20 | ## Setup
21 |
22 | Add bundle to your project:
23 |
24 | ```bash
25 | composer require welp/mailchimp-bundle
26 | ```
27 |
28 | Add `Welp\MailchimpBundle\WelpMailchimpBundle` to your `AppKernel.php`:
29 |
30 | ```php
31 | $bundles = [
32 | // ...
33 | new Welp\MailchimpBundle\WelpMailchimpBundle(),
34 | ];
35 | ```
36 |
37 | ## Minimal Configuration
38 |
39 | In your `config.yml`:
40 |
41 | ```yaml
42 | welp_mailchimp:
43 | api_key: YOURMAILCHIMPAPIKEY
44 | ```
45 |
46 | More configuration on the [documentation](configuration.md).
47 |
48 | ## Documentation
49 |
50 | * [Setup](setup.md)
51 | * [Configuration](configuration.md)
52 | * [Subscriber Provider](subscriber-provider.md)
53 | * [List Provider](list-provider.md)
54 | * [Usage](usage.md)
55 | * Synchronize merge fields
56 | * Full synchronization with command
57 | * Unit synchronization with events
58 | * Subscribe new User
59 | * Unsubscribe a User
60 | * Update a User
61 | * Change User's email address (WORKAROUND)
62 | * Delete a User
63 | * Retrieve [MailChimp Object](https://github.com/drewm/mailchimp-api) to make custom MailChimp API V3 requests
64 | * [Webhook](webhook.md)
65 | * Update User when subscribe/unsubscribe
66 |
67 | ## Contributing
68 |
69 | If you want to contribute to this project, look at [over here](CONTRIBUTING.md)
70 |
--------------------------------------------------------------------------------
/src/Resources/doc/list-provider.md:
--------------------------------------------------------------------------------
1 | # List Provider
2 |
3 | By default the list configuration is read from the `config.yml` when correctly defined according to [configuring your lists](configuration.md). This is done by the default list provider (`ConfigListProvider`). If you want to use a diffrent source for your list config you can create your own List Provider that should implement `Welp\MailChimpBundle\Provider\ListProviderInterface`.
4 |
5 | ```php
6 | em = $entityManager;
51 | $this->listEntity = $listEntity;
52 | $this->subscriberProvider = $subscriberProvider;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function getList($listId)
59 | {
60 | $list = $this->em->getRepository($this->listEntity)->findOneByListId($listId);
61 | $list->setProvider($this->subscriberProvider);
62 | return $list;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function getLists()
69 | {
70 | $lists = $this->em->getRepository($this->listEntity)->findAll();
71 | foreach($lists as $list)
72 | {
73 | //add the provider to the list
74 | $list->setProvider($this->subscriberProvider);
75 | }
76 | return $lists;
77 | }
78 | }
79 |
80 | ```
81 | *Got your SubscriberProvider service key saved as a string with your list config ?
82 | Make use of the `ProviderFactory` to get the the service:
83 | `$provider = $this->providerFactory->create($providerServiceKey);`*
84 |
85 | Define your List provider as a service:
86 |
87 | ```yaml
88 | doctrine.list.provider:
89 | class: YourApp\App\Provider\DoctrineListProvider
90 | public: true
91 | arguments:
92 | - '@doctrine.orm.entity_manager'
93 | - 'YourApp\App\Entity\SubscriberList'
94 | - '@example_user_provider'
95 | ```
96 |
97 | After this, don't forget to add the service key for your provider into your `config.yml`:
98 |
99 | ```yaml
100 | welp_mailchimp:
101 | api_key: ...
102 | list_provider: 'doctrine.list.provider'
103 | ...
104 | ```
105 |
--------------------------------------------------------------------------------
/src/Resources/doc/mailchimp-doc.md:
--------------------------------------------------------------------------------
1 | MailChimp Documentation and usefull links
2 |
3 | *
4 | *
5 | *
6 | *
7 | *
8 |
--------------------------------------------------------------------------------
/src/Resources/doc/setup.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | ## Installation
4 |
5 | Add bundle to your project:
6 |
7 | ```bash
8 | composer require welp/mailchimp-bundle
9 | ```
10 |
11 | Add `Welp\MailchimpBundle\WelpMailchimpBundle` to your `AppKernel.php`:
12 |
13 | ```php
14 | $bundles = [
15 | // ...
16 | new Welp\MailchimpBundle\WelpMailchimpBundle(),
17 | ];
18 | ```
19 |
20 | ## Minimal Configuration
21 |
22 | In your `config.yml`:
23 |
24 | ```yaml
25 | welp_mailchimp:
26 | api_key: YOURMAILCHIMPAPIKEY
27 | ```
28 |
29 | [More configuration](configuration.md)
30 |
--------------------------------------------------------------------------------
/src/Resources/doc/subscriber-language.md:
--------------------------------------------------------------------------------
1 | # Subscriber Language
2 |
3 | * [MailChimp Doc](http://kb.mailchimp.com/lists/managing-subscribers/view-and-edit-subscriber-languages)
4 |
5 | ## Language codes
6 |
7 | * English = en
8 | * Arabic = ar
9 | * Afrikaans = af
10 | * Belarusian = be
11 | * Bulgarian = bg
12 | * Catalan = ca
13 | * Chinese = zh
14 | * Croatian = hr
15 | * Czech = cs
16 | * Danish = da
17 | * Dutch = nl
18 | * Estonian = et
19 | * Farsi = fa
20 | * Finnish = fi
21 | * French (France) = fr
22 | * French (Canada) = fr_CA
23 | * German = de
24 | * Greek = el
25 | * Hebrew = he
26 | * Hindi = hi
27 | * Hungarian = hu
28 | * Icelandic = is
29 | * Indonesian = id
30 | * Irish = ga
31 | * Italian = it
32 | * Japanese = ja
33 | * Khmer = km
34 | * Korean = ko
35 | * Latvian = lv
36 | * Lithuanian = lt
37 | * Maltese = mt
38 | * Malay = ms
39 | * Macedonian = mk
40 | * Norwegian = no
41 | * Polish = pl
42 | * Portuguese (Brazil) = pt
43 | * Portuguese (Portugal) = pt_PT
44 | * Romanian = ro
45 | * Russian = ru
46 | * Serbian = sr
47 | * Slovak = sk
48 | * Slovenian = sl
49 | * Spanish (Mexico) = es
50 | * Spanish (Spain) = es_ES
51 | * Swahili = sw
52 | * Swedish = sv
53 | * Tamil = ta
54 | * Thai = th
55 | * Turkish = tr
56 | * Ukrainian = uk
57 | * Vietnamese = vi
58 |
--------------------------------------------------------------------------------
/src/Resources/doc/subscriber-provider.md:
--------------------------------------------------------------------------------
1 | # Subscriber Provider
2 |
3 | After [configuring your lists](configuration.md) in `config.yml`, you need to create at least one `Provider` that will be used by the Symfony command. Your provider should be accessible via a service key (the same you reference in `subscriber_provider` in the configuration above):
4 |
5 | ```yaml
6 | services:
7 | yourapp_mailchimp_subscriber_provider:
8 | class: YourApp\App\Newsletter\SubscriberProvider
9 | arguments: [@yourapp_user_repository]
10 | ```
11 |
12 | It should implement `Welp\MailChimpBundle\Provider\ProviderInterface` and return an array of `Welp\MailChimpBundle\Subscriber\Subscriber` objects. The first argument of the `Subscriber` object is its e-mail, the second argument is an array of merge fields values you need to add in MailChimp's backend in your list settings under `List fields and *|MERGE|* tags` (see this [guide on MailChimp](http://kb.mailchimp.com/merge-tags/using/getting-started-with-merge-tags) to add merge tags in your list) and the third is an array of options [See MailChimp Documentation](http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/).
13 |
14 | ```php
15 | userRepository = $userRepository;
39 | }
40 |
41 | public function getSubscribers()
42 | {
43 | $users = $this->userRepository->findSubscribers();
44 |
45 | $subscribers = array_map(function(User $user) {
46 | $subscriber = new Subscriber($user->getEmail(), [
47 | self::TAG_NICKNAME => $user->getNickname(),
48 | self::TAG_GENDER => $user->getGender(),
49 | self::TAG_BIRTHDATE => $user->getBirthdate() ? $user->getBirthdate()->format('Y-m-d') : null,
50 | self::TAG_CITY => $user->getCity(),
51 | self::TAG_LAST_ACTIVITY_DATE => $user->getLastActivityDate() ? $user->getLastActivityDate()->format('Y-m-d') : null,
52 | self::TAG_REGISTRATION_DATE => $user->getRegistrationDate() ? $user->getRegistrationDate()->format('Y-m-d') : null,
53 | ],[
54 | 'language' => 'fr',
55 | 'email_type' => 'html'
56 | ]);
57 |
58 | return $subscriber;
59 | }, $users);
60 |
61 | return $subscribers;
62 | }
63 | }
64 |
65 | ```
66 |
67 | We also provide a ready to use provider for the FosUserBundle -> FosSubscriberProvider. You just need to register the service into your app:
68 |
69 | ```yaml
70 | services:
71 | yourapp_mailchimp_fos_subscriber_provider:
72 | class: Welp\MailchimpBundle\Provider\FosSubscriberProvider
73 | arguments: [@fos_user.user_manager]
74 | ```
75 |
76 | After this, don't forget to add the service key for your list into your `config.yml`:
77 |
78 | ```yaml
79 | ...
80 | listId2:
81 | subscriber_provider: 'yourapp.provider2'
82 | ...
83 | ```
84 |
85 | ## Dynamic Subscriber Provider
86 |
87 | If you want to create a reusable provider for multiple list without defining a service for each one, you can make use of the `Welp\MailChimpBundle\Provider\DynamicProviderInterface`. It works exactly the same as the `ProviderInterface` except it has one setter method that is used to set the current list in the synchronisation process.
88 |
89 | Example implementation:
90 |
91 | ```php
92 | userRepository = $userRepository;
118 | }
119 |
120 | // List id is set for each list in the synchronisation process
121 | public function setListId(string $listId)
122 | {
123 | $this->listId = $listId;
124 | }
125 |
126 | public function getSubscribers()
127 | {
128 | //now make use of the list id when querying for users
129 | $users = $this->userRepository->findSubscribersByListId($this->listId);
130 |
131 | $subscribers = array_map(function(User $user) {
132 | $subscriber = new Subscriber($user->getEmail(), [
133 | self::TAG_NICKNAME => $user->getNickname(),
134 | self::TAG_GENDER => $user->getGender(),
135 | self::TAG_BIRTHDATE => $user->getBirthdate() ? $user->getBirthdate()->format('Y-m-d') : null,
136 | self::TAG_CITY => $user->getCity(),
137 | self::TAG_LAST_ACTIVITY_DATE => $user->getLastActivityDate() ? $user->getLastActivityDate()->format('Y-m-d') : null,
138 | self::TAG_REGISTRATION_DATE => $user->getRegistrationDate() ? $user->getRegistrationDate()->format('Y-m-d') : null,
139 | ],[
140 | 'language' => 'fr',
141 | 'email_type' => 'html'
142 | ]);
143 |
144 | return $subscriber;
145 | }, $users);
146 |
147 | return $subscribers;
148 | }
149 | }
150 |
151 |
152 | ```
--------------------------------------------------------------------------------
/src/Resources/doc/tests.md:
--------------------------------------------------------------------------------
1 | # Tests
2 |
3 | *
4 | *
5 |
6 | ## Unit tests
7 |
8 | bin/phpspec run
9 |
10 | ## Integration tests
11 |
12 | bin/phpunit -c .
13 |
14 | Look at the code, it works with a MailChimp account...
15 |
16 | ## Webhook tests
17 |
18 | use [ngrok](https://ngrok.com/) and
19 |
20 | ## Debug/dev mode
21 |
22 | The best solution to debug or dev environment is to create a free MailChimp account and use the API key of this free "debug/dev" MailChimp account.
23 |
24 | We are waiting for a sandbox from MailChimp developer in order to implements a pretty debug/dev mode.
25 |
--------------------------------------------------------------------------------
/src/Resources/doc/usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ## Synchronize merge fields
4 |
5 | Merge fields are values you can add to your subscribers (for example the firstname or birthdate of your user). You can then use these tags in your newsletters or create segments out of them.
6 |
7 | To learn more about merge tags, please see this [guide on MailChimp](http://kb.mailchimp.com/merge-tags/using/getting-started-with-merge-tags).
8 |
9 | To synchronize you need to create your **lists** in MailChimp backend first. Then you need to add them in your `config.yml` as shown in the [above configuration](configuration.md). The `options` you can provide are the same as the one found in [MailChimp API](http://developer.mailchimp.com/documentation/mailchimp/reference/lists/merge-fields/).
10 |
11 | You can then synchronize the merge fields using the `php app/console welp:mailchimp:synchronize-merge-fields` command. Note that every tag that are present in MailChimp but are not defined in your configuration **will be deleted along with associated values**.
12 |
13 | NOTE: MailChimp provide two default merge fields:
14 |
15 | * FNAME: Firstname
16 | * LNAME: Lastname
17 |
18 | **Known issues with MailChimp API V3 - 17/12/2016**
19 |
20 | * When you try to synchronize merge fields with choice type, the API doesn't handle update of choice fields... It returns an error 400. Workaround: you have to remove all your choice type merge fields from your list throught the MailChimp Admin panel and retry synchronize with the command.
21 |
22 | ## Full synchronization with command
23 |
24 | You can synchronize all subscribers of your project at once by calling the Symfony command `php app/console welp:mailchimp:synchronize-subscribers`.
25 | You can use the option `--follow-sync` to follow batches executions.
26 |
27 | It will first fetch all the subscribers already present in MailChimp and unsubscribe any subscribers that are not in your projet (they might have been deleted on the project side), it will then send all your subscribers to MailChimp, new subscribers will be added and existing subscribers will be updated.
28 |
29 | NOTE: you must have configure and create your own [subscriber provider](subscriber-provider.md).
30 |
31 | ## Unit synchronization with events
32 |
33 | If you want realtime synchronization, you can dispatch custom events on your controllers (or anywhere). The subscribe event can be used both for adding a new subscriber or updating an existing one. You can fired these events to trigger sync with MailChimp:
34 |
35 | SubscriberEvent::EVENT_SUBSCRIBE = 'welp.mailchimp.subscribe';
36 | SubscriberEvent::EVENT_UNSUBSCRIBE = 'welp.mailchimp.unsubscribe';
37 | SubscriberEvent::EVENT_PENDING = 'welp.mailchimp.pending';
38 | SubscriberEvent::EVENT_CLEAN = 'welp.mailchimp.clean';
39 | SubscriberEvent::EVENT_UPDATE = 'welp.mailchimp.update';
40 | SubscriberEvent::EVENT_CHANGE_EMAIL = 'welp.mailchimp.change_email';
41 | SubscriberEvent::EVENT_DELETE = 'welp.mailchimp.delete';
42 |
43 | ### Subscribe new User
44 |
45 | Here is an example of a subscribe event dispatch:
46 |
47 | ```php
48 | getEmail(), [
60 | 'FIRSTNAME' => $user->getFirstname(),
61 | ], [
62 | 'language' => 'fr'
63 | ]);
64 |
65 | $this->container->get('event_dispatcher')->dispatch(
66 | SubscriberEvent::EVENT_SUBSCRIBE,
67 | new SubscriberEvent('your_list_id', $subscriber)
68 | );
69 | }
70 | ```
71 |
72 | ### Unsubscribe a User
73 |
74 | Here is an example of unsubscribe event dispatch:
75 |
76 | ```php
77 | getEmail());
89 |
90 | $this->container->get('event_dispatcher')->dispatch(
91 | SubscriberEvent::EVENT_UNSUBSCRIBE,
92 | new SubscriberEvent('your_list_id', $subscriber)
93 | );
94 | }
95 | ```
96 |
97 | ### Pending a User
98 |
99 | Here is an example of pending event dispatch:
100 |
101 | ```php
102 | getEmail(), [
114 | 'FIRSTNAME' => $user->getFirstname(),
115 | ], [
116 | 'language' => 'fr'
117 | ]);
118 |
119 | $this->container->get('event_dispatcher')->dispatch(
120 | SubscriberEvent::EVENT_PENDING,
121 | new SubscriberEvent('your_list_id', $subscriber)
122 | );
123 | }
124 | ```
125 |
126 | ### Clean a User
127 |
128 | Here is an example of clean event dispatch:
129 |
130 | ```php
131 | getEmail(), [
143 | 'FIRSTNAME' => $user->getFirstname(),
144 | ], [
145 | 'language' => 'fr'
146 | ]);
147 |
148 | $this->container->get('event_dispatcher')->dispatch(
149 | SubscriberEvent::EVENT_CLEAN,
150 | new SubscriberEvent('your_list_id', $subscriber)
151 | );
152 | }
153 | ```
154 |
155 | ### Update a User
156 |
157 | If your User changes his information, you can sync with MailChimp:
158 |
159 | ```php
160 | getEmail(), [
172 | 'FIRSTNAME' => $user->getFirstname(),
173 | 'LASTNAME' => $user->getFirstname(),
174 | ], [
175 | 'language' => 'en'
176 | ]);
177 |
178 | $this->container->get('event_dispatcher')->dispatch(
179 | SubscriberEvent::EVENT_UPDATE,
180 | new SubscriberEvent('your_list_id', $subscriber)
181 | );
182 | }
183 | ```
184 |
185 | Note: we can't change the address email of a user... It's a MailChimp API V3 [issue](http://stackoverflow.com/questions/32224697/mailchimp-api-v3-0-change-subscriber-email?noredirect=1&lq=1). I already have contacted MailChimp, but you can contact them too.
186 |
187 | The only workaround right now to change user address mail is to delete the old member and add a new one...
188 |
189 | ### Change User's email address (WORKAROUND)
190 |
191 | If your User changes his email address, you can sync with MailChimp:
192 |
193 | ```php
194 | container->get('event_dispatcher')->dispatch(
204 | SubscriberEvent::EVENT_CHANGE_EMAIL,
205 | new SubscriberEvent('your_list_id', $subscriber, $oldEmail)
206 | );
207 |
208 | }
209 | ```
210 |
211 | It's an UGLY WORKAROUND... Waiting for MailChimp API V3 to be able to properly change email address of a member...
212 |
213 | ### Delete a User
214 |
215 | And finally delete a User
216 |
217 | Unsubscribe is simpler, you only need the email, all merge fields will be ignored:
218 |
219 | ```php
220 | getEmail());
232 |
233 | $this->container->get('event_dispatcher')->dispatch(
234 | SubscriberEvent::EVENT_DELETE,
235 | new SubscriberEvent('your_list_id', $subscriber)
236 | );
237 | }
238 | ```
239 |
240 | ## Retrieve MailChimp Object to make custom MailChimp API V3 requests
241 |
242 | You can also retrieve the MailChimp Object which comes from the library [drewm/mailchimp-api](https://github.com/drewm/mailchimp-api).
243 |
244 | The service key is `welp_mailchimp.mailchimp_master`.
245 |
246 | Example:
247 |
248 | ``` php
249 | container->get('welp_mailchimp.mailchimp_master');
253 | $list_id = 'b1234346';
254 |
255 | $result = $MailChimp->post("lists/$list_id/members", [
256 | 'email_address' => 'davy@example.com',
257 | 'status' => 'subscribed',
258 | ]);
259 |
260 | if ($MailChimp->success()) {
261 | print_r($result);
262 | } else {
263 | echo $MailChimp->getLastError();
264 | }
265 |
266 | ```
267 |
--------------------------------------------------------------------------------
/src/Resources/doc/webhook.md:
--------------------------------------------------------------------------------
1 | # Webhook
2 |
3 | * [Documentation](https://apidocs.mailchimp.com/webhooks/)
4 | * [How to set up webhooks](http://kb.mailchimp.com/integrations/api-integrations/how-to-set-up-webhooks)
5 | * [Webhooks API V3](http://developer.mailchimp.com/documentation/mailchimp/reference/lists/webhooks/)
6 |
7 | Webhooks will be triggered when an event occured in MailChimp, and it will call our webhook url and fired Webhook Events in our Symfony App. We will listen to these events in order to add our logic/workflow.
8 |
9 | ## Configuration
10 |
11 | You need to add the webhook routing to your app routing:
12 |
13 | `app/routing.yml`
14 |
15 | myapp_mailchimp_webhook:
16 | resource: "@WelpMailchimpBundle/Resources/config/routing.yml"
17 | prefix: /mailchimp
18 |
19 | Note: you can change the prefix as you like.
20 |
21 | This will generate an url to the webhook like this:
22 |
23 | Also, MailChimp recommand to protect webhook url with a token parameter. So you need to add the secret token to your list in your config.yml
24 |
25 | `config.yml`
26 |
27 | welp_mailchimp:
28 | api_key: 3419ca97412af7c2893b89894275b415-us14
29 | lists:
30 | ba039c6198:
31 | webhook_secret: thisisTheSecretPass
32 | ...
33 |
34 | Note: To access properly to the webhook function you will have to use the url with the secret parameter:
35 |
36 | ## Register the webhook manually
37 |
38 | See [How to set up webhooks](http://kb.mailchimp.com/integrations/api-integrations/how-to-set-up-webhooks).
39 |
40 | And the webhook url you have to register is:
41 |
42 |
43 | ## Command to automatically register webhook to lists
44 |
45 | There is a command to automatically register webhook to lists
46 |
47 | Before using it, you have to add the webhook_url into lists in `config.yml`
48 |
49 | `config.yml`
50 |
51 | welp_mailchimp:
52 | api_key: 3419ca97412af7c2893b89894275b415-us14
53 | lists:
54 | ba039c6198:
55 | webhook_secret: thisisTheSecretPass
56 | webhook_url: http://domain.com/mailchimp/webhook/endpoint
57 |
58 | Next in your terminal use this command `php app/console welp:mailchimp:webhook`. You can verify in your MailChimp List that the webhook has been added.
59 |
60 | ## Events to listen
61 |
62 | In order to integrate MailChimp into your app workflow, you can listen to different Event.
63 |
64 | Event you can listen:
65 |
66 | WebhookEvent::EVENT_SUBSCRIBE = 'welp.mailchimp.webhook.subscribe';
67 | WebhookEvent::EVENT_UNSUBSCRIBE = 'welp.mailchimp.webhook.unsubscribe';
68 | WebhookEvent::EVENT_PROFILE = 'welp.mailchimp.webhook.profile';
69 | WebhookEvent::EVENT_CLEANED = 'welp.mailchimp.webhook.cleaned';
70 | WebhookEvent::EVENT_UPEMAIL = 'welp.mailchimp.webhook.upemail';
71 | WebhookEvent::EVENT_CAMPAIGN = 'welp.mailchimp.webhook.campaign';
72 |
73 | Example:
74 |
75 | ### 1- Create listener
76 |
77 | ``` php
78 | container = $container;
97 | }
98 |
99 | public static function getSubscribedEvents()
100 | {
101 | return [
102 | WebhookEvent::EVENT_SUBSCRIBE => 'subscribe',
103 | WebhookEvent::EVENT_UNSUBSCRIBE => 'unsubscribe',
104 | WebhookEvent::EVENT_PROFILE => 'profile',
105 | WebhookEvent::EVENT_CLEANED => 'cleaned',
106 | WebhookEvent::EVENT_UPEMAIL => 'upemail',
107 | WebhookEvent::EVENT_CAMPAIGN => 'campaign'
108 | ];
109 | }
110 |
111 | public function subscribe(WebhookEvent $event){
112 | $logger = $this->container->get('logger');
113 | $logger->info('Subscribe Event:', $event->getData());
114 | }
115 |
116 | public function unsubscribe(WebhookEvent $event){
117 | $logger = $this->container->get('logger');
118 | $logger->info('Unsubscribe Event:', $event->getData());
119 | }
120 |
121 | public function profile(WebhookEvent $event){
122 | $logger = $this->container->get('logger');
123 | $logger->info('Profile Event:', $event->getData());
124 | }
125 |
126 | public function cleaned(WebhookEvent $event){
127 | $logger = $this->container->get('logger');
128 | $logger->info('Cleaned Event:', $event->getData());
129 | }
130 |
131 | public function upemail(WebhookEvent $event){
132 | $logger = $this->container->get('logger');
133 | $logger->info('Upemail Event:', $event->getData());
134 | }
135 |
136 | public function campaign(WebhookEvent $event){
137 | $logger = $this->container->get('logger');
138 | $logger->info('campaign Event:', $event->getData());
139 | }
140 |
141 |
142 | }
143 | ```
144 |
145 | ### 2- Register the listener into services.yml
146 |
147 | services:
148 | app.listener.mailchimp.webhook:
149 | class: AppBundle\Listener\MailchimpEventListener
150 | tags:
151 | - { name: kernel.event_subscriber }
152 | arguments:
153 | - @service_container
154 |
155 | ### 3- Test with ngrok (or other localhost tunnel) and you will see the result in app log:
156 |
157 | ...
158 | [2016-09-05 11:55:48] app.INFO: Unsubscribe Event: {"reason":"manual","id":"5c1b5a7c1e","email":"tztz@gmail.com","email_type":"html","web_id":"3375995","merges":{"EMAIL":"tztz@gmail.com","FNAME":"Tztz","LNAME":"TZST"},"list_id":"ba039c6198"} []
159 |
--------------------------------------------------------------------------------
/src/Subscriber/ListRepository.php:
--------------------------------------------------------------------------------
1 | mailchimp = $mailchimp;
38 | }
39 |
40 | /**
41 | * Get MailChimp Object to do custom actions
42 | * @return MailChimp https://github.com/drewm/mailchimp-api
43 | */
44 | public function getMailChimp()
45 | {
46 | return $this->mailchimp;
47 | }
48 |
49 | /**
50 | * Find MailChimp List by list Id
51 | * @param string $listId
52 | * @return array list http://developer.mailchimp.com/documentation/mailchimp/reference/lists/#read-get_lists_list_id
53 | */
54 | public function findById($listId)
55 | {
56 | $listData = $this->mailchimp->get("lists/$listId");
57 |
58 | if (!$this->mailchimp->success()) {
59 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
60 | }
61 | return $listData;
62 | }
63 |
64 | /**
65 | * core function to put (add or edit) subscriber to a list
66 | * @param string $listId
67 | * @param Subscriber $subscriber
68 | * @param string $status
69 | * @return array
70 | */
71 | protected function putSubscriberInList($listId, Subscriber $subscriber, $status)
72 | {
73 | if (!in_array($status, ['subscribed', 'unsubscribed', 'cleaned', 'pending', 'transactional'])) {
74 | throw new \RuntimeException('$status must be one of these values: subscribed, unsubscribed, cleaned, pending, transactional');
75 | }
76 | $subscriberHash = MailChimp::subscriberHash($subscriber->getEmail());
77 | $result = $this->mailchimp->put("lists/$listId/members/$subscriberHash",
78 | array_merge(
79 | $subscriber->formatMailChimp(),
80 | ['status' => $status]
81 | )
82 | );
83 |
84 | if (!$this->mailchimp->success()) {
85 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
86 | }
87 |
88 | return $result;
89 | }
90 |
91 | /**
92 | * Subscribe a Subscriber to a list
93 | * @param string $listId
94 | * @param Subscriber $subscriber
95 | * @return array
96 | */
97 | public function subscribe($listId, Subscriber $subscriber)
98 | {
99 | return $this->putSubscriberInList($listId, $subscriber, 'subscribed');
100 | }
101 |
102 | /**
103 | * Subscribe a Subscriber to a list
104 | * @param string $listId
105 | * @param Subscriber $subscriber
106 | */
107 | public function unsubscribe($listId, Subscriber $subscriber)
108 | {
109 | return $this->putSubscriberInList($listId, $subscriber, 'unsubscribed');
110 | }
111 |
112 | /**
113 | * Clean a Subscriber to a list
114 | * @param string $listId
115 | * @param Subscriber $subscriber
116 | */
117 | public function clean($listId, Subscriber $subscriber)
118 | {
119 | return $this->putSubscriberInList($listId, $subscriber, 'cleaned');
120 | }
121 |
122 | /**
123 | * Add/set pending a Subscriber to a list
124 | * @param string $listId
125 | * @param Subscriber $subscriber
126 | */
127 | public function pending($listId, Subscriber $subscriber)
128 | {
129 | return $this->putSubscriberInList($listId, $subscriber, 'pending');
130 | }
131 |
132 | /**
133 | * set transactional a Subscriber to a list
134 | * @param string $listId
135 | * @param Subscriber $subscriber
136 | */
137 | public function transactional($listId, Subscriber $subscriber)
138 | {
139 | return $this->putSubscriberInList($listId, $subscriber, 'transactional');
140 | }
141 |
142 | /**
143 | * Update a Subscriber to a list
144 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/#edit-patch_lists_list_id_members_subscriber_hash
145 | * @param string $listId
146 | * @param Subscriber $subscriber
147 | */
148 | public function update($listId, Subscriber $subscriber)
149 | {
150 | $subscriberHash = MailChimp::subscriberHash($subscriber->getEmail());
151 | $result = $this->mailchimp->patch("lists/$listId/members/$subscriberHash", $subscriber->formatMailChimp());
152 |
153 | if (!$this->mailchimp->success()) {
154 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
155 | }
156 |
157 | return $result;
158 | }
159 |
160 | /**
161 | * TODO not working with API V3... we can't change email of a user
162 | * one possible solution is to delete old subscriber and add a new one
163 | * with the same mergeFieds and Options...
164 | * Change email address
165 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/members/#edit-put_lists_list_id_members_subscriber_hash
166 | * @param string $listId
167 | * @param Subscriber $newSubscriber
168 | * @param string $oldEmailAddress
169 | */
170 | public function changeEmailAddress($listId, Subscriber $newSubscriber, $oldEmailAddress)
171 | {
172 | # @NOTE handle special cases:
173 | # 1. new email address already exists in List
174 | # 2. old email address not exists in list
175 | # 3. old or new email address doesn't exists in list
176 |
177 | $subscriberHash = MailChimp::subscriberHash($oldEmailAddress);
178 | $oldMember = $this->mailchimp->get("lists/$listId/members/$subscriberHash");
179 | if (!$this->mailchimp->success()) {
180 | // problem with the oldSubcriber (doest not exist, ...)
181 | // np we will take the new Subscriber
182 | $newMember = $newSubscriber->formatMailChimp();
183 | $newMember['status_if_new'] = 'subscribed';
184 | } else {
185 | // clean member
186 | unset($oldMember['_links']);
187 | unset($oldMember['id']);
188 | unset($oldMember['stats']);
189 | unset($oldMember['unique_email_id']);
190 | unset($oldMember['member_rating']);
191 | unset($oldMember['last_changed']);
192 | unset($oldMember['email_client']);
193 | unset($oldMember['list_id']);
194 |
195 | $newMember = $oldMember;
196 | $newMember['email_address'] = $newSubscriber->getEmail();
197 | $newMember['status_if_new'] = 'subscribed';
198 |
199 | // delete the old Member
200 | $deleteOld = $this->mailchimp->delete("lists/$listId/members/$subscriberHash");
201 | if (!$this->mailchimp->success()) {
202 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
203 | }
204 | }
205 |
206 | // add/update the new member
207 | $subscriberHash = MailChimp::subscriberHash($newSubscriber->getEmail());
208 | $result = $this->mailchimp->put("lists/$listId/members/$subscriberHash", $newMember);
209 | if (!$this->mailchimp->success()) {
210 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
211 | }
212 |
213 | return $result;
214 | }
215 |
216 | /**
217 | * Delete a Subscriber to a list
218 | * @param string $listId
219 | * @param Subscriber $subscriber
220 | */
221 | public function delete($listId, Subscriber $subscriber)
222 | {
223 | $subscriberHash = MailChimp::subscriberHash($subscriber->getEmail());
224 | $result = $this->mailchimp->delete("lists/$listId/members/$subscriberHash");
225 |
226 | if (!$this->mailchimp->success()) {
227 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
228 | }
229 |
230 | return $result;
231 | }
232 |
233 | /**
234 | * Subscribe a batch of Subscriber to a list
235 | * @param string $listId
236 | * @param array $subscribers
237 | * @return array $batchIds
238 | */
239 | public function batchSubscribe($listId, array $subscribers)
240 | {
241 | $batchResults = [];
242 | // as suggested in MailChimp API docs, we send multiple smaller requests instead of a bigger one
243 | $subscriberChunks = array_chunk($subscribers, self::SUBSCRIBER_BATCH_SIZE);
244 | foreach ($subscriberChunks as $subscriberChunk) {
245 | $Batch = $this->mailchimp->new_batch();
246 | foreach ($subscriberChunk as $index => $newsubscribers) {
247 | $subscriberHash = MailChimp::subscriberHash($newsubscribers->getEmail());
248 | $Batch->put("op$index", "lists/$listId/members/$subscriberHash", array_merge(
249 | $newsubscribers->formatMailChimp(),
250 | ['status' => 'subscribed']
251 | ));
252 | }
253 | $Batch->execute();
254 | $currentBatch = $Batch->check_status();
255 | array_push($batchResults, $currentBatch);
256 | }
257 | return $batchResults;
258 | }
259 |
260 | /**
261 | * Unsubscribe a batch of Subscriber to a list
262 | * @param string $listId
263 | * @param array $emails
264 | * @return array $batchIds
265 | */
266 | public function batchUnsubscribe($listId, array $emails)
267 | {
268 | $batchIds = [];
269 | // as suggested in MailChimp API docs, we send multiple smaller requests instead of a bigger one
270 | $emailsChunks = array_chunk($emails, self::SUBSCRIBER_BATCH_SIZE);
271 | foreach ($emailsChunks as $emailsChunk) {
272 | $Batch = $this->mailchimp->new_batch();
273 | foreach ($emailsChunk as $index => $email) {
274 | $emailHash = MailChimp::subscriberHash($email);
275 | $Batch->patch("op$index", "lists/$listId/members/$emailHash", [
276 | 'status' => 'unsubscribed'
277 | ]);
278 | }
279 | $result = $Batch->execute();
280 | $currentBatch = $Batch->check_status();
281 | array_push($batchIds, $currentBatch['id']);
282 | }
283 | return $batchIds;
284 | }
285 |
286 | /**
287 | * Delete a batch of Subscriber to a list
288 | * @param string $listId
289 | * @param array $emails
290 | * @return array $batchIds
291 | */
292 | public function batchDelete($listId, array $emails)
293 | {
294 | $batchIds = [];
295 | // as suggested in MailChimp API docs, we send multiple smaller requests instead of a bigger one
296 | $emailsChunks = array_chunk($emails, self::SUBSCRIBER_BATCH_SIZE);
297 | foreach ($emailsChunks as $emailsChunk) {
298 | $Batch = $this->mailchimp->new_batch();
299 | foreach ($emailsChunk as $index => $email) {
300 | $emailHash = MailChimp::subscriberHash($email);
301 | $Batch->delete("op$index", "lists/$listId/members/$emailHash");
302 | }
303 | $result = $Batch->execute();
304 | $currentBatch = $Batch->check_status();
305 | array_push($batchIds, $currentBatch['id']);
306 | }
307 | return $batchIds;
308 | }
309 |
310 | /**
311 | * Get Members of a list
312 | * @param string $listId
313 | * @return array
314 | */
315 | public function getMembers($listId)
316 | {
317 | $emails = [];
318 | $result = $this->mailchimp->get("lists/$listId/members");
319 |
320 | if (!$this->mailchimp->success()) {
321 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
322 | }
323 |
324 | return $result;
325 | }
326 |
327 | /**
328 | * Get an array of subscribers emails from a list
329 | * @param string $listId
330 | * @return array
331 | */
332 | public function getSubscriberEmails($listId)
333 | {
334 | $emails = [];
335 | $members = [];
336 | $offset=0;
337 | $maxresult = 200;
338 | $result = $this->mailchimp->get("lists/$listId/members", ['count'=> $maxresult]);
339 |
340 | if (!$this->mailchimp->success()) {
341 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
342 | }
343 |
344 | $totalItems = $result['total_items'];
345 | $members = array_merge($members, $result['members']);
346 |
347 | while ($offset < $totalItems) {
348 | $offset+=$maxresult;
349 | $result = $this->mailchimp->get("lists/$listId/members", [
350 | 'count' => $maxresult,
351 | 'offset' => $offset
352 | ]);
353 |
354 | if (!$this->mailchimp->success()) {
355 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
356 | }
357 | $members = array_merge($members, $result['members']);
358 | };
359 |
360 | foreach ($members as $key => $member) {
361 | array_push($emails, $member['email_address']);
362 | }
363 |
364 | return $emails;
365 | }
366 |
367 | /**
368 | * find all merge fields for a list
369 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/merge-fields/#
370 | * @param string $listId
371 | * @return array
372 | */
373 | public function getMergeFields($listId)
374 | {
375 | $result = $this->mailchimp->get("lists/$listId/merge-fields");
376 |
377 | # Handle mailchimp default count limit
378 | if ($result['total_items'] > self::MAILCHIMP_DEFAULT_COUNT_LIMIT) {
379 | $result = $this->mailchimp->get("lists/$listId/merge-fields", array("count" => $result['total_items']));
380 | }
381 |
382 | if (!$this->mailchimp->success()) {
383 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
384 | }
385 |
386 | return $result['merge_fields'];
387 | }
388 |
389 | /**
390 | * add merge field for a list
391 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/merge-fields/#
392 | * @param string $listId
393 | * @param array $mergeData ["name" => '', "type" => '']
394 | * @return array
395 | */
396 | public function addMergeField($listId, array $mergeData)
397 | {
398 | $result = $this->mailchimp->post("lists/$listId/merge-fields", $mergeData);
399 |
400 | if (!$this->mailchimp->success()) {
401 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
402 | }
403 |
404 | return $result;
405 | }
406 |
407 | /**
408 | * add merge field for a list
409 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/merge-fields/#edit-patch_lists_list_id_merge_fields_merge_id
410 | * @param string $listId
411 | * @param string $mergeId
412 | * @param array $mergeData ["name" => '', "type" => '', ...]
413 | * @return array
414 | */
415 | public function updateMergeField($listId, $mergeId, $mergeData)
416 | {
417 | $result = $this->mailchimp->patch("lists/$listId/merge-fields/$mergeId", $mergeData);
418 |
419 | if (!$this->mailchimp->success()) {
420 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
421 | }
422 |
423 | return $result;
424 | }
425 |
426 | /**
427 | * delete merge field for a list
428 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/merge-fields/#
429 | * @param string $listId
430 | * @param string $mergeId
431 | * @return array
432 | */
433 | public function deleteMergeField($listId, $mergeId)
434 | {
435 | $result = $this->mailchimp->delete("lists/$listId/merge-fields/$mergeId");
436 |
437 | if (!$this->mailchimp->success()) {
438 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
439 | }
440 |
441 | return $result;
442 | }
443 |
444 | /**
445 | * Automatically configure Webhook for a list
446 | * @param string $listId
447 | * @param string $webhookurl
448 | * @return array
449 | */
450 | public function registerMainWebhook($listId, $webhookurl)
451 | {
452 | // Configure webhook
453 | $subscribeWebhook = [
454 | 'url' => $webhookurl,
455 | 'events' => [
456 | 'subscribe' => true,
457 | 'unsubscribe' => true,
458 | 'profile' => true,
459 | 'cleaned' => true,
460 | 'upemail' => true,
461 | 'campaign' => true
462 | ],
463 | 'sources' => [
464 | 'user' => true,
465 | 'admin' => true,
466 | 'api' => false // to avoid double (infinite loop) update (update an subscriber with the API and the webhook reupdate the user, ...)
467 | ]
468 | ];
469 |
470 | return $this->addWebhook($listId, $subscribeWebhook);
471 | }
472 |
473 | /**
474 | * Add a new webhook to a list
475 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/webhooks/#
476 | * @param string $listId
477 | * @param array $webhookData
478 | * @return array
479 | */
480 | public function addWebhook($listId, array $webhookData)
481 | {
482 | $result = $this->mailchimp->post("lists/$listId/webhooks", $webhookData);
483 |
484 | if (!$this->mailchimp->success()) {
485 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
486 | }
487 |
488 | return $result;
489 | }
490 |
491 | /**
492 | * Get webhooks of a list
493 | * http://developer.mailchimp.com/documentation/mailchimp/reference/lists/webhooks/#
494 | * @param string $listId
495 | * @return array
496 | */
497 | public function getWebhooks($listId)
498 | {
499 | $result = $this->mailchimp->get("lists/$listId/webhooks");
500 |
501 | if (!$this->mailchimp->success()) {
502 | $this->throwMailchimpError($this->mailchimp->getLastResponse());
503 | }
504 |
505 | return $result;
506 | }
507 |
508 | /**
509 | * [throwMailchimpError description]
510 | * @param array $errorResponse [description]
511 | * @return void
512 | * @throws MailchimpException [description]
513 | */
514 | private function throwMailchimpError(array $errorResponse)
515 | {
516 | $errorArray = json_decode($errorResponse['body'], true);
517 | if (is_array($errorArray) && array_key_exists('errors', $errorArray)) {
518 | throw new MailchimpException(
519 | $errorArray['status'],
520 | $errorArray['detail'],
521 | $errorArray['type'],
522 | $errorArray['title'],
523 | $errorArray['errors'],
524 | $errorArray['instance']
525 | );
526 | } else {
527 | throw new MailchimpException(
528 | $errorArray['status'],
529 | $errorArray['detail'],
530 | $errorArray['type'],
531 | $errorArray['title'],
532 | null,
533 | $errorArray['instance']
534 | );
535 | }
536 | }
537 | }
538 |
--------------------------------------------------------------------------------
/src/Subscriber/ListSynchronizer.php:
--------------------------------------------------------------------------------
1 | listRepository = $listRepository;
21 | }
22 |
23 | /**
24 | * Synchronise user from provider with MailChimp List
25 | * @param SubscriberListInterface $list
26 | * @return void
27 | */
28 | public function synchronize(SubscriberListInterface $list)
29 | {
30 | $listData = $this->listRepository->findById($list->getListId());
31 |
32 | // get Subscribers from the provider
33 | $subscribers = $list->getProvider()->getSubscribers();
34 |
35 | // unsubscribe difference
36 | $this->unsubscribeDifference($list->getListId(), $subscribers);
37 | // subscribe the rest
38 | return $this->batchSubscribe($list->getListId(), $subscribers);
39 | }
40 |
41 | /**
42 | * Subscribe a batch of user
43 | * @param string $listId
44 | * @param array $subscribers
45 | * @return void
46 | */
47 | protected function batchSubscribe($listId, array $subscribers = [])
48 | {
49 | return $this->listRepository->batchSubscribe($listId, $subscribers);
50 | }
51 |
52 | /**
53 | * Unsubscribe the difference between the array subscriber an user
54 | * @param string $listId
55 | * @param array $subscribers
56 | * @return void
57 | */
58 | protected function unsubscribeDifference($listId, array $subscribers)
59 | {
60 | $mailchimpEmails = $this->listRepository->getSubscriberEmails($listId);
61 | $internalEmails = array_map(function (Subscriber $subscriber) {
62 | return $subscriber->getEmail();
63 | }, $subscribers);
64 |
65 | // emails that are present in mailchimp but not internally should be unsubscribed
66 | $diffenceEmails = array_diff($mailchimpEmails, $internalEmails);
67 | if (sizeof($diffenceEmails) == 0) {
68 | return;
69 | }
70 |
71 | $this->listRepository->batchUnsubscribe($listId, $diffenceEmails);
72 | }
73 |
74 | /**
75 | * Synchronize Merge fields of a list and the array $mergeFields
76 | * @param string $listId
77 | * @param array $mergeFields
78 | * @return void
79 | */
80 | public function synchronizeMergeFields($listId, array $mergeFields = [])
81 | {
82 | $mailChimpMergeFields = $this->listRepository->getMergeFields($listId);
83 |
84 | foreach ($mailChimpMergeFields as $tag) {
85 | if (!$this->tagExists($tag['tag'], $mergeFields)) {
86 | // tag only exist in mailchimp, we are removing it
87 | $this->listRepository->deleteMergeField($listId, $tag['merge_id']);
88 | }
89 | }
90 |
91 | foreach ($mergeFields as $tag) {
92 | if ($tagId = $this->tagExists($tag['tag'], $mailChimpMergeFields)) {
93 | // update mergeField in mailChimp
94 | $this->listRepository->updateMergeField($listId, $tagId, $tag);
95 | } else {
96 | $this->listRepository->addMergeField($listId, $tag);
97 | }
98 | }
99 | }
100 |
101 | /**
102 | * Test if the merge field Tag exists in an array
103 | * @param string $tagName
104 | * @param array $tags
105 | * @return mixed (Boolean true|false) or $tag['merge_id']
106 | */
107 | protected function tagExists($tagName, array $tags)
108 | {
109 | foreach ($tags as $tag) {
110 | if ($tag['tag'] == $tagName) {
111 | if (array_key_exists('merge_id', $tag)) {
112 | return $tag['merge_id'];
113 | }
114 | return true;
115 | }
116 | }
117 | return false;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Subscriber/Subscriber.php:
--------------------------------------------------------------------------------
1 | email = $email;
38 | $this->mergeFields = $mergeFields;
39 | $this->options = $options;
40 | }
41 |
42 | /**
43 | * Formate Subscriber for MailChimp API request
44 | * @return array
45 | */
46 | public function formatMailChimp()
47 | {
48 | $options = $this->options;
49 | if (!empty($this->getMergeFields())) {
50 | $options = array_merge([
51 | 'merge_fields' => $this->getMergeFields()
52 | ], $options);
53 | }
54 |
55 | return array_merge([
56 | 'email_address' => $this->getEmail()
57 | ], $options);
58 | }
59 |
60 | /**
61 | * Correspond to email_address in MailChimp request
62 | * @return string
63 | */
64 | public function getEmail()
65 | {
66 | return $this->email;
67 | }
68 |
69 | /**
70 | * Set mergefields
71 | * @param array $mergeFields
72 | * @return array ['TAGKEY' => value, ...]
73 | */
74 | public function setMergeFields(array $mergeFields)
75 | {
76 | // since fev2017, MailChimp API doesn't handle null value en throw 400
77 | // errors when you try to add subscriber with a mergefields value to null
78 | foreach ($mergeFields as $key => $value) {
79 | if ($value === null) {
80 | unset($mergeFields[$key]);
81 | }
82 | }
83 | $this->mergeFields = $mergeFields;
84 |
85 | return $this->mergeFields;
86 | }
87 |
88 | /**
89 | * Correspond to merge_fields in MailChimp request
90 | * @return array ['TAGKEY' => value, ...]
91 | */
92 | public function getMergeFields()
93 | {
94 | // since fev2017, MailChimp API doesn't handle null value en throw 400
95 | // errors when you try to add subscriber with a mergefields value to null
96 | foreach ($this->mergeFields as $key => $value) {
97 | if ($value === null) {
98 | unset($this->mergeFields[$key]);
99 | }
100 | }
101 | return $this->mergeFields;
102 | }
103 |
104 | /**
105 | * The rest of member options:
106 | * email_type, interests, language, vip, location, ip_signup, timestamp_signup, ip_opt, timestamp_opt
107 | * @return array
108 | */
109 | public function getOptions()
110 | {
111 | return $this->options;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Subscriber/SubscriberList.php:
--------------------------------------------------------------------------------
1 | listId = $listId;
52 | $this->provider = $provider;
53 | $this->mergeFields = $mergeFields;
54 |
55 | //If the provider implements DynamicProviderInterface, set the list id in the provider
56 | if ($this->provider instanceof DynamicProviderInterface) {
57 | $this->provider->setListId($this->listId);
58 | }
59 | }
60 |
61 | /**
62 | * get the MailChimp ListId
63 | * @return string
64 | */
65 | public function getListId()
66 | {
67 | return $this->listId;
68 | }
69 |
70 | /**
71 | * Get the subscriber provider
72 | * @return ProviderInterface
73 | */
74 | public function getProvider()
75 | {
76 | return $this->provider;
77 | }
78 |
79 | /**
80 | * Get the list merge fields
81 | * @return array
82 | */
83 | public function getMergeFields()
84 | {
85 | return $this->mergeFields;
86 | }
87 |
88 | /**
89 | * Get the list webhook URL
90 | * @return string
91 | */
92 | public function getWebhookUrl()
93 | {
94 | return $this->webhookUrl;
95 | }
96 |
97 | /**
98 | * Set the list webhook URL
99 | * @param string
100 | */
101 | public function setWebhookUrl($webhookUrl)
102 | {
103 | $this->webhookUrl = $webhookUrl;
104 | }
105 |
106 | /**
107 | * Get the list webhook secret
108 | * @return string
109 | */
110 | public function getWebhookSecret()
111 | {
112 | return $this->webhookSecret;
113 | }
114 |
115 | /**
116 | * Set the list webhook secret
117 | * @param string
118 | */
119 | public function setWebhookSecret($webhookSecret)
120 | {
121 | $this->webhookSecret = $webhookSecret;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Subscriber/SubscriberListInterface.php:
--------------------------------------------------------------------------------
1 | add(new SynchronizeSubscribersCommand());
21 |
22 | $command = $application->find('welp:mailchimp:synchronize-subscribers');
23 | $commandTester = new CommandTester($command);
24 | $commandTester->execute(array(
25 | 'command' => $command->getName(),
26 |
27 | // pass arguments to the helper
28 | //'username' => 'Wouter',
29 |
30 | // prefix the key with a double slash when passing options,
31 | // e.g: '--some-option' => 'option_value',
32 | ));
33 |
34 | // the output of the command in the console
35 | $output = $commandTester->getDisplay();
36 | //$this->assertContains('Username: Wouter', $output);
37 |
38 | */
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Provider/configListProviderTest.php:
--------------------------------------------------------------------------------
1 | providerFactory = $this->createMock(\Welp\MailchimpBundle\Provider\ProviderFactory::class);
18 |
19 | $this->providerFactory
20 | ->method('create')
21 | ->will($this->returnValue($this->createMock(\Welp\MailchimpBundle\Provider\FosSubscriberProvider::class)))
22 | ;
23 |
24 | $this->listConfig = array(
25 | 'sampleid' => array(
26 | 'subscriber_provider' => 'sample.provider',
27 | 'webhook_url' => 'url',
28 | 'webhook_secret' => 'secret',
29 | 'merge_fields' => array(
30 | array(
31 | 'tag' => 'FNAME',
32 | 'name' => 'First Name',
33 | 'type'=> 'text',
34 | 'public' => true
35 | ),
36 | array(
37 | 'tag' => 'LNAME',
38 | 'name' => 'Last Name',
39 | 'type'=> 'text',
40 | 'public' => true
41 | )
42 | )
43 | ),
44 | 'sampleid2' => array(
45 | 'subscriber_provider' => 'sample.provider',
46 | 'webhook_url' => 'url',
47 | 'webhook_secret' => 'secret',
48 | 'merge_fields' => array(
49 | array(
50 | 'tag' => 'FNAME2',
51 | 'name' => 'First Name',
52 | 'type'=> 'text',
53 | 'public' => true
54 | ),
55 | array(
56 | 'tag' => 'LNAME2',
57 | 'name' => 'Last Name',
58 | 'type'=> 'text',
59 | 'public' => true
60 | )
61 | )
62 | )
63 | );
64 | }
65 |
66 | public function testGetListsEmpty()
67 | {
68 | //test with empty lists
69 | $configListProvider = new ConfigListProvider($this->providerFactory, array());
70 | $this->expectException(\RuntimeException::class);
71 | $configListProvider->getLists();
72 | }
73 |
74 | public function testGetLists()
75 | {
76 |
77 | $configListProvider = new ConfigListProvider($this->providerFactory, $this->listConfig);
78 | $lists = $configListProvider->getLists();
79 |
80 | $this->assertEquals(2, count($lists));
81 | $this->assertTrue($lists[0] instanceof SubscriberList);
82 | $this->assertEquals("FNAME", $lists[0]->getMergeFields()[0]['tag']);
83 | }
84 |
85 | public function testGetList()
86 | {
87 |
88 | $configListProvider = new ConfigListProvider($this->providerFactory, $this->listConfig);
89 | $list = $configListProvider->getList("sampleid2");
90 |
91 | $this->assertTrue($list instanceof SubscriberList);
92 | $this->assertEquals("sampleid2", $list->getListId());
93 | $this->assertEquals("FNAME2", $list->getMergeFields()[0]['tag']);
94 | $this->assertEquals("LNAME2", $list->getMergeFields()[01]['tag']);
95 | $this->assertEquals("url", $list->getWebhookUrl());
96 | $this->assertEquals("secret", $list->getWebhookSecret());
97 | }
98 |
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/tests/Subscriber/ListRepositoryTest.php:
--------------------------------------------------------------------------------
1 | listRepository = new ListRepository($mailchimp);
25 | }
26 |
27 | public function testGetList()
28 | {
29 | $list = $this->listRepository->findById(self::LIST_ID);
30 | //var_dump($list);
31 | $this->assertNotEmpty($list);
32 | $this->assertEquals($list['id'], self::LIST_ID);
33 | }
34 |
35 | public function testSubscribe()
36 | {
37 | // /!\ if toto already exist, this test will throw an error...
38 | $subscriber = new Subscriber('toto@gmail.com', ['FNAME' => 'Toto', 'LNAME' => 'TEST'], ['language' => 'fr']);
39 | $result = $this->listRepository->subscribe(self::LIST_ID, $subscriber);
40 | //var_dump($result);
41 | $this->assertNotEmpty($result);
42 | $this->assertEquals($result['email_address'], 'toto@gmail.com');
43 | $this->assertEquals($result['status'], 'subscribed');
44 | }
45 |
46 | public function testUnsubscribe()
47 | {
48 | $subscriber = new Subscriber('toto@gmail.com', ['FNAME' => 'Toto', 'LNAME' => 'TEST'], ['language' => 'fr']);
49 | $result = $this->listRepository->unsubscribe(self::LIST_ID, $subscriber);
50 | //var_dump($result);
51 | $this->assertNotEmpty($result);
52 | $this->assertEquals($result['email_address'], 'toto@gmail.com');
53 | $this->assertEquals($result['status'], 'unsubscribed');
54 | }
55 |
56 | public function testDelete()
57 | {
58 | $subscriber = new Subscriber('toto@gmail.com', ['FNAME' => 'Toto', 'LNAME' => 'TEST'], ['language' => 'fr']);
59 | $result = $this->listRepository->delete(self::LIST_ID, $subscriber);
60 | $this->assertTrue($result);
61 | }
62 |
63 | public function testBatchSubscribe()
64 | {
65 | $subscribers = [];
66 | $subscriber1 = new Subscriber('tata@gmail.com', ['FNAME' => 'Tata', 'LNAME' => 'TAST'], ['language' => 'fr']);
67 | array_push($subscribers, $subscriber1);
68 | $subscriber2 = new Subscriber('tete@gmail.com', ['FNAME' => 'Tete', 'LNAME' => 'TEEST'], ['language' => 'fr']);
69 | array_push($subscribers, $subscriber2);
70 | $subscriber3 = new Subscriber('tztz@gmail.com', ['FNAME' => 'Tztz', 'LNAME' => 'TZST'], ['language' => 'fr']);
71 | array_push($subscribers, $subscriber3);
72 | $subscriber4 = new Subscriber('trtr@gmail.com', ['FNAME' => 'Trtr', 'LNAME' => 'TRST'], ['language' => 'fr']);
73 | array_push($subscribers, $subscriber4);
74 |
75 | $result = $this->listRepository->batchSubscribe(self::LIST_ID, $subscribers);
76 |
77 | //var_dump($result);
78 | $this->assertNotEmpty($result);
79 | }
80 |
81 | public function testBatchUnsubscribe()
82 | {
83 | $emails = [
84 | 'tata@gmail.com',
85 | 'tete@gmail.com',
86 | 'tztz@gmail.com',
87 | ];
88 |
89 | $result = $this->listRepository->batchUnsubscribe(self::LIST_ID, $emails);
90 |
91 | //var_dump($result);
92 | $this->assertNotEmpty($result);
93 | }
94 |
95 | public function testBatchDelete()
96 | {
97 | $emails = [
98 | 'tata@gmail.com',
99 | 'tete@gmail.com',
100 | 'tztz@gmail.com',
101 | 'trtr@gmail.com',
102 | ];
103 |
104 | $result = $this->listRepository->batchDelete(self::LIST_ID, $emails);
105 |
106 | //var_dump($result);
107 | $this->assertNotEmpty($result);
108 | }
109 |
110 | public function testChangeEmail()
111 | {
112 | // No solution so far with the API V3
113 | // Waiting for further informations
114 | /// http://stackoverflow.com/questions/32224697/mailchimp-api-v3-0-change-subscriber-email
115 | /*$subscriber = new Subscriber('toto@gmail.com', ['FNAME' => 'Toto', 'LNAME' => 'TEST'], ['language' => 'fr']);
116 | $this->listRepository->subscribe(self::LIST_ID, $subscriber);
117 |
118 | $result = $this->listRepository->changeEmailAddress(self::LIST_ID, $subscriber->getEmail(), 'ratata@gmail.com');
119 | var_dump($result);
120 | $this->assertNotEmpty($result);
121 | */
122 | }
123 |
124 | public function testMergeTags()
125 | {
126 | $result = $this->listRepository->addMergeField(self::LIST_ID, ['name' => 'Test', 'type' => 'text', 'tag' => 'MYTEST']);
127 | //var_dump($result);
128 | $this->assertNotEmpty($result);
129 | $this->assertEquals($result['tag'], 'MYTEST');
130 | $result2 = $this->listRepository->updateMergeField(self::LIST_ID, $result['merge_id'], ['tag' => 'SECONDTEST']);
131 | //var_dump($result2);
132 | $this->assertNotEmpty($result2);
133 | $this->assertEquals($result2['tag'], 'SECONDTEST');
134 | $result3 = $this->listRepository->deleteMergeField(self::LIST_ID, $result2['merge_id']);
135 | $this->assertTrue($result3);
136 | }
137 |
138 | public function testWebhook()
139 | {
140 | //$result = $this->listRepository->registerMainWebhook(self::LIST_ID, 'http://requestb.in/rkhf26rk');
141 |
142 | //var_dump($result);
143 | //$this->assertNotEmpty($result);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/tests/Subscriber/ListSynchronizerTest.php:
--------------------------------------------------------------------------------
1 | listRepository = new ListRepository($mailchimp);
24 | }
25 |
26 | public function testSynchronize()
27 | {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Subscriber/SubscriberTest.php:
--------------------------------------------------------------------------------
1 | getSubscriber($email, $fields, $options)->formatMailChimp();
24 |
25 | static::assertEquals($expected, $result);
26 | }
27 |
28 | /**
29 | * @return array
30 | */
31 | public function provider()
32 | {
33 | return [
34 | [
35 | 'test@gmail.com',
36 | ['field1' => 'field1', 'nullable' => null],
37 | ['option' => 'option_value'],
38 | //expected merge_fields
39 | ['merge_fields' => ['field1' => 'field1'], 'option' => 'option_value', 'email_address' => 'test@gmail.com'],
40 | ],
41 | ];
42 | }
43 |
44 | /**
45 | * @param $email
46 | * @param $fields
47 | * @param $options
48 | *
49 | * @return Subscriber
50 | */
51 | private function getSubscriber($email, $fields, $options)
52 | {
53 | return new Subscriber($email, $fields, $options);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------