RecordSelector::render()   F
last analyzed

Complexity

Conditions 27
Paths 378

Size

Total Lines 226

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 226
rs 0.8866
cc 27
nc 378
nop 2

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 Alpha\View\Widget;
4
5
use Alpha\Util\Config\ConfigProvider;
6
use Alpha\Util\Logging\Logger;
7
use Alpha\Util\Security\SecurityUtils;
8
use Alpha\Util\Service\ServiceFactory;
9
use Alpha\Util\Http\Request;
10
use Alpha\Model\Type\Relation;
11
use Alpha\Exception\IllegalArguementException;
12
use Alpha\Controller\Controller;
13
use Alpha\Controller\Front\FrontController;
14
15
/**
16
 * Record selection HTML widget.
17
 *
18
 * @since 1.0
19
 *
20
 * @author John Collins <[email protected]>
21
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
22
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
23
 * All rights reserved.
24
 *
25
 * <pre>
26
 * Redistribution and use in source and binary forms, with or
27
 * without modification, are permitted provided that the
28
 * following conditions are met:
29
 *
30
 * * Redistributions of source code must retain the above
31
 *   copyright notice, this list of conditions and the
32
 *   following disclaimer.
33
 * * Redistributions in binary form must reproduce the above
34
 *   copyright notice, this list of conditions and the
35
 *   following disclaimer in the documentation and/or other
36
 *   materials provided with the distribution.
37
 * * Neither the name of the Alpha Framework nor the names
38
 *   of its contributors may be used to endorse or promote
39
 *   products derived from this software without specific
40
 *   prior written permission.
41
 *
42
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
43
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
44
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
45
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
46
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
47
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
49
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
51
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
52
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
53
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55
 * </pre>
56
 */
57
class RecordSelector
58
{
59
    /**
60
     * The relation object that we are going to render a view for.
61
     *
62
     * @var \Alpha\Model\Type\Relation
63
     *
64
     * @since 1.0
65
     */
66
    private $relationObject = null;
67
68
    /**
69
     * The label text to use where required.
70
     *
71
     * @var string
72
     *
73
     * @since 1.0
74
     */
75
    private $label;
76
77
    /**
78
     * Used to indicate the reading side when accessing from MANY-TO-MANY relation
79
     * (leave blank for other relation types).
80
     *
81
     * @var string
82
     *
83
     * @since 1.0
84
     */
85
    private $accessingClassName;
86
87
    /**
88
     * Javascript to run when the widget opens in a new window.
89
     *
90
     * @var string
91
     *
92
     * @since 1.0
93
     */
94
    private $onloadJS = '';
95
96
    /**
97
     * The name of the HTML input box for storing the hidden and display values.
98
     *
99
     * @var string
100
     *
101
     * @since 1.0
102
     */
103
    private $name;
104
105
    /**
106
     * Trace logger.
107
     *
108
     * @var \Alpha\Util\Logging\Logger
109
     *
110
     * @since 1.0
111
     */
112
    private static $logger = null;
113
114
    /**
115
     * The constructor.
116
     *
117
     * @param \Alpha\Model\Type\Relation $relation
118
     * @param string                    $label
119
     * @param string                    $name
120
     * @param string                    $accessingClassName
121
     *
122
     * @since 1.0
123
     *
124
     * @throws \Alpha\Exception\IllegalArguementException
125
     */
126
    public function __construct($relation, $label = '', $name = '', $accessingClassName = '')
127
    {
128
        self::$logger = new Logger('RecordSelector');
129
        self::$logger->debug('>>__construct(relation=['.$relation.'], label=['.$label.'], name=['.$name.'], accessingClassName=['.$accessingClassName.'])');
130
131
        if (!$relation instanceof Relation) {
132
            throw new IllegalArguementException('Invalid Relation object provided to the RecordSelector constructor!');
133
        }
134
135
        $this->relationObject = $relation;
136
        $this->label = $label;
137
        $this->name = $name;
138
        $this->accessingClassName = $accessingClassName;
139
140
        self::$logger->debug('<<__construct');
141
    }
142
143
    /**
144
     * Renders the text boxes and buttons for the widget, that will appear in user forms.
145
     *
146
     * @param bool $expanded Render the related fields in expanded format or not (optional)
147
     * @param bool $buttons  Render buttons for expanding/contacting the related fields (optional)
148
     *
149
     * @return string
150
     *
151
     * @since 1.0
152
     */
153
    public function render($expanded = false, $buttons = true)
154
    {
155
        self::$logger->debug('>>render(expanded=['.$expanded.'], buttons=['.$buttons.'])');
156
157
        $config = ConfigProvider::getInstance();
158
        $sessionProvider = $config->get('session.provider.name');
159
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
160
161
        $fieldname = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt($this->name)) : $this->name);
