Test Failed
Push — main ( c8394f...8477f1 )
by Rafael
66:21
created

Forms::get()   F

Complexity

Conditions 21
Paths 3504

Size

Total Lines 110
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 82
nc 3504
nop 5
dl 0
loc 110
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Luracast\Restler\UI;
4
5
use Luracast\Restler\CommentParser;
6
use Luracast\Restler\Data\ApiMethodInfo;
7
use Luracast\Restler\Data\Text;
8
use Luracast\Restler\Data\ValidationInfo;
9
use Luracast\Restler\Data\Validator;
10
use Luracast\Restler\Defaults;
11
use Luracast\Restler\Format\UploadFormat;
12
use Luracast\Restler\Format\UrlEncodedFormat;
13
use Luracast\Restler\iFilter;
14
use Luracast\Restler\RestException;
15
use Luracast\Restler\Restler;
16
use Luracast\Restler\Routes;
17
use Luracast\Restler\Scope;
18
use Luracast\Restler\UI\Tags as T;
19
use Luracast\Restler\User;
20
use Luracast\Restler\Util;
21
22
/**
23
 * Utility class for automatically generating forms for the given http method
24
 * and api url
25
 *
26
 * @category   Framework
27
 * @package    Restler
28
 * @author     R.Arul Kumaran <[email protected]>
29
 * @copyright  2010 Luracast
30
 * @license    http://www.opensource.org/licenses/lgpl-license.php LGPL
31
 * @link       http://luracast.com/products/restler/
32
 *
33
 */
