Completed
Push — master ( f42a52...0a9499 )
by Thierry
02:50 queued 01:16
created

CallableClass::processRequest()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 0
dl 0
loc 26
rs 9.1928
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CallableClass.php - Jaxon callable class plugin
5
 *
6
 * This class registers user defined callable classes, generates client side javascript code,
7
 * and calls their methods 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\Request\Plugin;
23
24
use Jaxon\Jaxon;
25
use Jaxon\CallableClass as UserCallableClass;
26
use Jaxon\Plugin\Request as RequestPlugin;
27
use Jaxon\Request\Support\CallableRepository;
28
use Jaxon\Request\Target;
29
30
class CallableClass extends RequestPlugin
31
{
32
    use \Jaxon\Features\Config;
33
    use \Jaxon\Features\Template;
34
    use \Jaxon\Features\Validator;
35
    use \Jaxon\Features\Translator;
36
37
    /**
38
     * The callable repository
39
     *
40
     * @var CallableRepository
41
     */
42
    protected $xRepository = null;
43
44
    /**
45
     * The value of the class parameter of the incoming Jaxon request
46
     *
47
     * @var string
48
     */
49
    protected $sRequestedClass = null;
50
51
    /**
52
     * The value of the method parameter of the incoming Jaxon request
53
     *
54
     * @var string
55
     */
56
    protected $sRequestedMethod = null;
57
58
    /**
59
     * The class constructor
60
     *
61
     * @param CallableRepository        $xRepository
62
     */
63
    public function __construct(CallableRepository $xRepository)
64
    {
65
        $this->xRepository = $xRepository;
66
67
        if(!empty($_GET['jxncls']))
68
        {
69
            $this->sRequestedClass = trim($_GET['jxncls']);
70
        }
71
        if(!empty($_GET['jxnmthd']))
72
        {
73
            $this->sRequestedMethod = trim($_GET['jxnmthd']);
74
        }
75
        if(!empty($_POST['jxncls']))
76
        {
77
            $this->sRequestedClass = trim($_POST['jxncls']);
78
        }
79
        if(!empty($_POST['jxnmthd']))
80
        {
81
            $this->sRequestedMethod = trim($_POST['jxnmthd']);
82
        }
83
    }
84
85
    /**
86
     * Return the name of this plugin
87
     *
88
     * @return string
89
     */
90
    public function getName()
91
    {
92
        return Jaxon::CALLABLE_CLASS;
93
    }
94
95
    /**
96
     * Return the target class and method
97
     *
98
     * @return Target|null
99
     */
100
    public function getTarget()
101
    {
102
        if(!$this->sRequestedClass || !$this->sRequestedMethod)
103
        {
104
            return null;
105
        }
106
        return Target::makeClass($this->sRequestedClass, $this->sRequestedMethod);
107
    }
108
109
    /**
110
     * Register a callable class
111
     *
112
     * @param string        $sType          The type of request handler being registered
113
     * @param string        $sClassName     The name of the class being registered
114
     * @param array|string  $aOptions       The associated options
115
     *
116
     * @return boolean
117
     */
118
    public function register($sType, $sClassName, $aOptions)
119
    {
120
        $sType = trim($sType);
121
        if($sType != $this->getName())
122
        {
123
            return false;
124
        }
125
126
        if(!is_string($sClassName))
127
        {
128
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid-declaration'));
129
        }
130
        if(is_string($aOptions))
131
        {
132
            $aOptions = ['include' => $aOptions];
133
        }
134
        if(!is_array($aOptions))
135
        {
136
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid-declaration'));
137
        }
138
139
        $sClassName = trim($sClassName);
140
        $this->xRepository->addClass($sClassName, $aOptions);
141
142
        return true;
143
    }
144
145
    /**
146
     * Generate a hash for the registered callable objects
147
     *
148
     * @return string
149
     */
150
    public function generateHash()
151
    {
152
        $this->xRepository->createCallableObjects();
153
        $aNamespaces = $this->xRepository->getNamespaces();
154
        $aCallableObjects = $this->xRepository->getCallableObjects();
155
        $sHash = '';
156
157
        foreach($aNamespaces as $sNamespace => $aOptions)
158
        {
159
            $sHash .= $sNamespace . $aOptions['separator'];
160
        }
161
        foreach($aCallableObjects as $sClassName => $xCallableObject)
162
        {
163
            $sHash .= $sClassName . implode('|', $xCallableObject->getMethods());
164
        }
165
166
        return md5($sHash);
167
    }
168
169
    /**
170
     * Generate client side javascript code for the registered callable objects
171
     *
172
     * @return string
173
     */
174
    public function getScript()
175
    {
176
        $this->xRepository->createCallableObjects();
177
        $aNamespaces = $this->xRepository->getNamespaces();
178
        $aCallableObjects = $this->xRepository->getCallableObjects();
179
        $aCallableOptions = $this->xRepository->getCallableOptions();
180
        $sPrefix = $this->getOption('core.prefix.class');
181
        $aJsClasses = [];
182
        $sCode = '';
183
184
        $xCallableClass = new \ReflectionClass(UserCallableClass::class);
185
        $aCallableMethods = [];
186
        foreach($xCallableClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $xMethod)
187
        {
188
            $aCallableMethods[] = $xMethod->getName();
189
        }
190
191
        foreach(array_keys($aNamespaces) as $sNamespace)
192
        {
193
            $offset = 0;
194
            $sJsNamespace = str_replace('\\', '.', $sNamespace);
195
            $sJsNamespace .= '.Null'; // This is a sentinel. The last token is not processed in the while loop.
196
            while(($dotPosition = strpos($sJsNamespace, '.', $offset)) !== false)
197
            {
198
                $sJsClass = substr($sJsNamespace, 0, $dotPosition);
199
                // Generate code for this object
200
                if(!key_exists($sJsClass, $aJsClasses))
201
                {
202
                    $sCode .= "$sPrefix$sJsClass = {};\n";
203
                    $aJsClasses[$sJsClass] = $sJsClass;
204
                }
205
                $offset = $dotPosition + 1;
206
            }
207
        }
208
209
        foreach($aCallableObjects as $sClassName => $xCallableObject)
210
        {
211
            $aConfig = $aCallableOptions[$sClassName];
212
            $aCommonConfig = key_exists('*', $aConfig) ? $aConfig['*'] : [];
213
214
            $aProtectedMethods = is_subclass_of($sClassName, UserCallableClass::class) ? $aCallableMethods : [];
215
            $aMethods = [];
216
            foreach($xCallableObject->getMethods() as $sMethodName)
217
            {
218
                // Don't export methods of the CallableClass class
219
                if(in_array($sMethodName, $aProtectedMethods))
220
                {
221
                    continue;
222
                }
223
                // Specific options for this method
224
                $aMethodConfig = key_exists($sMethodName, $aConfig) ?
225
                    array_merge($aCommonConfig, $aConfig[$sMethodName]) : $aCommonConfig;
226
                $aMethods[] = [
227
                    'name' => $sMethodName,
228
                    'config' => $aMethodConfig,
229
                ];
230
            }
231
232
            $sCode .= $this->render('jaxon::support/object.js', [
233
                'sPrefix' => $sPrefix,
234
                'sClass' => $xCallableObject->getJsName(),
235
                'aMethods' => $aMethods,
236
            ]);
237
        }
238
239
        return $sCode;
240
    }
241
242
    /**
243
     * Check if this plugin can process the incoming Jaxon request
244
     *
245
     * @return boolean
246
     */
247
    public function canProcessRequest()
248
    {
249
        // Check the validity of the class name
250
        if(($this->sRequestedClass !== null && !$this->validateClass($this->sRequestedClass)) ||
251
            ($this->sRequestedMethod !== null && !$this->validateMethod($this->sRequestedMethod)))
252
        {
253
            $this->sRequestedClass = null;
254
            $this->sRequestedMethod = null;
255
        }
256
        return ($this->sRequestedClass !== null && $this->sRequestedMethod !== null);
257
    }
258
259
    /**
260
     * Process the incoming Jaxon request
261
     *
262
     * @return boolean
263
     */
264
    public function processRequest()
265
    {
266
        if(!$this->canProcessRequest())
267
        {
268
            return false;
269
        }
270
271
        // Find the requested method
272
        $xCallableObject = $this->xRepository->getCallableObject($this->sRequestedClass);
273
        if(!$xCallableObject || !$xCallableObject->hasMethod($this->sRequestedMethod))
274
        {
275
            // Unable to find the requested object or method
276
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid',
277
                ['class' => $this->sRequestedClass, 'method' => $this->sRequestedMethod]));
278
        }
279
280
        // Call the requested method
281
        $di = jaxon()->di();
282
        $aArgs = $di->getRequestHandler()->processArguments();
283
        $xResponse = $xCallableObject->call($this->sRequestedMethod, $aArgs);
284
        if(($xResponse))
285
        {
286
            $di->getResponseManager()->append($xResponse);
287
        }
288
        return true;
289
    }
290
}
291