Completed
Pull Request — development (#3050)
by John
14:46
created

Menu::processAreaSubsections()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 41
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 18
nc 9
nop 3
dl 0
loc 41
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This class contains a standard way of displaying side/drop down menus.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 * license:   BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version   2.0 dev
15
 *
16
 */
17
18
declare(strict_types=1);
19
20
namespace ElkArte\Menu;
21
22
use HttpReq;
23
24
/**
25
 * This class implements a standard way of creating menus
26
 */
27
class Menu
28
{
29
	/** @var HttpReq */
30
	protected $req;
31
32
	/** @var array Will hold the created $context */
33
	protected $menuContext = [];
34
35
	/** @var string Used for profile menu for own / any */
36
	protected $permissionSet;
37
38
	/** @var bool If we found the menu item selected */
39
	protected $foundSection = false;
40
41
	/** @var string Current area */
42
	protected $currentArea = '';
43
44
	/** @var null|string The current subaction of the system */
45
	protected $currentSubaction = '';
46
47
	/** @var array Will hold the selected menu data that is returned to the caller */
48
	private $includeData = [];
49
50
	/** @var int Unique menu number */
51
	private $maxMenuId = 0;
52
53
	/** @var array  Holds menu options set by AddOptions */
54
	public $menuOptions = [];
55
56
	/** @var array  Holds menu definition structure set by AddAreas */
57
	public $menuData = [];
58
59
	/**
60
	 * Initial processing for the menu
61
	 *
62
	 * @param HttpReq|null $req
63
	 */
64
	public function __construct(HttpReq $req = null)
65
	{
66
		global $context;
67
68
		// Access to post/get data
69
		$this->req = $req ?: HttpReq::instance();
70
71
		// Every menu gets a unique ID, these are shown in first in, first out order.
72
		$this->maxMenuId = ($context['max_menu_id'] ?? 0) + 1;
73
74
		// This will be all the data for this menu
75
		$this->menuContext = [];
76
77
		// This is necessary only in profile (at least for the core), but we do it always because it's easier
78
		$this->permissionSet = !empty($context['user']['is_owner']) ? 'own' : 'any';
79
80
		// We may have a current subaction
81
		$this->currentSubaction = $context['current_subaction'] ?? null;
82
	}
83
84
	/**
85
	 * Create a menu
86
	 *
87
	 * @return array
88
	 * @throws \Elk_Exception
89
	 */
90
	public function prepareMenu(): array
91
	{
92
		// Process the menu Options
93
		$this->processMenuOptions();
94
95
		// Check the menus urls
96
		$this->setBaseUrl();
97
98
		// Process the menu Data
99
		$this->processMenuData();
100
101
		// Check the menus urls
102
		$this->setActiveButtons();
103
104
		// Make sure we created some awesome sauce
105
		if (!$this->validateData())
106
		{
107
			throw new \Elk_Exception('no_access', false);
108
		}
109
110
		// Finally - return information on the selected item.
111
		return $this->includeData + [
112
				'current_action' => $this->menuContext['current_action'],
113
				'current_area' => $this->currentArea,
114
				'current_section' => !empty($this->menuContext['current_section']) ? $this->menuContext['current_section'] : '',
115
				'current_subsection' => $this->currentSubaction,
116
			];
117
	}
118
119
	/**
120
	 * Prepares tabs for the template.
121
	 *
122
	 * This should be called after the area is dispatched, because areas
123
	 * are usually in their own file. Those files, once dispatched to, hold
124
	 * some data for the tabs which must be specially combined with subaction
125
	 * data for everything to work properly.
126
	 *
127
	 * Seems complicated, yes.
128
	 */
129
	public function prepareTabData(): void
130
	{
131
		global $context;
132
133
		// Handy shortcut.
134
		$tabContext = &$context['menu_data_' . $this->maxMenuId]['tab_data'];
135
136
		// Tabs are really just subactions.
137
		if (isset($tabContext['tabs'], $this->menuContext['sections'][$this->menuContext['current_section']]['areas'][$this->currentArea]['subsections']))
138
		{
139
			$tabContext['tabs'] = array_replace_recursive(
140
				$tabContext['tabs'],
141
				$this->menuContext['sections'][$this->menuContext['current_section']]['areas'][$this->currentArea]['subsections']
142
			);
143
144
			// Has it been deemed selected?
145
			$tabContext = array_merge($tabContext, $tabContext['tabs'][$this->currentSubaction]);
146
		}
147
	}
148
149
	/**
150
	 * Performs a sanity check that a menu was created successfully
151
	 *
152
	 *   - If it fails to find valid data, will reset max_menu_id and any menu context created
153
	 *
154
	 * @return bool
155
	 */
156
	private function validateData(): bool
157
	{
158
		if (empty($this->menuContext['sections']))
159
		{
160
			return false;
161
		}
162
163
		// Check we had something - for sanity sake.
164
		return !empty($this->includeData);
165
	}
166
167
	/**
168
	 * Process the array of MenuOptions passed to the class
169
	 */
170
	protected function processMenuOptions(): void
171
	{
172
		global $context;
173
174
		// What is the general action of this menu i.e. $scripturl?action=XYZ.
175
		$this->menuContext['current_action'] = $this->menuOptions['action'] ?? $context['current_action'];
176
177
		// What is the current area selected?
178
		$this->currentArea = $this->req->getQuery('area', 'trim|strval', $this->menuOptions['area'] ?? '');
179
180
		$this->buildAdditionalParams();
181
		$this->buildTemplateVars();
182
	}
183
184
	/**
185
	 * Build the menuOption additional parameters for use in the url
186
	 */
187
	private function buildAdditionalParams(): void
188
	{
189
		global $context;
190
191
		$this->menuContext['extra_parameters'] = '';
192
193
		if (!empty($this->menuOptions['extra_url_parameters']))
194
		{
195
			foreach ($this->menuOptions['extra_url_parameters'] as $key => $value)
196
			{
197
				$this->menuContext['extra_parameters'] .= ';' . $key . '=' . $value;
198
			}
199
		}
200
201
		// Only include the session ID in the URL if it's strictly necessary.
202
		if (empty($this->menuOptions['disable_url_session_check']))
203
		{
204
			$this->menuContext['extra_parameters'] .= ';' . $context['session_var'] . '=' . $context['session_id'];
205
		}
206
	}
207
208
	/**
209
	 * Process the menuData array passed to the class
210
	 *
211
	 *   - Only processes areas that are enabled and that the user has permissions
212
	 */
213
	protected function processMenuData(): void
214
	{
215
		// Now setup the context correctly.
216
		foreach ($this->menuData as $sectionId => $section)
217
		{
218
			// Is this section enabled? and do they have permissions?
219
			if ($section->isEnabled() && $this->checkPermissions($section))
220
			{
221
				$this->setSectionContext($sectionId, $section);
222
223
				// Process this menu section
224
				$this->processSectionAreas($sectionId, $section);
225
			}
226
		}
227
	}
228
229
	/**
230
	 * Determines if the user has the permissions to access the section/area
231
	 *
232
	 * If said item did not provide any permission to check, full
233
	 * unfettered access is assumed.
234
	 *
235
	 * The profile areas are a bit different in that each permission is
236
	 * divided into two sets: "own" for owner and "any" for everyone else.
237
	 *
238
	 * @param MenuItem $obj area or section being checked
239
	 *
240
	 * @return bool
241
	 */
242
	private function checkPermissions(MenuItem $obj): bool
243
	{
244
		if (!empty($obj->getPermission()))
245
		{
246
			// The profile menu has slightly different permissions
247
			if (is_array($obj->getPermission()) && isset($obj->getPermission()['own'], $obj->getPermission()['any']))
248
			{
249
				return allowedTo($obj->getPermission()[$this->permissionSet]);
250
			}
251
252
			return allowedTo($obj->getPermission());
253
		}
254
255
		return true;
256
	}
257
258
	/**
259
	 * Checks if the area has a label or not
260
	 *
261
	 * @param string   $areaId
262
	 * @param MenuArea $area
263
	 *
264
	 * @return bool
265
	 */
266
	private function areaHasLabel(string $areaId, MenuArea $area): bool
267
	{
268
		global $txt;
269
270
		return !empty($area->getLabel()) || isset($txt[$areaId]);
271
	}
272
273
	/**
274
	 * Main processing for creating the menu items for all sections
275
	 *
276
	 * @param string      $sectionId
277
	 * @param MenuSection $section
278
	 */
279
	protected function processSectionAreas(string $sectionId, MenuSection $section): void
280
	{
281
		// Now we cycle through the sections to pick the right area.
282
		foreach ($section->getAreas() as $areaId => $area)
283
		{
284
			// Is the area enabled, Does the user have permission and it has some form of a name
285
			if ($area->isEnabled() && $this->checkPermissions($area) && $this->areaHasLabel($areaId, $area))
286
			{
287
				// Make sure we have a valid current area
288
				$this->setFirstAreaCurrent($sectionId, $areaId, $area);
289
290
				// If this is hidden from view don't do the rest.
291
				if (!$area->isHidden())
292
				{
293
					// First time this section?
294
					$this->setAreaContext($sectionId, $areaId, $area);
295
296
					// Maybe a custom url
297
					$this->setAreaUrl($sectionId, $areaId, $area);
298
299
					// Even a little icon
300
					$this->setAreaIcon($sectionId, $areaId, $area);
301
302
					// Did it have subsections?
303
					$this->processAreaSubsections($sectionId, $areaId, $area);
304
				}
305
306
				// Is this the current section?
307
				$this->checkCurrentSection($sectionId, $areaId, $area);
308
			}
309
		}
310
	}
311
312
	/**
313
	 * Checks the menu item to see if it is the currently selected one
314
	 *
315
	 * @param string   $sectionId
316
	 * @param string   $areaId
317
	 * @param MenuArea $area
318
	 */
319
	private function checkCurrentSection(string $sectionId, string $areaId, MenuArea $area): void
320
	{
321
		// Is this the current section?
322
		if ($this->currentArea == $areaId && !$this->foundSection)
323
		{
324
			$this->setAreaCurrent($sectionId, $areaId, $area);
325
326
			// Only do this once, m'kay?
327
			$this->foundSection = true;
328
		}
329
	}
330
331
	/**
332
	 * @param string   $sectionId
333
	 * @param string   $areaId
334
	 * @param MenuArea $area
335
	 */
336
	private function setFirstAreaCurrent(string $sectionId, string $areaId, MenuArea $area): void
337
	{
338
		// If we don't have an area then the first valid one is our choice.
339
		if (empty($this->currentArea))
340
		{
341
			$this->setAreaCurrent($sectionId, $areaId, $area);
342
		}
343
	}
344
345
	/**
346
	 * Simply sets the current area
347
	 *
348
	 * @param string   $sectionId
349
	 * @param string   $areaId
350
	 * @param MenuArea $area
351
	 */
352
	private function setAreaCurrent(string $sectionId, string $areaId, MenuArea $area): void
353
	{
354
		// Update the context if required - as we can have areas pretending to be others. ;)
355
		$this->menuContext['current_section'] = $sectionId;
356
		$this->currentArea = $area->getSelect() ?: $areaId;
357
		$this->includeData = $area->toArray();
358
	}
359
360
	/**
361
	 * @param MenuItem $obj
362
	 * @param integer  $idx
363
	 *
364
	 * @return string
365
	 */
366
	private function parseCounter(MenuItem $obj, int $idx): string
367
	{
368
		global $settings;
369
370
		$counter = '';
371
		if (isset($this->menuOptions['counters']) && !empty($this->menuOptions['counters'][$obj->getCounter()]))
372
		{
373
			$counter = sprintf(
374
				$settings['menu_numeric_notice'][$idx],
375
				$this->menuOptions['counters'][$obj->getCounter()]
376
			);
377
		}
378
379
		return $counter;
380
	}
381
382
	/**
383
	 * Sets the various section ID items
384
	 *
385
	 * What it does:
386
	 *   - If the ID is not set, sets it and sets the section title
387
	 *   - Sets the section title
388
	 *
389
	 * @param string      $sectionId
390
	 * @param MenuSection $section
391
	 */
392
	private function setSectionContext(string $sectionId, MenuSection $section): void
393
	{
394
		global $txt;
395
396
		$this->menuContext['sections'][$sectionId] = [
397
			'id' => $sectionId,
398
			'label' => ($section->getLabel() ?: $txt[$sectionId]) . $this->parseCounter($section, 0),
399
			'url' => $this->menuContext['base_url'] . $this->menuContext['extra_parameters'],
400
		];
401
	}
402
403
	/**
404
	 * Sets the various area items
405
	 *
406
	 * What it does:
407
	 *   - If the ID is not set, sets it and sets the area title
408
	 *   - Sets the area title
409
	 *
410
	 * @param string   $sectionId
411
	 * @param string   $areaId
412
	 * @param MenuArea $area
413
	 */
414
	private function setAreaContext(string $sectionId, string $areaId, MenuArea $area): void
415
	{
416
		global $txt;
417
418
		$this->menuContext['sections'][$sectionId]['areas'][$areaId] = [
419
			'label' => ($area->getLabel() ?: $txt[$areaId]) . $this->parseCounter($area, 1),
420
		];
421
	}
422
423
	/**
424
	 * Set the URL for the menu item
425
	 *
426
	 * @param string   $sectionId
427
	 * @param string   $areaId
428
	 * @param MenuArea $area
429
	 */
430
	private function setAreaUrl(string $sectionId, string $areaId, MenuArea $area): void
431
	{
432
		$area->setUrl(
433
			$this->menuContext['sections'][$sectionId]['areas'][$areaId]['url'] =
434
				$area->getUrl(
435
				) ?: $this->menuContext['base_url'] . ';area=' . $areaId . $this->menuContext['extra_parameters']
436
		);
437
	}
438
439
	/**
440
	 * Set the menu icon
441
	 *
442
	 * @param string   $sectionId
443
	 * @param string   $areaId
444
	 * @param MenuArea $area
445
	 */
446
	private function setAreaIcon(string $sectionId, string $areaId, MenuArea $area): void
447
	{
448
		global $settings;
449
450
		// Work out where we should get our menu images from.
451
		$imagePath = file_exists($settings['theme_dir'] . '/images/admin/change_menu.png')
452
			? $settings['images_url'] . '/admin'
453
			: $settings['default_images_url'] . '/admin';
454
455
		// Does this area have its own icon?
456
		if (!empty($area->getIcon()))
457
		{
458
			$this->menuContext['sections'][$sectionId]['areas'][$areaId]['icon'] =
459
				'<img ' . (!empty($area->getClass()) ? 'class="' . $area->getClass(
460
					) . '" ' : 'style="background: none"') . ' src="' . $imagePath . '/' . $area->getIcon(
461
				) . '" alt="" />&nbsp;&nbsp;';
462
		}
463
		else
464
		{
465
			$this->menuContext['sections'][$sectionId]['areas'][$areaId]['icon'] = '';
466
		}
467
	}
468
469
	/**
470
	 * Processes all of the subsections for a menu item
471
	 *
472
	 * @param string   $sectionId
473
	 * @param string   $areaId
474
	 * @param MenuArea $area
475
	 */
476
	protected function processAreaSubsections(string $sectionId, string $areaId, MenuArea $area): void
477
	{
478
		// If there are subsections for this menu item
479
		if (!empty($area->getSubsections()))
480
		{
481
			$this->menuContext['sections'][$sectionId]['areas'][$areaId]['subsections'] = [];
482
			$firstSubId = '';
483
484
			// For each subsection process the options
485
			$subSections = array_filter(
486
				$area->getSubsections(),
487
				function ($sub) {
488
					return $this->checkPermissions($sub) && $sub->isEnabled();
489
				}
490
			);
491
			/** @var string $subId */
492
			foreach ($subSections as $subId => $sub)
493
			{
494
				$this->menuContext['sections'][$sectionId]['areas'][$areaId]['subsections'][$subId] = [
495
					'label' => $sub->getLabel() . $this->parseCounter($sub, 2),
496
				];
497
498
				$this->setSubsSectionUrl($sectionId, $areaId, $subId, $sub);
499
500
				if ($this->currentArea == $areaId)
501
				{
502
					if (empty($firstSubId))
503
					{
504
						$firstSubId = $subId;
505
					}
506
					$this->setCurrentSubSection($subId, $sub);
507
				}
508
			}
509
510
			// Is this the current subsection?
511
			if (empty($this->currentSubaction))
512
			{
513
				$this->currentSubaction = $firstSubId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $firstSubId can also be of type integer. However, the property $currentSubaction is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
514
			}
515
		}
516
	}
517
518
	/**
519
	 * @param string         $sectionId
520
	 * @param string         $areaId
521
	 * @param string         $subId
522
	 * @param MenuSubsection $sub
523
	 */
524
	private function setSubsSectionUrl(string $sectionId, string $areaId, string $subId, MenuSubsection $sub): void
525
	{
526
		$sub->setUrl(
527
			$this->menuContext['sections'][$sectionId]['areas'][$areaId]['subsections'][$subId]['url'] =
528
				$sub->getUrl(
529
				) ?: $this->menuContext['base_url'] . ';area=' . $areaId . ';sa=' . $subId . $this->menuContext['extra_parameters']
530
		);
531
	}
532
533
	/**
534
	 * Set the current subsection
535
	 *
536
	 * @param string         $subId
537
	 * @param MenuSubsection $sub
538
	 */
539
	private function setCurrentSubSection(string $subId, MenuSubsection $sub): void
540
	{
541
		// Is this the current subsection?
542
		$subIdCheck = $this->req->getQuery('sa', 'trim', null);
543
		if (
544
			$subIdCheck == $subId
545
			|| in_array($subIdCheck, $sub->getActive(), true)
546
			|| empty($this->currentSubaction) && $sub->isDefault()
547
		)
548
		{
549
			$this->currentSubaction = $subId;
550
		}
551
	}
552
553
	/**
554
	 * Checks and updates base and section urls
555
	 */
556
	private function setBaseUrl(): void
557
	{
558
		global $scripturl;
559
560
		// Should we use a custom base url, or use the default?
561
		$this->menuContext['base_url'] = $this->menuOptions['base_url'] ?? sprintf(
562
				'%s?action=%s',
563
				$scripturl,
564
				$this->menuContext['current_action']
565
			);
566
	}
567
568
	/**
569
	 * Checks and updates base and section urls
570
	 */
571
	private function setActiveButtons(): void
572
	{
573
		// If there are sections quickly goes through all the sections to check if the base menu has an url
574
		if (!empty($this->menuContext['current_section']))
575
		{
576
			$this->menuContext['sections'][$this->menuContext['current_section']]['selected'] = true;
577
			$this->menuContext['sections'][$this->menuContext['current_section']]['areas'][$this->currentArea]['selected'] =
578
				true;
579
580
			if (!empty($this->menuContext['sections'][$this->menuContext['current_section']]['areas'][$this->currentArea]['subsections'][$this->currentSubaction]))
581
			{
582
				$this->menuContext['sections'][$this->menuContext['current_section']]['areas'][$this->currentArea]['subsections'][$this->currentSubaction]['selected'] =
583
					true;
584
			}
585
		}
586
	}
587
588
	/**
589
	 * Add the base menu options for this menu
590
	 *
591
	 * @param array $menuOptions an array of options that can be used to override some default behaviours.
592
	 *                           It can accept the following indexes:
593
	 *                           - action                    => overrides the default action
594
	 *                           - current_area              => overrides the current area
595
	 *                           - extra_url_parameters      => an array or pairs or parameters to be added to the url
596
	 *                           - disable_url_session_check => (boolean) if true the session var/id are omitted from
597
	 *                           the url
598
	 *                           - base_url                  => an alternative base url
599
	 *                           - menu_type                 => alternative menu types?
600
	 *                           - can_toggle_drop_down      => (boolean) if the menu can "toggle"
601
	 *                           - template_name             => an alternative template to load (instead of Generic)
602
	 *                           - layer_name                => alternative layer name for the menu
603
	 *                           - hook                      => hook name to call integrate_ . 'hook name' . '_areas'
604
	 *                           - default_include_dir       => directory to include for function support
605
	 */
606
	public function addOptions(array $menuOptions): void
607
	{
608
		$this->menuOptions = array_merge($this->menuOptions, $menuOptions);
609
	}
610
611
	/**
612
	 * @param string      $id
613
	 * @param MenuSection $section
614
	 *
615
	 * @return $this
616
	 */
617
	public function addSection(string $id, MenuSection $section): Menu
618
	{
619
		$this->menuData[$id] = $section;
620
621
		return $this;
622
	}
623
624
	/**
625
	 * The theme needs some love, too.
626
	 */
627
	private function buildTemplateVars(): void
628
	{
629
		global $userInfo, $options;
630
631
		if (empty($this->menuOptions['menu_type']))
632
		{
633
			$this->menuOptions['menu_type'] = empty($options['use_sidebar_menu']) ? 'dropdown' : 'sidebar';
634
		}
635
		$this->menuOptions['can_toggle_drop_down'] =
636
			!$userInfo['is_guest'] || !empty($this->menuOptions['can_toggle_drop_down']);
637
638
		$this->menuOptions['template_name'] = $this->menuOptions['template_name'] ?? 'GenericMenu';
639
		$this->menuOptions['layer_name'] =
640
			($this->menuOptions['layer_name'] ?? 'generic_menu') . '_' . $this->menuOptions['menu_type'];
641
	}
642
643
	/**
644
	 * Finalizes items so the computed menu can be used
645
	 *
646
	 * What it does:
647
	 *   - Sets the menu layer in the template stack
648
	 *   - Loads context with the computed menu context
649
	 *   - Sets current subaction and current max menu id
650
	 */
651
	public function setContext(): void
652
	{
653
		global $context;
654
655
		// Almost there - load the template and add to the template layers.
656
		theme()->getTemplates()->load($this->menuOptions['template_name']);
657
		theme()->getLayers()->add($this->menuOptions['layer_name']);
658
659
		// Set it all to context for template consumption
660
		$this->menuContext['layer_name'] = $this->menuOptions['layer_name'];
661
		$this->menuContext['can_toggle_drop_down'] = $this->menuOptions['can_toggle_drop_down'];
662
		$context['max_menu_id'] = $this->maxMenuId;
663
		$context['current_subaction'] = $this->currentSubaction;
664
		$this->menuContext['current_subsection'] = $this->currentSubaction;
665
		$this->menuContext['current_area'] = $this->currentArea;
666
		$context['menu_data_' . $this->maxMenuId] = $this->menuContext;
667
	}
668
669
	/**
670
	 * Delete a menu.
671
	 *
672
	 * Checks to see if this menu been loaded into context
673
	 * and, if so, resets $context['max_menu_id'] back to the
674
	 * last known menu (if any) and remove the template layer
675
	 * if there aren't any other known menus.
676
	 */
677
	public function destroy(): void
678
	{
679
		global $context;
680
681
		// Has this menu been loaded into context?
682
		if (isset($context[$menuName = 'menu_data_' . $this->maxMenuId]))
683
		{
684
			// Decrement the pointer if this is the final menu in the series.
685
			if ($this->maxMenuId == $context['max_menu_id'])
686
			{
687
				$context['max_menu_id'] = max($context['max_menu_id'] - 1, 0);
688
			}
689
690
			// Remove the template layer if this was the only menu left.
691
			if ($context['max_menu_id'] == 0)
692
			{
693
				theme()->getLayers()->remove($context[$menuName]['layer_name']);
694
			}
695
696
			unset($context[$menuName]);
697
		}
698
	}
699
}
700