162
163
        $html = '';
164
165
        // render text-box for many-to-one relations
166
        if ($this->relationObject->getRelationType() == 'MANY-TO-ONE') {
167
            // value to appear in the text-box
168
            $inputBoxValue = $this->relationObject->getRelatedClassDisplayFieldValue();
169
170
            $html .= '<div class="form-group">';
171
            $html .= '<label for="'.$this->name.'_display">'.$this->label.'</label>';
172
173
            $html .= '<input type="text" size="70" class="form-control" name="'.$this->name.'_display" id="'.$this->name.'_display" value="'.$inputBoxValue.'" disabled/>';
174
175
            $js = " if(window.jQuery) {
176
                        window.jQuery.dialog = new BootstrapDialog({
177
                            title: 'Please select',
178
                            message: 'Loading...',
179
                            onshow: function(dialogRef){
180
                                dialogRef.getModalBody().load('".$config->get('app.url')."/recordselector/12m/'+document.getElementById('".$fieldname."').value+'/".$this->name.'/'.urlencode($this->relationObject->getRelatedClass()).'/'.$this->relationObject->getRelatedClassField().'/'.$this->relationObject->getRelatedClassDisplayField()."');
181
                            },
182
                            buttons: [
183
                            {
184
                                icon: 'glyphicon glyphicon-remove',
185
                                label: 'Cancel',
186
                                cssClass: 'btn btn-default btn-xs',
187
                                action: function(dialogItself){
188
                                    dialogItself.close();
189
                                }
190
                            }
191
                        ]
192
                        });
193
                        window.jQuery.dialog.open();
194
                    }";
195
196
            $tmp = new Button($js, 'Select', 'relBut', '', 'glyphicon-check');
197
            $html .= '<div class="centered lower">'.$tmp->render().'</div>';
198
199
            // hidden field to store the actual value of the relation
200
            $html .= '<input type="hidden" name="'.$fieldname.'" id="'.$fieldname.'" value="'.$this->relationObject->getValue().'"/>';
201
202
            if ($this->relationObject->getRule() != '') {
203
                $html .= '<input type="hidden" id="'.$fieldname.'_msg" value="'.$this->relationObject->getHelper().'"/>';
204
                $html .= '<input type="hidden" id="'.$fieldname.'_rule" value="'.$this->relationObject->getRule().'"/>';
205
            }
206
207
            $html .= '</div>';
208
        }
209
210
        // render read-only list for one-to-many relations
