__('Loaded Plugins'), 'avail' => __('Available Plugins'), // classic calls 'install' => __('Install'), 'enable' => __('Enable'), 'disable' => __('Disable'), 'uninstall' => __('Uninstall'), 'check' => __('Check Configuration'), 'confirm' => __('Install Prompt Confirmation'), // removed plugin data handling 'remove_data' => __('Remove Plugin Data'), // load order switching 'moveup' => __('Move Up'), 'movedown' => __('Move Down'), // plugin archiving 'archive' => __('Archive'), 'restore' => __('Archive Restore'), 'delete' => __('Archive Delete'), // manage downloaded content 'load' => __('Install from Downloaded Plugins'), 'readme' => __('View the Plugins Readme File'), 'changelog' => __('View the Plugins ChangeLog File'), 'latest' => __('Fetch Latest Plugin Archives'), // remote poller plugin functions 'remote_enable' => __('Remote Enable'), 'remote_disable' => __('Remote Disable'), // drag and drop 'ajax_dnd' => __('Drag and Drop'), ]; $status_names = [ -1 => __('Not Compatible'), -2 => __('Disabled Naming Errors'), -3 => __('Disabled Invalid Directory'), -4 => __('Disabled No INFO File'), -5 => __('Disabled Directory Missing'), 0 => __('Loaded, Not Installed'), 1 => __('Installed/Active'), 2 => __('Configuration Issues'), 3 => __('Awaiting Upgrade'), 4 => __('Installed/Inactive'), 5 => __('Installed or Active'), 6 => __('Installable'), 7 => __('Disabled by Error'), 8 => __('Archived'), ]; // temporary workaround till project finished db_execute("CREATE TABLE IF NOT EXISTS `plugin_available` ( `plugin` varchar(32) NOT NULL DEFAULT '', `description` varchar(128) NOT NULL DEFAULT '', `author` varchar(40) NOT NULL DEFAULT '', `webpage` varchar(128) NOT NULL DEFAULT '', `tag_name` varchar(20) NOT NULL DEFAULT '', `published_at` timestamp NULL DEFAULT NULL, `compat` varchar(20) NOT NULL DEFAULT '', `requires` varchar(128) NOT NULL DEFAULT '', `body` blob DEFAULT NULL, `info` blob DEFAULT NULL, `readme` blob DEFAULT NULL, `changelog` blob DEFAULT NULL, `archive` longblob DEFAULT NULL, `last_updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`plugin`,`tag_name`)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC"); db_execute("CREATE TABLE IF NOT EXISTS `plugin_archive` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `plugin` varchar(32) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', `author` varchar(64) NOT NULL DEFAULT '', `webpage` varchar(255) NOT NULL DEFAULT '', `user_id` int(10) unsigned NOT NULL DEFAULT 0, `version` varchar(10) NOT NULL DEFAULT '', `requires` varchar(128) DEFAULT '', `compat` varchar(20) NOT NULL DEFAULT '', `dir_md5sum` varchar(32) NOT NULL DEFAULT '', `last_updated` timestamp NULL DEFAULT NULL, `archive` longblob DEFAULT NULL, PRIMARY KEY (`id`), KEY `directory` (`plugin`)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC"); // get the list of installed plugins $pluginslist = plugins_retrieve_plugin_list(); set_default_action('list'); global $plugins_integrated; /** * this is for legacy support for plugins like syslog * that are dependent on the mode request variable * to be set. */ if (isrv('mode')) { srv('action', gnrv('mode')); if (isrv('id')) { srv('plugin', gnrv('id')); } } $action = gnrv('action'); // pre-check for actions that will fail by default if (isrv('plugin')) { gfrv('plugin', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([a-zA-Z0-9 _]+)$/']]); $plugin = sanitize_search_string(grv('plugin')); $safe_actions = [ 'changelog', 'readme', 'load', 'install', 'confirm', 'delete', 'ajax_dnd', 'remove_data' ]; $display_action = ucwords(str_replace('_', ' ', $action)); if (!in_array($plugin, $pluginslist, true) && !in_array($action, $safe_actions, true)) { raise_message('invalid_plugin', __esc('The action \'%s\' on Plugin \'%s\' can not be performed due to the Plugin in it\'s current state.', $display_action, $plugin), MESSAGE_LEVEL_ERROR); header('Location: plugins.php'); exit; } if (in_array($plugin, $plugins_integrated, true)) { raise_message('invalid_plugin_action', __esc('The action \'%s\' on Plugin \'%s\' can not be taken as the Plugin is integrated.', $display_action, $plugin), MESSAGE_LEVEL_ERROR); header('Location: plugins.php'); exit; } } else { $plugin = ''; } switch($action) { case 'list': case 'avail': top_header(); update_show_current(); bottom_footer(); break; case 'load': $tag = gnrv('tag'); api_plugin_archive_restore($plugin, $tag, 'available'); header('Location: plugins.php'); exit; case 'readme': $tag = gnrv('tag'); api_plugin_get_available_file_contents($plugin, $tag, 'readme'); break; case 'changelog': $tag = gnrv('tag'); api_plugin_get_available_file_contents($plugin, $tag, 'changelog'); break; case 'latest': if (read_config_option('github_access_token') != '') { $running = is_process_running('pfetch', 'master', 0); if ($running === false) { $php_binary = read_config_option('path_php_binary'); exec_background($php_binary, CACTI_PATH_CLI . '/fetch_plugins.php'); usleep(300000); raise_message('fetch_background', __('The fetch latest plugins process has been launched into background.'), MESSAGE_LEVEL_INFO); } elseif ($running === true) { raise_message('fetch_background', __('The fetch latest plugins process has already been started.'), MESSAGE_LEVEL_WARN); } header('Location: plugins.php'); } else { raise_message('get_latest1', __('You must enter your GitHub user, repo and personal access token before you can refresh the plugins. You can set the GitHub defaults under Console > Configuration > Settings > General.'), MESSAGE_LEVEL_ERROR); header('Location: plugins.php'); } break; case 'install': api_plugin_install($plugin); define('IN_PLUGIN_INSTALL', 1); if ($_SESSION['sess_plugins_state'] >= 0) { header('Location: plugins.php?state=5'); } else { header('Location: plugins.php'); } break; case 'uninstall': define('IN_PLUGIN_INSTALL', 1); api_plugin_uninstall($plugin); header('Location: plugins.php'); break; case 'remove': /* Force Uninstall of an orphaned plugin (e.g. its directory is missing), so its entries can be purged even though its own uninstall hook can no longer run. api_plugin_uninstall() skips the (unavailable) hook and removes the plugin's hooks, realms, plugin_config row, and - with $tables = true - the tables/columns it created (via plugin_db_changes). Guard the destructive path: only proceed when the plugin is genuinely orphaned (its directory is absent) and no other active plugin still requires it, so a forged POST can not force-remove a healthy or depended-upon plugin (which would run its uninstall hook and drop its tables without the intended confirmation). */ if (is_dir(CACTI_PATH_PLUGINS . '/' . $plugin)) { raise_message('force_remove_present', __esc('Plugin \'%s\' can not be Force Uninstalled because its directory is still present. Use the normal Uninstall action instead.', $plugin), MESSAGE_LEVEL_ERROR); header('Location: plugins.php'); break; } $required = db_fetch_cell_prepared('SELECT GROUP_CONCAT(directory) FROM plugin_config WHERE requires LIKE ? AND status IN (1, 4, 7)', ['%' . $plugin . '%']); if ($required != '') { raise_message('force_remove_required', __esc('Plugin \'%s\' can not be Force Uninstalled because it is still required by: \'%s\'', $plugin, ucfirst($required)), MESSAGE_LEVEL_ERROR); header('Location: plugins.php'); break; } define('IN_PLUGIN_INSTALL', 1); api_plugin_uninstall($plugin, true); /* The plugin's directory is gone, so api_plugin_uninstall() can not set $plugin_found and skips its own final replication; replicate here so remote pollers also drop the now-removed orphan. */ api_plugin_replicate_config(); header('Location: plugins.php'); break; case 'remove_data': api_plugin_remove_data($plugin); header('Location: plugins.php'); break; case 'disable': api_plugin_disable($plugin); header('Location: plugins.php'); break; case 'enable': api_plugin_enable($plugin); header('Location: plugins.php'); break; case 'check': $response = api_plugin_check_config($plugin); if ($response === true) { // set the status as installable again if check passes db_execute_prepared('UPDATE plugin_config SET status = 0 WHERE directory = ?', [$plugin]); raise_message('plugin_good', __esc('Plugin \'%s\' has passed it\'s Configuration Check test and can not be Installed', $plugin), MESSAGE_LEVEL_INFO); } elseif ($response === null) { raise_message('plugin_good', __esc('Plugin \'%s\' Check Configuration function returned a null response which is invalid. Please check with Plugin Developer for an update.', $plugin), MESSAGE_LEVEL_WARN); } header('Location: plugins.php'); break; case 'moveup': api_plugin_moveup($plugin); header('Location: plugins.php'); break; case 'movedown': api_plugin_movedown($plugin); header('Location: plugins.php'); break; case 'remote_enable': if (POLLER_ID > 1) { db_execute_prepared('UPDATE plugin_config SET status = 1 WHERE directory = ?', [$plugin], false, $local_db_cnn_id); } header('Location: plugins.php'); break; case 'remote_disable': if (POLLER_ID > 1) { db_execute_prepared('UPDATE plugin_config SET status = 4 WHERE directory = ?', [$plugin], false, $local_db_cnn_id); } header('Location: plugins.php'); break; case 'restore': $id = gfrv('id'); api_plugin_archive_restore($plugin, $id, 'archive'); header('Location: plugins.php'); break; case 'delete': $id = gfrv('id'); api_plugin_archive_remove($plugin, $id); header('Location: plugins.php'); break; case 'archive': $archive_note = gnrv('archive_note'); api_plugin_archive($plugin, $archive_note); header('Location: plugins.php'); break; case 'ajax_dnd': $new_order = gnrv('dnd'); api_plugin_reorder($new_order); header('Location: plugins.php'); break; } exit; function plugins_retrieve_plugin_list() : array { $pluginslist = []; $temp = db_fetch_assoc('SELECT directory AS plugin FROM plugin_config ORDER BY name'); foreach ($temp as $t) { $pluginslist[] = $t['plugin']; } return $pluginslist; } function plugins_temp_table_exists(string $table) : mixed { return cacti_sizeof(db_fetch_row("SHOW TABLES LIKE '$table'")); } function plugins_load_temp_table() : string { global $plugins, $plugins_integrated, $local_db_cnn_id; $table = 'plugin_temp_table_' . rand(); $x = 0; while ($x < 30) { if (!plugins_temp_table_exists($table)) { $_SESSION['plugin_temp_table'] = $table; db_execute("CREATE TEMPORARY TABLE IF NOT EXISTS $table LIKE plugin_config"); db_execute("TRUNCATE $table"); // Cacti strips NO_AUTO_VALUE_ON_ZERO on connect (database.php). Without it, // a row with id=0 in plugin_config (e.g. from a plugin upgrade script) is // reassigned by AUTO_INCREMENT to the next sequence value, causing a 1062 // collision when another row already holds that id. $orig_sql_mode = db_fetch_cell('SELECT @@SESSION.sql_mode'); db_execute("SET SESSION sql_mode = CONCAT_WS(',', @@SESSION.sql_mode, 'NO_AUTO_VALUE_ON_ZERO')"); db_execute("INSERT INTO $table SELECT * FROM plugin_config"); db_execute_prepared('SET SESSION sql_mode = ?', [$orig_sql_mode]); break; } else { $table = 'plugin_temp_table_' . rand(); } $x++; } if (!db_column_exists($table, 'requires')) { db_execute("ALTER TABLE $table CHANGE COLUMN directory plugin varchar(32) NOT NULL default '', CHANGE COLUMN name description varchar(64) NOT NULL default '', ADD COLUMN compat varchar(64) NOT NULL default '', ADD COLUMN remote_status tinyint(2) DEFAULT '0' AFTER status, ADD COLUMN capabilities varchar(128) DEFAULT NULL, ADD COLUMN requires varchar(80) DEFAULT NULL, ADD COLUMN dir_md5sum varchar(32) DEFAULT NULL"); } if (POLLER_ID > 1) { $status = db_fetch_assoc('SELECT directory AS plugin, status FROM plugin_config', false, $local_db_cnn_id); if (cacti_sizeof($status)) { foreach ($status as $r) { $exists = db_fetch_cell_prepared("SELECT id FROM $table WHERE plugin = ?", [$r['plugin']]); if ($exists) { $capabilities = api_plugin_remote_capabilities($r['plugin']); db_execute_prepared("UPDATE $table SET capabilities = ? WHERE plugin = ?", [$capabilities, $r['plugin']]); db_execute_prepared("UPDATE $table SET remote_status = ? WHERE plugin = ?", [$r['status'], $r['plugin']]); } else { db_execute_prepared("UPDATE $table SET status = -2, remote_status = ? WHERE plugin = ?", [$r['status'], $r['plugin']]); } } } } $path = CACTI_PATH_PLUGINS . '/'; $dh = opendir($path); $cinfo = []; if ($dh !== false) { while (($file = readdir($dh)) !== false) { if (is_dir("$path$file") && file_exists("$path$file/setup.php") && !in_array($file, $plugins_integrated, true)) { $info_file = "$path$file/INFO"; $md5sum = md5sum_path("$path$file"); if (file_exists($info_file)) { $cinfo[$file] = plugin_load_info_file($info_file); $pluginslist[] = $file; } else { $cinfo[$file] = plugin_load_info_defaults($info_file, false); } $exists = db_fetch_cell_prepared("SELECT COUNT(*) FROM $table WHERE plugin = ?", [$file]); $plugin_name = $cinfo[$file]['name']; if (!$exists) { db_execute_prepared("INSERT INTO $table (plugin, description, status, author, webpage, version, requires, compat, dir_md5sum) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [ $plugin_name, $cinfo[$file]['longname'], $cinfo[$file]['status'], $cinfo[$file]['author'], $cinfo[$file]['homepage'], $cinfo[$file]['version'], $cinfo[$file]['requires'], $cinfo[$file]['compat'], $md5sum ] ); } else { db_execute_prepared("UPDATE $table SET requires = ?, dir_md5sum = ?, compat = ? WHERE plugin = ?", [$cinfo[$file]['requires'], $md5sum, $cinfo[$file]['compat'], $plugin_name]); } } } closedir($dh); } $found_plugins = array_keys($cinfo); $plugins = db_fetch_assoc('SELECT id, directory AS plugin, status FROM plugin_config'); if (cacti_sizeof($plugins)) { foreach ($plugins as $plugin) { if (!in_array($plugin['plugin'], $found_plugins, true)) { $plugin['status'] = '-5'; $exists = db_fetch_cell_prepared("SELECT COUNT(*) FROM $table WHERE plugin = ?", [$plugin['plugin']]); if (!$exists) { $md5sum = md5sum_path($path); db_execute_prepared("INSERT INTO $table (plugin, description, status, author, webpage, version, requires, dir_md5sum) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ $plugin['plugin'], $plugin['longname'], $plugin['status'], $plugin['author'], $plugin['homepage'], $plugin['version'], $plugin['requires'], $md5sum ] ); } else { $md5sum = md5sum_path($path); db_execute_prepared("UPDATE $table SET status = ?, dir_md5sum = ? WHERE plugin = ?", [$plugin['status'], $md5sum, $plugin['plugin']]); } } } } return $table; } function update_show_current() : void { global $plugins, $pluginslist, $status_names, $actions, $item_rows; // drop anything left behind by plugins that are no longer installed plugin_clean_old_plugin_info(); // ================= input validation and session storage ================= $filters = [ 'rows' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-1' ], 'page' => [ 'filter' => FILTER_VALIDATE_INT, 'default' => '1' ], 'type' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-1' ], 'filter' => [ 'filter' => FILTER_DEFAULT, 'pageset' => true, 'default' => '' ], 'sort_column' => [ 'filter' => FILTER_CALLBACK, 'default' => 'pi.plugin', 'options' => ['options' => 'sanitize_search_string'] ], 'sort_direction' => [ 'filter' => FILTER_CALLBACK, 'default' => 'ASC', 'options' => ['options' => 'sanitize_search_string'] ], 'state' => [ 'filter' => FILTER_VALIDATE_INT, 'pageset' => true, 'default' => '-99' ] ]; validate_store_request_vars($filters, 'sess_plugins'); // ================= input validation ================= $table = plugins_load_temp_table(); $uninstall_msg = __esc('Uninstalling this Plugin and may remove all Plugin Data and Settings. If you really want to Uninstall the Plugin, click \'Uninstall\' below. Otherwise click \'Cancel\'.'); $uninstall_title = __esc('Are you sure you want to Uninstall?'); /* JSON-encoded so the values are injected into JavaScript without inline PHP-in-quoted-string, and action=remove is carried in the dialog form (POST body) rather than a URL. */ $forceremove_cfg = json_encode([ 'title' => __('Are you sure you want to Force Uninstall?'), 'message' => __('The directory for this Plugin is missing, so it can not be uninstalled normally. Force Uninstall permanently removes all of its entries from the Cacti plugin tables (configuration, hooks, permissions, and any tables or columns it created). This can not be undone. If you really want to Force Uninstall the Plugin, click \'Force Uninstall\' below. Otherwise click \'Cancel\'.'), 'continue' => __('Force Uninstall'), 'cancel' => __('Cancel'), ]); $rmdata_msg = __esc('Removing Plugin Data and Settings for will remove all Plugin Data and Settings. If you really want to Remove Data and Settings for this Plugin, click \'Remove Data\' below. Otherwise click \'Cancel\'.'); $rmdata_title = __esc('Are you sure you want to Remove all Plugin Data and Settings?'); $resarchive_msg = __esc('Restoring this Plugin Archive will overwrite the current Plugin directory. If you really want to Restore this Plugin Archive, click \'Restore\' below. Otherwise click \'Cancel\'.'); $resarchive_title = __esc('Are you sure you want to Restore this Archive?'); $rmarchive_msg = __esc('Deleting this Plugin Archive is not reversible without a table restore. If you really want to Delete the Plugin Archive, click \'Delete\' below. Otherwise click \'Cancel\'.'); $rmarchive_title = __esc('Are you sure you want to Delete this Archive?'); $archive_msg = __esc('Archiving makes a backup of the plugin that you may restore at a later date. Before archiving, you may enter an archive note that will be stored with the Archive for later reference. If you really want to Archive the Plugin, click \'Delete\' below. Otherwise click \'Cancel\'.'); $archive_form = '

 

