Passed
Pull Request — master (#7016)
by
unknown
09:12
created

c2_try_load_legacy_plugin_obj()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 20
c 0
b 0
f 0
nc 8
nop 1
dl 0
loc 33
rs 8.0555
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * @author Julio Montoya <[email protected]> BeezNest 2012
7
 * @author Angel Fernando Quiroz Campos <[email protected]>
8
 */
9
10
use Chamilo\CoreBundle\Framework\Container;
11
12
$cidReset = true;
13
require_once __DIR__.'/../inc/global.inc.php';
14
15
api_protect_admin_script();
16
17
$pluginRepo = Container::getPluginRepository();
18
19
$plugin = $pluginRepo->getInstalledByName($_GET['plugin']);
20
21
if (!$plugin) {
22
    api_not_allowed(true);
23
}
24
25
$accessUrl = Container::getAccessUrlUtil()->getCurrent();
26
$pluginConfiguration = $plugin->getOrCreatePluginConfiguration($accessUrl);
27
28
$appPlugin  = new AppPlugin();
29
$pluginInfo = $appPlugin->getPluginInfo($plugin->getTitle(), true) ?? [];
30
$prevDefaultVis = $pluginInfo['settings']['defaultVisibilityInCourseHomepage'] ?? null;
31
$prevToolEnable = $pluginInfo['settings']['tool_enable'] ?? null;
32
33
/**
34
 * Optional best-effort loader for legacy plugin object.
35
 * Returns a Plugin instance or null. We DO NOT abort if null.
36
 */
37
function c2_try_load_legacy_plugin_obj(string $title): ?Plugin
38
{
39
    // Try helper first (handles title normalization through repositories)
40
    $obj = Container::getPluginHelper()->loadLegacyPlugin($title);
41
    if ($obj instanceof Plugin) {
42
        return $obj;
43
    }
44
45
    // Minimal extra attempts with common casings and suffix
46
    $sysPath  = api_get_path(SYS_PLUGIN_PATH);
47
    $studly   = implode('', array_map('ucfirst', preg_split('/[^a-z0-9]+/i', $title)));
48
    $dirs     = array_unique([$title, strtolower($title), ucfirst(strtolower($title)), $studly]);
49
    foreach ($dirs as $dir) {
50
        $base   = $sysPath.$dir.'/';
51
        $classS = implode('', array_map('ucfirst', preg_split('/[^a-z0-9]+/i', (string)$dir)));
52
        $classes = array_unique([$dir, $dir.'Plugin', $classS, $classS.'Plugin']);
53
        foreach ($classes as $cls) {
54
            $paths = [$base.'src/'.$cls.'.php', $base.$cls.'.php'];
55
            foreach ($paths as $p) {
56
                if (is_readable($p)) {
57
                    require_once $p;
58
                    if (class_exists($cls) && method_exists($cls, 'create')) {
59
                        $maybe = $cls::create();
60
                        if ($maybe instanceof Plugin) {
61
                            return $maybe;
62
                        }
63
                    }
64
                }
65
            }
66
        }
67
    }
68
69
    return null;
70
}
71
72
// Try to ensure we have a Plugin instance, but DO NOT hard-fail if we can't.
73
$legacyObj = $pluginInfo['obj'] ?? null;
74
if (!$legacyObj instanceof Plugin) {
75
    $legacyObj = c2_try_load_legacy_plugin_obj($plugin->getTitle());
76
    if ($legacyObj instanceof Plugin) {
77
        $pluginInfo['obj'] = $legacyObj;
78
    }
79
}
80
81
/** @var Plugin|null $objPlugin */
82
$objPlugin = $pluginInfo['obj'] ?? null;
83
84
$em = Container::getEntityManager();
85
86
$currentUrl = api_get_self().'?plugin='.$plugin->getTitle();
87
$backUrl    = api_get_path(WEB_CODE_PATH).'admin/settings.php?category=Plugins';
88
89
$headerHtml = '
90
<div class="mb-4 flex items-center justify-between">
91
  <h2 class="text-2xl font-semibold text-gray-90">'.htmlspecialchars($pluginInfo['title'] ?? $plugin->getTitle(), ENT_QUOTES).'</h2>
92
  <a href="'.$backUrl.'" class="btn btn--sm btn--plain" title="'.get_lang('Back').'">
93
    <i class="mdi mdi-arrow-left"></i> '.get_lang('Back to plugins').'
94
  </a>
95
</div>
96
';
97
98
$content = $headerHtml;
99
100
/**
101
 * Decide if there are editable settings to render.
102
 * - Remove legacy 'tool_enable' from the list before deciding.
103
 */
104
$declaredFieldNames =
105
    $objPlugin instanceof Plugin
106
        ? $objPlugin->getFieldNames()
107
        : (isset($pluginInfo['settings']) && is_array($pluginInfo['settings']) ? array_keys($pluginInfo['settings']) : []);
108
109
$editableFieldNames = array_values(array_diff($declaredFieldNames, ['tool_enable']));
110
$hasEditableFields  = \count($editableFieldNames) > 0;
111
112
if (isset($pluginInfo['settings_form']) && $hasEditableFields) {
113
    /** @var FormValidator $form */
114
    $form = $pluginInfo['settings_form'];
115
    if (!empty($form)) {
116
        // Ensure proper form action and method
117
        $form->updateAttributes(['action' => $currentUrl, 'method' => 'POST']);
118
119
        // Drop legacy toggle from defaults, so it won't get posted accidentally
120
        if (isset($pluginInfo['settings'])) {
121
            unset($pluginInfo['settings']['tool_enable']);
122
            $form->setDefaults($pluginInfo['settings']);
123
        }
124
125
        // Best-effort: hide legacy 'tool_enable' if some plugin still injects it
126
        $content .= '<style>
127
            [name="tool_enable"],
128
            input[name*="[tool_enable]"],
129
            select[name*="[tool_enable]"],
130
            label[for="tool_enable"] { display:none !important; }
131
        </style>';
132
133
        $content .= $form->toHtml();
134
    }
135
} else {
136
    // No editable settings: show a small info block (no empty form, no Save button).
137
    $content .= '
138
    <div class="rounded-md border border-gray-20 bg-gray-05 p-4 text-sm text-gray-80">
139
      <div class="font-semibold mb-1">'.get_lang('Information').'</div>
140
      <p class="m-0">'.get_lang('This plugin has no configurable settings. Activation is managed from the plugins list.').'</p>
141
    </div>';
142
}
143
144
if (isset($form) && $hasEditableFields) {
145
    if ($form->validate()) {
146
        $values = $form->getSubmitValues();
147
148
        // Fix only for bbb (keep previous behavior)
149
        if ('bbb' == $plugin->getTitle() && !isset($values['global_conference_allow_roles'])) {
150
            $values['global_conference_allow_roles'] = [];
151
        }
152
153
        // Never persist legacy toggle even if posted by mistake (deep filter)
154
        $stripToolEnable = function (&$arr) use (&$stripToolEnable) {
155
            if (!is_array($arr)) { return; }
156
            foreach ($arr as $k => &$v) {
157
                if ($k === 'tool_enable') {
158
                    unset($arr[$k]);
159
                    continue;
160
                }
161
                if (is_array($v)) {
162
                    $stripToolEnable($v);
163
                }
164
            }
165
        };
166
        $stripToolEnable($values);
167
168
        // Reserved/irrelevant keys we don't want to persist
169
        $formName = method_exists($form, 'getAttribute') ? ($form->getAttribute('name') ?: 'form') : 'form';
170
        $reservedKeys = ['submit', 'submit_button', '_token', '_qf__'.$formName];
171
        foreach ($reservedKeys as $rk) {
172
            if (isset($values[$rk])) {
173
                unset($values[$rk]);
174
            }
175
        }
176
177
        // Build safe whitelist
178
        if ($objPlugin instanceof Plugin) {
179
            /** @var array<int, string> $pluginFields */
180
            $pluginFields = $objPlugin->getFieldNames();
181
            $toPersist = array_intersect_key($values, array_flip($pluginFields));
182
        } elseif (!empty($pluginInfo['settings']) && is_array($pluginInfo['settings'])) {
183
            $whitelist = array_keys($pluginInfo['settings']);
184
            $toPersist = array_intersect_key($values, array_flip($whitelist));
185
        } else {
186
            $toPersist = $values;
187
        }
188
189
        // Persist configuration JSON
190
        $pluginConfiguration->setConfiguration($toPersist);
191
        $em->flush();
192
193
        Event::addEvent(
194
            LOG_PLUGIN_CHANGE,
195
            LOG_PLUGIN_SETTINGS_CHANGE,
196
            $plugin->getTitle(),
197
            api_get_utc_datetime(),
198
            api_get_user_id()
199
        );
200
201
        // Compute only visibility delta; activation is managed from the plugins list (C2 source of truth)
202
        $newDefaultVis = $values['defaultVisibilityInCourseHomepage'] ?? $prevDefaultVis;
203
204
        // Re-seed course icons only if plugin object exists, is course plugin, and is currently active
205
        if ($objPlugin instanceof Plugin) {
206
            $isEnabledNow = Container::getPluginHelper()->isPluginEnabled($plugin->getTitle());
207
            $objPlugin->get_settings(true);
208
209
            if (!empty($objPlugin->isCoursePlugin) && $isEnabledNow) {
210
                if ($newDefaultVis !== $prevDefaultVis) {
211
                    // Recreate links to match new default visibility
212
                    $objPlugin->uninstall_course_fields_in_all_courses();
213
                    $objPlugin->install_course_fields_in_all_courses();
214
                }
215
            }
216
217
            // Allow plugins to run post-config hooks
218
            $objPlugin->performActionsAfterConfigure();
219
220
            // Optional tab management (preserve existing behavior)
221
            if (isset($values['show_main_menu_tab'])) {
222
                $objPlugin->manageTab($values['show_main_menu_tab']);
223
            }
224
        }
225
226
        Display::addFlash(Display::return_message(get_lang('Update successful'), 'success'));
227
        header("Location: $currentUrl");
228
        exit;
229
    } else {
230
        foreach ($form->_errors as $error) {
231
            Display::addFlash(Display::return_message($error, 'error'));
232
        }
233
    }
234
}
235
236
$interbreadcrumb[] = [
237
    'url' => api_get_path(WEB_CODE_PATH).'admin/index.php',
238
    'name' => get_lang('Administration'),
239
];
240
$interbreadcrumb[] = [
241
    'url' => api_get_path(WEB_CODE_PATH).'admin/settings.php?category=Plugins',
242
    'name' => get_lang('Plugins'),
243
];
244
245
$tpl = new Template($plugin->getTitle(), true, true, false, true, false);
246
$tpl->assign('content', $content);
247
$tpl->display_one_col_template();
248