211
        if ($this->relationObject->getRelationType() == 'ONE-TO-MANY') {
212
            $objects = $this->relationObject->getRelated();
213
214
            if (count($objects) > 0) {
215
                // render tags differently
216
                if ($this->name == 'tags' && $this->relationObject->getRelatedClass() == 'TagObject') {
217
                    $html .= '<p><strong>'.$this->label.':</strong>';
218
219
                    foreach ($objects as $tag) {
220
                        $html .= ' <a href="'.$config->get('app.url').'/search/'.$tag->get('content').'">'.$tag->get('content').'</a>';
221
                    }
222
223
                    $html .= '</p>';
224
                } else {
225
                    $html .= '<div><strong>'.$this->label.':</strong>';
226
                    if ($buttons) {
227
                        $html .= '<div class="spread">';
228
                        $tmp = new Button("document.getElementById('relation_field_".$this->name."').style.display = '';", 'Show', $this->name.'DisBut', '', 'glyphicon-list');
229
                        $html .= $tmp->render();
230
                        $tmp = new Button("document.getElementById('relation_field_".$this->name."').style.display = 'none';", 'Hide', $this->name.'HidBut', '', 'glyphicon-minus');
231
                        $html .= $tmp->render();
232
                        $html .= '</div>';
233
                    }
234
                    $html .= '</div>';
235
236
                    $html .= '<div id="relation_field_'.$this->name.'" style="display:'.($expanded ? '' : 'none').';">';
237
238
                    $customControllerName = Controller::getCustomControllerName(get_class($objects[0]));
239
240
                    $request = new Request(array('method' => 'GET'));
241
                    $URI = $request->getURI();
242
243
                    foreach ($objects as $obj) {
244
245
                        // check to see if we are in the admin back-end
246
                        if (mb_strpos($URI, '/tk/') !== false) {
247
                            $viewURL = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType='.get_class($obj).'&ActiveRecordID='.$obj->getID());
248
                            $editURL = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType='.get_class($obj).'&ActiveRecordID='.$obj->getID().'&view=edit');
249
                        } else {
250
                            if (isset($customControllerName)) {
251
                                if ($config->get('app.use.pretty.urls')) {
252
                                    $viewURL = $config->get('app.url').'/'.urlencode($customControllerName).'/ActiveRecordID/'.$obj->getID();
253
                                    $editURL = $config->get('app.url').'/'.urlencode($customControllerName).'/ActiveRecordID/'.$obj->getID().'/edit';
254
                                } else {
255
                                    $viewURL = $config->get('app.url').'/?act='.urlencode($customControllerName).'&ActiveRecordID='.$obj->getID();
256
                                    $editURL = $config->get('app.url').'/?act='.urlencode($customControllerName).'&ActiveRecordID='.$obj->getID().'&view=edit';
257
                                }
258
                            } else {
259
                                $viewURL = $config->get('app.url').'/record/'.urlencode(get_class($obj)).'/'.$obj->getID();
260
                                $editURL = $config->get('app.url').'/record/'.urlencode(get_class($obj)).'/'.$obj->getID().'/edit';
261
                            }
262
                        }
263
264
                        /*
265
                         * If any display headers were set with setRelatedClassHeaderFields, use them otherwise
266
                         * use the ID of the related class as the only header.
267
                         */
268
                        $headerFields = $this->relationObject->getRelatedClassHeaderFields();
269
                        if (count($headerFields) > 0) {
270
                            foreach ($headerFields as $field) {
271
                                $label = $obj->getDataLabel($field);
272
                                $value = $obj->get($field);
273
274
                                if ($field == 'created_by' || $field == 'updated_by') {
275
                                    $person = new PersonObject();
276
                                    $person->load($value);
277
                                    $value = $person->getUsername();
278
                                }
279
280
                                $html .= '<em>'.$label.': </em>'.$value.'&nbsp;&nbsp;&nbsp;&nbsp;';
281
                            }
282
                            // if the related Record has been updated, render the update time
283
                            if ($obj->getCreateTS() != $obj->getUpdateTS()) {
284
                                try {
285
                                    $html .= '<em>'.$obj->getDataLabel('updated_ts').': </em>'.$obj->get('updated_ts');
286
                                } catch (IllegalArguementException $e) {
287
                                    $html .= '<em>Updated: </em>'.$obj->get('updated_ts');
288
                                }
289
                            }
290
                        } else {
291
                            $html .= '<em>'.$obj->getDataLabel('ID').': </em>'.$obj->get('ID');
292
                        }
293
                        // ensures that line returns are rendered
294
                        $value = str_replace("\n", '<br>', $obj->get($this->relationObject->getRelatedClassDisplayField()));
295
                        $html .= '<p>'.$value.'</p>';
296
297
                        $html .= '<div class="centered">';
298
                        $html .= '<a href="'.$viewURL.'">View</a>';
299
                        // if the current user owns it, they get the edit link
300
                        if ($session->get('currentUser') != null && $session->get('currentUser')->getID() == $obj->getCreatorId()) {
301
                            $html .= '&nbsp;&nbsp;&nbsp;&nbsp;<a href="'.$editURL.'">Edit</a>';
302
                        }
303
                        $html .= '</div>';
304
                    }
305
                    $html .= '</div>';
306
                }
307
            }
308
        }
309
310
        // render text-box for many-to-many relations
311
        if ($this->relationObject->getRelationType() == 'MANY-TO-MANY') {
312
            // value to appear in the text-box
313
            $inputBoxValue = $this->relationObject->getRelatedClassDisplayFieldValue($this->accessingClassName);
314
            // replace commas with line returns
315
            $inputBoxValue = str_replace(',', "\n", $inputBoxValue);
316
317
            $html .= '<div class="form-group">';
318
            $html .= '<label for="'.$this->name.'_display">'.$this->label.'</label>';
319
320
            $html .= '<textarea id="'.$this->name.'_display" class="form-control" rows="5" readonly>';
321
            $html .= $inputBoxValue;
322
            $html .= '</textarea>';
323
324
            $fieldname1 = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt($this->name)) : $this->name);
325
            $fieldname2 = ($config->get('security.encrypt.http.fieldnames') ? base64_encode(SecurityUtils::encrypt($this->name.'_ID')) : $this->name.'_ID');
326
327
            $js = "if(window.jQuery) {
