Passed
Pull Request — master (#47)
by Romain
03:39
created

Hook::preventEvalNeverIdealStuff()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 6
nop 1
dl 0
loc 26
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * Copyright (C) 2018
5
 * Nathan Boiron <[email protected]>
6
 * Romain Canon <[email protected]>
7
 *
8
 * This file is part of the TYPO3 NotiZ project.
9
 * It is free software; you can redistribute it and/or modify it
10
 * under the terms of the GNU General Public License, either
11
 * version 3 of the License, or any later version.
12
 *
13
 * For the full copyright and license information, see:
14
 * http://www.gnu.org/licenses/gpl-3.0.html
15
 */
16
17
namespace CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\Connection;
18
19
use Closure;
20
use CuyZ\Notiz\Core\Definition\Tree\AbstractDefinitionComponent;
21
use CuyZ\Notiz\Core\Event\Runner\EventRunner;
22
use CuyZ\Notiz\Core\Event\Runner\EventRunnerContainer;
23
use CuyZ\Notiz\Core\Exception\ClassNotFoundException;
24
use CuyZ\Notiz\Core\Exception\WrongFormatException;
25
use TYPO3\CMS\Core\Utility\ArrayUtility;
26
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
27
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
28
29
class Hook extends AbstractDefinitionComponent implements Connection
30
{
31
    const INTERNAL_HOOK_KEY = '__notiz';
32
33
    /**
34
     * @var string
35
     *
36
     * @validate NotEmpty
37
     */
38
    protected $path;
39
40
    /**
41
     * @var string
42
     *
43
     * @validate Romm.ConfigurationObject:ClassExists
44
     */
45
    protected $interface;
46
47
    /**
48
     * @var string
49
     *
50
     * @validate RegularExpression(regularExpression=/^[_a-zA-Z]+\w*$/)
51
     */
52
    protected $method;
53
54
    /**
55
     * @return string
56
     */
57
    public function getPath()
58
    {
59
        return $this->path;
60
    }
61
62
    /**
63
     * Registers the hook in TYPO3.
64
     *
65
     * Two registration methods exist. The first one is pretty clean, the other
66
     * probably already killed dozens of puppies.
67
     *
68
     * @param EventRunner $eventRunner
69
     */
70
    public function register(EventRunner $eventRunner)
71
    {
72
        if ($this->hookIsRegistered()) {
73
            return;
74
        }
75
76
        if (!empty($this->interface)
77
            || !empty($this->method)
78
        ) {
79
            $closure = $this->preventEvalNeverIdealStuff($eventRunner);
80
        } else {
81
            $closure = function () use ($eventRunner) {
82
                return call_user_func_array($eventRunner->getCallable(), func_get_args());
83
            };
84
        }
85
86
        $this->injectHookInGlobalArray($closure);
87
88
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
89
            $this->injectHookInFrontendController($closure);
0 ignored issues
show
Deprecated Code introduced by
The function CuyZ\Notiz\Core\Definiti...kInFrontendController() has been deprecated: Must be removed when TYPO3 v7 is not supported anymore. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

89
            /** @scrutinizer ignore-deprecated */ $this->injectHookInFrontendController($closure);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
90
        }
91
    }
92
93
    /**
94
     * @param Closure|string $closure
95
     */
96
    protected function injectHookInGlobalArray($closure)
97
    {
98
        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'] = ArrayUtility::setValueByPath(
99
            $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'],
100
            $this->getFullPath(),
101
            $closure,
102
            '|'
103
        );
104
    }
105
106
    /**
107
     * @param Closure|string $closure
108
     *
109
     * @deprecated Must be removed when TYPO3 v7 is not supported anymore.
110
     */
111
    protected function injectHookInFrontendController($closure)
112
    {
113
        $tsfe = $this->getTypoScriptFrontendController();
114
115
        if ($tsfe) {
0 ignored issues
show
introduced by
The condition $tsfe can never be true.
Loading history...
116
            $tsfe->TYPO3_CONF_VARS['SC_OPTIONS'] = ArrayUtility::setValueByPath(
117
                $tsfe->TYPO3_CONF_VARS['SC_OPTIONS'],
118
                $this->getFullPath(),
119
                $closure,
120
                '|'
121
            );
122
        }
123
    }
124
125
    /**
126
     * This method is called when a hook is bound to a class that must implement
127
     * an interface.
128
     *
129
     * In this case, to keep the handling we need to dynamically create some
130
     * proxy class that will call the internal API itself.
131
     *
132
     * If you don't want to lose your faith in humanity, you probably should not
133
     * read the code below. Really.
134
     *
135
     * @param EventRunner $eventRunner
136
     * @return string
137
     *
138
     * @throws ClassNotFoundException
139
     * @throws WrongFormatException
140
     */
141
    protected function preventEvalNeverIdealStuff(EventRunner $eventRunner)
142
    {
143
        $className = 'notiz_hook_' . sha1($eventRunner->getEventDefinition()->getFullIdentifier());
144
145
        $implements = $this->interface
146
            ? 'implements ' . $this->interface
147
            : '';
148
149
        $method = $this->method ?: 'run';
150
151
        if ($this->interface
152
            && !interface_exists($this->interface)
153
        ) {
154
            throw ClassNotFoundException::eventHookInterfaceNotFound($this->interface, $this);
155
        }
156
157
        if (!preg_match('/^[_a-zA-Z]+\w*$/', $method)) {
158
            throw WrongFormatException::eventHookMethodNameWrongFormat($method, $this);
159
        }
160
161
        $classPhpCode = $this->anotherNonUsefulSystem($className, $implements, $method, $eventRunner);
162
163
        // Please lord, forgive me.
164
        eval($classPhpCode);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
165
166
        return $className;
167
    }
168
169
    /**
170
     * @see \CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\Connection\Hook::preventEvalNeverIdealStuff
171
     *
172
     * @param string $className
173
     * @param string $implements
174
     * @param string $method
175
     * @param EventRunner $eventRunner
176
     * @return string
177
     */
178
    protected function anotherNonUsefulSystem($className, $implements, $method, EventRunner $eventRunner)
179
    {
180
        $eventRunnerContainerClass = EventRunnerContainer::class;
181
182
        return <<<PHP
183
class $className $implements
184
{
185
    public function $method(...\$arguments)
186
    {
187
        \$eventRunner = $eventRunnerContainerClass::getInstance()->get('{$eventRunner->getEventDefinition()->getFullIdentifier()}');
188
         
189
        call_user_func_array(\$eventRunner->getCallable(), \$arguments);       
190
    }
191
}
192
PHP;
193
    }
194
195
    /**
196
     * @return bool
197
     */
198
    protected function hookIsRegistered()
199
    {
200
        return ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'], $this->getFullPath(), '|');
201
    }
202
203
    /**
204
     * @return string
205
     */
206
    protected function getFullPath()
207
    {
208
        return $this->path . '|' . self::INTERNAL_HOOK_KEY;
209
    }
210
211
    /**
212
     * @return TypoScriptFrontendController
213
     */
214
    protected function getTypoScriptFrontendController()
215
    {
216
        return $GLOBALS['TSFE'];
217
    }
218
}
219