'; $archive_title = __esc('Are you sure you want to Archive this Plugin?'); html_filter_start_box(__('Plugin Management'), '100%', false, true, 'center', ''); ?>
'>
[ 'display' => __('Actions'), 'align' => 'left', 'sort' => '', 'tip' => __('Actions available include \'Restore\', \'Delete\'.') ], 'pa.plugin' => [ 'display' => __('Plugin Name'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The name for this Plugin. The name is controlled by the directory it resides in.') ], 'pi.description' => [ 'display' => __('Plugin Description'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('A description that the Plugins author has given to the Plugin.') ], 'nosort01' => [ 'display' => __('Archive Notes'), 'align' => 'left', 'tip' => __('Hover over the Notes column to see the Archive notes.') ], 'pi.status' => [ 'display' => POLLER_ID == 1 ? __('Status') : __('Main / Remote Status'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Status of this available Plugin. Loadable means it is currently not installed and can be loaded.') ], 'pi.author' => [ 'display' => __('Author'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The author of this Plugin.') ], 'pa.compat' => [ 'display' => __('Cacti'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Cacti version ranges required to use this Plugin.') ], 'nosort02' => [ 'display' => __('Versions'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The Installed version over the Archived version of the Plugin.') ], 'pa.archive_length' => [ 'display' => __('Size'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The compressed size of this Plugin in bytes.') ], 'requires' => [ 'display' => __('Requires'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('This Plugin requires the following Plugins be installed first.') ], 'pa.last_updated' => [ 'display' => __('Archive Date'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The date that this Plugin was Archived.') ], 'pi.last_updated' => [ 'display' => __('Installed/Upgraded'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The date that this Plugin was last Installed or Upgraded.') ], ]; break; case 6: $display_text = [ 'nosort0' => [ 'display' => __('Actions'), 'align' => 'left', 'sort' => '', 'tip' => __('Actions available include \'Install\', \'Activate\', \'Disable\', \'Enable\', \'Uninstall\'.') ], 'pi.plugin' => [ 'display' => __('Plugin Name'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The name for this Plugin. The name is controlled by the directory it resides in.') ], 'pi.description' => [ 'display' => __('Plugin Description'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('A description that the Plugins author has given to the Plugin.') ], 'status' => [ 'display' => POLLER_ID == 1 ? __('Status') : __('Main / Remote Status'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The status of this Plugin.') ], 'author' => [ 'display' => __('Author'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The author of this Plugin.') ], 'nosort1' => [ 'display' => __('Cacti Releases'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Cacti Releases that are eligible to use this Plugin. The format of the allowed versions follows common naming.') ], 'pi.version' => [ 'display' => __('Installed Version'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The currently installed version of this Plugin.') ], 'nosort2' => [ 'display' => __('Version'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The Available version for install for this Plugin.') ], 'pa.archive_length' => [ 'display' => __('Size'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The compressed size of this Plugin in bytes.') ], 'nosort3' => [ 'display' => __('Requires'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('This Plugin requires the following Plugins be installed first.') ], 'pa.published_at' => [ 'display' => __('Last Published'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The date the release was published or develop was last pushed.') ], 'pi.last_updated' => [ 'display' => __('Installed/Upgraded'), 'align' => 'right', 'sort' => 'DESC', 'tip' => __('The date that this Plugin was last installed or upgraded.') ], ]; break; default: $display_text = [ 'nosort' => [ 'display' => __('Actions'), 'align' => 'left', 'sort' => '', 'tip' => __('Actions available include \'Install\', \'Activate\', \'Disable\', \'Enable\', \'Uninstall\'.') ], 'pi.plugin' => [ 'display' => __('Plugin Name'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The name for this Plugin. The name is controlled by the directory it resides in.') ], 'pi.description' => [ 'display' => __('Plugin Description'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('A description that the Plugins author has given to the Plugin.') ], 'pi.status' => [ 'display' => POLLER_ID == 1 ? __('Status') : __('Main / Remote Status'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Status of this available Plugin. Loadable means it is currently not installed and can be loaded.') ], 'pi.author' => [ 'display' => __('Author'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The author of this Plugin.') ], 'pa.compat' => [ 'display' => __('Cacti'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('The Cacti version ranges required to use this Plugin.') ], 'pi.requires' => [ 'display' => __('Plugin Requires'), 'align' => 'left', 'sort' => 'ASC', 'tip' => __('This Plugin requires the following Plugins be installed first.') ], 'pi.version' => [ 'display' => __('Version'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The version of this Plugin.') ], 'pi.last_updated' => [ 'display' => __('Installed/Upgraded'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The date that this Plugin was last installed or upgraded.') ], 'pi.id' => [ 'display' => __('Load Order'), 'align' => 'right', 'sort' => 'ASC', 'tip' => __('The load order of the Plugin. You can change the load order by first sorting by it, then moving a Plugin either up or down.') ] ]; break; } html_header_sort($display_text, grv('sort_column'), grv('sort_direction'), 1); $i = 0; if (cacti_sizeof($plugins)) { $j = 0; foreach ($plugins as $plugin) { if ((isset($plugins[$j + 1]) && $plugins[$j + 1]['status'] < 0) || (!isset($plugins[$j + 1]))) { $last_plugin = true; } else { $last_plugin = false; } if ($plugin['status'] <= 0 || (grv('sort_column') != 'pi.id')) { $load_ordering = false; } else { $load_ordering = true; } print ""; switch(grv('state')) { case 8: print format_archive_plugin_row($plugin, $table); break; case 6: print format_available_plugin_row($plugin, $table); break; default: print format_plugin_row($plugin, $last_plugin, $load_ordering, $table); break; } form_end_row(); $i++; $j++; } } else { print '' . __('No Plugins Found') . ''; } html_end_box(false); if (cacti_sizeof($plugins)) { print $nav; } form_end(); db_execute("DROP TABLE $table"); } function format_plugin_row(array $plugin, bool $last_plugin, bool $include_ordering, string $table) : string { global $status_names; static $first_plugin = true; static $row_id = 1; $row = plugin_actions($plugin, $table); $uname = cacti_strtoupper($plugin['plugin']); if ($uname == $plugin['plugin']) { $plugin_name = $uname; } else { $plugin_name = ucfirst($plugin['plugin']); } $row .= "" . filter_value($plugin_name, grv('filter')) . ''; $row .= "" . filter_value($plugin['description'], grv('filter')) . ''; if ($plugin['status'] == '-1') { $status = plugin_is_compatible($plugin['plugin']); $row .= "" . __('Not Compatible, \'%s\'', $status['requires']); } elseif ($plugin['status'] < -1) { $row .= "" . __('Plugin Error'); } else { $row .= "" . $status_names[$plugin['status']]; } if ($plugin['last_updated'] != '0000-00-00 00:00:00') { if (read_config_option('github_allow_unsafe') == 'on') { $newer = db_fetch_cell_prepared('SELECT COUNT(*) FROM plugin_available WHERE plugin = ? AND published_at > ?', [$plugin['plugin'], $plugin['last_updated']]); } else { $newer = db_fetch_cell_prepared('SELECT COUNT(*) FROM plugin_available WHERE plugin = ? AND last_updated > ? AND tag_name != "develop"', [$plugin['plugin'], $plugin['last_updated']]); } } else { $newer = 0; } if ($newer > 0) { $row .= ", " . __('New Version') . ''; } if (POLLER_ID > 1) { if (isset($plugin['capabilities']) && (str_contains($plugin['capabilities'], 'remote_collect:1') || str_contains($plugin['capabilities'], 'remote_poller:1'))) { if ($plugin['remote_status'] == '-1') { $status = plugin_is_compatible($plugin['plugin']); $row .= ' / ' . __('Not Compatible, \'%s\'', $status['requires']); } elseif ($plugin['remote_status'] < -1) { $row .= ' / ' . __('Plugin Error'); } else { $row .= ' / ' . $status_names[$plugin['remote_status']]; } } else { $row .= ' / ' . __('N/A'); } } $row .= ''; if ($plugin['requires'] != '') { $requires = explode(' ', $plugin['requires']); foreach ($requires as $r) { $nr[] = ucfirst($r); } $requires = implode(', ', $nr); } else { $requires = $plugin['requires']; } if ($plugin['last_updated'] == '0000-00-00 00:00:00') { $last_updated = __('N/A'); } elseif (isset($plugin['last_updated'])) { $last_updated = substr($plugin['last_updated'], 0, 16); } else { $last_updated = __('N/A'); } $plugin['compat'] = plugin_display_compat($plugin['compat']); $row .= "" . filter_value($plugin['author'], grv('filter')) . ''; $row .= "" . htmle($plugin['compat']) . ''; $row .= "" . htmle($requires) . ''; $row .= "" . htmle($plugin['version']) . ''; $row .= "" . $last_updated . ''; if ($include_ordering) { $row .= ""; if (!$first_plugin) { $row .= ""; } else { $row .= ''; } if ($last_plugin === false) { $row .= ""; } else { $row .= ''; } $row .= ''; } else { $row .= ''; } if ($include_ordering) { $first_plugin = false; } $row_id++; return $row; } function plugin_check_available_status(array $plugin, string &$row) : void { if (cacti_version_compare(CACTI_VERSION, $plugin['avail_compat'], '<')) { $row .= "" . __('Cacti Upgrade Required') . ''; } else { $row .= "" . __('Compatible') . ''; } } function format_available_plugin_row(array $plugin, string $table) : string { global $status_names; // action icons $row = ""; // remove leading 'v' off tag names for compares $avail_version = ltrim($plugin['avail_tag_name'], 'v'); if (plugin_valid_version_range($plugin['avail_compat'])) { if (plugin_valid_dependencies($plugin['avail_requires'])) { if ($plugin['version'] == '') { if ($avail_version != 'develop') { $row .= ""; } else { $row .= ""; } $status = __('Compatible, Loadable'); } elseif ($avail_version == 'develop') { $row .= ""; $status = __('Compatible, Upgrade'); } elseif (cacti_version_compare($avail_version, $plugin['version'], '<')) { $row .= ""; $status = __('Compatible, Downgrade'); } elseif (cacti_version_compare($avail_version, $plugin['version'], '=')) { $row .= ""; $status = __('Compatible, Same Version'); } else { $row .= ""; $status = __('Compatible, Upgrade'); } } else { $row .= ""; $status = __('Not Compatible, Dependencies'); } } else { $row .= ""; $status = __('Not Compatible, Cacti Version'); } $row .= ""; // no link to the changelog unless it exists if ($plugin['changelog'] > 0) { $row .= ""; } $row .= ''; $uname = cacti_strtoupper($plugin['plugin']); if ($uname == $plugin['plugin']) { $plugin_name = $uname; } else { $plugin_name = ucfirst($plugin['plugin']); } $row .= "" . filter_value($plugin_name, grv('filter')) . ''; $row .= "" . filter_value($plugin['avail_description'], grv('filter')) . ''; $row .= "" . $status . ''; if ($plugin['avail_requires'] != '') { $requires = explode(' ', $plugin['avail_requires']); foreach ($requires as $r) { $nr[] = ucfirst($r); } $requires = implode(', ', $nr); } else { $requires = $plugin['avail_requires']; } $plugin['avail_compat'] = plugin_display_compat($plugin['avail_compat']); $row .= "" . filter_value($plugin['avail_author'], grv('filter')) . ''; $row .= "" . htmle($plugin['avail_compat']) . ''; if ($plugin['version'] == '') { $row .= "" . __esc('Not Loaded') . ''; } else { $row .= "" . htmle($plugin['version']) . ''; } if ($plugin['last_updated'] == '0000-00-00 00:00:00' || $plugin['last_updated'] == '') { $last_updated = __('N/A'); } else { $last_updated = substr($plugin['last_updated'], 0, 16); } if ($plugin['avail_tag_name'] !== 'develop') { $tag_version = str_replace('v', '', $plugin['avail_tag_name']); } else { $tag_version = $plugin['avail_tag_name']; } $row .= "" . htmle($tag_version) . ''; $size = $plugin['archive_length']; $suffix = ''; if ($size > 1024) { $suffix = ' KB'; $size /= 1024; } if ($size > 1024) { $suffix = ' MB'; $size /= 1024; } $row .= "" . number_format_i18n($size, 1) . $suffix . ''; $row .= "" . htmle($requires) . ''; $row .= "" . substr($plugin['avail_published'], 0, 16) . ''; $row .= "" . $last_updated . ''; return $row; } function format_archive_plugin_row(array $plugin, string $table) : string { global $status_names; static $first_plugin = true; // action icons $row = ""; if (plugin_valid_version_range($plugin['archive_compat'])) { if (plugin_valid_dependencies($plugin['archive_requires'])) { if ($plugin['version'] == '') { $row .= ""; $status = __('Compatible, Loadable'); } elseif (cacti_version_compare($plugin['archive_version'], $plugin['version'], '<')) { $row .= ""; $status = __('Compatible, Downgrade'); } elseif (cacti_version_compare($plugin['archive_version'], $plugin['version'], '=')) { $row .= ""; $status = __('Compatible, Same Version'); } else { $row .= ""; $status = __('Compatible, Upgradable'); } } else { $row .= ""; $status = __('Not Compatible, Dependencies'); } } else { $row .= ""; $status = __('Not Compatible, Cacti Version'); } $row .= ""; $row .= ''; $uname = cacti_strtoupper($plugin['plugin']); if ($uname == $plugin['plugin']) { $plugin_name = $uname; } else { $plugin_name = ucfirst($plugin['plugin']); } $row .= "" . filter_value($plugin_name, grv('filter')) . ''; $row .= "" . filter_value($plugin['description'], grv('filter')) . ''; if ($plugin['archive_note'] != '') { $row .= "" . __esc('Notes') . ''; } else { $row .= "-"; } $row .= "" . $status . ''; if ($plugin['archive_requires'] != '') { $requires = explode(' ', $plugin['archive_requires']); foreach ($requires as $r) { $nr[] = ucfirst($r); } $requires = implode(', ', $nr); } else { $requires = $plugin['archive_requires']; } $plugin['archive_compat'] = plugin_display_compat($plugin['archive_compat']); $row .= "" . filter_value($plugin['author'], grv('filter')) . ''; $row .= "" . htmle($plugin['archive_compat']) . ''; if ($plugin['version'] == '') { $row .= "" . __esc('Not Installed') . ' / ' . htmle($plugin['archive_version']) . ''; } else { $row .= "" . htmle($plugin['version']) . ' / ' . htmle($plugin['archive_version']) . ''; } $size = $plugin['archive_length']; $suffix = ''; if ($size > 1024) { $suffix = ' KB'; $size /= 1024; } if ($size > 1024) { $suffix = ' MB'; $size /= 1024; } $row .= "" . number_format_i18n($size, 1) . $suffix . ''; $row .= "" . htmle($plugin['archive_requires']) . ''; if ($plugin['last_updated'] == '0000-00-00 00:00:00') { $last_updated = __('N/A'); } elseif (!empty($plugin['last_updated'])) { $last_updated = substr($plugin['last_updated'], 0, 16); } else { $last_updated = __('N/A'); } $archive_date = substr($plugin['archive_date'], 0, 16); $row .= "" . $archive_date . ''; $row .= "" . $last_updated . ''; return $row; } function plugin_required_for_others(array $plugin, string $table) : mixed { $required_for_others = db_fetch_cell("SELECT GROUP_CONCAT(plugin) FROM $table WHERE requires LIKE '%" . $plugin['plugin'] . "%' AND status IN (1,4,7)"); if ($required_for_others) { $parts = explode(',', $required_for_others); foreach ($parts as $p) { $np[] = ucfirst($p); } return implode(', ', $np); } else { return false; } } function plugin_required_installed(array $plugin, string $table) : string { $not_installed = ''; api_plugin_can_install($plugin['plugin'], $not_installed); return $not_installed; } function plugin_display_compat(string $compat) : string { $compat = explode(' ', $compat); foreach ($compat as $index => $c) { if (str_contains($c, '>=')) { $compat[$index] = str_replace('>=', '>= ', $c); } elseif (str_contains($c, '<=')) { $compat[$index] = str_replace('<=', '<= ', $c); } elseif (str_contains($c, '>')) { $compat[$index] = str_replace('>', '> ', $c); } elseif (str_contains($c, '<')) { $compat[$index] = str_replace('<', '< ', $c); } else { $compat[$index] = '>= ' . $c; } } return implode(' ', $compat); } function plugin_get_install_links(array $plugin, string $table) : string { $path = CACTI_PATH_PLUGINS . '/' . $plugin['plugin']; $link = ''; if ($plugin['status'] == 0) { if (!file_exists("$path/setup.php")) { $link .= ""; } elseif (!file_exists("$path/INFO")) { $link .= ""; } else { $not_installed = plugin_required_installed($plugin, $table); if ($not_installed != '') { $link .= ""; } else { $link .= ""; } $link .= ""; } $link .= ""; $setup_file = CACTI_PATH_BASE . '/plugins/' . $plugin['plugin'] . '/setup.php'; if (file_exists($setup_file)) { require_once($setup_file); $has_data_function = "plugin_{$plugin['plugin']}_has_data"; $rm_data_function = "plugin_{$plugin['plugin']}_remove_data"; if (function_exists($has_data_function) && function_exists($rm_data_function) && $has_data_function()) { $link .= ""; } } } return $link; } function plugin_actions(array $plugin, string $table) : string { global $pluginslist, $plugins_integrated; $link = ''; $archived = db_fetch_cell_prepared('SELECT COUNT(*) FROM plugin_archive WHERE plugin = ? AND dir_md5sum = ?', [$plugin['plugin'], $plugin['dir_md5sum']]); switch ($plugin['status']) { case '0': // Not Installed $link .= plugin_get_install_links($plugin, $table); break; case '1': // Currently Active $required = plugin_required_for_others($plugin, $table); if ($required != '') { $link .= ""; } else { $link .= ""; } $link .= ""; if ($archived) { $link .= ""; } else { $link .= ""; } break; case '2': // Configuration issues $link .= ""; $link .= ""; $link .= ""; break; case '4': // Installed but not active $required = plugin_required_for_others($plugin, $table); if ($required != '') { $link .= ""; } else { $link .= ""; } $link .= ""; if ($archived) { $link .= ""; } else { $link .= ""; } break; case '7': // Installed but errored $required = plugin_required_for_others($plugin, $table); if ($required != '') { $link .= ""; } else { $link .= ""; } $link .= ""; if ($archived) { $link .= ""; } else { $link .= ""; } break; case '-5': // Plugin directory missing if (is_dir(CACTI_PATH_PLUGINS . '/' . $plugin['plugin'])) { /* Directory still present but not loadable (e.g. missing setup.php); withhold Force Uninstall and report the underlying error instead. */ $link .= ""; } else { $required = plugin_required_for_others($plugin, $table); if ($required != '') { $link .= ""; } else { $link .= ""; } } break; case '-4': // Plugins should have INFO file since 1.0.0 $link .= ""; break; case '-3': // Plugins can have spaces in their names $link .= ""; break; case '-2': // Naming issues $link .= ""; break; default: // Old PIA $path = CACTI_PATH_PLUGINS . '/' . $plugin['plugin']; if (!file_exists("$path/setup.php")) { $link .= ""; } elseif (!file_exists("$path/INFO")) { $link .= ""; } elseif (in_array($plugin['plugin'], $plugins_integrated, true)) { $link .= ""; } else { $link .= ""; } break; } if (POLLER_ID > 1) { if (isset($plugin['capabilities']) && (str_contains($plugin['capabilities'], 'remote_collect:1') || str_contains($plugin['capabilities'], 'remote_poller:1'))) { if ($plugin['remote_status'] == 1) { // Installed and Active // ToDo: Disabling here does not make much sense as the main will be replicated // with any change of any other plugin thus undoing. Fix that moving forward // $link .= ""; } elseif ($plugin['remote_status'] == 4) { // Installed but inactive if ($plugin['status'] == 1) { $link .= ""; } } } } $link .= ''; return $link; }