328
                        BootstrapDialog.show({
329
                            title: 'Please select',
330
                            message: 'Loading...',
331
                            onshow: function(dialogRef){
332
                                dialogRef.getModalBody().load('".$config->get('app.url')."/recordselector/m2m/'+document.getElementById('".$fieldname2."').value+'/".$this->name.'/'.urlencode($this->relationObject->getRelatedClass('left')).'/'.$this->relationObject->getRelatedClassDisplayField('left').'/'.urlencode($this->relationObject->getRelatedClass('right')).'/'.$this->relationObject->getRelatedClassDisplayField('right').'/'.urlencode($this->accessingClassName)."/'+document.getElementById('".$fieldname1."').value);
333
                            },
334
                            buttons: [
335
                            {
336
                                icon: 'glyphicon glyphicon-remove',
337
                                label: 'Cancel',
338
                                cssClass: 'btn btn-default btn-xs',
339
                                action: function(dialogItself){
340
                                    dialogItself.close();
341
                                }
342
                            },
343
                            {
344
                                icon: 'glyphicon glyphicon-ok',
345
                                label: 'Okay',
346
                                cssClass: 'btn btn-default btn-xs',
347
                                action: function(dialogItself) {
348
                                    setParentFieldValues();
349
                                    $('[id=\'".$this->name."_display\']').blur();
350
                                    dialogItself.close();
351
                                }
352
                            }
353
                        ]
354
                        });
355
                    }";
356
357
            $tmp = new Button($js, 'Select', 'relBut', '', 'glyphicon-check');
358
            $html .= '<div class="centered lower">'.$tmp->render().'</div>';
359
360
            $html .= '</div>';
361
362
            // hidden field to store the ID of the current record
363
            $html .= '<input type="hidden" name="'.$fieldname2.'" id="'.$fieldname2.'" value="'.$this->relationObject->getValue().'"/>';
364
365
            // hidden field to store the IDs of the related records on the other side of the rel (this is what we check for when saving)
366
            if ($this->relationObject->getSide($this->accessingClassName) == 'left') {
367
                $lookupIDs = $this->relationObject->getLookup()->loadAllFieldValuesByAttribute('leftID', $this->relationObject->getValue(), 'rightID', 'DESC');
368
            } else {
369
                $lookupIDs = $this->relationObject->getLookup()->loadAllFieldValuesByAttribute('rightID', $this->relationObject->getValue(), 'leftID', 'DESC');
370
            }
371
372
            $html .= '<input type="hidden" name="'.$fieldname1.'" id="'.$fieldname1.'" value="'.implode(',', $lookupIDs).'"/>';
373
        }
374
375
        self::$logger->debug('<<__render [html]');
376
377
        return $html;
378
    }
379
380
    /**
381
     * Returns the HTML for the record selector that will appear in a pop-up window.
382
     *
383
     * @param string $fieldname  The hidden HTML form field in the parent to pass values back to.
384
     * @param array  $lookupIDs An optional array of related look-up IDs, only required for rendering MANY-TO-MANY rels
385
     *
386
     * @since 1.0
387
     *
388
     * @return string
389
     */
390
    public function renderSelector($fieldname, $lookupIDs = array())
