Passed
Push — master ( 99973f...077dc5 )
by
unknown
08:40
created

AppPlugin::performActionsWhenDeletingItem()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 6
nop 2
dl 0
loc 20
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/* See license terms in /license.txt */
3
4
use Chamilo\CoreBundle\Enums\ToolIcon;
5
use Chamilo\CoreBundle\Framework\Container;
6
use Symfony\Component\Finder\Finder;
7
8
/**
9
 * Class AppPlugin.
10
 */
11
class AppPlugin
12
{
13
    public $plugin_regions = [
14
        'main_top',
15
        'main_bottom',
16
        'login_top',
17
        'login_bottom',
18
        'menu_top',
19
        'menu_bottom',
20
        'content_top',
21
        'content_bottom',
22
        'header_main',
23
        'header_center',
24
        'header_left',
25
        'header_right',
26
        'pre_footer',
27
        'footer_left',
28
        'footer_center',
29
        'footer_right',
30
        'menu_administrator',
31
        'course_tool_plugin',
32
    ];
33
34
    public $installedPluginListName = [];
35
    public $installedPluginListObject = [];
36
    private static $instance;
37
38
    /**
39
     * Constructor.
40
     */
41
    public function __construct()
42
    {
43
    }
44
45
    /**
46
     * @return AppPlugin
47
     */
48
    public static function getInstance()
49
    {
50
        if (!isset(self::$instance)) {
51
            self::$instance = new self();
52
        }
53
54
        return self::$instance;
55
    }
56
57
    /**
58
     * Read plugin from path.
59
     */
60
    public function read_plugins_from_path(): array
61
    {
62
        /* We scan the plugin directory. Each folder is a potential plugin. */
63
        $pluginPath = api_get_path(SYS_PLUGIN_PATH);
64
        $finder = (new Finder())->directories()->depth('== 0')->sortByName()->in($pluginPath);
65
66
        $plugins = [];
67
68
        foreach ($finder as $file) {
69
            $plugins[] = $file->getFilename();
70
        }
71
72
        return $plugins;
73
    }
74
75
    /**
76
     * @return array
77
     */
78
    public function getInstalledPluginListName()
79
    {
80
        if (empty($this->installedPluginListName)) {
81
            $this->installedPluginListName = $this->getInstalledPlugins();
82
        }
83
84
        return $this->installedPluginListName;
85
    }
86
87
    /**
88
     * @return array List of Plugin
89
     */
90
    public function getInstalledPluginListObject()
91
    {
92
        if (empty($this->installedPluginListObject)) {
93
            $this->setInstalledPluginListObject();
94
        }
95
96
        return $this->installedPluginListObject;
97
    }
98
99
    public function setInstalledPluginListObject()
100
    {
101
        $pluginListName = $this->getInstalledPluginListName();
102
        $pluginList = [];
103
        if (!empty($pluginListName)) {
104
            foreach ($pluginListName as $pluginName) {
105
                $pluginInfo = $this->getPluginInfo($pluginName, true);
106
                if (isset($pluginInfo['plugin_class'])) {
107
                    $pluginList[] = $pluginInfo['plugin_class']::create();
108
                }
109
            }
110
        }
111
        $this->installedPluginListObject = $pluginList;
112
    }
113
114
    /**
115
     * @param string $plugin
116
     *
117
     * @return bool
118
     */
119
    public function isInstalled($plugin)
120
    {
121
        $list = self::getInstalledPlugins(false);
0 ignored issues
show
Bug Best Practice introduced by
The method AppPlugin::getInstalledPlugins() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

121
        /** @scrutinizer ignore-call */ 
122
        $list = self::getInstalledPlugins(false);
Loading history...
122
123
        return in_array($plugin, $list);
124
    }
125
126
    public function getInstalledPlugins(bool $fromDatabase = true): array
127
    {
128
        static $installedPlugins = null;
129
130
        if (false === $fromDatabase && is_array($installedPlugins)) {
131
            return $installedPlugins;
132
        }
133
134
        if ($fromDatabase || null === $installedPlugins) {
135
            $installedPlugins = [];
136
137
            $plugins = Container::getPluginRepository()->getInstalledPlugins();
138
139
            foreach ($plugins as $plugin) {
140
                $installedPlugins[] = $plugin->getTitle();
141
            }
142
        }
143
144
        return $installedPlugins;
145
    }
146
147
    public function getInstalledPluginsInCurrentUrl()
148
    {
149
        $installedPlugins = [];
150
        $urlId = api_get_current_access_url_id();
151
        $plugins = api_get_settings_params(
152
            [
153
                'variable = ? AND selected_value = ? AND category = ? AND access_url = ?' => ['status', 'installed', 'Plugins', $urlId],
154
            ]
155
        );
156
157
        if (!empty($plugins)) {
158
            foreach ($plugins as $row) {
159
                $installedPlugins[$row['subkey']] = true;
160
            }
161
            $installedPlugins = array_keys($installedPlugins);
162
        }
163
164
        return $installedPlugins;
165
    }
166
167
    /**
168
     * Returns a list of all official (delivered with the Chamilo package)
169
     * plugins. This list is maintained manually and updated with every new
170
     * release to avoid hacking.
171
     *
172
     * @return array
173
     */
174
    public static function getOfficialPlugins(): array
175
    {
176
        // Please keep this list alphabetically sorted
177
        return [
178
            'AzureActiveDirectory',
179
            'Bbb',
180
            'BeforeLogin',
181
            'BuyCourses',
182
            'CardGame',
183
            'CheckExtraFieldAuthorCompany',
184
            'CleanDeletedFiles',
185
            'CourseBlock',
186
            'CourseHomeNotify',
187
            'CourseLegal',
188
            'CustomCertificate',
189
            'CustomFooter',
190
            'Dashboard',
191
            'Dictionary',
192
            'EmbedRegistry',
193
            'ExerciseSignature',
194
            'ExtAuthChamiloLogoutButtonBehaviour',
195
            'ExternalNotificationConnect',
196
            'ExtraMenuFromWebservice',
197
            'GoogleMaps',
198
            'GradingElectronic',
199
            'H5pImport',
200
            'HelloWorld',
201
            'ImsLti',
202
            'Justification',
203
            'LearningCalendar',
204
            'LtiProvider',
205
            'MaintenanceMode',
206
            'MigrationMoodle',
207
            'Mobidico',
208
            'NoSearchIndex',
209
            'NotebookTeacher',
210
            'PauseTraining',
211
            'Pens',
212
            'Positioning',
213
            'QuestionOptionsEvaluation',
214
            'Redirection',
215
            'Resubscription',
216
            'Rss',
217
            'SearchCourse',
218
            'ShowRegions',
219
            'ShowUserInfo',
220
            'Static',
221
            'StudentFollowUp',
222
            'SurveyExportCsv',
223
            'SurveyExportTxt',
224
            'Test2Pdf',
225
            'TopLinks',
226
            'Tour',
227
            'UserRemoteService',
228
            'XApi',
229
            'Zoom',
230
        ];
231
    }
232
233
    public static function isOfficial(string $title): bool
234
    {
235
        return in_array($title, self::getOfficialPlugins());
236
    }
237
238
    public function install(string $pluginName): void
239
    {
240
        $pluginPath = api_get_path(SYS_PLUGIN_PATH).$pluginName.'/install.php';
241
242
        if (is_file($pluginPath) && is_readable($pluginPath)) {
243
            // Execute the install procedure.
244
245
            require $pluginPath;
246
        }
247
    }
248
249
    public function uninstall(string $pluginName): void
250
    {
251
        // First call the custom uninstallation to allow full access to global settings
252
        $pluginPath = api_get_path(SYS_PLUGIN_PATH).$pluginName.'/uninstall.php';
253
        if (is_file($pluginPath) && is_readable($pluginPath)) {
254
            // Execute the uninstall procedure.
255
256
            require $pluginPath;
257
        }
258
    }
259
260
    /**
261
     * @param string $pluginName
262
     *
263
     * @return array
264
     */
265
    public function get_areas_by_plugin($pluginName)
266
    {
267
        $result = api_get_settings('Plugins');
268
        $areas = [];
269
        foreach ($result as $row) {
270
            if ($pluginName == $row['selected_value']) {
271
                $areas[] = $row['variable'];
272
            }
273
        }
274
275
        return $areas;
276
    }
277
278
    /**
279
     * @param string $pluginName
280
     *
281
     * @return bool
282
     */
283
    public function is_valid_plugin($pluginName)
284
    {
285
        if (is_dir(api_get_path(SYS_PLUGIN_PATH).$pluginName)) {
286
            if (is_file(api_get_path(SYS_PLUGIN_PATH).$pluginName.'/index.php')) {
287
                return true;
288
            }
289
        }
290
291
        return false;
292
    }
293
294
    /**
295
     * @return array
296
     */
297
    public function getPluginRegions()
298
    {
299
        sort($this->plugin_regions);
300
301
        return $this->plugin_regions;
302
    }
303
304
    /**
305
     * @param string           $region
306
     * @param Twig_Environment $template
307
     * @param bool             $forced
308
     *
309
     * @return string|null
310
     */
311
    public function loadRegion($pluginName, $region, $template, $forced = false)
312
    {
313
        if ('course_tool_plugin' == $region) {
314
            return '';
315
        }
316
317
        ob_start();
318
        $this->getAllPluginContentsByRegion($pluginName, $region, $template, $forced);
319
        $content = ob_get_contents();
320
        ob_end_clean();
321
322
        return $content;
323
    }
324
325
    /**
326
     * Loads the translation files inside a plugin if exists.
327
     * It loads by default english see the hello world plugin.
328
     *
329
     * @param string $plugin_name
330
     *
331
     * @todo add caching
332
     */
333
    public function load_plugin_lang_variables($plugin_name)
334
    {
335
        $language_interface = api_get_language_isocode();
336
        $root = api_get_path(SYS_PLUGIN_PATH);
337
        $strings = null;
338
339
        // 1. Loading english if exists
340
        $english_path = $root.$plugin_name.'/lang/english.php';
341
        if (is_readable($english_path)) {
342
            include $english_path;
343
344
            foreach ($strings as $key => $string) {
0 ignored issues
show
Bug introduced by
The expression $strings of type null is not traversable.
Loading history...
345
                $GLOBALS[$key] = $string;
346
            }
347
        }
348
349
        // 2. Loading the system language
350
        if ('english' != $language_interface) {
351
            $path = $root.$plugin_name."/lang/$language_interface.php";
352
            if (is_readable($path)) {
353
                include $path;
354
                if (!empty($strings)) {
355
                    foreach ($strings as $key => $string) {
356
                        $GLOBALS[$key] = $string;
357
                    }
358
                }
359
            } else {
360
                /*$interfaceLanguageId = api_get_language_id($language_interface);
361
                $interfaceLanguageInfo = api_get_language_info($interfaceLanguageId);
362
                $languageParentId = intval($interfaceLanguageInfo['parent_id']);
363
364
                if ($languageParentId > 0) {
365
                    $languageParentInfo = api_get_language_info($languageParentId);
366
                    $languageParentFolder = $languageParentInfo['dokeos_folder'];
367
368
                    $parentPath = "{$root}{$plugin_name}/lang/{$languageParentFolder}.php";
369
                    if (is_readable($parentPath)) {
370
                        include $parentPath;
371
                        if (!empty($strings)) {
372
                            foreach ($strings as $key => $string) {
373
                                $this->strings[$key] = $string;
374
                            }
375
                        }
376
                    }
377
                }*/
378
            }
379
        }
380
    }
381
382
    /**
383
     * @param string           $region
384
     * @param Twig_Environment $template
385
     * @param bool             $forced
386
     *
387
     * @return bool
388
     *
389
     * @todo improve this function
390
     */
391
    public function getAllPluginContentsByRegion($plugin_name, $region, $template, $forced = false)
392
    {
393
        // The plugin_info variable is available inside the plugin index
394
        $plugin_info = $this->getPluginInfo($plugin_name, $forced);
395
396
        // We also know where the plugin is
397
        $plugin_info['current_region'] = $region;
398
399
        // Loading the plugin/XXX/index.php file
400
        $plugin_file = api_get_path(SYS_PLUGIN_PATH)."$plugin_name/index.php";
401
402
        if (file_exists($plugin_file)) {
403
            //Loading the lang variables of the plugin if exists
404
            self::load_plugin_lang_variables($plugin_name);
0 ignored issues
show
Bug Best Practice introduced by
The method AppPlugin::load_plugin_lang_variables() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

404
            self::/** @scrutinizer ignore-call */ 
405
                  load_plugin_lang_variables($plugin_name);
Loading history...
405
406
            // Printing the plugin index.php file
407
            require $plugin_file;
408
409
            // If the variable $_template is set we assign those values to be accessible in Twig
410
            if (isset($_template)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $_template seems to never exist and therefore isset should always be false.
Loading history...
411
                $_template['plugin_info'] = $plugin_info;
412
            } else {
413
                $_template = [];
414
                $_template['plugin_info'] = $plugin_info;
415
            }
416
417
            // Setting the plugin info available in the template if exists.
418
            //$template->addGlobal($plugin_name, $_template);
419
420
            // Loading the Twig template plugin files if exists
421
            $templateList = [];
422
            if (isset($plugin_info) && isset($plugin_info['templates'])) {
423
                $templateList = $plugin_info['templates'];
424
            }
425
426
            if (!empty($templateList)) {
427
                foreach ($templateList as $pluginTemplate) {
428
                    if (!empty($pluginTemplate)) {
429
                        $templatePluginFile = "$plugin_name/$pluginTemplate"; // for twig
430
                        //$template->render($templatePluginFile, []);
431
                    }
432
                }
433
            }
434
        }
435
436
        return true;
437
    }
438
439
    /**
440
     * Loads plugin info.
441
     *
442
     * @staticvar array $plugin_data
443
     *
444
     * @param string $pluginName
445
     * @param bool   $forced     load from DB or from the static array
446
     *
447
     * @return array
448
     *
449
     * @todo filter setting_form
450
     */
451
    public function getPluginInfo($pluginName, $forced = false)
452
    {
453
        $plugin_info = [];
454
        $pluginPath  = api_get_path(SYS_PLUGIN_PATH);
455
456
        $pluginDir = null;
457
        foreach ([$pluginName, strtolower($pluginName), ucfirst(strtolower($pluginName))] as $dir) {
458
            $path = $pluginPath . "$dir/plugin.php";
459
            if (is_file($path)) {
460
                require_once $path;
461
                $pluginDir = $dir;
462
                break;
463
            }
464
        }
465
466
        $instance = null;
467
        if (isset($plugin_info['plugin_class']) && class_exists($plugin_info['plugin_class'], false)) {
468
            $cls = $plugin_info['plugin_class'];
469
            $instance = method_exists($cls, 'create') ? $cls::create() : new $cls();
470
            if (method_exists($instance, 'get_info')) {
471
                $plugin_info = $instance->get_info();
472
            }
473
        }
474
475
        $repo   = Container::getPluginRepository();
476
        $entity = $repo->findOneByTitle($pluginName) ?: $repo->findOneByTitle(ucfirst(strtolower($pluginName)));
477
        if ($entity) {
478
            $configByUrl = $entity->getConfigurationsByAccessUrl(Container::getAccessUrlUtil()->getCurrent());
479
            $plugin_info['settings'] = $configByUrl?->getConfiguration() ?? [];
480
        }
481
482
        return $plugin_info;
483
    }
484
485
    /**
486
     * Get the template list.
487
     *
488
     * @param string $pluginName
489
     *
490
     * @return bool
491
     */
492
    public function get_templates_list($pluginName)
493
    {
494
        $plugin_info = $this->getPluginInfo($pluginName);
495
        if (isset($plugin_info) && isset($plugin_info['templates'])) {
496
            return $plugin_info['templates'];
497
        }
498
499
        return false;
500
    }
501
502
    /**
503
     * Remove all regions of an specific plugin.
504
     *
505
     * @param string $plugin
506
     */
507
    public function removeAllRegions($plugin)
508
    {
509
        $access_url_id = api_get_current_access_url_id();
510
        if (!empty($plugin)) {
511
            api_delete_settings_params(
512
                [
513
                    'category = ? AND type = ? AND access_url = ? AND subkey = ? ' => [
514
                        'Plugins',
515
                        'region',
516
                        $access_url_id,
517
                        $plugin,
518
                    ],
519
                ]
520
            );
521
        }
522
    }
523
524
    /**
525
     * Add a plugin to a region.
526
     *
527
     * @param string $plugin
528
     * @param string $region
529
     */
530
    public function add_to_region($plugin, $region)
531
    {
532
        api_add_setting(
533
            $plugin,
534
            $region,
535
            $plugin,
536
            'region',
537
            'Plugins',
538
            $plugin,
539
            '',
540
            '',
541
            '',
542
            api_get_current_access_url_id(),
543
            1
544
        );
545
    }
546
547
    /**
548
     * @param int $courseId
549
     */
550
    public function install_course_plugins(int $courseId): void
551
    {
552
        $pluginList = $this->getInstalledPluginListObject();
553
        if (empty($pluginList)) {
554
            return;
555
        }
556
557
        $accessUrl = Container::getAccessUrlUtil()->getCurrent();
558
        $pluginRepo = Container::getPluginRepository();
559
560
        /** @var Plugin $obj */
561
        foreach ($pluginList as $obj) {
562
            if (empty($obj->isCoursePlugin)) {
563
                continue;
564
            }
565
566
            $entity = $pluginRepo->findOneByTitle($obj->get_name());
567
            $rel    = $entity?->getConfigurationsByAccessUrl($accessUrl);
568
            if (!$rel || !$rel->isActive()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rel->isActive() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
569
                continue;
570
            }
571
572
            $obj->get_settings(true);
573
574
            $obj->course_install($courseId);
575
        }
576
    }
577
578
    /**
579
     * Trigger for Plugin::doWhenDeleting[Item] functions.
580
     *
581
     * @param string $itemType
582
     * @param int    $itemId
583
     */
584
    public function performActionsWhenDeletingItem($itemType, $itemId)
585
    {
586
        $pluginList = $this->getInstalledPluginListObject();
587
588
        if (empty($pluginList)) {
589
            return;
590
        }
591
592
        /** @var Plugin $pluginObj */
593
        foreach ($pluginList as $pluginObj) {
594
            switch ($itemType) {
595
                case 'course':
596
                    $pluginObj->doWhenDeletingCourse($itemId);
597
                    break;
598
                case 'session':
599
                    $pluginObj->doWhenDeletingSession($itemId);
600
                    break;
601
                case 'user':
602
                    $pluginObj->doWhenDeletingUser($itemId);
603
                    break;
604
            }
605
        }
606
    }
607
608
    /**
609
     * Add the course settings to the course settings form.
610
     *
611
     * @param FormValidator $form
612
     */
613
    public function add_course_settings_form($form)
614
    {
615
        $pluginList = $this->getInstalledPluginListObject();
616
        /** @var Plugin $obj */
617
        foreach ($pluginList as $obj) {
618
            $pluginName = $obj->get_name();
619
            $pluginTitle = $obj->get_title();
620
            if (!empty($obj->course_settings)) {
621
                $icon = Display::getMdiIcon(
622
                    ToolIcon::PLUGIN,
623
                    'ch-tool-icon',
624
                    null,
625
                    ICON_SIZE_SMALL,
626
                    Security::remove_XSS($pluginTitle)
627
                );
628
                $form->addHtml('<div class="panel panel-default">');
629
                $form->addHtml('
630
                    <div class="panel-heading" role="tab" id="heading-'.$pluginName.'-settings">
631
                        <h4 class="panel-title">
632
                            <a class="collapsed"
633
                                role="button" data-toggle="collapse" data-parent="#accordion"
634
                                href="#collapse-'.$pluginName.'-settings" aria-expanded="false"
635
                                aria-controls="collapse-'.$pluginName.'-settings">
636
                ');
637
                $form->addHtml($icon.' '.$pluginTitle);
638
                $form->addHtml('
639
                            </a>
640
                        </h4>
641
                    </div>
642
                ');
643
                $form->addHtml('
644
                    <div
645
                        id="collapse-'.$pluginName.'-settings"
646
                        class="panel-collapse collapse" role="tabpanel"
647
                        aria-labelledby="heading-'.$pluginName.'-settings">
648
                        <div class="panel-body">
649
                '
650
                );
651
652
                $groups = [];
653
                foreach ($obj->course_settings as $setting) {
654
                    if (false === $obj->validateCourseSetting($setting['name'])) {
655
                        continue;
656
                    }
657
                    if ('checkbox' !== $setting['type']) {
658
                        $form->addElement($setting['type'], $setting['name'], $obj->get_lang($setting['name']));
659
                    } else {
660
                        $element = &$form->createElement(
661
                            $setting['type'],
662
                            $setting['name'],
663
                            '',
664
                            $obj->get_lang($setting['name'])
665
                        );
666
                        $courseSetting = api_get_course_setting($setting['name']);
667
                        if (-1 === $courseSetting) {
668
                            $defaultValue = api_get_plugin_setting($pluginName, $setting['name']);
669
                            if (!empty($defaultValue)) {
670
                                if ('true' === $defaultValue) {
671
                                    $element->setChecked(true);
672
                                }
673
                            }
674
                        }
675
676
                        if (isset($setting['init_value']) && 1 == $setting['init_value']) {
677
                            $element->setChecked(true);
678
                        }
679
                        $form->addElement($element);
680
681
                        if (isset($setting['group'])) {
682
                            $groups[$setting['group']][] = $element;
683
                        }
684
                    }
685
                }
686
                foreach ($groups as $k => $v) {
687
                    $form->addGroup($groups[$k], $k, [$obj->get_lang($k)]);
688
                }
689
                $form->addButtonSave(get_lang('Save settings'));
690
                $form->addHtml(
691
            '
692
                        </div>
693
                    </div>
694
                '
695
        );
696
                $form->addHtml('</div>');
697
            }
698
        }
699
    }
700
701
    /**
702
     * Get all course settings from all installed plugins.
703
     *
704
     * @return array
705
     */
706
    public function getAllPluginCourseSettings()
707
    {
708
        $pluginList = $this->getInstalledPluginListObject();
709
        /** @var Plugin $obj */
710
        $courseSettings = [];
711
        if (!empty($pluginList)) {
712
            foreach ($pluginList as $obj) {
713
                $pluginCourseSetting = $obj->getCourseSettings();
714
                $courseSettings = array_merge($courseSettings, $pluginCourseSetting);
715
            }
716
        }
717
718
        return $courseSettings;
719
    }
720
721
    /**
722
     * When saving the plugin values in the course settings, check whether
723
     * a callback method should be called and send it the updated settings.
724
     *
725
     * @param array $values The new settings the user just saved
726
     */
727
    public function saveCourseSettingsHook($values)
728
    {
729
        $pluginList = $this->getInstalledPluginListObject();
730
731
        /** @var Plugin $obj */
732
        foreach ($pluginList as $obj) {
733
            $settings = $obj->getCourseSettings();
734
            $subValues = [];
735
            if (!empty($settings)) {
736
                foreach ($settings as $v) {
737
                    if (isset($values[$v])) {
738
                        $subValues[$v] = $values[$v];
739
                    }
740
                }
741
            }
742
743
            if (!empty($subValues)) {
744
                $obj->course_settings_updated($subValues);
745
            }
746
        }
747
    }
748
749
    /**
750
     * @param array            $pluginRegionList
751
     * @param string           $pluginRegion
752
     * @param Twig_Environment $twig
753
     */
754
    public function setPluginRegion($pluginRegionList, $pluginRegion, $twig)
755
    {
756
        $regionContent = $this->loadRegion(
757
            $pluginRegionList,
758
            $pluginRegion,
759
            $twig,
760
            true //$this->force_plugin_load
761
        );
762
763
        //$twig->addGlobal('plugin_'.$pluginRegion, $regionContent);
764
    }
765
}
766