ThumbnailAspect   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 104
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 9
lcom 1
cbo 5
dl 0
loc 104
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A injectSettings() 0 4 1
B optimizeThumbnail() 0 46 8
1
<?php
2
namespace MOC\ImageOptimizer\Aspects;
3
4
use Neos\Eel\CompilingEvaluator;
5
use Neos\Eel\Exception;
6
use Neos\Eel\Utility;
7
use Neos\Flow\Annotations as Flow;
8
use Neos\Flow\Aop\JoinPointInterface;
9
use Neos\Flow\Log\Utility\LogEnvironment;
10
use Neos\Flow\Package\Exception\UnknownPackageException;
11
use Neos\Flow\Package\PackageManager;
12
use Neos\Flow\ResourceManagement\ResourceManager;
13
use Neos\Media\Domain\Model\Thumbnail;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\LogLevel;
16
17
/**
18
 * @Flow\Scope("singleton")
19
 * @Flow\Aspect
20
 */
21
class ThumbnailAspect
22
{
23
    /**
24
     * @var LoggerInterface
25
     * @Flow\Inject
26
     */
27
    protected $systemLogger;
28
29
    /**
30
     * @Flow\Inject
31
     * @var PackageManager
32
     */
33
    protected $packageManager;
34
35
    /**
36
     * @Flow\Inject
37
     * @var ResourceManager
38
     */
39
    protected $resourceManager;
40
41
    /**
42
     * @Flow\Inject
43
     * @var CompilingEvaluator
44
     */
45
    protected $eelEvaluator;
46
47
    /**
48
     * @var array
49
     */
50
    protected $settings;
51
52
    /**
53
     * @param array $settings
54
     * @return void
55
     */
56
    public function injectSettings(array $settings)
57
    {
58
        $this->settings = $settings;
59
    }
60
61
    /**
62
     * After a thumbnail has been refreshed the resource is optimized, meaning the
63
     * image is only optimized once when created.
64
     *
65
     * A new resource is generated for every thumbnail, meaning the original is
66
     * never touched.
67
     *
68
     * Only local file system target is supported to keep it from being blocking.
69
     * It would however be possible to create a local copy of the resource,
70
     * process it, import it and set that as the thumbnail resource.
71
     *
72
     * @Flow\AfterReturning("method(Neos\Media\Domain\Model\Thumbnail->refresh())")
73
     * @param JoinPointInterface $joinPoint The current join point
74
     * @return void
75
     * @throws Exception
76
     * @throws UnknownPackageException
77
     */
78
    public function optimizeThumbnail(JoinPointInterface $joinPoint)
79
    {
80
        /** @var Thumbnail $thumbnail */
81
        $thumbnail = $joinPoint->getProxy();
82
        $thumbnailResource = $thumbnail->getResource();
83
        if (!$thumbnailResource) {
84
            return;
85
        }
86
87
        $streamMetaData = stream_get_meta_data($thumbnailResource->getStream());
88
        $pathAndFilename = $streamMetaData['uri'];
89
90
        $useGlobalBinary = $this->settings['useGlobalBinary'];
91
        $binaryRootPath = 'Private/Library/node_modules/';
92
        $file = escapeshellarg($pathAndFilename);
93
        $imageType = $thumbnailResource->getMediaType();
94
95
        if (!array_key_exists($imageType, $this->settings['formats'])) {
96
            $this->systemLogger->info(sprintf('Unsupported type "%s" skipped in optimizeThumbnail', $imageType), LogEnvironment::fromMethodName(__METHOD__));
97
            return;
98
        }
99
100
        $librarySettings = $this->settings['formats'][$imageType];
101
102
        if ($librarySettings['enabled'] === false) {
103
            return;
104
        }
105
106
        if ($librarySettings['useGlobalBinary'] === true) {
107
            $useGlobalBinary = true;
108
        }
109
110
        $library = $librarySettings['library'];
111
        $binaryPath = $librarySettings['binaryPath'];
112
        $eelExpression = $librarySettings['arguments'];
113
        $parameters = array_merge($librarySettings['parameters'], ['file' => $file]);
114
        $arguments = Utility::evaluateEelExpression($eelExpression, $this->eelEvaluator, $parameters);
115
116
        $binaryPath = $useGlobalBinary === true ? $this->settings['globalBinaryPath'] . $library : $this->packageManager->getPackage('MOC.ImageOptimizer')->getResourcesPath() . $binaryRootPath . $binaryPath;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Neos\Flow\Package\PackageInterface as the method getResourcesPath() does only exist in the following implementations of said interface: Neos\Flow\Package, Neos\Flow\Package\Package, Neos\FluidAdaptor\Package, Neos\Media\Package.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
117
        $cmd = escapeshellcmd($binaryPath) . ' ' . $arguments;
118
        $output = [];
119
        exec($cmd, $output, $result);
120
        $failed = (int)$result !== 0;
121
122
        $this->systemLogger->log($failed ? LogLevel::ERROR : LogLevel::INFO, $cmd . ' (' . ($failed ? 'Error: ' . $result : 'OK') . ')', array_merge(LogEnvironment::fromMethodName(__METHOD__), $output));
123
    }
124
}
125