391
    {
392
        self::$logger->debug('>>renderSelector(fieldname=['.$fieldname.'], lookupIDs=['.var_export($lookupIDs, true).'])');
393
394
        $config = ConfigProvider::getInstance();
395
396
        $html = '<script language="JavaScript">
397
            var selectedIDs = new Object();
398
399
            function toggelOID(oid, displayValue, isSelected) {
400
                if(isSelected)
401
                    selectedIDs[oid] = displayValue;
402
                else
403
                    delete selectedIDs[oid];
404
            }
405
406
            function setParentFieldValues() {
407
                var IDs;
408
                var displayValues;
409
410
                for(key in selectedIDs) {
411
                    if(IDs == null)
412
                        IDs = key;
413
                    else
414
                        IDs = IDs + \',\' + key;
415
416
                    if(displayValues == null)
417
                        displayValues = selectedIDs[key];
418
                    else
419
                        displayValues = displayValues + \'\\n\' + selectedIDs[key];
420
                }
421
422
                if(IDs == null) {
423
                    document.getElementById(\''.$fieldname.'\').value = "00000000000";
424
                    document.getElementById(\''.$fieldname.'_display\').value = "";
425
                }else{
426
                    document.getElementById(\''.$fieldname.'\').value = IDs;
427
                    document.getElementById(\''.$fieldname.'_display\').value = displayValues;
428
                }
429
            }
430
431
            </script>';
432
433
        if ($this->relationObject->getRelationType() == 'MANY-TO-MANY') {
434
            $classNameLeft = $this->relationObject->getRelatedClass('left');
435
            $classNameRight = $this->relationObject->getRelatedClass('right');
436
437
            if ($this->accessingClassName == $classNameLeft) {
438
                $tmpObject = new $classNameRight();
439
                $fieldName = $this->relationObject->getRelatedClassDisplayField('right');
440
                $fieldLabel = $tmpObject->getDataLabel($fieldName);
441
                $oidLabel = $tmpObject->getDataLabel('ID');
442
443
                $objects = $tmpObject->loadAll(0, 0, 'ID', 'ASC', true);
444
445
                self::$logger->debug('['.count($objects).'] related ['.$classNameLeft.'] objects loaded');
446
            } else {
447
                $tmpObject = new $classNameLeft();
448
                $fieldName = $this->relationObject->getRelatedClassDisplayField('left');
449
                $fieldLabel = $tmpObject->getDataLabel($fieldName);
450
                $oidLabel = $tmpObject->getDataLabel('ID');
451
452
                $objects = $tmpObject->loadAll(0, 0, 'ID', 'ASC', true);
453
454
                self::$logger->debug('['.count($objects).'] related ['.$classNameLeft.'] objects loaded');
455
            }
456
457
            $html .= '<table cols="3" class="table table-bordered">';
458
            $html .= '<tr>';
459
            $html .= '<th>'.$oidLabel.'</th>';
460
            $html .= '<th>'.$fieldLabel.'</th>';
461
            $html .= '<th>Connect?</th>';
462
            $html .= '</tr>';
463
464
            foreach ($objects as $obj) {
465
                $html .= '<tr>';
466
                $html .= '<td width="20%">';
467
                $html .= $obj->getID();
468
                $html .= '</td>';
469
                $html .= '<td width="60%">';
470
                $html .= $obj->get($fieldName);
471
                $html .= '</td>';
472
                $html .= '<td width="20%">';
473
474
                if (in_array($obj->getID(), $lookupIDs)) {
475
                    $this->onloadJS .= 'toggelOID(\''.$obj->getID().'\',\''.$obj->get($fieldName).'\',true);';
476
                    $html .= '<input name = "'.$obj->getID().'" type="checkbox" checked onclick="toggelOID(\''.$obj->getID().'\',\''.$obj->get($fieldName).'\',this.checked);"/>';
477
                } else {
478
                    $html .= '<input name = "'.$obj->getID().'" type="checkbox" onclick="toggelOID(\''.$obj->getID().'\',\''.$obj->get($fieldName).'\',this.checked);"/>';
479
                }
480
                $html .= '</td>';
481
                $html .= '</tr>';
482
            }
483
            $html .= '</table>';
484
        } else {
485
            $className = $this->relationObject->getRelatedClass();
486
487
            $tmpObject = new $className();
488
            $label = $tmpObject->getDataLabel($this->relationObject->getRelatedClassDisplayField());
489
            $oidLabel = $tmpObject->getDataLabel('ID');
490
491
            $objects = $tmpObject->loadAll(0, 0, 'ID', 'DESC');
492
493
            $html = '<table cols="3" width="100%" class="bordered">';
494
            $html .= '<tr>';
495
            $html .= '<th>'.$oidLabel.'</th>';
496
            $html .= '<th>'.$label.'</th>';
497
            $html .= '<th>Connect?</th>';
498
            $html .= '</tr>';
499
500
            foreach ($objects as $obj) {
501
                $html .= '<tr>';
502
                $html .= '<td width="20%">';
503
                $html .= $obj->getID();
504
                $html .= '</td>';
505
                $html .= '<td width="60%">';
506
                $html .= $obj->get($this->relationObject->getRelatedClassDisplayField());
507
                $html .= '</td>';
508
                $html .= '<td width="20%">';
509
                if ($obj->getID() == $this->relationObject->getValue()) {
510
                    $html .= '<img src="'.$config->get('app.url').'/images/icons/accept_ghost.png"/>';
511
                } else {
512
                    $tmp = new Button("document.getElementById('".$fieldname."').value = '".$obj->getID()."'; document.getElementById('".$fieldname."_display').value = '".$obj->get($this->relationObject->getRelatedClassDisplayField())."'; $('[Id=".$fieldname."_display]').blur(); window.jQuery.dialog.close();", '', 'selBut', $config->get('app.url').'/images/icons/accept.png');
513
                    $html .= $tmp->render();
514
                }
515
                $html .= '</td>';
516
                $html .= '</tr>';
517
            }
518
            $html .= '</table>';
519
        }
520
521
        $html .= '<script type="text/javascript">'.
522
                '$(document).ready(function() {';
523
524
        $html .= $this->onloadJS;
525
526
        $html .= '});</script>';
527
528
        self::$logger->debug('<<renderSelector[html]');
529
530
        return $html;
531
    }
532
}
533