Issues (3627)

bundles/PluginBundle/Helper/IntegrationHelper.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\PluginBundle\Helper;
13
14
use Doctrine\ORM\EntityManager;
15
use Mautic\CoreBundle\Helper\BundleHelper;
16
use Mautic\CoreBundle\Helper\CoreParametersHelper;
17
use Mautic\CoreBundle\Helper\DateTimeHelper;
18
use Mautic\CoreBundle\Helper\PathsHelper;
19
use Mautic\CoreBundle\Helper\TemplatingHelper;
20
use Mautic\PluginBundle\Entity\Integration;
21
use Mautic\PluginBundle\Entity\Plugin;
22
use Mautic\PluginBundle\Integration\AbstractIntegration;
23
use Mautic\PluginBundle\Integration\UnifiedIntegrationInterface;
24
use Mautic\PluginBundle\Model\PluginModel;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\Finder\Finder;
27
28
class IntegrationHelper
29
{
30
    /**
31
     * @var ContainerInterface
32
     */
33
    private $container;
34
35
    /**
36
     * @var EntityManager
37
     */
38
    protected $em;
39
40
    /**
41
     * @var PathsHelper
42
     */
43
    protected $pathsHelper;
44
45
    /**
46
     * @var BundleHelper
47
     */
48
    protected $bundleHelper;
49
50
    /**
51
     * @var CoreParametersHelper
52
     */
53
    protected $coreParametersHelper;
54
55
    /**
56
     * @var TemplatingHelper
57
     */
58
    protected $templatingHelper;
59
60
    /**
61
     * @var PluginModel
62
     */
63
    protected $pluginModel;
64
65
    private $integrations = [];
66
67
    private $available = [];
68
69
    private $byFeatureList = [];
70
71
    private $byPlugin = [];
72
73
    public function __construct(
74
        ContainerInterface $container,
75
        EntityManager $em,
76
        PathsHelper $pathsHelper,
77
        BundleHelper $bundleHelper,
78
        CoreParametersHelper $coreParametersHelper,
79
        TemplatingHelper $templatingHelper,
80
        PluginModel $pluginModel
81
    ) {
82
        $this->container            = $container;
83
        $this->em                   = $em;
84
        $this->pathsHelper          = $pathsHelper;
85
        $this->bundleHelper         = $bundleHelper;
86
        $this->pluginModel          = $pluginModel;
87
        $this->coreParametersHelper = $coreParametersHelper;
88
        $this->templatingHelper     = $templatingHelper;
89
    }
90
91
    /**
92
     * Get a list of integration helper classes.
93
     *
94
     * @param array|string $specificIntegrations
95
     * @param array        $withFeatures
96
     * @param bool         $alphabetical
97
     * @param int|null     $pluginFilter
98
     * @param bool|false   $publishedOnly
99
     *
100
     * @return mixed
101
     *
102
     * @throws \Doctrine\ORM\ORMException
103
     */
104
    public function getIntegrationObjects($specificIntegrations = null, $withFeatures = null, $alphabetical = false, $pluginFilter = null, $publishedOnly = false)
105
    {
106
        // Build the service classes
107
        if (empty($this->available)) {
108
            $this->available = [];
109
110
            // Get currently installed integrations
111
            $integrationSettings = $this->getIntegrationSettings();
112
113
            // And we'll be scanning the addon bundles for additional classes, so have that data on standby
114
            $plugins = $this->bundleHelper->getPluginBundles();
115
116
            // Get a list of already installed integrations
117
            $integrationRepo = $this->em->getRepository('MauticPluginBundle:Integration');
118
            //get a list of plugins for filter
119
            $installedPlugins = $this->pluginModel->getEntities(
120
                [
121
                    'hydration_mode' => 'hydrate_array',
122
                    'index'          => 'bundle',
123
                ]
124
            );
125
126
            $newIntegrations = [];
127
128
            // Scan the plugins for integration classes
129
            foreach ($plugins as $plugin) {
130
                // Do not list the integration if the bundle has not been "installed"
131
                if (!isset($plugin['bundle']) || !isset($installedPlugins[$plugin['bundle']])) {
132
                    continue;
133
                }
134
135
                if (is_dir($plugin['directory'].'/Integration')) {
136
                    $finder = new Finder();
137
                    $finder->files()->name('*Integration.php')->in($plugin['directory'].'/Integration')->ignoreDotFiles(true);
138
139
                    $id                  = $installedPlugins[$plugin['bundle']]['id'];
140
                    $this->byPlugin[$id] = [];
141
                    $pluginReference     = $this->em->getReference('MauticPluginBundle:Plugin', $id);
142
                    $pluginNamespace     = str_replace('MauticPlugin', '', $plugin['bundle']);
143
144
                    foreach ($finder as $file) {
145
                        $integrationName = substr($file->getBaseName(), 0, -15);
146
147
                        if (!isset($integrationSettings[$integrationName])) {
148
                            $newIntegration = new Integration();
149
                            $newIntegration->setName($integrationName)
150
                                ->setPlugin($pluginReference);
151
                            $integrationSettings[$integrationName] = $newIntegration;
152
                            $integrationContainerKey               = strtolower("mautic.integration.{$integrationName}");
153
154
                            // Initiate the class in order to get the features supported
155
                            if ($this->container->has($integrationContainerKey)) {
156
                                $this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
157
158
                                $features = $this->integrations[$integrationName]->getSupportedFeatures();
159
                                $newIntegration->setSupportedFeatures($features);
160
161
                                // Go ahead and stash it since it's built already
162
                                $this->integrations[$integrationName]->setIntegrationSettings($newIntegration);
163
164
                                $newIntegrations[] = $newIntegration;
165
166
                                unset($newIntegration);
167
                            }
168
                        }
169
170
                        /** @var \Mautic\PluginBundle\Entity\Integration $settings */
171
                        $settings                          = $integrationSettings[$integrationName];
172
                        $this->available[$integrationName] = [
173
                            'isPlugin'    => true,
174
                            'integration' => $integrationName,
175
                            'settings'    => $settings,
176
                            'namespace'   => $pluginNamespace,
177
                        ];
178
179
                        // Sort by feature and plugin for later
180
                        $features = $settings->getSupportedFeatures();
181
                        foreach ($features as $feature) {
182
                            if (!isset($this->byFeatureList[$feature])) {
183
                                $this->byFeatureList[$feature] = [];
184
                            }
185
                            $this->byFeatureList[$feature][] = $integrationName;
186
                        }
187
                        $this->byPlugin[$id][] = $integrationName;
188
                    }
189
                }
190
            }
191
192
            $coreIntegrationSettings = $this->getCoreIntegrationSettings();
193
194
            // Scan core bundles for integration classes
195
            foreach ($this->bundleHelper->getMauticBundles() as $coreBundle) {
196
                if (
197
                    // Skip plugin bundles
198
                    false !== strpos($coreBundle['relative'], 'app/bundles')
199
                    // Skip core bundles without an Integration directory
200
                    && is_dir($coreBundle['directory'].'/Integration')
201
                ) {
202
                    $finder = new Finder();
203
                    $finder->files()->name('*Integration.php')->in($coreBundle['directory'].'/Integration')->ignoreDotFiles(true);
204
205
                    $coreBundleNamespace = str_replace('Mautic', '', $coreBundle['bundle']);
206
207
                    foreach ($finder as $file) {
208
                        $integrationName = substr($file->getBaseName(), 0, -15);
209
210
                        if (!isset($coreIntegrationSettings[$integrationName])) {
211
                            $newIntegration = new Integration();
212
                            $newIntegration->setName($integrationName);
213
                            $integrationSettings[$integrationName] = $newIntegration;
214
215
                            $integrationContainerKey = strtolower("mautic.integration.{$integrationName}");
216
217
                            // Initiate the class in order to get the features supported
218
                            if ($this->container->has($integrationContainerKey)) {
219
                                $this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
220
                                $features                             = $this->integrations[$integrationName]->getSupportedFeatures();
221
                                $newIntegration->setSupportedFeatures($features);
222
223
                                // Go ahead and stash it since it's built already
224
                                $this->integrations[$integrationName]->setIntegrationSettings($newIntegration);
225
226
                                $newIntegrations[] = $newIntegration;
227
                            } else {
228
                                continue;
229
                            }
230
                        }
231
232
                        /** @var \Mautic\PluginBundle\Entity\Integration $settings */
233
                        $settings                          = isset($coreIntegrationSettings[$integrationName]) ? $coreIntegrationSettings[$integrationName] : $newIntegration;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newIntegration does not seem to be defined for all execution paths leading up to this point.
Loading history...
234
                        $this->available[$integrationName] = [
235
                            'isPlugin'    => false,
236
                            'integration' => $integrationName,
237
                            'settings'    => $settings,
238
                            'namespace'   => $coreBundleNamespace,
239
                        ];
240
                    }
241
                }
242
            }
243
244
            // Save newly found integrations
245
            if (!empty($newIntegrations)) {
246
                $integrationRepo->saveEntities($newIntegrations);
247
                unset($newIntegrations);
248
            }
249
        }
250
251
        // Ensure appropriate formats
252
        if (null !== $specificIntegrations && !is_array($specificIntegrations)) {
253
            $specificIntegrations = [$specificIntegrations];
254
        }
255
256
        if (null !== $withFeatures && !is_array($withFeatures)) {
257
            $withFeatures = [$withFeatures];
258
        }
259
260
        // Build the integrations wanted
261
        if (!empty($pluginFilter)) {
262
            // Filter by plugin
263
            $filteredIntegrations = $this->byPlugin[$pluginFilter];
264
        } elseif (!empty($specificIntegrations)) {
265
            // Filter by specific integrations
266
            $filteredIntegrations = $specificIntegrations;
267
        } else {
268
            // All services by default
269
            $filteredIntegrations = array_keys($this->available);
270
        }
271
272
        // Filter by features
273
        if (!empty($withFeatures)) {
274
            $integrationsWithFeatures = [];
275
            foreach ($withFeatures as $feature) {
276
                if (isset($this->byFeatureList[$feature])) {
277
                    $integrationsWithFeatures = $integrationsWithFeatures + $this->byFeatureList[$feature];
278
                }
279
            }
280
281
            $filteredIntegrations = array_intersect($filteredIntegrations, $integrationsWithFeatures);
282
        }
283
284
        $returnServices = [];
285
286
        // Build the classes if not already
287
        foreach ($filteredIntegrations as $integrationName) {
288
            if (!isset($this->available[$integrationName]) || ($publishedOnly && !$this->available[$integrationName]['settings']->isPublished())) {
289
                continue;
290
            }
291
292
            if (!isset($this->integrations[$integrationName])) {
293
                $integration             = $this->available[$integrationName];
294
                $integrationContainerKey = strtolower("mautic.integration.{$integrationName}");
295
296
                if ($this->container->has($integrationContainerKey)) {
297
                    $this->integrations[$integrationName] = $this->container->get($integrationContainerKey);
298
                    $this->integrations[$integrationName]->setIntegrationSettings($integration['settings']);
299
                }
300
            }
301
302
            if (isset($this->integrations[$integrationName])) {
303
                $returnServices[$integrationName] = $this->integrations[$integrationName];
304
            }
305
        }
306
307
        foreach ($returnServices as $key => $value) {
308
            if (!$value) {
309
                unset($returnServices[$key]);
310
            }
311
        }
312
313
        if (empty($alphabetical)) {
314
            // Sort by priority
315
            uasort($returnServices, function ($a, $b) {
316
                $aP = (int) $a->getPriority();
317
                $bP = (int) $b->getPriority();
318
319
                if ($aP === $bP) {
320
                    return 0;
321
                }
322
323
                return ($aP < $bP) ? -1 : 1;
324
            });
325
        } else {
326
            // Sort by display name
327
            uasort($returnServices, function ($a, $b) {
328
                $aName = $a->getDisplayName();
329
                $bName = $b->getDisplayName();
330
331
                return strcasecmp($aName, $bName);
332
            });
333
        }
334
335
        return $returnServices;
336
    }
337
338
    /**
339
     * Get a single integration object.
340
     *
341
     * @param $name
342
     *
343
     * @return AbstractIntegration|bool
344
     */
345
    public function getIntegrationObject($name)
346
    {
347
        $integrationObjects = $this->getIntegrationObjects($name);
348
349
        return ((isset($integrationObjects[$name]))) ? $integrationObjects[$name] : false;
350
    }
351
352
    /**
353
     * Gets a count of integrations.
354
     *
355
     * @param $plugin
356
     *
357
     * @return int
358
     */
359
    public function getIntegrationCount($plugin)
360
    {
361
        if (!is_array($plugin)) {
362
            $plugins = $this->coreParametersHelper->get('plugin.bundles');
363
            if (array_key_exists($plugin, $plugins)) {
364
                $plugin = $plugins[$plugin];
365
            } else {
366
                // It doesn't exist so return 0
367
368
                return 0;
369
            }
370
        }
371
372
        if (is_dir($plugin['directory'].'/Integration')) {
373
            $finder = new Finder();
374
            $finder->files()->name('*Integration.php')->in($plugin['directory'].'/Integration')->ignoreDotFiles(true);
375
376
            return iterator_count($finder);
377
        }
378
379
        return 0;
380
    }
381
382
    /**
383
     * Returns popular social media services and regex URLs for parsing purposes.
384
     *
385
     * @param bool $find If true, array of regexes to find a handle will be returned;
386
     *                   If false, array of URLs with a placeholder of %handle% will be returned
387
     *
388
     * @return array
389
     *
390
     * @todo Extend this method to allow plugins to add URLs to these arrays
391
     */
392
    public function getSocialProfileUrlRegex($find = true)
393
    {
394
        if ($find) {
395
            //regex to find a match
396
            return [
397
                'twitter'  => "/twitter.com\/(.*?)($|\/)/",
398
                'facebook' => [
399
                    "/facebook.com\/(.*?)($|\/)/",
400
                    "/fb.me\/(.*?)($|\/)/",
401
                ],
402
                'linkedin'  => "/linkedin.com\/in\/(.*?)($|\/)/",
403
                'instagram' => "/instagram.com\/(.*?)($|\/)/",
404
                'pinterest' => "/pinterest.com\/(.*?)($|\/)/",
405
                'klout'     => "/klout.com\/(.*?)($|\/)/",
406
                'youtube'   => [
407
                    "/youtube.com\/user\/(.*?)($|\/)/",
408
                    "/youtu.be\/user\/(.*?)($|\/)/",
409
                ],
410
                'flickr' => "/flickr.com\/photos\/(.*?)($|\/)/",
411
                'skype'  => "/skype:(.*?)($|\?)/",
412
            ];
413
        } else {
414
            //populate placeholder
415
            return [
416
                'twitter'    => 'https://twitter.com/%handle%',
417
                'facebook'   => 'https://facebook.com/%handle%',
418
                'linkedin'   => 'https://linkedin.com/in/%handle%',
419
                'instagram'  => 'https://instagram.com/%handle%',
420
                'pinterest'  => 'https://pinterest.com/%handle%',
421
                'klout'      => 'https://klout.com/%handle%',
422
                'youtube'    => 'https://youtube.com/user/%handle%',
423
                'flickr'     => 'https://flickr.com/photos/%handle%',
424
                'skype'      => 'skype:%handle%?call',
425
            ];
426
        }
427
    }
428
429
    /**
430
     * Get array of integration entities.
431
     *
432
     * @return mixed
433
     */
434
    public function getIntegrationSettings()
435
    {
436
        return $this->em->getRepository('MauticPluginBundle:Integration')->getIntegrations();
437
    }
438
439
    public function getCoreIntegrationSettings()
440
    {
441
        return $this->em->getRepository('MauticPluginBundle:Integration')->getCoreIntegrations();
442
    }
443
444
    /**
445
     * Get the user's social profile data from cache or integrations if indicated.
446
     *
447
     * @param \Mautic\LeadBundle\Entity\Lead $lead
448
     * @param array                          $fields
449
     * @param bool                           $refresh
450
     * @param string                         $specificIntegration
451
     * @param bool                           $persistLead
452
     * @param bool                           $returnSettings
453
     *
454
     * @return array
455
     */
456
    public function getUserProfiles($lead, $fields = [], $refresh = false, $specificIntegration = null, $persistLead = true, $returnSettings = false)
457
    {
458
        $socialCache     = $lead->getSocialCache();
459
        $featureSettings = [];
460
        if ($refresh) {
461
            //regenerate from integrations
462
            $now = new DateTimeHelper();
463
464
            //check to see if there are social profiles activated
465
            $socialIntegrations = $this->getIntegrationObjects($specificIntegration, ['public_profile', 'public_activity']);
466
467
            /* @var \MauticPlugin\MauticSocialBundle\Integration\SocialIntegration $sn */
468
            foreach ($socialIntegrations as $integration => $sn) {
469
                $settings        = $sn->getIntegrationSettings();
470
                $features        = $settings->getSupportedFeatures();
471
                $identifierField = $this->getUserIdentifierField($sn, $fields);
472
473
                if ($returnSettings) {
474
                    $featureSettings[$integration] = $settings->getFeatureSettings();
475
                }
476
477
                if ($identifierField && $settings->isPublished()) {
478
                    $profile = (!isset($socialCache[$integration])) ? [] : $socialCache[$integration];
479
480
                    //clear the cache
481
                    unset($profile['profile'], $profile['activity']);
482
483
                    if (in_array('public_profile', $features) && $sn->isAuthorized()) {
484
                        $sn->getUserData($identifierField, $profile);
485
                    }
486
487
                    if (in_array('public_activity', $features) && $sn->isAuthorized()) {
488
                        $sn->getPublicActivity($identifierField, $profile);
489
                    }
490
491
                    if (!empty($profile['profile']) || !empty($profile['activity'])) {
492
                        if (!isset($socialCache[$integration])) {
493
                            $socialCache[$integration] = [];
494
                        }
495
496
                        $socialCache[$integration]['profile']     = (!empty($profile['profile'])) ? $profile['profile'] : [];
497
                        $socialCache[$integration]['activity']    = (!empty($profile['activity'])) ? $profile['activity'] : [];
498
                        $socialCache[$integration]['lastRefresh'] = $now->toUtcString();
499
                    }
500
                } elseif (isset($socialCache[$integration])) {
501
                    //integration is now not applicable
502
                    unset($socialCache[$integration]);
503
                }
504
            }
505
506
            if ($persistLead && !empty($socialCache)) {
507
                $lead->setSocialCache($socialCache);
508
                $this->em->getRepository('MauticLeadBundle:Lead')->saveEntity($lead);
509
            }
510
        } elseif ($returnSettings) {
511
            $socialIntegrations = $this->getIntegrationObjects($specificIntegration, ['public_profile', 'public_activity']);
512
            foreach ($socialIntegrations as $integration => $sn) {
513
                $settings                      = $sn->getIntegrationSettings();
514
                $featureSettings[$integration] = $settings->getFeatureSettings();
515
            }
516
        }
517
518
        if ($specificIntegration) {
519
            return ($returnSettings) ? [[$specificIntegration => $socialCache[$specificIntegration]], $featureSettings]
520
                : [$specificIntegration => $socialCache[$specificIntegration]];
521
        }
522
523
        return ($returnSettings) ? [$socialCache, $featureSettings] : $socialCache;
524
    }
525
526
    /**
527
     * @param      $lead
528
     * @param bool $integration
529
     *
530
     * @return array
531
     */
532
    public function clearIntegrationCache($lead, $integration = false)
533
    {
534
        $socialCache = $lead->getSocialCache();
535
        if (!empty($integration)) {
536
            unset($socialCache[$integration]);
537
        } else {
538
            $socialCache = [];
539
        }
540
        $lead->setSocialCache($socialCache);
541
        $this->em->getRepository('MauticLeadBundle:Lead')->saveEntity($lead);
542
543
        return $socialCache;
544
    }
545
546
    /**
547
     * Gets an array of the HTML for share buttons.
548
     */
549
    public function getShareButtons()
550
    {
551
        static $shareBtns = [];
552
553
        if (empty($shareBtns)) {
554
            $socialIntegrations = $this->getIntegrationObjects(null, ['share_button'], true);
555
            $templating         = $this->templatingHelper->getTemplating();
556
557
            /**
558
             * @var string
559
             * @var \Mautic\PluginBundle\Integration\AbstractIntegration $details
560
             */
561
            foreach ($socialIntegrations as $integration => $details) {
562
                /** @var \Mautic\PluginBundle\Entity\Integration $settings */
563
                $settings = $details->getIntegrationSettings();
564
565
                $featureSettings = $settings->getFeatureSettings();
566
                $apiKeys         = $details->decryptApiKeys($settings->getApiKeys());
567
                $plugin          = $settings->getPlugin();
568
                $shareSettings   = isset($featureSettings['shareButton']) ? $featureSettings['shareButton'] : [];
569
570
                //add the api keys for use within the share buttons
571
                $shareSettings['keys']   = $apiKeys;
572
                $shareBtns[$integration] = $templating->render($plugin->getBundle().":Integration/$integration:share.html.php", [
573
                    'settings' => $shareSettings,
574
                ]);
575
            }
576
        }
577
578
        return $shareBtns;
579
    }
580
581
    /**
582
     * Loops through field values available and finds the field the integration needs to obtain the user.
583
     *
584
     * @param $integrationObject
585
     * @param $fields
586
     *
587
     * @return bool
588
     */
589
    public function getUserIdentifierField($integrationObject, $fields)
590
    {
591
        $identifierField = $integrationObject->getIdentifierFields();
592
        $identifier      = (is_array($identifierField)) ? [] : false;
593
        $matchFound      = false;
594
595
        $findMatch = function ($f, $fields) use (&$identifierField, &$identifier, &$matchFound) {
596
            if (is_array($identifier)) {
597
                //there are multiple fields the integration can identify by
598
                foreach ($identifierField as $idf) {
599
                    $value = (is_array($fields[$f]) && isset($fields[$f]['value'])) ? $fields[$f]['value'] : $fields[$f];
600
601
                    if (!in_array($value, $identifier) && false !== strpos($f, $idf)) {
602
                        $identifier[$f] = $value;
603
                        if (count($identifier) === count($identifierField)) {
604
                            //found enough matches so break
605
                            $matchFound = true;
606
                            break;
607
                        }
608
                    }
609
                }
610
            } elseif ($identifierField === $f || false !== strpos($f, $identifierField)) {
611
                $matchFound = true;
612
                $identifier = (is_array($fields[$f])) ? $fields[$f]['value'] : $fields[$f];
613
            }
614
        };
615
616
        $groups = ['core', 'social', 'professional', 'personal'];
617
        $keys   = array_keys($fields);
618
        if (0 !== count(array_intersect($groups, $keys)) && count($keys) <= 4) {
619
            //fields are group
620
            foreach ($fields as $groupFields) {
621
                $availableFields = array_keys($groupFields);
622
                foreach ($availableFields as $f) {
623
                    $findMatch($f, $groupFields);
624
625
                    if ($matchFound) {
626
                        break;
627
                    }
628
                }
629
            }
630
        } else {
631
            $availableFields = array_keys($fields);
632
            foreach ($availableFields as $f) {
633
                $findMatch($f, $fields);
634
635
                if ($matchFound) {
636
                    break;
637
                }
638
            }
639
        }
640
641
        return $identifier;
642
    }
643
644
    /**
645
     * Get the path to the integration's icon relative to the site root.
646
     *
647
     * @param $integration
648
     *
649
     * @return string
650
     */
651
    public function getIconPath($integration)
652
    {
653
        $systemPath  = $this->pathsHelper->getSystemPath('root');
654
        $bundlePath  = $this->pathsHelper->getSystemPath('bundles');
655
        $pluginPath  = $this->pathsHelper->getSystemPath('plugins');
656
        $genericIcon = $bundlePath.'/PluginBundle/Assets/img/generic.png';
657
658
        if (is_array($integration)) {
659
            // A bundle so check for an icon
660
            $icon = $pluginPath.'/'.$integration['bundle'].'/Assets/img/icon.png';
661
        } elseif ($integration instanceof Plugin) {
662
            // A bundle so check for an icon
663
            $icon = $pluginPath.'/'.$integration->getBundle().'/Assets/img/icon.png';
664
        } elseif ($integration instanceof UnifiedIntegrationInterface) {
665
            return $integration->getIcon();
666
        }
667
668
        if (file_exists($systemPath.'/'.$icon)) {
669
            return $icon;
670
        }
671
672
        return $genericIcon;
673
    }
674
}
675