CallableFunctionPlugin::setTarget()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * CallableFunctionPlugin.php - Jaxon user function plugin
5
 *
6
 * This class registers user defined functions, generates client side javascript code,
7
 * and calls them on user request
8
 *
9
 * @package jaxon-core
10
 * @author Jared White
11
 * @author J. Max Wilson
12
 * @author Joseph Woolley
13
 * @author Steffen Konerow
14
 * @author Thierry Feuzeu <[email protected]>
15
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
16
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
17
 * @copyright 2016 Thierry Feuzeu <[email protected]>
18
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
19
 * @link https://github.com/jaxon-php/jaxon-core
20
 */
21
22
namespace Jaxon\Plugin\Request\CallableFunction;
23
24
use Jaxon\Jaxon;
25
use Jaxon\Di\Container;
26
use Jaxon\App\I18n\Translator;
27
use Jaxon\Exception\RequestException;
28
use Jaxon\Exception\SetupException;
29
use Jaxon\Plugin\AbstractRequestPlugin;
30
use Jaxon\Request\Target;
31
use Jaxon\Request\Validator;
32
use Jaxon\Response\AbstractResponse;
33
use Jaxon\Utils\Template\TemplateEngine;
34
use Psr\Http\Message\ServerRequestInterface;
35
use Exception;
36
37
use function array_keys;
38
use function implode;
39
use function is_array;
40
use function is_string;
41
use function md5;
42
use function trim;
43
44
class CallableFunctionPlugin extends AbstractRequestPlugin
45
{
46
    /**
47
     * The registered functions names
48
     *
49
     * @var array
50
     */
51
    protected $aFunctions = [];
52
53
    /**
54
     * The registered functions options
55
     *
56
     * @var array
57
     */
58
    protected $aOptions = [];
59
60
    /**
61
     * The constructor
62
     *
63
     * @param string $sPrefix
64
     * @param bool $bDebug
65
     * @param Container $di
66
     * @param TemplateEngine $xTemplateEngine
67
     * @param Translator $xTranslator
68
     * @param Validator $xValidator
69
     */
70
    public function __construct(private string $sPrefix, private bool $bDebug,
71
        private Container $di, private TemplateEngine $xTemplateEngine,
72
        private Translator $xTranslator, private Validator $xValidator)
73
    {}
74
75
    /**
76
     * @inheritDoc
77
     */
78
    public function getName(): string
79
    {
80
        return Jaxon::CALLABLE_FUNCTION;
81
    }
82
83
    /**
84
     * @inheritDoc
85
     * @throws SetupException
86
     */
87
    public function checkOptions(string $sCallable, $xOptions): array
88
    {
89
        if(!$this->xValidator->validateFunction(trim($sCallable)))
90
        {
91
            throw new SetupException($this->xTranslator->trans('errors.objects.invalid-declaration'));
92
        }
93
        if(is_string($xOptions))
94
        {
95
            $xOptions = ['include' => $xOptions];
96
        }
97
        elseif(!is_array($xOptions))
98
        {
99
            throw new SetupException($this->xTranslator->trans('errors.objects.invalid-declaration'));
100
        }
101
        return $xOptions;
102
    }
103
104
    /**
105
     * Register a user defined function
106
     *
107
     * @param string $sType    The type of request handler being registered
108
     * @param string $sCallable    The name of the function being registered
109
     * @param array $aOptions    The associated options
110
     *
111
     * @return bool
112
     */
113
    public function register(string $sType, string $sCallable, array $aOptions): bool
114
    {
115
        $sPhpFunction = trim($sCallable);
116
        $sFunction = $sPhpFunction;
117
        // Check if an alias is defined
118
        if(isset($aOptions['alias']))
119
        {
120
            $sFunction = (string)$aOptions['alias'];
121
            unset($aOptions['alias']);
122
        }
123
        $this->aFunctions[$sFunction] = $sPhpFunction;
124
        $this->aOptions[$sFunction] = $aOptions;
125
        return true;
126
    }
127
128
    /**
129
     * @inheritDoc
130
     */
131
    public function getHash(): string
132
    {
133
        return md5(implode('', array_keys($this->aFunctions)));
134
    }
135
136
    /**
137
     * @inheritDoc
138
     */
139
    public function getCallable(string $sCallable)
140
    {
141
        $sFunction = trim($sCallable);
142
        if(!isset($this->aFunctions[$sFunction]))
143
        {
144
            return null;
145
        }
146
        $xCallable = new CallableFunction($this->di, $sFunction,
147
            $this->sPrefix . $sFunction, $this->aFunctions[$sFunction]);
148
        foreach($this->aOptions[$sFunction] as $sName => $sValue)
149
        {
150
            $xCallable->configure($sName, $sValue);
151
        }
152
        return $xCallable;
153
    }
154
155
    /**
156
     * Generate the javascript function stub that is sent to the browser on initial page load
157
     *
158
     * @param CallableFunction $xFunction
159
     *
160
     * @return string
161
     */
162
    private function getCallableScript(CallableFunction $xFunction): string
163
    {
164
        return $this->xTemplateEngine->render('jaxon::callables/function.js', [
165
            'sName' => $xFunction->getName(),
166
            'sJsName' => $xFunction->getJsName(),
167
            'aOptions' => $xFunction->getOptions(),
168
        ]);
169
    }
170
171
    /**
172
     * @inheritDoc
173
     */
174
    public function getScript(): string
175
    {
176
        $code = '';
177
        foreach(array_keys($this->aFunctions) as $sFunction)
178
        {
179
            $xFunction = $this->getCallable($sFunction);
180
            $code .= $this->getCallableScript($xFunction);
0 ignored issues
show
Bug introduced by
It seems like $xFunction can also be of type null; however, parameter $xFunction of Jaxon\Plugin\Request\Cal...in::getCallableScript() does only seem to accept Jaxon\Plugin\Request\Cal...nction\CallableFunction, maybe add an additional type check? ( Ignorable by Annotation )

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

180
            $code .= $this->getCallableScript(/** @scrutinizer ignore-type */ $xFunction);
Loading history...
181
        }
182
        return $code;
183
    }
184
185
    /**
186
     * @inheritDoc
187
     */
188
    public static function canProcessRequest(ServerRequestInterface $xRequest): bool
189
    {
190
        $aCall = $xRequest->getAttribute('jxncall');
191
        // throw new \Exception(json_encode(['call' => $aCall]));
192
        return $aCall !== null && ($aCall['type'] ?? '') === 'func' && isset($aCall['name']);
193
    }
194
195
    /**
196
     * @inheritDoc
197
     */
198
    public function setTarget(ServerRequestInterface $xRequest): Target
199
    {
200
        $aCall = $xRequest->getAttribute('jxncall');
201
        $this->xTarget = Target::makeFunction(trim($aCall['name']));
202
        return $this->xTarget;
203
    }
204
205
    /**
206
     * @param Exception $xException
207
     * @param string $sErrorMessage
208
     *
209
     * @throws RequestException
210
     * @return void
211
     */
212
    private function throwException(Exception $xException, string $sErrorMessage): void
213
    {
214
        $this->di->getLogger()->error($xException->getMessage());
215
        throw new RequestException($sErrorMessage . (!$this->bDebug ? '' :
216
            "\n" . $xException->getMessage()));
217
    }
218
219
    /**
220
     * @inheritDoc
221
     * @throws RequestException
222
     */
223
    public function processRequest(): ?AbstractResponse
224
    {
225
        $sRequestedFunction = $this->xTarget->getFunctionName();
226
227
        // Security check: make sure the requested function was registered.
228
        if(!$this->xValidator->validateFunction($sRequestedFunction) ||
229
            !isset($this->aFunctions[$sRequestedFunction]))
230
        {
231
            // Unable to find the requested function
232
            throw new RequestException($this->xTranslator->trans('errors.functions.invalid',
233
                ['name' => $sRequestedFunction]));
234
        }
235
236
        try
237
        {
238
            /** @var CallableFunction */
239
            $xFunction = $this->getCallable($sRequestedFunction);
240
        }
241
        catch(Exception $e)
242
        {
243
            // Unable to find the requested function
244
            $this->throwException($e, $this->xTranslator->trans('errors.functions.invalid',
245
                ['name' => $sRequestedFunction]));
246
        }
247
        try
248
        {
249
            return $xFunction->call($this->xTarget->args());
250
        }
251
        catch(Exception $e)
252
        {
253
            // Unable to execute the requested function
254
            $this->throwException($e, $this->xTranslator->trans('errors.functions.call',
255
                ['name' => $sRequestedFunction]));
256
        }
257
    }
258
}
259