Completed
Pull Request — master (#506)
by Paweł
21:46 queued 11:28
created

WordpressAdapter::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Core Bundle.
7
 *
8
 * Copyright 2018 Sourcefabric z.ú. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2018 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\CoreBundle\Adapter;
18
19
use Doctrine\ORM\EntityManagerInterface;
20
use GuzzleHttp\ClientInterface;
21
use GuzzleHttp\Exception\RequestException;
22
use GuzzleHttp\Psr7\Response as GuzzleResponse;
23
use SWP\Bundle\ContentBundle\Manager\MediaManagerInterface;
24
use SWP\Bundle\CoreBundle\Model\ArticleInterface;
25
use SWP\Bundle\CoreBundle\Model\ExternalArticle;
26
use SWP\Bundle\CoreBundle\Model\ExternalArticleInterface;
27
use SWP\Bundle\CoreBundle\Model\OutputChannelInterface;
28
use SWP\Bundle\CoreBundle\OutputChannel\External\Wordpress\Post;
29
use SWP\Bundle\CoreBundle\OutputChannel\External\Wordpress\PostInterface;
30
use SWP\Component\Storage\Repository\RepositoryInterface;
31
use Symfony\Component\Serializer\Encoder\JsonEncoder;
32
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
33
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
34
use Symfony\Component\Serializer\Serializer;
35
use Symfony\Component\Serializer\SerializerInterface;
36
37
final class WordpressAdapter implements AdapterInterface
38
{
39
    public const STATUS_DRAFT = 'draft';
40
41
    public const STATUS_PUBLISHED = 'publish';
42
43
    /**
44
     * @var ClientInterface
45
     */
46
    private $client;
47
48
    /**
49
     * @var RepositoryInterface
50
     */
51
    private $externalArticleRepository;
52
53
    /**
54
     * @var EntityManagerInterface
55
     */
56
    private $externalArticleManager;
57
58
    /**
59
     * @var MediaManagerInterface
60
     */
61
    private $mediaManager;
62
63
    /**
64
     * WordpressAdapter constructor.
65
     *
66
     * @param ClientInterface        $client
67
     * @param RepositoryInterface    $externalArticleRepository
68
     * @param EntityManagerInterface $externalArticleManager
69
     * @param MediaManagerInterface  $mediaManager
70
     */
71
    public function __construct(
72
        ClientInterface $client,
73
        RepositoryInterface $externalArticleRepository,
74
        EntityManagerInterface $externalArticleManager,
75
        MediaManagerInterface $mediaManager
76
    ) {
77
        $this->client = $client;
78
        $this->externalArticleRepository = $externalArticleRepository;
79
        $this->externalArticleManager = $externalArticleManager;
80
        $this->mediaManager = $mediaManager;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function create(OutputChannelInterface $outputChannel, ArticleInterface $article): void
87
    {
88
        $post = $this->createPost($outputChannel, $article);
89
        $post->setStatus(self::STATUS_DRAFT);
90
        $response = $this->send($outputChannel, 'posts', $post);
91
        if (201 === $response->getStatusCode()) {
92
            $responseData = \json_decode($response->getBody()->getContents(), true);
93
            $externalArticle = new ExternalArticle($article, (string) $responseData['id'], 'draft');
94
            if (isset($responseData['link'])) {
95
                $externalArticle->setLiveUrl($responseData['link']);
96
            }
97
            if (null !== $responseData['featured_media']) {
98
                $externalArticle->setExtra(['featured_media' => $responseData['featured_media']]);
99
            }
100
            $article->setExternalArticle($externalArticle);
101
            $this->externalArticleRepository->add($externalArticle);
102
        }
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function update(OutputChannelInterface $outputChannel, ArticleInterface $article): void
109
    {
110
        $externalArticle = $this->getExternalArticle($article);
111
        $this->handleArticleUpdate($outputChannel, $article, $externalArticle->getStatus());
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function publish(OutputChannelInterface $outputChannel, ArticleInterface $article): void
118
    {
119
        $this->handleArticleUpdate($outputChannel, $article, self::STATUS_PUBLISHED);
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function unpublish(OutputChannelInterface $outputChannel, ArticleInterface $article): void
126
    {
127
        $this->handleArticleUpdate($outputChannel, $article, self::STATUS_DRAFT);
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function supports(OutputChannelInterface $outputChannel): bool
134
    {
135
        return OutputChannelInterface::TYPE_WORDPRESS === $outputChannel->getType();
136
    }
137
138
    /**
139
     * @param OutputChannelInterface $outputChannel
140
     * @param ArticleInterface       $article
141
     * @param string                 $status
142
     */
143
    private function handleArticleUpdate(OutputChannelInterface $outputChannel, ArticleInterface $article, string $status): void
144
    {
145
        $post = $this->createPost($outputChannel, $article);
146
        $post->setStatus($status);
147
        $externalArticle = $this->getExternalArticle($article);
148
        $response = $this->send($outputChannel, sprintf('posts/%s', $externalArticle->getExternalId()), $post);
149
150
        if (self::STATUS_PUBLISHED === $status) {
151
            $externalArticle->setPublishedAt(new \DateTime());
152
        } elseif (self::STATUS_DRAFT === $status && $externalArticle->getPublishedAt() instanceof \DateTime) {
153
            $externalArticle->setUnpublishedAt(new \DateTime());
154
        }
155
156
        $this->handleExternalArticleUpdate($article, $externalArticle, $response);
157
    }
158
159
    /**
160
     * @param ArticleInterface $article
161
     *
162
     * @return ExternalArticleInterface
163
     */
164
    private function getExternalArticle(ArticleInterface $article): ExternalArticleInterface
165
    {
166
        $externalArticle = $article->getExternalArticle();
167
        if (null === $externalArticle) {
168
            throw new \BadMethodCallException('You try to work on not existing external article');
169
        }
170
171
        return $externalArticle;
172
    }
173
174
    /**
175
     * @param ArticleInterface         $article
176
     * @param ExternalArticleInterface $externalArticle
177
     * @param GuzzleResponse           $response
178
     */
179
    private function handleExternalArticleUpdate(ArticleInterface $article, ExternalArticleInterface $externalArticle, GuzzleResponse $response): void
180
    {
181
        if (200 === $response->getStatusCode()) {
182
            $responseData = \json_decode($response->getBody()->getContents(), true);
183
            $externalArticle->setStatus($responseData['status']);
184
            if (isset($responseData['link'])) {
185
                $externalArticle->setLiveUrl($responseData['link']);
186
            }
187
            if (null !== $responseData['featured_media']) {
188
                $externalArticle->setExtra(['featured_media' => $responseData['featured_media']]);
189
            }
190
            $externalArticle->setUpdatedAt(new \DateTime());
191
            $article->setExternalArticle($externalArticle);
192
            $this->externalArticleManager->flush();
193
        }
194
    }
195
196
    /**
197
     * @param OutputChannelInterface $outputChannel
198
     * @param ArticleInterface       $article
199
     *
200
     * @return Post
201
     */
202
    private function createPost(OutputChannelInterface $outputChannel, ArticleInterface $article): Post
203
    {
204
        $post = new Post();
205
        $post->setTitle($article->getTitle());
206
        $post->setContent($article->getBody());
207
        $post->setSlug($article->getSlug());
208
        $post->setType(PostInterface::TYPE_STANDARD);
209
210
        if (null !== $featureMedia = $article->getFeatureMedia()) {
211
            $image = $featureMedia->getImage();
212
            $edge = 'media';
213
            $externalArticle = $article->getExternalArticle();
214
            if (null !== $externalArticle) {
215
                if (null !== $featuredMediaId = $externalArticle->getExtra()['featured_media']) {
216
                    $edge .= '/'.$featuredMediaId;
217
                }
218
            }
219
220
            try {
221
                $response = $this->send(
222
                    $outputChannel,
223
                    $edge,
224
                    new Post(),
225
                    [
226
                        'headers' => [
227
                            'Content-Type' => $featureMedia->getMimetype(),
228
                            'Content-Disposition' => 'attachment; filename="'.$image->getAssetId().'.'.$image->getFileExtension().'"',
229
                        ],
230
                        'body' => $this->mediaManager->getFile($article->getFeatureMedia()->getImage()),
231
                        'timeout' => 5,
232
                    ]
233
                );
234
                $decodedBody = \json_decode($response->getBody()->getContents(), true);
235
                $post->setFeaturedMedia($decodedBody['id']);
236
            } catch (RequestException $e) {
237
                // ignore feature media
238
            }
239
        }
240
241
        $post->setTags($article->getKeywords());
242
243
        return $post;
244
    }
245
246
    /**
247
     * @param OutputChannelInterface $outputChannel
248
     * @param string                 $endpoint
249
     * @param Post                   $post
250
     * @param array|null             $requestOptions
251
     *
252
     * @return GuzzleResponse
253
     */
254
    private function send(OutputChannelInterface $outputChannel, string $endpoint, Post $post, array $requestOptions = null): GuzzleResponse
255
    {
256
        $url = $outputChannel->getConfig()['url'];
257
        $authorizationKey = $outputChannel->getConfig()['authorization_key'];
258
259
        if (null === $requestOptions) {
260
            $requestOptions = [
261
                'headers' => [
262
                    'Content-Type' => 'application/json',
263
                ],
264
                'body' => $this->getSerializer()->serialize($post, 'json'),
265
                'timeout' => 5,
266
            ];
267
        }
268
269
        if (isset($requestOptions['headers'])) {
270
            $requestOptions['headers']['Authorization'] = $authorizationKey;
271
        }
272
        /** @var \GuzzleHttp\Psr7\Response $response */
273
        $response = $this->client->post($url.'/wp-json/wp/v2/'.$endpoint, $requestOptions);
274
275
        return $response;
276
    }
277
278
    /**
279
     * @return SerializerInterface
280
     */
281
    private function getSerializer(): SerializerInterface
282
    {
283
        $encoders = [new JsonEncoder()];
284
        $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
285
        $normalizers = [$normalizer];
286
287
        return new Serializer($normalizers, $encoders);
288
    }
289
}
290