Passed
Push — master ( d99b2c...0c4ec8 )
by
unknown
23:31
created

AbstractTreeView   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 632
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 57
eloc 177
dl 0
loc 632
rs 5.04
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getLanguageService() 0 3 1
A init() 0 14 4
A getRootRecord() 0 3 1
A getTitleStr() 0 5 2
A addTagAttributes() 0 3 1
A getRootIcon() 0 4 1
A determineScriptUrl() 0 4 1
A getCount() 0 21 1
A getIcon() 0 17 4
A __construct() 0 3 1
C getTree() 0 75 15
A getDataInit() 0 24 2
A PMicon() 0 7 2
A getDataNext() 0 9 3
A getDataFree() 0 3 1
A getThisScript() 0 3 2
A addField() 0 4 4
A reset() 0 7 1
A getBackendUser() 0 3 1
A getTitleAttrib() 0 3 1
A PM_ATagWrap() 0 9 5
A getDataCount() 0 3 1
A getRecord() 0 3 1
A expandNext() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractTreeView often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractTreeView, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Backend\Tree\View;
17
18
use TYPO3\CMS\Backend\Routing\UriBuilder;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\QueryHelper;
23
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
25
use TYPO3\CMS\Core\Imaging\Icon;
26
use TYPO3\CMS\Core\Imaging\IconFactory;
27
use TYPO3\CMS\Core\Localization\LanguageService;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
30
/**
31
 * Base class for creating a browsable array/page/folder tree in HTML
32
 */
