Completed
Pull Request — master (#5)
by
unknown
12:20 queued 12s
created

RedirectFrontendNodeRoutePartHandler::endsWith()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 3
eloc 2
nc 3
nop 2
1
<?php
2
namespace MOC\Redirects\Routing;
3
4
use Doctrine\ORM\QueryBuilder;
5
use TYPO3\Flow\Annotations as Flow;
6
use TYPO3\Flow\Core\Bootstrap;
7
use TYPO3\Flow\Http\Uri;
8
use TYPO3\Flow\Mvc\Routing\DynamicRoutePart;
9
use TYPO3\TYPO3CR\Domain\Model\NodeData;
10
11
/**
12
 * A route part handler for finding nodes specifically in the website's frontend.
13
 */
14
class RedirectFrontendNodeRoutePartHandler extends DynamicRoutePart
15
{
16
17
    /**
18
     * @Flow\Inject
19
     * @var \Doctrine\Common\Persistence\ObjectManager
20
     */
21
    protected $entityManager;
22
23
    /**
24
     * @Flow\Inject
25
     * @var Bootstrap
26
     */
27
    protected $bootstrap;
28
29
    /**
30
     * @param string $requestPath The request path to be matched
31
     * @return string value to match
32
     */
33
    protected function findValueToMatch($requestPath)
34
    {
35
        return $requestPath;
36
    }
37
38
    /**
39
     * @param string $haystack
40
     * @param string $needle
41
     * @return bool
42
     */
43
    function endsWith($haystack, $needle) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
44
        return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== FALSE);
45
    }
46
47
    /**
48
     * @param string $haystack
49
     * @param string $needle
50
     * @return bool
51
     */
52
    function startsWith($haystack, $needle) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
53
        return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
54
    }
55
56
    /**
57
     * @param string $requestPath
58
     * @return boolean TRUE if the $requestPath could be matched, otherwise FALSE
59
     */
60
    protected function matchValue($requestPath)
61
    {
62
        /** @var Uri $uri */
63
        $uri = $this->bootstrap->getActiveRequestHandler()->getHttpRequest()->getUri();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TYPO3\Flow\Core\RequestHandlerInterface as the method getHttpRequest() does only exist in the following implementations of said interface: TYPO3\Flow\Http\RequestHandler, TYPO3\Setup\Core\RequestHandler.

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...
64
        $relativeUrl = rtrim($uri->getPath(), '/');
65
        if($this->startsWith($relativeUrl, '/')){
66
            $tempRelativeUrl = substr($relativeUrl, 1);
67
        }
68
        $relativeUrlWithQueryString = $relativeUrl . ($uri->getQuery() ? '?' . $uri->getQuery() : '');
69
        $absoluteUrl = $uri->getHost() . $relativeUrl;
70
        $absoluteUrlWithQueryString = $uri->getHost() . $relativeUrlWithQueryString;
71
72
        if (empty($relativeUrl)) {
73
            return false;
74
        }
75
76
        /** @var QueryBuilder $queryBuilder */
77
        $queryBuilder = $this->entityManager->createQueryBuilder();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Common\Persistence\ObjectManager as the method createQueryBuilder() does only exist in the following implementations of said interface: Doctrine\ORM\Decorator\EntityManagerDecorator, Doctrine\ORM\EntityManager.

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...
78
        $queryBuilder->select('n')
79
            ->distinct()
80
            ->from('TYPO3\TYPO3CR\Domain\Model\NodeData', 'n')
81
            ->where('n.workspace = :workspace')
82
            ->setParameter('workspace', 'live')
83
            ->andWhere('n.properties LIKE :relativeUrl')
84
            ->setParameter('relativeUrl', '%"redirectUrl"%' . str_replace('/', '\\\\\\/', $tempRelativeUrl) . '%');
0 ignored issues
show
Bug introduced by
The variable $tempRelativeUrl does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
85
        $query = $queryBuilder->getQuery();
86
        $nodes = $query->getResult();
87
        if (empty($nodes)) {
88
            return false;
89
        }
90
91
        foreach ($nodes as $node) {
92
            /** @var NodeData $node */
93
            // Prevent partial matches
94
            $redirectUrl = preg_replace('#^https?://#', '', $node->getProperty('redirectUrl'));
95
            if($this->endsWith($redirectUrl,'/') === TRUE){
96
                $redirectUrl = substr($redirectUrl, 0, -1);
97
            }
98
            if($this->startsWith($relativeUrl,'/') === TRUE){
99
                $relativeUrl = substr($relativeUrl,1);
100
            }
101
102
            if (in_array($redirectUrl,
103
                array($relativeUrl, $relativeUrlWithQueryString, $absoluteUrl, $absoluteUrlWithQueryString), true)) {
104
105
                $matchingNode = $node;
106
                break;
107
            }
108
        }
109
        if (!isset($matchingNode)) {
110
            return false;
111
        }
112
113
        $this->setName('node');
114
        $this->value = $matchingNode->getPath();
115
        return true;
116
    }
117
118
    /**
119
     * @param string $value value to resolve
120
     * @return boolean TRUE if value could be resolved successfully, otherwise FALSE.
121
     */
122
    protected function resolveValue($value)
123
    {
124
        return false;
125
    }
126
127
}
128