Passed
Push — master ( e31cd2...e6253a )
by Romain
03:51
created

Hook::injectHookInFrontendController()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 10
rs 10
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\Frontend\Controller\TypoScriptFrontendController;
27
28
class Hook extends AbstractDefinitionComponent implements Connection
29
{
30
    const INTERNAL_HOOK_KEY = '__notiz';
31
32
    /**
33
     * @var string
34
     *
35
     * @validate NotEmpty
36
     */
37
    protected $path;
38
39
    /**
40
     * @var string
41
     *
42
     * @validate Romm.ConfigurationObject:ClassExists
43
     */
44
    protected $interface;
45
46
    /**
47
     * @var string
48
     *
49
     * @validate RegularExpression(regularExpression=/^[_a-zA-Z]+\w*$/)
50
     */
51
    protected $method;
52
53
    /**
54
     * @return string
55
     */
56
    public function getPath()
57
    {
58
        return $this->path;
59
    }
60
61
    /**
62
     * Registers the hook in TYPO3.
63
     *
64
     * Two registration methods exist. The first one is pretty clean, the other
65
     * probably already killed dozens of puppies.
66
     *
67
     * @param EventRunner $eventRunner
68
     */
69
    public function register(EventRunner $eventRunner)
70
    {
71
        if ($this->hookIsRegistered()) {
72
            return;
73
        }
74
75
        if (!empty($this->interface)
76
            || !empty($this->method)
77
        ) {
78
            $closure = $this->preventEvalNeverIdealStuff($eventRunner);
79
        } else {
80
            $closure = function () use ($eventRunner) {
81
                return call_user_func_array($eventRunner->getCallable(), func_get_args());
82
            };
83
        }
84
85
        $this->injectHookInGlobalArray($closure);
86
    }
87
88
    /**
89
     * @param Closure|string $closure
90
     */
91
    protected function injectHookInGlobalArray($closure)
92
    {
93
        $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'] = ArrayUtility::setValueByPath(
94
            $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'],
95
            $this->getFullPath(),
96
            $closure,
97
            '|'
98
        );
99
    }
100
101
    /**
102
     * This method is called when a hook is bound to a class that must implement
103
     * an interface.
104
     *
105
     * In this case, to keep the handling we need to dynamically create some
106
     * proxy class that will call the internal API itself.
107
     *
108
     * If you don't want to lose your faith in humanity, you probably should not
109
     * read the code below. Really.
110
     *
111
     * @param EventRunner $eventRunner
112
     * @return string
113
     *
114
     * @throws ClassNotFoundException
115
     * @throws WrongFormatException
116
     */
117
    protected function preventEvalNeverIdealStuff(EventRunner $eventRunner)
118
    {
119
        $className = 'notiz_hook_' . sha1($eventRunner->getEventDefinition()->getFullIdentifier());
120
121
        $implements = $this->interface
122
            ? 'implements ' . $this->interface
123
            : '';
124
125
        $method = $this->method ?: 'run';
126
127
        if ($this->interface
128
            && !interface_exists($this->interface)
129
        ) {
130
            throw ClassNotFoundException::eventHookInterfaceNotFound($this->interface, $this);
131
        }
132
133
        if (!preg_match('/^[_a-zA-Z]+\w*$/', $method)) {
134
            throw WrongFormatException::eventHookMethodNameWrongFormat($method, $this);
135
        }
136
137
        $classPhpCode = $this->anotherNonUsefulSystem($className, $implements, $method, $eventRunner);
138
139
        // Please lord, forgive me.
140
        eval($classPhpCode);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
141
142
        return $className;
143
    }
144
145
    /**
146
     * @see \CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\Connection\Hook::preventEvalNeverIdealStuff
147
     *
148
     * @param string $className
149
     * @param string $implements
150
     * @param string $method
151
     * @param EventRunner $eventRunner
152
     * @return string
153
     */
154
    protected function anotherNonUsefulSystem($className, $implements, $method, EventRunner $eventRunner)
155
    {
156
        $eventRunnerContainerClass = EventRunnerContainer::class;
157
158
        return <<<PHP
159
class $className $implements
160
{
161
    public function $method(...\$arguments)
162
    {
163
        \$eventRunner = $eventRunnerContainerClass::getInstance()->get('{$eventRunner->getEventDefinition()->getFullIdentifier()}');
164
         
165
        call_user_func_array(\$eventRunner->getCallable(), \$arguments);       
166
    }
167
}
168
PHP;
169
    }
170
171
    /**
172
     * @return bool
173
     */
174
    protected function hookIsRegistered()
175
    {
176
        return ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'], $this->getFullPath(), '|');
177
    }
178
179
    /**
180
     * @return string
181
     */
182
    protected function getFullPath()
183
    {
184
        return $this->path . '|' . self::INTERNAL_HOOK_KEY;
185
    }
186
187
    /**
188
     * @return TypoScriptFrontendController
189
     */
190
    protected function getTypoScriptFrontendController()
191
    {
192
        return $GLOBALS['TSFE'];
193
    }
194
}
195