33
abstract class AbstractTreeView
34
{
35
    // EXTERNAL, static:
36
37
    // Holds the current script to reload to.
38
    /**
39
     * @var string
40
     */
41
    public $thisScript = '';
42
43
    // Used if the tree is made of records (not folders for ex.)
44
    /**
45
     * @var string
46
     */
47
    public $title = 'no title';
48
49
    /**
50
     * Needs to be initialized with $GLOBALS['BE_USER']
51
     * Done by default in init()
52
     *
53
     * @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication|string
54
     */
55
    public $BE_USER = '';
56
57
    /**
58
     * Needs to be initialized with e.g. $GLOBALS['BE_USER']->returnWebmounts()
59
     * Default setting in init() is 0 => 0
60
     * The keys are mount-ids (can be anything basically) and the
61
     * values are the ID of the root element (COULD be zero or anything else.
62
     * For pages that would be the uid of the page, zero for the pagetree root.)
63
     *
64
     * @var array|null
65
     */
66
    public $MOUNTS;
67
68
    /**
69
     * Database table to get the tree data from.
70
     * Leave blank if data comes from an array.
71
     *
72
     * @var string
73
     */
74
    public $table = '';
75
76
    /**
77
     * Defines the field of $table which is the parent id field (like pid for table pages).
78
     *
79
     * @var string
80
     */
81
    public $parentField = 'pid';
82
83
    /**
84
     * WHERE clause used for selecting records for the tree. Is set by function init.
85
     * Only makes sense when $this->table is set.
86
     *
87
     * @see init()
88
     * @var string
89
     */
90
    public $clause = '';
91
92
    /**
93
     * Field for ORDER BY. Is set by function init.
94
     * Only makes sense when $this->table is set.
95
     *
96
     * @see init()
97
     * @var string
98
     */
99
    public $orderByFields = '';
100
101
    /**
102
     * Default set of fields selected from the tree table.
103
     * Make SURE that these fields names listed herein are actually possible to select from $this->table (if that variable is set to a TCA table name)
104
     *
105
     * @see addField()
106
     * @var array
107
     */
108
    public $fieldArray = [
109
        'uid',
110
        'pid',
111
        'title',
112
        'is_siteroot',
113
        'doktype',
114
        'nav_title',
115
        'mount_pid',
116
        'php_tree_stop',
117
        't3ver_state',
118
        'hidden',
119
        'starttime',
120
        'endtime',
121
        'fe_group',
122
        'module',
123
        'extendToSubpages',
124
        'nav_hide',
125
        't3ver_wsid',
126
    ];
127
128
    /**
129
     * List of other fields which are ALLOWED to set (here, based on the "pages" table!)
130
     *
131
     * @see addField()
132
     * @var string
133
     */
134
    public $defaultList = 'uid,pid,tstamp,sorting,deleted,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,crdate,cruser_id';
135
136
    /**
137
     * If 1, HTML code is also accumulated in ->tree array during rendering of the tree
138
     *
139
     * @var int
140
     */
141
    public $makeHTML = 1;
142
143
    /**
144
     * If TRUE, records as selected will be stored internally in the ->recs array
145
     *
146
     * @var int
147
     */
148
    public $setRecs = 0;
149
150
    // *********
151
    // Internal
152
    // *********
153
    // For record trees:
154
    // one-dim array of the uid's selected.
155
    /**
156
     * @var array
157
     */
158
    public $ids = [];
159
160
    // The hierarchy of element uids
161
    /**
162
     * @var array
163
     */
164
    public $ids_hierarchy = [];
165
166
    // The hierarchy of versioned element uids
167
    /**
168
     * @var array
169
     */
170
    public $orig_ids_hierarchy = [];
171
172
    // Temporary, internal array
173
    /**
174
     * @var array
175
     */
176
    public $buffer_idH = [];
177
178
    // For both types
179
    // Tree is accumulated in this variable
180
    /**
181
     * @var array
182
     */
183
    public $tree = [];
184
185
    // Holds (session stored) information about which items in the tree are unfolded and which are not.
186
    /**
187
     * @var array
188
     */
189
    public $stored = [];
190
191
    // Points to the current mountpoint key
192
    /**
193
     * @var int
194
     */
195
    public $bank = 0;
196
197
    // Accumulates the displayed records.
198
    /**
199
     * @var array
200
     */
201
    public $recs = [];
202
203
    /**
204
     * Constructor
205
     */
206
    public function __construct()
207
    {
208
        $this->determineScriptUrl();
209
    }
210
211
    /**
212
     * Sets the script url depending on being a module or script request
213
     */
214
    protected function determineScriptUrl()
215
    {
216
        $this->thisScript = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoutePath(
217
            $GLOBALS['TYPO3_REQUEST']->getAttribute('route')->getPath()
218
        );
219
    }
220
221
    /**
222
     * @return string
223
     */
224
    protected function getThisScript()
225
    {
226
        return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
227
    }
228
229
    /**
230
     * Initialize the tree class. Needs to be overwritten
231
     *
232
     * @param string $clause Record WHERE clause
233
     * @param string $orderByFields Record ORDER BY field
234
     */
235
    public function init($clause = '', $orderByFields = '')
236
    {
237
        // Setting BE_USER by default
238
        $this->BE_USER = $GLOBALS['BE_USER'];
239
        // Setting clause
240
        if ($clause) {
241
            $this->clause = $clause;
242
        }
243
        if ($orderByFields) {
244
            $this->orderByFields = $orderByFields;
245
        }
246
        if (!is_array($this->MOUNTS)) {
247
            // Dummy
248
            $this->MOUNTS = [0 => 0];
249
        }
250
    }
251
252
    /**
253
     * Adds a fieldname to the internal array ->fieldArray
254
     *
255
     * @param string $field Field name to
256
     * @param bool $noCheck If set, the fieldname will be set no matter what. Otherwise the field name must either be found as key in $GLOBALS['TCA'][$table]['columns'] or in the list ->defaultList
257
     */
258
    public function addField($field, $noCheck = false)
259
    {
260
        if ($noCheck || is_array($GLOBALS['TCA'][$this->table]['columns'][$field]) || GeneralUtility::inList($this->defaultList, $field)) {
261
            $this->fieldArray[] = $field;
262
        }
263
    }
264
265
    /**
266
     * Resets the tree, recs, ids, ids_hierarchy and orig_ids_hierarchy internal variables. Use it if you need it.
267
     */
268
    public function reset()
269
    {
270
        $this->tree = [];
271
        $this->recs = [];
272
        $this->ids = [];
273
        $this->ids_hierarchy = [];
274
        $this->orig_ids_hierarchy = [];
275
    }
276
277
    /*******************************************
278
     *
279
     * rendering parts
280
     *
281
     *******************************************/
282
    /**
283
     * Generate the plus/minus icon for the browsable tree.
284
     *
285
     * @param array $row Record for the entry
286
     * @param int $a The current entry number
287
     * @param int $c The total number of entries. If equal to $a, a "bottom" element is returned.
288
     * @param int $nextCount The number of sub-elements to the current element.
289
     * @param bool $isOpen The element was expanded to render subelements if this flag is set.
290
     * @return string Image tag with the plus/minus icon.
291
     * @internal
292
     * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::PMicon()
293
     */
294
    public function PMicon($row, $a, $c, $nextCount, $isOpen)
295
    {
296
        if ($nextCount) {
297
            $bMark = $this->bank . '_' . $row['uid'];
298
            return $this->PM_ATagWrap($bMark, $isOpen);
299
        }
300
        return '';
301
    }
302
303
    /**
304
     * Wrap the plus/minus icon in a link
305
     *
306
     * @param string $bMark If set, the link will have an anchor point (=$bMark) and a name attribute (=$bMark)
307
     * @param bool $isOpen
308
     * @return string Link-wrapped input string
309
     * @internal
310
     */
311
    public function PM_ATagWrap($bMark = '', $isOpen = false)
312
    {
313
        if ($this->thisScript) {
314
            $anchor = $bMark ? '#' . $bMark : '';
315
            $name = $bMark ? ' name="' . $bMark . '"' : '';
316
            $aUrl = $this->getThisScript() . $anchor;
317
            return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed') . '" href="' . htmlspecialchars($aUrl) . '"' . $name . '><i class="fa"></i></a>';
318
        }
319
        return '';
320
    }
321
322
    /**
323
     * Adds attributes to image tag.
324
     *
325
     * @param string $icon Icon image tag
326
     * @param string $attr Attributes to add, eg. ' border="0"'
327
     * @return string Image tag, modified with $attr attributes added.
328
     */
329
    public function addTagAttributes($icon, $attr)
330
    {
331
        return preg_replace('/ ?\\/?>$/', '', $icon) . ' ' . $attr . ' />';
332
    }
333
334
    /*******************************************
335
     *
336
     * tree handling
337
     *
338
     *******************************************/
339
    /**
340
     * Returns TRUE/FALSE if the next level for $id should be expanded - based on
341
     * data in $this->stored[][] and ->expandAll flag.
342
     * Extending parent function
343
     *
344
     * @param int $id Record id/key
345
     * @return bool
346
     * @internal
347
     * @see \TYPO3\CMS\Backend\Tree\View\PageTreeView::expandNext()
348
     */
349
    public function expandNext($id)
350
    {
351
        return !empty($this->stored[$this->bank][$id]);
352
    }
353
354
    /******************************
355
     *
356
     * Functions that might be overwritten by extended classes
357
     *
358
     ********************************/
359
    /**
360
     * Returns the root icon for a tree/mountpoint (defaults to the globe)
361
     *
362
     * @param array $rec Record for root.
363
     * @return string Icon image tag.
364
     */
365
    public function getRootIcon($rec)
366
    {
367
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
368
        return $iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render();
369
    }
370
371
    /**
372
     * Get the icon markup for the row
373
     *
374
     * @param array $row The row to get the icon for
375
     * @return string The icon markup, wrapped into a span tag, with the records title as title attribute
376
     */
377
    public function getIcon($row): string
378
    {
379
        if (is_int($row)) {
0 ignored issues
show
introduced by
The condition is_int($row) is always false.
Loading history...
380
            trigger_error(
381
                'Calling ' . __METHOD__ . ' with argument $row containing the records uid is deprecated and will be removed in v12. Use the full row instead.',
382
                E_USER_DEPRECATED
383
            );
384
385
            $row = BackendUtility::getRecord($this->table, $row);
386
            if ($row === null) {
387
                return '';
388
            }
389
        }
390
        $title = $this->getTitleAttrib($row);
391
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
392
        $icon = $row['is_siteroot'] ? $iconFactory->getIcon('apps-pagetree-folder-root', Icon::SIZE_SMALL) : $iconFactory->getIconForRecord($this->table, $row, Icon::SIZE_SMALL);
393
        return '<span title="' . $title . '">' . $icon->render() . '</span>';
394
    }
395
396
    /**
397
     * Returns the title for the input record. If blank, a "no title" label (localized) will be returned.
398
     * Do NOT htmlspecialchar the string from this function - has already been done.
399
     *
400
     * @param array $row The input row array (where the key "title" is used for the title)
401
     * @param int $titleLen Title length (30)
402
     * @return string The title.
403
     */
404
    public function getTitleStr($row, $titleLen = 30)
405
    {
406
        $title = htmlspecialchars(GeneralUtility::fixed_lgd_cs($row['title'], $titleLen));
407
        $title = trim($row['title']) === '' ? '<em>[' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>' : $title;
408
        return $title;
409
    }
410
411
    /**
412
     * Returns the value for the image "title" attribute
413
     *
414
     * @param array $row The input row array (where the key "title" is used for the title)
415
     * @return string The attribute value (is htmlspecialchared() already)
416
     */
417
    public function getTitleAttrib($row)
418
    {
419
        return htmlspecialchars($row['title']);
420
    }
421
422
    /********************************
423
     *
424
     * tree data building
425
     *
426
     ********************************/
427
    /**
428
     * Fetches the data for the tree
429
     *
430
     * @param int $uid item id for which to select subitems (parent id)
431
     * @param int $depth Max depth (recursivity limit)
432
     * @param string $depthData HTML-code prefix for recursive calls.
433
     * @return int The count of items on the level
434
     */
435
    public function getTree($uid, $depth = 999, $depthData = '')
436
    {
437
        // Buffer for id hierarchy is reset:
438
        $this->buffer_idH = [];
439
        // Init vars
440
        $depth = (int)$depth;
441
        $HTML = '';
442
        $a = 0;
443
        $res = $this->getDataInit($uid);
444
        $c = $this->getDataCount($res);
445
        $crazyRecursionLimiter = 9999;
446
        $idH = [];
447
        // Traverse the records:
448
        while ($crazyRecursionLimiter > 0 && ($row = $this->getDataNext($res))) {
449
            /** @var array $row */
450
            if (!$this->getBackendUser()->isInWebMount($this->table === 'pages' ? $row : $row['pid'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getBackendUser()-...' ? $row : $row['pid']) of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
451
                // Current record is not within web mount => skip it
452
                continue;
453
            }
454
455
            $a++;
456
            $crazyRecursionLimiter--;
457
            $newID = $row['uid'];
458
            if ($newID == 0) {
459
                throw new \RuntimeException('Endless recursion detected: TYPO3 has detected an error in the database. Please fix it manually (e.g. using phpMyAdmin) and change the UID of ' . $this->table . ':0 to a new value. See https://forge.typo3.org/issues/16150 to get more information about a possible cause.', 1294586383);
460
            }
461
            // Reserve space.
462
            $this->tree[] = [];
463
            end($this->tree);
464
            // Get the key for this space
465
            $treeKey = key($this->tree);
466
            // If records should be accumulated, do so
467
            if ($this->setRecs) {
468
                $this->recs[$row['uid']] = $row;
469
            }
470
            // Accumulate the id of the element in the internal arrays
471
            $this->ids[] = ($idH[$row['uid']]['uid'] = $row['uid']);
472
            $this->ids_hierarchy[$depth][] = $row['uid'];
473
            $this->orig_ids_hierarchy[$depth][] = $row['_ORIG_uid'] ?: $row['uid'];
474
475
            // Make a recursive call to the next level
476
            $nextLevelDepthData = $depthData . '<span class="treeline-icon treeline-icon-' . ($a === $c ? 'clear' : 'line') . '"></span>';
477
            $hasSub = $this->expandNext($newID) && !$row['php_tree_stop'];
478
            if ($depth > 1 && $hasSub) {
479
                $nextCount = $this->getTree($newID, $depth - 1, $nextLevelDepthData);
480
                if (!empty($this->buffer_idH)) {
481
                    $idH[$row['uid']]['subrow'] = $this->buffer_idH;
482
                }
483
                // Set "did expand" flag
484
                $isOpen = true;
485
            } else {
486
                $nextCount = $this->getCount($newID);
487
                // Clear "did expand" flag
488
                $isOpen = false;
489
            }
490
            // Set HTML-icons, if any:
491
            if ($this->makeHTML) {
492
                $HTML = $this->PMicon($row, $a, $c, $nextCount, $isOpen);
493
            }
494
            // Finally, add the row/HTML content to the ->tree array in the reserved key.
495
            $this->tree[$treeKey] = [
496
                'row' => $row,
497
                'HTML' => $HTML,
498
                'invertedDepth' => $depth,
499
                'depthData' => $depthData,
500
                'bank' => $this->bank,
501
                'hasSub' => $nextCount && $hasSub,
502
                'isFirst' => $a === 1,
503
                'isLast' => $a === $c,
504
            ];
505
        }
506
507
        $this->getDataFree($res);
508
        $this->buffer_idH = $idH;
509
        return $c;
510
    }
511
512
    /********************************
513
     *
514
     * Data handling
515
     * Works with records and arrays
516
     *
517
     ********************************/
518
    /**
519
     * Returns the number of records having the parent id, $uid
520
     *
521
     * @param int $uid Id to count subitems for
522
     * @return int
523
     * @internal
524
     */
525
    public function getCount($uid)
526
    {
527
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
528
        $queryBuilder->getRestrictions()
529
                ->removeAll()
530
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
531
                ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace));
532
        $count = $queryBuilder
533
                ->count('uid')
534
                ->from($this->table)
535
                ->where(
536
                    $queryBuilder->expr()->eq(
537
                        $this->parentField,
538
                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
539
                    ),
540
                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
541
                )
542
                ->execute()
543
                ->fetchColumn();
544
545
        return (int)$count;
546
    }
547
548
    /**
549
     * Returns root record for uid (<=0)
550
     *
551
     * @return array Array with title/uid keys with values of $this->title/0 (zero)
552
     */
553
    public function getRootRecord()
554
    {
555
        return ['title' => $this->title, 'uid' => 0];
556
    }
557
558
    /**
559
     * Returns the record for a uid.
560
     * For tables: Looks up the record in the database.
561
     * For arrays: Returns the fake record for uid id.
562
     *
563
     * @param int $uid UID to look up
564
     * @return array The record
565
     */
566
    public function getRecord($uid)
567
    {
568
        return BackendUtility::getRecordWSOL($this->table, $uid);
569
    }
570
571
    /**
572
     * Getting the tree data: Selecting/Initializing data pointer to items for a certain parent id.
573
     * For tables: This will make a database query to select all children to "parent"
574
     * For arrays: This will return key to the ->dataLookup array
575
     *
576
     * @param int $parentId parent item id
577
     *
578
     * @return mixed Data handle (Tables: An sql-resource, arrays: A parentId integer. -1 is returned if there were NO subLevel.)
579
     * @internal
580
     */
581
    public function getDataInit($parentId)
582
    {
583
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
584
        $queryBuilder->getRestrictions()
585
                ->removeAll()
586
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
587
                ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->getBackendUser()->workspace));