34
class Forms implements iFilter
35
{
36
    const FORM_KEY = 'form_key';
37
    public static $filterFormRequestsOnly = false;
38
39
    public static $excludedPaths = array();
40
41
    private static $style;
42
    /**
43
     * @var bool should we fill up the form using given data?
44
     */
45
    public static $preFill = true;
46
    /**
47
     * @var ValidationInfo
48
     */
49
    public static $validationInfo = null;
50
    protected static $inputTypes = array(
51
        'hidden',
52
        'password',
53
        'button',
54
        'image',
55
        'file',
56
        'reset',
57
        'submit',
58
        'search',
59
        'checkbox',
60
        'radio',
61
        'email',
62
        'text',
63
        'color',
64
        'date',
65
        'datetime',
66
        'datetime-local',
67
        'email',
68
        'month',
69
        'number',
70
        'range',
71
        'search',
72
        'tel',
73
        'time',
74
        'url',
75
        'week',
76
    );
77
    protected static $fileUpload = false;
78
    private static $key = array();
79
    /**
80
     * @var ApiMethodInfo;
81
     */
82
    private static $info;
83
84
    public static function setStyles(HtmlForm $style)
85
    {
86
        static::$style = get_class($style);
87
    }
88
89
    /**
90
     * Get the form
91
     *
92
     * @param string $method   http method to submit the form
93
     * @param string $action   relative path from the web root. When set to null
94
     *                         it uses the current api method's path
95
     * @param bool   $dataOnly if you want to render the form yourself use this
96
     *                         option
97
     * @param string $prefix   used for adjusting the spacing in front of
98
     *                         form elements
99
     * @param string $indent   used for adjusting indentation
100
     *
101
     * @return array|T
102
     *
103
     * @throws RestException
104
     */
105
    public static function get($method = 'POST', $action = null, $dataOnly = false, $prefix = '', $indent = '    ')
106
    {
107
        if (!static::$style) {
108
            static::$style = 'Luracast\\Restler\\UI\HtmlForm';
109
        }
110
111
        try {
112
            /** @var Restler $restler */
113
            $restler = Scope::get('Restler');
114
            if (is_null($action)) {
115
                $action = $restler->url;
116
            }
117
118
            $info = $restler->url == $action
119
            && Util::getRequestMethod() == $method
120
                ? $restler->apiMethodInfo
121
                : Routes::find(
122
                    trim($action, '/'),
123
                    $method,
124
                    $restler->getRequestedApiVersion(),
125
                    static::$preFill ||
126
                    ($restler->requestMethod == $method &&
127
                        $restler->url == $action)
128
                        ? $restler->getRequestData()
129
                        : array()
130
                );
131
        } catch (RestException $e) {
132
            //echo $e->getErrorMessage();
133
            $info = false;
134
        }
135
        if (!$info) {
136
            throw new RestException(500, 'invalid action path for form `' . $method . ' ' . $action . '`');
137
        }
138
        static::$info = $info;
139
        $m = $info->metadata;
140
        $r = static::fields($dataOnly);
141
        if ($method != 'GET' && $method != 'POST') {
142
            if (empty(Defaults::$httpMethodOverrideProperty)) {
143
                throw new RestException(
144
                    500,
145
                    'Forms require `Defaults::\$httpMethodOverrideProperty`' .
146
                    "for supporting HTTP $method"
147
                );
148
            }
149
            if ($dataOnly) {
150
                $r[] = array(
151
                    'tag'   => 'input',
152
                    'name'  => Defaults::$httpMethodOverrideProperty,
153
                    'type'  => 'hidden',
154
                    'value' => 'method',
155
                );
156
            } else {
157
                $r[] = T::input()
158
                    ->name(Defaults::$httpMethodOverrideProperty)
159
                    ->value($method)
160
                    ->type('hidden');
161
            }
162
163
            $method = 'POST';
164
        }
165
        if (session_id() != '') {
166
            $form_key = static::key($method, $action);
167
            if ($dataOnly) {
168
                $r[] = array(
169
                    'tag'   => 'input',
170
                    'name'  => static::FORM_KEY,
171
                    'type'  => 'hidden',
172
                    'value' => 'hidden',
173
                );
174
            } else {
175
                $key = T::input()
176
                    ->name(static::FORM_KEY)
177
                    ->type('hidden')
178
                    ->value($form_key);
179
                $r[] = $key;
180
            }
181
        }
182
183
        $s = array(
184
            'tag'   => 'button',
185
            'type'  => 'submit',
186
            'label' =>
187
                Util::nestedValue($m, 'return', CommentParser::$embeddedDataName, 'label')
188
                    ?: 'Submit'
189
        );
190
191
        if (!$dataOnly) {
192
            $s = Emmet::make(static::style('submit', $m), $s);
193
        }
194
        $r[] = $s;
195
        $t = array(
196
            'action' => $restler->getBaseUrl() . '/' . rtrim($action, '/'),
197
            'method' => $method,
198
        );
199
        if (static::$fileUpload) {
200
            static::$fileUpload = false;
201
            $t['enctype'] = 'multipart/form-data';
202
        }
203
        if (isset($m[CommentParser::$embeddedDataName])) {
204
            $t += $m[CommentParser::$embeddedDataName];
205
        }
206
        if (!$dataOnly) {
207
            $t = Emmet::make(static::style('form', $m), $t);
208
            $t->prefix = $prefix;
209
            $t->indent = $indent;
210
            $t[] = $r;
211
        } else {
212
            $t['fields'] = $r;
213
        }
214
        return $t;
215
    }
216
217
    public static function style($name, array $metadata, $type = '')
218
    {
219
        if (isset($metadata[CommentParser::$embeddedDataName][$name])) {
220
            return $metadata[CommentParser::$embeddedDataName][$name];
221
        }
222
        $style = static::$style . '::' . $name;
223
        $typedStyle = $style . '_' . $type;
224
        if (defined($typedStyle)) {
225
            return constant($typedStyle);
226
        }
227
        if (defined($style)) {
228
            return constant($style);
229
        }
230
        return null;
231
    }
232
233
    public static function fields($dataOnly = false)
234
    {
235
        $m = static::$info->metadata;
236
        $params = $m['param'];
237
        $values = static::$info->parameters;
238
        $r = array();
239
        foreach ($params as $k => $p) {
240
            $value = Util::nestedValue($values, $k);
241
            if (
242
                is_scalar($value) ||
243
                ($p['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
244
                is_object($value) && $p['type'] == get_class($value)
245
            ) {
246
                $p['value'] = $value;
247
            }
248
            static::$validationInfo = $v = new ValidationInfo($p);
249
            if ($v->from == 'path') {
250
                continue;
251
            }
252
            if (!empty($v->children)) {
253
                $t = Emmet::make(static::style('fieldset', $m), array('label' => $v->label));
254
                foreach ($v->children as $n => $c) {
255
                    $value = Util::nestedValue($v->value, $n);
256
                    if (
257
                        is_scalar($value) ||
258
                        ($c['type'] == 'array' && is_array($value) && $value == array_values($value)) ||
259
                        is_object($value) && $c['type'] == get_class($value)
260
                    ) {
261
                        $c['value'] = $value;
262
                    }
263
                    static::$validationInfo = $vc = new ValidationInfo($c);
264
                    if ($vc->from == 'path') {
265
                        continue;
266
                    }
267
                    $vc->name = $v->name . '[' . $vc->name . ']';
268
                    $t [] = static::field($vc, $dataOnly);
269
                }
270
                $r[] = $t;
271
                static::$validationInfo = null;
272
            } else {
273
                $f = static::field($v, $dataOnly);
274
                $r [] = $f;
275
            }
276
            static::$validationInfo = null;
277
        }
278
        return $r;
279
    }
280
281
    /**
282
     * @param ValidationInfo $p
283
     *
284
     * @param bool           $dataOnly
285
     *
286
     * @return array|T
287
     */
288
    public static function field(ValidationInfo $p, $dataOnly = false)
289
    {
290
        if (is_string($p->value)) {
291
            //prevent XSS attacks
292
            $p->value = htmlspecialchars($p->value, ENT_QUOTES | ENT_HTML401, 'UTF-8');
293
        }
294
        $type = $p->field ?: static::guessFieldType($p);
295
        $tag = in_array($type, static::$inputTypes)
296
            ? 'input' : $type;
297
        $options = array();
298
        $name = $p->name;
299
        $multiple = null;
300
        if ($p->type == 'array' && $p->contentType != 'associative') {
301
            $name .= '[]';
302
            $multiple = true;
303
        }
304
        if ($p->choice) {
305
            foreach ($p->choice as $i => $choice) {
306
                $option = array('name' => $name, 'value' => $choice);
307
                $option['text'] = isset($p->rules['select'][$i])
308
                    ? $p->rules['select'][$i]
309
                    : $choice;
310
                if ($choice == $p->value) {
311
                    $option['selected'] = true;
312
                }
313
                $options[] = $option;
314
            }
315
        } elseif ($p->type == 'boolean' || $p->type == 'bool') {
316
            if (Text::beginsWith($type, 'radio') || Text::beginsWith($type, 'select')) {
317
                $options[] = array(
318
                    'name'  => $p->name,
319
                    'text'  => ' Yes ',
320
                    'value' => 'true'
321
                );
322
                $options[] = array(
323
                    'name'  => $p->name,
324
                    'text'  => ' No ',
325
                    'value' => 'false'
326
                );
327
                if ($p->value || $p->default) {
328
                    $options[0]['selected'] = true;
329
                }
330
            } else { //checkbox
331
                $r = array(
332
                    'tag'     => $tag,
333
                    'name'    => $name,
334
                    'type'    => $type,
335
                    'label'   => $p->label,
336
                    'value'   => 'true',
337
                    'default' => $p->default,
338
                );
339
                $r['text'] = 'Yes';
340
                if ($p->default) {
341
                    $r['selected'] = true;
342
                }
343
                if (isset($p->rules)) {
344
                    $r += $p->rules;
345
                }
346
            }
347
        }
348
        if (empty($r)) {
349
            $r = array(
350
                'tag'      => $tag,
351
                'name'     => $name,
352
                'type'     => $type,
353
                'label'    => $p->label,
354
                'value'    => $p->value,
355
                'default'  => $p->default,
356
                'options'  => & $options,
357
                'multiple' => $multiple,
358
            );
359
            if (isset($p->rules)) {
360
                $r += $p->rules;
361
            }
362
        }
363
        if ($type == 'file') {
364
            static::$fileUpload = true;
365
            if (empty($r['accept'])) {
366
                $r['accept'] = implode(', ', UploadFormat::$allowedMimeTypes);
367
            }
368
        }
369
        if (!empty(Validator::$exceptions[$name]) && static::$info->url == Scope::get('Restler')->url) {
370
            $r['error'] = 'has-error';
371
            $r['message'] = Validator::$exceptions[$p->name]->getMessage();
372
        }
373
374
        if (true === $p->required) {
375
            $r['required'] = 'required';
376
        }
377
        if (isset($p->rules['autofocus'])) {
378
            $r['autofocus'] = 'autofocus';
379
        }
380
        /*
381
        echo "<pre>";
382
        print_r($r);
383
        echo "</pre>";
384
        */
385
        if ($dataOnly) {
386
            return $r;
387
        }
388
        if (isset($p->rules['form'])) {
389
            return Emmet::make($p->rules['form'], $r);
390
        }
391
        $m = static::$info->metadata;
392
        $t = Emmet::make(static::style($type, $m, $p->type) ?: static::style($tag, $m, $p->type), $r);
393
        return $t;
394
    }
395
396
    protected static function guessFieldType(ValidationInfo $p, $type = 'type')
397
    {
398
        if (in_array($p->$type, static::$inputTypes)) {
399
            return $p->$type;
400
        }
401
        if ($p->choice) {
402
            return $p->type == 'array' ? 'checkbox' : 'select';
403
        }
404
        switch ($p->$type) {
405
            case 'boolean':
406
                return 'radio';
407
            case 'int':
408
            case 'number':
409
            case 'float':
410
                return 'number';
411
            case 'array':
412
                return static::guessFieldType($p, 'contentType');
413
        }
414
        if ($p->name == 'password') {
415
            return 'password';
416
        }
417
        return 'text';
418
    }
419
420
    /**
421
     * Get the form key
422
     *
423
     * @param string $method   http method for form key
424
     * @param string $action   relative path from the web root. When set to null
425
     *                         it uses the current api method's path
426
     *
427
     * @return string generated form key
428
     */
429
    public static function key($method = 'POST', $action = null)
430
    {
431
        if (is_null($action)) {
432
            $action = Scope::get('Restler')->url;
433
        }
434
        $target = "$method $action";
435
        if (empty(static::$key[$target])) {
436
            static::$key[$target] = md5($target . User::getIpAddress() . uniqid(mt_rand()));
437
        }
438
        $_SESSION[static::FORM_KEY] = static::$key;
439
        return static::$key[$target];
440
    }
441
442
    /**
443
     * Access verification method.
444
     *
445
     * API access will be denied when this method returns false
446
     *
447
     * @return boolean true when api access is allowed false otherwise
448
     *
449
     * @throws RestException 403 security violation
450
     */
451
    public function __isAllowed()
452
    {
453
        if (session_id() == '') {
454
            session_start();
455
        }
456
        /** @var Restler $restler */
457
        $restler = $this->restler;
458
        $url = $restler->url;
459
        foreach (static::$excludedPaths as $exclude) {
460
            if (empty($exclude)) {
461
                if ($url == $exclude) {
462
                    return true;
463
                }
464
            } elseif (Text::beginsWith($url, $exclude)) {
465
                return true;
466
            }
467
        }
468
        $check = static::$filterFormRequestsOnly
469
            ? $restler->requestFormat instanceof UrlEncodedFormat || $restler->requestFormat instanceof UploadFormat
470
            : true;
471
        if (!empty($_POST) && $check) {
472
            if (
473
                isset($_POST[static::FORM_KEY]) &&
474
                ($target = Util::getRequestMethod() . ' ' . $restler->url) &&
475
                isset($_SESSION[static::FORM_KEY][$target]) &&
476
                $_POST[static::FORM_KEY] == $_SESSION[static::FORM_KEY][$target]
477
            ) {
478
                return true;
479
            }
480
            throw new RestException(403, 'Insecure form submission');
481
        }
482
        return true;
483
    }
484
}
485