Skip to content
Snippets Groups Projects
Select Git revision
  • f8a72bed4bb372d4a61fdba1a38524d3bbd2f78a
  • master default protected
  • improvements-2023
  • studip-5.3
  • 4.5
  • v3.1.0
  • v3.0
  • v2.1.1
  • v2.1.0
  • v2.0.1
10 results

plugin.manifest

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    plugin.php 22.59 KiB
    <?php
    # Lifter010: TODO
    /**
     * plugin.php - plugin administration controller
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License as
     * published by the Free Software Foundation; either version 2 of
     * the License, or (at your option) any later version.
     *
     * @author      Elmar Ludwig
     * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
     * @category    Stud.IP
     * @package     admin
     */
    
    class Admin_PluginController extends AuthenticatedController
    {
        private $plugin_admin;
    
        /**
         * Common tasks for all actions.
         */
        public function before_filter(&$action, &$args)
        {
            global $perm;
    
            parent::before_filter($action, $args);
    
            // user must have root permission
            $perm->check('root');
    
            // set page title and navigation
            PageLayout::setTitle(_('Verwaltung von Plugins'));
            Navigation::activateItem('/admin/config/plugins');
    
            $this->plugin_admin = new PluginAdministration();
    
            if (Request::int('reset_filter')) {
                $GLOBALS['user']->cfg->delete('PLUGINADMIN_DISPLAY_SETTINGS');
            }
            // Extract display settings
            $settings = $current = $GLOBALS['user']->cfg->PLUGINADMIN_DISPLAY_SETTINGS;
    
            foreach ((array)$settings as $key => $value) {
                $settings[$key] = Request::get($key, $settings[$key]) ?: null;
            }
    
            if ($settings !== $current) {
                $GLOBALS['user']->cfg->store('PLUGINADMIN_DISPLAY_SETTINGS', $settings);
            }
    
            $this->plugin_filter = $settings['plugin_filter'];
            $this->core_filter   = $settings['core_filter'];
    
            $views = Sidebar::get()->addWidget(new ViewsWidget());
            $views->addLink(
                _('Pluginverwaltung'),
                $this->indexURL()
            )->setActive($action === 'index');
            $views->addLink(
                _('Weitere Plugins installieren'),
                $this->searchURL()
            )->setActive($action === 'search');
            $views->addLink(
                _('Vorhandene Plugins registrieren'),
                $this->unregisteredURL()
            )->setActive($action === 'unregistered');
        }
    
        /**
         * Validate ticket (passed via request environment).
         * This method always checks Request::quoted('ticket').
         *
         * @throws InvalidArgumentException  if ticket is not valid
         */
        private function check_ticket()
        {
            if (!check_ticket(Request::option('studip_ticket'))) {
                throw new InvalidArgumentException(_('Das Ticket für diese Aktion ist ungültig.'));
            }
        }
    
        /**
         * Try to get update information for a list of plugins. If no
         * update information is available, an error message is set in
         * this controller and an empty array is returned.
         *
         * @param array $plugins array of plugin meta data
         */
        private function get_update_info($plugins)
        {
            try {
                return $this->plugin_admin->getUpdateInfo($plugins);
            } catch (Exception $ex) {
                PageLayout::postError(
                    _('Informationen über Plugin-Updates sind nicht verfügbar.'),
                    [$ex->getMessage()]
                );
    
                // Read current information from local files
                $update_info = [];
                $plugin_manager = PluginManager::getInstance();
                foreach ($plugins as $plugin) {
                    $plugin_path = Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
                    $manifest    = $plugin_manager->getPluginManifest($plugin_path);
                    $update_info[$plugin['id']] = ['version' => $manifest['version']];
                }
                return $update_info;
            }
        }
    
        /**
         * Display the list of installed plugins and show all available
         * updates (if any).
         */
        public function index_action()
        {
            // Check if an activation error has been flashed from the last request
            if (isset($this->flash['activation-error'])) {
                PageLayout::postError(
                    $this->get_template_factory()->render(
                        'admin/plugin/activation-error-form.php',
                        $this->flash['activation-error'] + ['controller' => $this]
                    )
                );
            }
    
            $plugin_manager = PluginManager::getInstance();
    
            $plugins = $plugin_manager->getPluginInfos($this->plugin_filter);
    
            if ($this->core_filter && $this->core_filter !== 'yes') {
                $plugins = array_filter($plugins, function ($plugin) {
                    return ($this->core_filter === 'no' && !$plugin['core'])
                        || ($this->core_filter === 'only' && $plugin['core']);
                });
            }
    
            $this->plugins       = $plugins;
            $this->plugin_types  = $this->plugin_admin->getPluginTypes();
            $this->update_info   = $this->get_update_info($this->plugins);
            $this->migrations    = $this->plugin_admin->getMigrationInfo();
            $this->num_updates   = 0;
    
            foreach ($this->update_info as $id => $info) {
                if (isset($info['update']) && !$this->plugins[$id]['depends']) {
                    $this->num_updates += 1;
                }
            }
        }
    
        /**
         * Save the modified plugin configuration (status and position).
         */
        public function save_action()
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin_filter = Request::option('plugin_filter', '');
            $type = $plugin_filter != '' ? $plugin_filter : NULL;
            $plugins = $plugin_manager->getPluginInfos($type);
    
            $force = (bool) Request::int('force');
    
            $this->check_ticket();
    
            // update enabled/disabled status and position if set
            $messages = [];
            $errors   = [];
            $memory   = [];
            foreach ($plugins as $plugin){
                // Skip plugins that are currently not visible due to filter settings
                if (!Request::submittedSome('position_' . $plugin['id'], 'enabled_' . $plugin['id'])) {
                    continue;
                }
    
                $enabled = Request::int('enabled_' . $plugin['id'], 0);
                $navpos = Request::int('position_' . $plugin['id']);
    
                $result = $plugin_manager->setPluginEnabled($plugin['id'], $enabled, $force);
                if ($result === false) {
                    $error = $enabled
                           ? _('Plugin "%s" hat die Aktivierung verhindert')
                           : _('Plugin "%s" hat die Deaktivierung verhindert');
                    $errors[$plugin['id']] = sprintf($error, $plugin['name']);
    
                    $memory[$plugin['id']] = $enabled;
                } elseif ($result === true) {
                    $message = $enabled
                             ? _('Plugin "%s" wurde aktiviert')
                             : _('Plugin "%s" wurde deaktiviert');
                    $messages[] = sprintf($message, $plugin['name']);
                }
    
                if (isset($navpos)) {
                    $result = $plugin_manager->setPluginPosition($plugin['id'], max($navpos, 1));
                    if ($result) {
                        $messages[] = sprintf(
                            _('Die Position von Plugin "%s" wurde verändert.'),
                            $plugin['name']
                        );
                    }
                }
            }
    
            if (count($errors) > 0) {
                // Unfortunately, we need to flash this since it needs a fresh
                // ticket (the current one is invalid due to the redirection)
                $this->flash['activation-error'] = compact('memory', 'errors');
            }
            if (count($messages) > 0) {
                PageLayout::postSuccess(
                    _('Die folgenden Änderungen wurden durchgeführt:'),
                    array_map('htmlReady', $messages)
                );
            }
            $this->redirect('admin/plugin?plugin_filter=' . $plugin_filter);
        }
    
        /**
         * Compare two plugins by their score (used for sorting).
         */
        private function compare_score($plugin1, $plugin2)
        {
            return $plugin2['score'] - $plugin1['score'];
        }
    
        /**
         * Search the list of available plugins or display the most
         * recommended plugins if the user did not trigger a search.
         */
        public function search_action()
        {
            Helpbar::Get()->addPlainText(_('Empfohlene Plugins'), _('In der Liste "Empfohlene Plugins" finden Sie von anderen Betreibern empfohlene Plugins.'), Icon::create('info'));
            Helpbar::Get()->addPlainText(_('Upload'), _('Alternativ können Plugins und Plugin-Updates auch als ZIP-Datei hochgeladen werden.'), Icon::create('info'));
    
            $search = Request::int('reset-search')
                    ? null
                    : Request::get('search');
    
            // search for plugins in all repositories
            try {
                $repository = new PluginRepository();
                $search_results = $repository->getPlugins($search);
            } catch (Exception $ex) {
                $search_results = [];
            }
    
            $plugins = PluginManager::getInstance()->getPluginInfos();
    
            // filter out already installed plugins
            foreach ($plugins as $plugin) {
                if (isset($search_results[$plugin['name']])) {
                    unset($search_results[$plugin['name']]);
                }
            }
    
            if ($search === null) {
                // sort plugins by score
                uasort($search_results, [$this, 'compare_score']);
                $search_results = array_slice($search_results, 0, 6);
            } else {
                // sort plugins by name
                uksort($search_results, 'strnatcasecmp');
            }
    
            $this->search         = $search;
            $this->search_results = $search_results;
            $this->plugins        = $plugins;
    
            $search_widget = Sidebar::get()->addWidget(new SearchWidget());
            $search_widget->setTitle(_('Plugins suchen'));
            $search_widget->addNeedle(_('Pluginname'), 'search', true, null, null, $search);
    
            $links = Sidebar::get()->addWidget(new LinksWidget());
            $links->setTitle(_('Verweise'));
            $links->addLink(
                _('Alle Plugins im Plugin-Marktplatz'),
                'https://plugins.studip.de/',
                Icon::create('export'),
                ['target' => '_blank', 'rel' => 'noopener noreferrer']
            );
        }
    
        /**
         * Install a given plugin, either by URL (from the repository)
         * or using a file uploaded by the administrator.
         */
        public function install_action()
        {
            $this->check_ticket();
    
            $plugin_manager = PluginManager::getInstance();
            $this->flash['plugins_disabled'] = $plugin_manager->isPluginsDisabled();
            $this->flash['plugin_url'] = Request::get('plugin_url');
    
            if (isset($_FILES['upload_file'])) {
                $upload_file = tempnam(Config::get()->TMP_PATH, 'plugin');
    
                if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload_file)) {
                    $this->flash['upload_file'] = $upload_file;
                }
            }
    
            $plugin_manager->setPluginsDisabled(true);
            $this->redirect('admin/plugin/internal_install');
        }
    
        /**
         * Install a given plugin, either by URL (from the repository)
         * or using a file uploaded by the administrator.
         * Note: This action is only called internally via redirect.
         */
        public function internal_install_action()
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin_manager->setPluginsDisabled($this->flash['plugins_disabled']);
    
            $plugin_url = $this->flash['plugin_url'];
    
            try {
                if (isset($plugin_url)) {
                    $this->plugin_admin->installPluginFromURL($plugin_url);
                } else if (Config::get()->PLUGINS_UPLOAD_ENABLE) {
                    // process the upload and register plugin in the database
                    $upload_file = $this->flash['upload_file'];
                    $this->plugin_admin->installPlugin($upload_file);
                }
    
                PageLayout::postSuccess(_('Das Plugin wurde erfolgreich installiert.'));
            } catch (PluginInstallationException $ex) {
                PageLayout::postError($ex->getMessage());
            }
    
            if (isset($upload_file)) {
                unlink($upload_file);
            }
    
            $this->redirect('admin/plugin');
        }
    
        /**
         * Ask for confirmation from the user before deleting a plugin.
         *
         * @param int $plugin_id id of plugin to delete
         */
        public function ask_delete_action($plugin_id)
        {
            $plugin = PluginManager::getInstance()->getPluginInfoById($plugin_id);
    
            if (!$plugin['core']) {
                PageLayout::postQuestion(
                    sprintf(
                        _('Wollen Sie wirklich "%s" deinstallieren?'),
                        htmlReady($plugin['name'])
                    ),
                    $this->url_for("admin/plugin/delete/{$plugin_id}")
                )->includeTicket();
            }
    
            $this->redirect('admin/plugin');
        }
    
        /**
         * Completely delete a plugin from the system.
         *
         * @param int $plugin_id id of plugin to delete
         */
        public function delete_action($plugin_id)
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin_filter = Request::option('plugin_filter', '');
            $plugin = $plugin_manager->getPluginInfoById($plugin_id);
    
            $this->check_ticket();
    
            if (isset($plugin) && !$plugin['core']) {
                $this->plugin_admin->uninstallPlugin($plugin);
                PageLayout::postSuccess(sprintf(
                    _('Das Plugin "%s" wurde deinstalliert.'),
                    $plugin['name']
                ));
            }
    
            $this->redirect('admin/plugin?plugin_filter=' . $plugin_filter);
        }
    
        /**
         * Download a ZIP file containing the given plugin.
         *
         * @param int $plugin_id id of plugin to download
         */
        public function download_action($plugin_id)
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin = $plugin_manager->getPluginInfoById($plugin_id);
    
            // prepare file name for download
            $pluginpath = Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
            $manifest = $plugin_manager->getPluginManifest($pluginpath);
            $filename = $plugin['class'] . '-' . $manifest['version'] . '.zip';
            $filepath = Config::get()->TMP_PATH . '/' . $filename;
    
            FileArchiveManager::createArchiveFromPhysicalFolder(
                $pluginpath,
                $filepath
            );
    
            $this->render_temporary_file($filepath, $filename, 'application/zip');
        }
    
        /**
         * Install updates for all selected plugins.
         */
        public function install_updates_action()
        {
            $this->check_ticket();
    
            $plugin_manager = PluginManager::getInstance();
            $this->flash['plugins_disabled'] = $plugin_manager->isPluginsDisabled();
            $this->flash['plugin_filter'] = Request::option('plugin_filter', '');
            $this->flash['update'] = Request::intArray('update');
    
            $plugin_manager->setPluginsDisabled(true);
            $this->redirect('admin/plugin/internal_install_updates');
        }
    
        /**
         * Install updates for all selected plugins.
         * Note: This action is only called internally via redirect.
         */
        public function internal_install_updates_action()
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin_manager->setPluginsDisabled($this->flash['plugins_disabled']);
    
            $plugins = $plugin_manager->getPluginInfos();
            $plugin_filter = $this->flash['plugin_filter'];
            $update_info = $this->plugin_admin->getUpdateInfo($plugins);
    
            $update = $this->flash['update'];
    
            if (!empty($update)) { // update each plugin in turn
                foreach ($update as $id) {
                    if (isset($update_info[$id]['update'])) {
                        try {
                            $update_url = $update_info[$id]['update']['url'];
                            $this->plugin_admin->installPluginFromURL($update_url);
                        } catch (PluginInstallationException $ex) {
                            $update_errors[] = sprintf('%s: %s', $plugins[$id]['name'], $ex->getMessage());
                        }
                    }
                }
            }
    
            // collect and report errors
            if (isset($update_errors)) {
                $error = ngettext(
                    'Beim Update ist ein Fehler aufgetreten:',
                    'Beim Update sind Fehler aufgetreten:',
                    count($update_errors)
                );
                PageLayout::postError($error, $update_errors);
            } else {
                PageLayout::postSuccess(_('Update erfolgreich installiert.'));
            }
    
            $this->redirect('admin/plugin?plugin_filter=' . $plugin_filter);
        }
    
        /**
         * Show a page describing this plugin's meta data and description,
         * if available.
         *
         * @param int $plugin_id if of plugin to show manifest
         */
        public function manifest_action($plugin_id)
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin = $plugin_manager->getPluginInfoById($plugin_id);
            PageLayout::setTitle(sprintf(_('Details von %s'), $plugin['name']));
    
            // retrieve manifest
            $pluginpath = Config::get()->PLUGINS_PATH . '/' . $plugin['path'];
            $manifest = $plugin_manager->getPluginManifest($pluginpath);
    
            $this->plugin   = $plugin;
            $this->manifest = $manifest;
        }
    
        /**
         * migrate a plugin to top version
         *
         * @param int $plugin_id id of plugin to migrate
         */
        public function migrate_action($plugin_id)
        {
            $plugin_manager = PluginManager::getInstance();
            $plugin_filter = Request::option('plugin_filter', '');
            $plugin = $plugin_manager->getPluginInfoById($plugin_id);
            $log = $this->plugin_admin->migratePlugin($plugin_id);
            if ($log) {
                PageLayout::postMessage(MessageBox::success(_('Die Migration wurde durchgeführt.'), array_map('htmlReady', explode("\n", trim($log)))));
            } else {
                PageLayout::postMessage(MessageBox::error(_('Die Migration konnte nicht durchgeführt werden.')));
            }
            $this->redirect('admin/plugin?plugin_filter=' . $plugin_filter);
        }
    
        public function unregistered_action()
        {
            $plugins = $this->plugin_admin->scanPluginDirectory();
            usort($plugins, function ($a, $b) {
                return $a['pluginname'] <=> $b['pluginname'];
            });
            $this->unknown_plugins = $plugins;
        }
    
        /**
         * register a plugin in database when it
         * already exists in file system
         *
         * @param int $number number of found plugin
         */
        public function register_action($number)
        {
            CSRFProtection::verifyUnsafeRequest();
            $unknown_plugins = $this->plugin_admin->scanPluginDirectory();
            $plugin = $unknown_plugins[$number];
    
            try {
                $this->plugin_admin->registerPlugin($plugin['path']);
                PageLayout::postSuccess(_('Das Plugin wurde erfolgreich installiert.'));
            } catch (PluginInstallationException $ex) {
                PageLayout::postError($ex->getMessage());
            }
            $this->redirect('admin/plugin');
        }
    
        public function edit_automaticupdate_action($plugin_id = null)
        {
            $this->plugin = $plugin_id
                            ? PluginManager::getInstance()->getPluginInfoById($plugin_id)
                            : [];
            if (Request::isPost()) {
                CSRFProtection::verifyUnsafeRequest();
                $this->check_ticket();
                if (!$plugin_id) {
                    $plugin_id = $this->plugin_admin->installPluginFromURL(Request::get('automatic_update_url'));
                    $this->plugin = PluginManager::getInstance()->getPluginInfoById($plugin_id);
                }
                $token = $this->plugin['automatic_update_secret'] ?: md5(uniqid());
                $statement = DBManager::get()->prepare("
                    UPDATE plugins
                    SET automatic_update_url = :url,
                        automatic_update_secret = :secret
                    WHERE pluginid = :id
                ");
                $statement->execute([
                    'id'     => $plugin_id,
                    'url'    => Request::get('automatic_update_url'),
                    'secret' => Request::get('use_security_token') ? $token : null
                ]);
                PageLayout::postMessage(MessageBox::success(_('Daten gespeichert.')));
                if (Request::get('automatic_update_url') && Request::get('use_security_token')) {
                    PageLayout::postInfo(_('Unten können Sie den Security Token jetzt heraus kopieren.'));
                }
                $this->redirect("admin/plugin/edit_automaticupdate/{$plugin_id}");
            }
    
            if ($plugin_id) {
                PageLayout::setTitle(sprintf(_('Automatisches Update für %s'), $this->plugin['name']));
            } else {
                PageLayout::setTitle(_('Plugin von URL installieren'));
            }
        }
    
        public function edit_description_action(Plugin $plugin)
        {
            $this->plugin = PluginManager::getInstance()->getPluginById($plugin->getId());
            $this->metadata = $this->plugin->getMetadata();
            $this->form = \Studip\Forms\Form::fromSORM($plugin, [
                'legend' => _('Pluginbeschreibung'),
                'fields' => [
                    'description' => [
                        'label' => _('Beschreibung'),
                        'type' => 'i18n_formatted'
                    ],
                    'manifest_info_de' => [
                        'label' => _('Standardbeschreibung des Plugins'),
                        'type' => 'info',
                        'value' => $this->metadata['descriptionlong'] ?? $this->metadata['description'] ?? '',
                        'if' => "STUDIPFORM_SELECTEDLANGUAGES.description === 'de_DE'"
                    ],
                    'manifest_info_en' => [
                        'label' => sprintf(_('Standardbeschreibung des Plugins (%s)'), _('Englisch')),
                        'type' => 'info',
                        'value' => $this->metadata['descriptionlong_en'] ?? $this->metadata['description_en'] ?? null,
                        'if' => "STUDIPFORM_SELECTEDLANGUAGES.description === 'en_GB'"
                    ],
                    'description_mode' => [
                        'label' => _('Modus der neuen Beschreibung'),
                        'type' => 'select',
                        'options' => [
                            'add' => _('Hinzufügen zur Standardbeschreibung'),
                            'override_description' => _('Standardbeschreibung überschreiben'),
                            'replace_all' => _('Beschreibungsfenster komplett ersetzen durch Beschreibung')
                        ]
                    ],
                    'highlight_until' => [
                        'label' => _('In Veranstaltungen bewerben bis (oder leer lassen)'),
                        'type' => 'datetimepicker'
                    ],
                    'highlight_text' => [
                        'label' => _('Bewerbungs-Infotext')
                    ]
                ]
            ])->autoStore()
              //->setDebugMode(true)
              ->setURL(URLHelper::getURL('dispatch.php/admin/plugin/index'));
        }
    
    }