588
        $queryBuilder
589
                ->select(...$this->fieldArray)
590
                ->from($this->table)
591
                ->where(
592
                    $queryBuilder->expr()->eq(
593
                        $this->parentField,
594
                        $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT)
595
                    ),
596
                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
597
                );
598
599
        foreach (QueryHelper::parseOrderBy($this->orderByFields) as $orderPair) {
600
            [$fieldName, $order] = $orderPair;
601
            $queryBuilder->addOrderBy($fieldName, $order);
602
        }
603
604
        return $queryBuilder->execute();
605
    }
606
607
    /**
608
     * Getting the tree data: Counting elements in resource
609
     *
610
     * @param mixed $res Data handle
611
     * @return int number of items
612
     * @internal
613
     * @see getDataInit()
614
     */
615
    public function getDataCount(&$res)
616
    {
617
        return $res->rowCount();
618
    }
619
620
    /**
621
     * Getting the tree data: next entry
622
     *
623
     * @param mixed $res Data handle
624
     *
625
     * @return array|bool item data array OR FALSE if end of elements.
626
     * @internal
627
     * @see getDataInit()
628
     */
629
    public function getDataNext(&$res)
630
    {
631
        while ($row = $res->fetch()) {
632
            BackendUtility::workspaceOL($this->table, $row, $this->getBackendUser()->workspace, true);
633
            if (is_array($row)) {
634
                break;
635
            }
636
        }
637
        return $row;
638
    }
639
640
    /**
641
     * Getting the tree data: frees data handle
642
     *
643
     * @param mixed $res Data handle
644
     * @internal
645
     */
646
    public function getDataFree(&$res)
647
    {
648
        $res->closeCursor();
649
    }
650
651
    /**
652
     * @return LanguageService
653
     */
654
    protected function getLanguageService()
655
    {
656
        return $GLOBALS['LANG'];
657
    }
658
659
    /**
660
     * @return BackendUserAuthentication
661
     */
662
    protected function getBackendUser()
663
    {
664
        return $GLOBALS['BE_USER'];
665
    }
666
}
667