· 6 years ago · Sep 03, 2019, 07:44 AM
1<?php
2namespace WSS\Service;
3
4use WSS\Entity\Asset\MamaplaCategory;
5use WSS\Mamapla\ThemeTreeParser;
6use WSS\Util\MtomSoapClient;
7use Zend\Soap\Client;
8use WSS\Mamapla\Synchronization;
9use Zend\Cache\Storage\StorageInterface;
10use WSS\Alfresco\AlfrescoUtils;
11use Zend\Uri\UriFactory;
12use WSS\Mamapla\Factory;
13use Doctrine\DBAL\Exception\ServerException;
14use WSS\Service\Alfresco\QueryCacheService;
15use WSS\Mamapla\Asset;
16use WSS\Mamapla\Utils;
17use WSS\Mamapla\DuplicateException;
18use WSS\Entity\Asset\StructuredKeyword;
19use WSS\Rest\Request;
20
21/**
22 * So far in in experimental state!
23 * Will be used to synchronize data with Mamapla/Brandmaker
24 *
25 * Implements part of the Mamapla Media Pool Asset SOAP API
26 * See
27 * @link https://schott.brandmaker.com/webservices/MediaPool/?wsdl
28 * @link https://projectboard.dbc-gmbh.com/attachments/download/10585/Marketing-Efficiency-Cloud_WebServices_MediaPool-Asset-API-SOAP_5.9.pdf
29 *
30 * @author wichmann
31 *
32 */
33class MamaplaService
34{
35
36 const MAMAPLA_CATEGORY_TREE_ROOT_PATH = '/cm:categoryRoot/cm:generalclassifiable/cm:MaMaPla';
37
38 /**
39 * Mamapla config, see mamapla.global.php
40 * @var array
41 */
42 private $config;
43
44 /**
45 * @var AlfrescoService
46 */
47 private $alfService;
48
49 /**
50 *
51 * @var SearchService
52 */
53 private $searchService;
54
55 /**
56 *
57 * @var MediaService
58 */
59 private $mediaService;
60
61 /**
62 *
63 * @var FilteredEntityManager
64 */
65 private $em;
66 /**
67 * permanent cache for storing job state
68 * @var StorageInterface
69 */
70 private $cache;
71
72 /**
73 * Cached sftp resource
74 *
75 * @var string
76 */
77 private $sftp;
78
79 /**
80 *
81 * @var Client
82 */
83 private $client;
84
85 /**
86 *
87 * @var QueryCacheService
88 */
89 private $queryCacheService;
90
91 /**
92 *
93 * @param array $config Mamapla config
94 * @param AlfrescoService $alfService
95 * @param SearchService $searchService
96 * @param FilteredEntityManager $em
97 * @param MediaService $mediaService
98 * @param StorageInterface $cache
99 */
100 public function __construct($config, AlfrescoService $alfService, SearchService $searchService, FilteredEntityManager $em,
101 MediaService $mediaService, StorageInterface $cache, QueryCacheService $queryCacheService)
102 {
103 $this->config = $config;
104 $this->alfService = $alfService;
105 $this->searchService = $searchService;
106 $this->em = $em;
107 $this->mediaService = $mediaService;
108 $this->cache = $cache;
109 $this->queryCacheService = $queryCacheService;
110 }
111
112 /**
113 * Publishes a video into Online folder
114 * @param \WSS_Asset_Base $asset
115 */
116 public function moveToOnline(\WSS_Asset_Base $asset, $suffix = null, $path=null)
117 {
118 $params = $asset->toAlfrescoUpload();
119 $params['destination'] = \WSS_Asset_XPath::convertToOnline(dirname($asset->getQnamePath()));
120 if ($suffix!=null) {
121 $params['suffix'] = $suffix;
122 }
123 $params['owner'] = $this->alfService->loginKey();
124 \WSS_Logger::debug("moveToOnline $asset with getQnamePath: " . $asset->getQnamePath());
125 \WSS_Logger::debug("moveToOnline $asset with destination: " . $params['destination']);
126 \WSS_Asset_ManagerFactory::createCommonAdmin()->persist($params, $path);
127 }
128
129 /**
130 * @param \WSS_Asset_Base $asset
131 */
132 public function moveToArchive(\WSS_Asset_Base $asset)
133 {
134 $params = $asset->toAlfrescoUpload();
135 $params['destination'] = \WSS_Asset_XPath::convertToArchive(dirname($asset->getQnamePath()));
136 $params['suffix'] = '_';
137 $params['owner'] = $this->alfService->loginKey();
138 \WSS_Asset_ManagerFactory::createCommonAdmin()->persist($params);
139 }
140
141 /**
142 * @param \WSS_Asset_Base $asset
143 */
144 public function moveFromArchiveToMamapla(\WSS_Asset_Base $asset)
145 {
146 $params = $asset->toAlfrescoUpload();
147 $params['destination'] = \WSS_Asset_XPath::convertArchiveToMamapla(dirname($asset->getQnamePath()));
148 $params['suffix'] = '_';
149 $params['owner'] = $this->alfService->loginKey();
150 \WSS_Asset_ManagerFactory::createCommonAdmin()->persist($params);
151 }
152
153 /**
154 * @return array
155 */
156 public function getConfig()
157 {
158 return $this->config;
159 }
160
161 /**
162 * @param array $config
163 */
164 public function setConfig($config)
165 {
166 $this->config = $config;
167 }
168
169 /**
170 *
171 * @param string $unitKey
172 * @param string $path of page which may map to special category, has priority
173 * @return string|NULL full cat xpath encoded
174 */
175 public function contextToCategory($unitKey, $path = null) {
176 if (!empty($path)) {
177 foreach ($this->config['path.category.map'] as $regex => $cat) {
178 if (preg_match($regex, $path)===1) {
179 return $cat;
180 }
181 }
182 }
183 if (isset($this->config['unit.category.map'][$unitKey])) {
184 return $this->config['unit.category.map'][$unitKey];
185 } else {
186 return null;
187 }
188 }
189
190 /**
191 * Checks given cats for necessary additions and returns only these additional cats.
192 * @param array $sourceCats
193 * @return string[]
194 */
195 public function syncCategoryAdditions(array $sourceCats)
196 {
197 return AlfrescoUtils::addCategories($sourceCats, $this->config['sync.category.additions']);
198 }
199
200 /**
201 *
202 * @param string $virtualDb
203 * @throws \InvalidArgumentException
204 * @return string|NULL unit key, null if moved to not relevant Mamapla pool or archive or recycle bin
205 */
206 public function mapToUnit($virtualDb)
207 {
208 if (preg_match('/\btest\b/i', $virtualDb)) {
209 return 'test';
210 } else if (in_array($virtualDb, $this->config['sync.vdb.exclustions'])) {
211 return null;
212 } else if (empty($virtualDb)) {
213 throw new \InvalidArgumentException("VDB '$virtualDb' has no CMS unit pool mapping");
214 } else {
215 if (isset($this->config['vdb'][$virtualDb])) {
216 return $this->config['vdb'][$virtualDb];
217 } else {
218 throw new \InvalidArgumentException("VDB '$virtualDb' has no CMS unit pool mapping");
219 }
220 }
221 }
222
223 /**
224 * Updates field mamaplaIdDuplicate for non Mamapla media in Alfresco.
225 * mamaplaIdDuplicate like M-123 references a medium in Mamapla with the same file hash, so really the same file.
226 * Alfresco media published from Mamapla should have a duplicate in Mamapla, which is their origin. Therefore we do not set
227 * mamaplaIdDuplicate for published Mamapla media.
228 * @see
229 */
230 public function updateMamaplaIdDuplicate()
231 {
232 $alfService = $this->alfService;
233 // Loop on units only to not have to large media sets at once
234 foreach (\WSS_Unit_Table::instance()->fetchAll() as $unit) {
235 $this->alfService->task('TYPE:"{http://www.alfresco.org/model/content/1.0}content" AND ' . \WSS_Asset_XPath::FILTER_ONLINE . ' AND PATH:"'
236 . \WSS_Asset_XPath::newInstanceFromUnit($unit)->toOnlineDownloadFolderRecursive() . '" and NOT schott:mamaplaId:"M*" ', function($asset) use ($alfService) {
237 /* @var $asset \WSS_Asset_Base */
238 $mamaplaAsset = $this->mediumByHashForAsset($asset->getId()) /* @var $mamaplaAsset \WSS_Asset_Base */;
239 if (!$mamaplaAsset->isEmpty()) {
240 if ($asset->getMamaplaIdDuplicate() != $mamaplaAsset->getMamaplaId()) {
241 //echo "update duplicate $asset\n";
242 $params = [
243 'id' => $asset->getId(),
244 'mamaplaIdDuplicate' => $mamaplaAsset->getMamaplaId()
245 ];
246 $params['owner'] = $this->alfService->loginKey();
247 $alfService->assetUpload($alfService->systemLoginAdmin(AlfrescoService::TENANT_COMMON), $params);
248 }
249 }
250 }, true);
251 }
252 }
253
254 public function mediumByHashForAsset($assetId)
255 {
256 return $this->mediumByHash($this->alfService->internalAssetUri($assetId, $this->alfService->systemLoginAdmin(AlfrescoService::TENANT_COMMON)));
257 }
258
259 /**
260 *
261 * @param string $path absolute file path
262 * @return \WSS_Asset_Base
263 */
264 public function mediumByHash($path)
265 {
266 $params = new \stdClass();
267 $params->mediaDetailsData = new \stdClass();
268 $params->mediaDetailsData->mediaHash = base64_encode(md5_file($path, true));
269
270 $mediaDetails = $this->createSoapClient()->getMediaDetails($params);
271 if (isset($mediaDetails->return->success) && ! $mediaDetails->return->success) {
272 \WSS_Logger::debug("Not found in mamapla: $path " . json_encode($mediaDetails) . " \n");
273 return \WSS_Asset_Null::newInstance();
274 } else {
275 \WSS_Logger::debug("Found in mamapla: $path " . json_encode($mediaDetails) . "\n");
276 return Factory::newInstance()->createBySoap($mediaDetails, $this);
277 }
278
279 return $mediaDetails;
280 }
281
282 /**
283 * Doeas a SOAP call on getMediaDetails.
284 * @param string $id Mamapla ID
285 * @throws \InvalidArgumentException
286 * @return Asset
287 */
288 public function getMediaDetails($id)
289 {
290 $params = new \stdClass();
291 $params->mediaDetailsData = new \stdClass();
292 $params->mediaDetailsData->mediaGuid = Utils::idToInt($id);
293
294 $mediaDetails = $this->createSoapClient()->getMediaDetails($params);
295 if (isset($mediaDetails->return->success) && ! $mediaDetails->return->success) {
296 throw new \InvalidArgumentException("mediaDetailsData($id) not found: " . json_encode($mediaDetails));
297 } else {
298 \WSS_Logger::debug("mediaDetailsData($id) found: " . json_encode($mediaDetails));
299 return Factory::newInstance()->createBySoap($mediaDetails, $this);
300 }
301
302 }
303
304 /**
305 * Publishes media in Mamapla using given filename.
306 * @param string $id Mamapla ID without
307 */
308 public function webPublishedMedia($id, $fileName)
309 {
310 // see webPublishedMediaArgument in WSDL
311 $params = new \stdClass();
312 $params->webPublishedMediaData = new \stdClass();
313 $params->webPublishedMediaData->mediaGUID = Utils::idToInt($id);
314 $params->webPublishedMediaData->fileName = $fileName;
315
316 $webPublishedMedia = $this->createSoapClient()->webPublishedMedia($params);
317 if (isset($webPublishedMedia->return->success) && $webPublishedMedia->return->success) {
318 \WSS_Logger::debug("webPublishedMedia($id) success: " . json_encode($webPublishedMedia));
319 } else {
320 throw new \InvalidArgumentException("Web publishing did not work for webPublishedMedia($id, $fileName): " . json_encode($webPublishedMedia));
321 }
322
323 }
324
325 /**
326 * Returns Alfresco category path to given Mamapla theme ID
327 * @param int $id Mamapla theme ID
328 */
329 public function themePath($id)
330 {
331 if (empty($this->themeById($id))) {
332 throw new \InvalidArgumentException("themePath() No theme found for $id");
333 }
334 return self::MAMAPLA_CATEGORY_TREE_ROOT_PATH . '/cm:' . implode('/cm:', array_map(function($item) {return $item->name;}, $this->themeById($id)->pathToRoot()));
335 }
336
337 //TODO how to add missing categories? Is there already function to use MamaplaCategory#jsonSerialize() with AlfService::categoryTreeAdd()
338 //TODO Can we somehow also implement mamapla theme move sync? may be first create destination, use categoryReplaceReferences
339 // to move media to new theme structure and finally delete old now obsolete Alf themes.
340 /**
341 * Sync with Mamapla
342 * We support the following changes in Mamapla: new theme, deleted theme, name update.
343 * For move operations of themes in Mamapla only a check is done, but they are not supported by theme sync.
344 * As of 2018-08-22 there seemed to have been no theme move actions in Mamapla.
345 *
346 * TODO assert ->
347 * This method does rely on MamaplaCategory db to be in sync with Alfresco Mamapla theme tree. Therefore
348 * it is important that it gets only processed on fixed servers which are responsible for
349 * Mamapla synchronisation. Otherwise sync may produced a corrupted theme tree because of an outdated
350 * db.
351 * @see MamaplaCategory::jsonSerialize()
352 * @see AlfrescoService::categoryTreeAdd()
353 */
354 private function syncThemeTree()
355 {
356
357 $themes = $this->themesFromMamapla();
358 // Try to find deleted themes
359 $currentIds = array_map(function($v) {return $v->id;}, $themes);
360
361 //TODO Test! Does it work?
362 // Try to find deleted themes
363 // Iterate on stored themes to find the deleted ones
364 foreach ($this->em->getRepository(MamaplaCategory::class)->findAll() as $oldTheme) {
365 if (array_search($oldTheme->id, $currentIds) === false) {
366 // Corresponding Alfresco category requires deletion
367 $obsoleteCatPath = AlfrescoUtils::encodeISO9075Path($this->themePath($oldTheme->id));
368 // Should not be necessary to do cache clear before removal but just to be on the save side.
369 $this->queryCacheService->clearByPath($obsoleteCatPath);
370 $obsoleteCatId = \WSS_Asset_ManagerFactory::createCommonAdmin()
371 ->createQuery()->setFullQuery('PATH:"' . $obsoleteCatPath . '"')
372 ->getResultItem()->getId();
373 if ($obsoleteCatId) {
374 \WSS_Logger::info("Theme needs deletion in Alfresco: " . $this->themePath($oldTheme->id) . " with Alf ID $obsoleteCatId");
375 $this->alfService->categoryDelete($obsoleteCatId);
376 $this->queryCacheService->clearByPath($obsoleteCatPath);
377 }
378 }
379 }
380
381 // If deletion in Alf works well, then delete also in DB. We do this afterwards
382 // because the DB storage is only source to find old obsolete themes
383 foreach ($this->em->getRepository(MamaplaCategory::class)->findAll() as $oldTheme) {
384 // Make sure that theme still exists and is not deleted in cascade deletion.
385 if (array_search($oldTheme->id, $currentIds) === false && $this->em->find(MamaplaCategory::class, $oldTheme->id)) {
386 \WSS_Logger::info("Theme needs deletion in DB: " . $this->themePath($oldTheme->id));
387 $this->em->remove($oldTheme);
388 }
389 }
390 $this->em->flush();
391 $this->em->clear();
392
393 // Add new and update names of existing ones. Has to be done after deletion to avoid name conflicts.
394 foreach ($themes as $theme) { /* @var $theme MamaplaCategory */
395 $oldTheme = $this->themeById($theme->id);
396 // Only store new Mamapla categories.
397 // It is important to not update existing ones.
398 //TODO true? Why not use categoryUpdateName()?
399 // Changed names and structure would always result in newly added Alfresco categories as duplicates of the old ones.
400 if ($oldTheme==null) {
401 $this->em->merge($theme);
402 } else if ($oldTheme->name!=$theme->name) {
403 // Corresponding Alfresco category requires deletion
404 $outdatedCatPath = AlfrescoUtils::encodeISO9075Path($this->themePath($oldTheme->id));
405 // Should not be necessary to do cache clear before removal but just to be on the save side.
406 $this->queryCacheService->clearByPath($outdatedCatPath);
407 $outdatedCatId = \WSS_Asset_ManagerFactory::createCommonAdmin()
408 ->createQuery()->setFullQuery('PATH:"' . $outdatedCatPath . '"')
409 ->getResultItem()->getId();
410 // Just in case DB is outdated and update is already done in Alfresco we do not check if old cat path still exists in Alfresco.
411 \WSS_Logger::info("Theme name update in Alfresco: " . $this->themePath($oldTheme->id) . " with Alf ID $outdatedCatId done. Update also in DB to new name: " . $theme->name);
412 $this->em->merge($theme);
413 $this->queryCacheService->clearByPath($outdatedCatPath);
414 }
415 }
416 $this->em->flush();
417 // important to force loading from db again. Otherwise OneToMany subCategories is not filled
418 $this->em->clear();
419
420 // Check for theme move actions in Mamapla
421 foreach ($themes as $theme) { /* @var $theme MamaplaCategory */
422 $oldTheme = $this->themeById($theme->id);
423 $themePath = AlfrescoUtils::encodeISO9075Path($this->themePath($oldTheme->id));
424 $alfCat = \WSS_Asset_ManagerFactory::createCommonAdmin()
425 ->createQuery()->setFullQuery('PATH:"' . $themePath . '"')
426 ->getResultItem();
427 if ($alfCat->isEmpty()) {
428 \WSS_Logger::info("Theme path '$themePath' cannot be found as category path in Alfresco. Thme might have moved in Mamapla.");
429 }
430 }
431
432
433 // Update for new Alf categories
434 $themeTree = $this->themeTree();
435 //echo json_encode($themeTree);
436 $this->alfService->categoryTreeAdd(json_encode($themeTree));
437
438 }
439
440 private function themesFromMamapla()
441 {
442
443 try {
444
445 $client = new MtomSoapClient(
446 $this->config['uri'] . '/webservices/Theme/?wsdl',
447 [
448 'soap_version' => SOAP_1_1,
449 'login' => $this->config['api']['login'],
450 'password' => $this->config['api']['password'],
451 'use' => SOAP_LITERAL,
452 // only for debugging:
453 //'trace' => 1,
454 //'exceptions' => 0
455 ]
456 );
457
458 $params = new \stdClass();
459 $params->receiveFullThemeTree = new \stdClass();
460 $result = $client->receiveFullThemeTree($params);
461
462 if (!isset($result->return->themes)) {
463 throw new \InvalidArgumentException("Loading themes failed");
464 } else {
465 return ThemeTreeParser::instance()->parse($result->return->themes);
466 }
467
468 } catch (\Exception $e) {
469 // We should only catch SoapFault as remote ServerException which sometimes occures due to
470 // Mamapla instability. But catching only SoapFault does not seem to work.
471 throw new \WSS\Mamapla\ServerException('Mamapla sync of theme tree failed due to Mamapla server fault',null, $e);
472 }
473
474 }
475
476 /**
477 * Updates stored data of Mamapla theme tree in DB and returns tree in format to be Json encoded
478 * as structure for Alfresco updates.
479 * @throws \InvalidArgumentException
480 * @return \stdClass
481 */
482 private function themeTree()
483 {
484
485 $mamaplaCategory = new MamaplaCategory();
486 $mamaplaCategory->name = 'MaMaPla';
487 $mamaplaCategory->subCategories = $this->themeRoots();
488
489 $obj = new \stdClass();
490 $obj->cats[] = $mamaplaCategory;
491 $obj->root = null;
492
493 return $obj;
494 }
495
496 /**
497 * Empties stored themes in DB
498 */
499 private function emptyThemeDb()
500 {
501 // empty table first
502 $rsm = new \Doctrine\ORM\Query\ResultSetMapping();
503 // otherwise problems with parent foreign key
504 $this->em->getConnection()->query('SET FOREIGN_KEY_CHECKS=0');
505 $this->em->createQueryBuilder()
506 ->delete('' . MamaplaCategory::class . '')
507 ->getQuery()
508 ->execute();
509 $this->em->flush();
510 $this->em->getConnection()->query('SET FOREIGN_KEY_CHECKS=1');
511 $this->em->flush();
512
513 }
514
515 /**
516 * Fetches keywords from database
517 * @see StructuredKeyword
518 * @return array list of keywords
519 */
520 public function structuredKeywords()
521 {
522 $keywords = [];
523 $entities = $this->em->createQueryBuilder()->select('s')->from(StructuredKeyword::class, 's')->getQuery()->execute();
524 foreach($entities as $entity){ /* @var $entity StructuredKeyword */
525 $keywords[] = $entity->structuredKeyword;
526 }
527 return $keywords;
528 }
529
530 /**
531 * Gets list of structured keywords from meta data XML from Mamapla and sync database table with list.
532 * Check data/mamapla/last-GetPublishedMedias.xml for list from all currently published media.
533 * written by Mamapla Synchronization.
534 * update in DB is part of Mamapla Synchronization.
535 */
536 public function updateStructuredKeywords()
537 {
538 $reader = new \Zend\Config\Reader\Xml();
539 $data = $reader->fromFile(Synchronization::backupPath());
540 $keywords = [];
541 foreach($data['published_media'] as $media){
542 if(key_exists('structured_keywords', $media)){
543 foreach($media['structured_keywords']['keywords'] as $keyword){
544 if(is_string($keyword)){
545 $keywords[$keyword] = true;
546 }
547 else if(is_array($keyword['keyword'])){
548 $keywords[$keyword['keyword'][1]['_']] = true;
549 }
550 else {
551 $keywords[$keyword['keyword']] = true;
552 }
553 }
554 }
555 }
556 $this->em->createQueryBuilder()->delete(StructuredKeyword::class)->getQuery()->execute();
557 $keywords = array_keys($keywords);
558 usort($keywords, 'strnatcasecmp');
559 foreach($keywords as $key => $keyword){
560 $entity = new StructuredKeyword();
561 $entity->structuredKeyword = $keyword;
562 $this->em->persist($entity);
563 $this->em->flush();
564 }
565 }
566
567 /**
568 *
569 * @param string $source_path
570 */
571 public function themeRoots()
572 {
573 return $this->em->createQuery('SELECT u FROM ' . MamaplaCategory::class. ' u ' . ' WHERE u.parent is null')
574 ->getResult();
575 }
576
577 /**
578 *
579 * @param int $id
580 * @return MamaplaCategory
581 */
582 public function themeById($id)
583 {
584 return $this->em->find(MamaplaCategory::class, $id);
585 }
586
587 /**
588 * Returns null if no Mamapla category path.
589 * @param string $path
590 * @return MamaplaCategory|null
591 */
592 public function themeByPath($path)
593 {
594 if (strpos($path, static::MAMAPLA_CATEGORY_TREE_ROOT_PATH)===false) {
595 return null;
596 }
597 $path = AlfrescoUtils::qnamePathToNamePath(str_replace(static::MAMAPLA_CATEGORY_TREE_ROOT_PATH, '', $path));
598 $children = $this->themeRoots();
599 $theme = null;
600 foreach (explode('/', $path) as $name) {
601 foreach ($children as $child) { /* @var MamaplaCategory */
602 if ($child->name == $name) {
603 $theme = $child;
604 $children = $child->subCategories;
605 break;
606 }
607 }
608 }
609 return $theme;
610 }
611
612 public function themeByParent($parent)
613 {
614 return $this->em->createQuery('SELECT u FROM ' . MamaplaCategory::class. ' u ' . ' WHERE u.parent = :parent')
615 ->setParameter('parent', $parent)
616 ->getResult();
617 }
618
619 public function themesAddToAsset(array $themes, Asset $asset)
620 {
621 $asset->setCategoryPaths(array_map(function($i){
622 return $this->themePath($i);
623 }, $themes));
624 }
625
626 /**
627 *
628 * @param int|string $id migth be either of type "123" or "M-123"
629 * @param string $prevFormat values are small, middle, large, thumbnail
630 * @return \WSS_File_Temporary
631 */
632 public function fetchPreview($id, $prevFormat)
633 {
634 $id = Utils::idToInt($id);
635
636 $source = UriFactory::factory($this->config['uri'] . '/rest/mp/previews/'.$prevFormat.'/asset/'.$id.'/')
637 ->setUserInfo($this->config['api']['login']. ':' . $this->config['api']['password'])
638 ->toString();
639 $dest = \WSS_File_Temporary::newInstanceRandomFilename('mamaplaPreview');
640 copy($source, $dest->get_path());
641
642 return $dest;
643 }
644
645 /**
646 * Experimental and does not work right now because of 403 forbidden response.
647 * @link https://projectboard.dbc-gmbh.com/attachments/download/10620/Marketing-Efficiency-Cloud_WebServices_MediaPool-Search-API_5.9.pdf
648 * @param string $query
649 */
650 public function search($query)
651 {
652 $request = new Request();
653 $request->getQuery()->set('query',$query)
654 ->set('resultDataType', 'bm');
655
656 $client = \WSS\Rest\Client::newInstance($this->config['uri'] . '/rest/mp/search');
657 // Does not work:
658 $client->setAuth($this->config['api']['login'], $this->config['api']['password']);
659 \WSS_Logger::debug("Mamapla search($query) call: $request and URI ". $client->getBaseUri());
660 // So login does not work as expected. But with have access. Just login with 'schott.cms'
661 // in https://schott.brandmaker.com/ and then try to access
662 // https://schott.brandmaker.com/rest/mp/search?query=image&resultDataType=bm
663 // and you will get data.
664
665 $response = $client->sendUri($request);
666
667
668 echo $response->getHttpResponse()->toString();
669
670 echo $response->getBody();
671 }
672
673 /**
674 *
675 * @return \Zend\Soap\Client
676 */
677 private function createSoapClient()
678 {
679
680 if (! isset($this->client)) {
681 $this->client = new Client($this->config['uri'] . '/webservices/MediaPool/?wsdl');
682 $this->client->setSoapVersion(SOAP_1_1);
683 $this->client->setHttpLogin($this->config['api']['login']);
684 $this->client->setHttpPassword($this->config['api']['password']);
685 }
686
687 return $this->client;
688 }
689
690 /**
691 * Loads medium into memory to send it via a SOAP document.
692 * Implements uploadMediaData() of Mamapla SOAP API.
693 * This seems to work only for small media up to around 100kb. For larger once we get errors like
694 * "A JSONArray text must start with '[' at 1 [character 2 line 1]"
695 * @param Asset $asset contains name for filename for upload.
696 * @param string $path to uploaded file
697 * @throws \InvalidArgumentException|DuplicateException
698 * @return Asset with added new ID
699 */
700 public function uploadMedia(Asset $asset, $path)
701 {
702
703 $params = new \stdClass();
704 $params->uploadMediaData = new \stdClass();
705 $params->uploadMediaData->fileName = $asset->getName();
706 // Base 64 encoding done by SoapClient
707 $params->uploadMediaData->fileData = file_get_contents($path);
708 $response = $this->createSoapClient()->uploadMedia($params);
709
710 // exampel: {"return":{"success":true,"error":"","mediaGuid":"87845"}}
711 \WSS_Logger::debug("uploadMedia(Asset $asset, $path) response: $path " . json_encode($response) . " \n");
712 if (isset($response->return->success) && $response->return->success) {
713 return $asset->setMamaplaId(\WSS_Asset_Base::MAMAPLA_ID_PREFIX . $response->return->mediaGuid);
714 } else {
715 if ($e = DuplicateException::checkForDuplication($response->return->error)) {
716 return $e;
717 } else {
718 \WSS_Logger::warn("uploadMedia(Asset $asset) failed: " . json_encode($response));
719 throw new \InvalidArgumentException($response->return->error);
720 }
721 }
722
723 }
724
725
726 /**
727 * Same as uploadMedia but works on all file sizes. Implements uploadMediaAsStream() for uploadind new media.
728 * The implementation has to work stream base and avoids loading the complete medium file into memeory.
729 * Standard PHP SOAP extension does not allow this. Also \nusoap_client_mime loads file into memory.
730 * Therefore a custom implementation based for example on curl is required.
731 * Test the SOAP Mediapool Asset API.
732 * @see \WSS\Rest\Client::sendFormdataCurl() see for stream based curl upload
733 * @TODO just not working test code, implement me, is to replace MamaplaService::uploadMedia()
734 * @see MamaplaService::uploadMedia()
735 * @param Asset $asset
736 * @param string $path
737 */
738 public function uploadMediaAsStream(Asset $asset, $path)
739 {
740 $params = new \stdClass();
741 $params->mediaDetailsData = new \stdClass();
742 $params->mediaDetailsData->mediaGuid = Utils::idToInt('22797');
743
744// $params = new \stdClass();
745// $params->fileName = $asset->getName();
746// $params->fileData = 'cid:'.$asset->getName();
747 $wsdlfile = $this->config['uri'] . '/webservices/MediaPool/?wsdl'; // Edit this
748
749 $options = array(
750 'url' => $this->config['uri'] . '/webservices/MediaPool/?wsdl', // e.g. 'https://webservice.example.com/'
751 'userpwd' => $this->config['api']['login'].':'.$this->config['api']['password'] // e.g. 'superSecretPassphrase'
752 );
753 $client = new \WSS\Rest\SoapClientCurl($wsdlfile, $options);
754 try {
755// $response = $client->uploadMediaAsStream($params);
756 $response = $client->getMediaDetails($params);
757 print_r($response);
758 } catch (\SoapFault $e) {
759 echo $e->getMessage();
760 }
761
762 }
763
764 /**
765 * Same as uploadMediaAsStream() but for media file updates.
766 * @TODO implement me
767 * @param Asset $asset
768 * @param string $path
769 */
770 public function uploadMediaVersionAsStream(Asset $asset, $path)
771 {
772
773 }
774
775 /**
776 * Updates meta data of existing Mamapla medium.
777 * See uploadMetadataArgument in WSDL.
778 * @param Asset $asset
779 * @link https://schott.brandmaker.com/webservices/MediaPool/?wsdl
780 * @throws \InvalidArgumentException
781 * @return Asset
782 */
783 public function uploadMetaData(Asset $asset)
784 {
785 if (empty($asset->getMamaplaId())) {
786 throw new \InvalidArgumentException("uploadMetaData(Asset $asset) called without set Mamapla ID");
787 }
788
789 $params = new \stdClass();
790 // See uploadMetadataArgument in WSDL
791 $params->uploadMetaData = $asset->toMamaplaUpload($this);
792 // Additional Mamapla upload options:
793 $params->uploadMetaData->strict = false;
794 // Better not set this, because it would require complete update of all props to be save.
795 //$params->uploadMetaData->keepIfEmpty = false;
796 \WSS_Logger::debug("uploadMetaData() upload: " . json_encode($params));
797
798 $response = $this->createSoapClient()->uploadMetaData($params);
799 // exampel: {"return":{"success":true,"error":"","mediaGuid":"87845"}}
800 if (isset($response->return->success) && $response->return->success) {
801 \WSS_Logger::debug("uploadMetaData(Asset $asset) success; response: " . json_encode($response));
802 return $asset;
803 } else {
804 \WSS_Logger::warn("uploadMetaData(Asset $asset) failed: " . json_encode($response));
805 throw new \InvalidArgumentException($response->return->error);
806 }
807
808 }
809
810 public function syncPublishedMedia()
811 {
812
813 // Just to be on save side, make also sure that no media conversion is running.
814 $this->mediaService->busyCheck();
815 $cacheKey = 'syncPublishedMedia';
816// if ($this->cache->hasItem($cacheKey))
817// {
818// throw new \OverflowException("Mamapla syncPublishedMedia already started!");
819// }
820// $this->cache->setItem($cacheKey, true);
821
822 try {
823
824 $uri = $this->config['uri'] . '/GetPublishedMedias.do';
825
826 $this->syncThemeTree();
827
828 $sync = new Synchronization($uri, $this, $this->searchService, $this->mediaService, $this->alfService);
829 $sync->synchronize();
830
831 } finally {
832 $this->cache->removeItem($cacheKey);
833 }
834
835 }
836
837 /**
838 * We have to create a sftp connection and reuse the connection for multiple file transfers.
839 * It is no option to simply work with a sftp URI in a copy() call, because this requires
840 * a new login for each transfer and login is very slow with Mamapla sftp.
841 */
842 private function sftp()
843 {
844 if (! $this->sftp) {
845 $config = $this->config['sftp'];
846 $connection = ssh2_connect($config['host'], $config['port']);
847 ssh2_auth_password($connection, $config['login'], $config['password']);
848 $this->sftp = ssh2_sftp($connection);
849 }
850
851 return $this->sftp;
852 }
853
854 public function copyAsset($filename)
855 {
856 $sftp = $this->sftp();
857 $destination = \WSS_File_Temporary::newInstanceRandomFilename('mamapla-test');
858
859 // Because of buggy ssh2 extension we have to wrap the sftp resource with intval(). See
860 // http://stackoverflow.com/questions/41118475/segmentation-fault-on-fopen-using-sftp-and-ssh2
861 // https://bugs.php.net/bug.php?id=73524
862 $istream = fopen("ssh2.sftp://" . intval($sftp) . $this->config['sftp']['publishing_folder'] . '/' . $filename, 'r');
863 $ostream = fopen($destination->get_path(), 'w');
864 stream_copy_to_stream($istream, $ostream);
865 \WSS_Logger::debug("Mamapla file $filename copied to: " . $destination->get_path());
866
867 fclose($istream);
868 fclose($ostream);
869
870 return $destination->get_path();
871 }
872}