mirror of
https://github.com/Piwigo/Piwigo.git
synced 2025-04-27 11:49:56 +03:00
plugins go now in the #plugins table
git-svn-id: http://piwigo.org/svn/trunk@1584 68402e56-0260-453c-a942-63ccdbb3a9ee
This commit is contained in:
parent
9c3e182268
commit
a81bac0f15
10 changed files with 306 additions and 85 deletions
|
@ -24,7 +24,9 @@
|
|||
// | USA. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
function get_plugins()
|
||||
/* Returns an array of plugins defined in the plugin directory
|
||||
*/
|
||||
function get_fs_plugins()
|
||||
{
|
||||
$plugins = array();
|
||||
|
||||
|
@ -39,7 +41,7 @@ function get_plugins()
|
|||
and file_exists($path.'/index.php')
|
||||
)
|
||||
{
|
||||
$plugin = array('name'=>'?', 'version'=>'?', 'uri'=>'', 'description'=>'');
|
||||
$plugin = array('name'=>'?', 'version'=>'0', 'uri'=>'', 'description'=>'');
|
||||
$plg_data = implode( '', file($path.'/index.php') );
|
||||
|
||||
if ( preg_match("|Plugin Name: (.*)|i", $plg_data, $val) )
|
||||
|
@ -66,39 +68,6 @@ function get_plugins()
|
|||
return $plugins;
|
||||
}
|
||||
|
||||
function activate_plugin($plugin_name)
|
||||
{
|
||||
global $conf;
|
||||
$arr = get_active_plugins(false);
|
||||
array_push($arr, $plugin_name);
|
||||
if ($arr != array_unique($arr) )
|
||||
return false; // just added the same one
|
||||
$conf['active_plugins'] = implode(',', $arr);
|
||||
pwg_query('
|
||||
UPDATE '.CONFIG_TABLE.'
|
||||
SET value="'.$conf['active_plugins'].'"
|
||||
WHERE param="active_plugins"');
|
||||
return true;
|
||||
}
|
||||
|
||||
function deactivate_plugin($plugin_name)
|
||||
{
|
||||
global $conf;
|
||||
$arr = get_active_plugins(false);
|
||||
$idx = array_search($plugin_name, $arr);
|
||||
if ($idx!==false)
|
||||
{
|
||||
unset( $arr[$idx] );
|
||||
$conf['active_plugins'] = implode(',', $arr);
|
||||
pwg_query('
|
||||
UPDATE '.CONFIG_TABLE.'
|
||||
SET value="'.$conf['active_plugins'].'"
|
||||
WHERE param="active_plugins"');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*allows plugins to add their content to the administration page*/
|
||||
function add_plugin_admin_menu($title, $func)
|
||||
{
|
||||
|
|
|
@ -36,35 +36,132 @@ check_status(ACCESS_ADMINISTRATOR);
|
|||
|
||||
$my_base_url = PHPWG_ROOT_PATH.'admin.php?page=plugins';
|
||||
|
||||
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | perform requested actions |
|
||||
// +-----------------------------------------------------------------------+
|
||||
if ( isset($_REQUEST['action']) and isset($_REQUEST['plugin']) )
|
||||
{
|
||||
if ( $_REQUEST['action']=='deactivate')
|
||||
$plugin_id = $_REQUEST['plugin'];
|
||||
$crt_db_plugin = get_db_plugins('', $plugin_id);
|
||||
if (!empty($crt_db_plugin))
|
||||
{
|
||||
$result = deactivate_plugin( $_REQUEST['plugin'] );
|
||||
$crt_db_plugin=$crt_db_plugin[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = activate_plugin( $_REQUEST['plugin'] );
|
||||
unset($crt_db_plugin);
|
||||
}
|
||||
if ($result)
|
||||
{ // we need a redirect so that we really reload it
|
||||
redirect($my_base_url.'&'.$_REQUEST['action'].'='.$result);
|
||||
}
|
||||
else
|
||||
|
||||
$file_to_include = PHPWG_PLUGINS_PATH.$plugin_id.'/maintain.inc.php';
|
||||
|
||||
switch ( $_REQUEST['action'] )
|
||||
{
|
||||
array_push( $page['errors'], 'Plugin activation/deactivation error' );
|
||||
case 'install':
|
||||
if ( !empty($crt_db_plugin))
|
||||
{
|
||||
die ('CANNOT install - ALREADY INSTALLED');
|
||||
}
|
||||
$fs_plugins = get_fs_plugins();
|
||||
if ( !isset( $fs_plugins[$plugin_id] ) )
|
||||
{
|
||||
die ('CANNOT install - NO SUCH PLUGIN');
|
||||
}
|
||||
$query = '
|
||||
INSERT INTO '.PLUGINS_TABLE.' (id,version) VALUES ("'
|
||||
.$plugin_id.'","'.$fs_plugins[$plugin_id]['version'].'"
|
||||
)';
|
||||
pwg_query($query);
|
||||
|
||||
// MAYBE TODO HERE = what if we die or we fail ???
|
||||
@include_once($file_to_include);
|
||||
if ( function_exists('plugin_install') )
|
||||
{
|
||||
plugin_install($plugin_id);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'activate':
|
||||
if ( !isset($crt_db_plugin) )
|
||||
{
|
||||
die ('CANNOT '. $_REQUEST['action'] .' - NOT INSTALLED');
|
||||
}
|
||||
if ($crt_db_plugin['state']!='inactive')
|
||||
{
|
||||
die('invalid current state '.$crt_db_plugin['state']);
|
||||
}
|
||||
$query = '
|
||||
UPDATE '.PLUGINS_TABLE.' SET state="active" WHERE id="'.$plugin_id.'"';
|
||||
pwg_query($query);
|
||||
|
||||
// MAYBE TODO HERE = what if we die or we fail ???
|
||||
@include_once($file_to_include);
|
||||
if ( function_exists('plugin_activate') )
|
||||
{
|
||||
plugin_activate($plugin_id);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'deactivate':
|
||||
if ( !isset($crt_db_plugin) )
|
||||
{
|
||||
die ('CANNOT '. $_REQUEST['action'] .' - NOT INSTALLED');
|
||||
}
|
||||
if ($crt_db_plugin['state']!='active')
|
||||
{
|
||||
die('invalid current state '.$crt_db_plugin['state']);
|
||||
}
|
||||
$query = '
|
||||
UPDATE '.PLUGINS_TABLE.' SET state="inactive" WHERE id="'.$plugin_id.'"';
|
||||
pwg_query($query);
|
||||
|
||||
// MAYBE TODO HERE = what if we die or we fail ???
|
||||
@include_once($file_to_include);
|
||||
if ( function_exists('plugin_deactivate') )
|
||||
{
|
||||
plugin_deactivate($plugin_id);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'uninstall':
|
||||
if ( !isset($crt_db_plugin) )
|
||||
{
|
||||
die ('CANNOT '. $_REQUEST['action'] .' - NOT INSTALLED');
|
||||
}
|
||||
$query = '
|
||||
DELETE FROM '.PLUGINS_TABLE.' WHERE id="'.$plugin_id.'"';
|
||||
pwg_query($query);
|
||||
|
||||
// MAYBE TODO HERE = what if we die or we fail ???
|
||||
@include_once($file_to_include);
|
||||
if ( function_exists('plugin_uninstall') )
|
||||
{
|
||||
plugin_uninstall($plugin_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// do the redirection so that we allow the plugins to load/unload
|
||||
redirect($my_base_url);
|
||||
}
|
||||
|
||||
|
||||
$active_plugins = get_active_plugins();
|
||||
$active_plugins = array_flip($active_plugins);
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | start template output |
|
||||
// +-----------------------------------------------------------------------+
|
||||
$fs_plugins = get_fs_plugins();
|
||||
$db_plugins = get_db_plugins();
|
||||
$db_plugins_by_id=array();
|
||||
foreach ($db_plugins as &$db_plugin)
|
||||
{
|
||||
$db_plugins_by_id[$db_plugin['id']] = &$db_plugin;
|
||||
}
|
||||
|
||||
$plugins = get_plugins();
|
||||
|
||||
$template->set_filenames(array('plugins' => 'admin/plugins.tpl'));
|
||||
|
||||
|
||||
trigger_event('plugin_admin_menu');
|
||||
|
||||
$template->assign_block_vars('plugin_menu.menu_item',
|
||||
|
@ -89,36 +186,86 @@ if ( isset($page['plugin_admin_menu']) )
|
|||
}
|
||||
|
||||
$num=0;
|
||||
foreach( $plugins as $plugin_id => $plugin )
|
||||
foreach( $fs_plugins as $plugin_id => $fs_plugin )
|
||||
{
|
||||
$action_url = $my_base_url.'&plugin='.$plugin_id;
|
||||
if ( isset( $active_plugins[$plugin_id] ) )
|
||||
$display_name = $fs_plugin['name'];
|
||||
if ( !empty($fs_plugin['uri']) )
|
||||
{
|
||||
$action_url .= '&action=deactivate';
|
||||
$action_name = l10n('Deactivate');
|
||||
}
|
||||
else
|
||||
{
|
||||
$action_url .= '&action=activate';
|
||||
$action_name = l10n('Activate');
|
||||
}
|
||||
$display_name = $plugin['name'];
|
||||
if ( !empty($plugin['uri']) )
|
||||
{
|
||||
$display_name='<a href="'.$plugin['uri'].'">'.$display_name.'</a>';
|
||||
$display_name='<a href="'.$fs_plugin['uri'].'">'.$display_name.'</a>';
|
||||
}
|
||||
$template->assign_block_vars( 'plugins.plugin',
|
||||
array(
|
||||
'NAME' => $display_name,
|
||||
'VERSION' => $plugin['version'],
|
||||
'DESCRIPTION' => $plugin['description'],
|
||||
'VERSION' => $fs_plugin['version'],
|
||||
'DESCRIPTION' => $fs_plugin['description'],
|
||||
'CLASS' => ($num++ % 2 == 1) ? 'row2' : 'row1',
|
||||
'L_ACTION' => $action_name,
|
||||
'U_ACTION' => $action_url,
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$action_url = $my_base_url.'&plugin='.$plugin_id;
|
||||
if ( isset($db_plugins_by_id[$plugin_id]) )
|
||||
{ // already in the database
|
||||
// MAYBE TODO HERE: check for the version and propose upgrade action
|
||||
switch ($db_plugins_by_id[$plugin_id]['state'])
|
||||
{
|
||||
case 'active':
|
||||
$template->assign_block_vars( 'plugins.plugin.action',
|
||||
array(
|
||||
'U_ACTION' => $action_url . '&action=deactivate',
|
||||
'L_ACTION' => l10n('Deactivate'),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'inactive':
|
||||
$template->assign_block_vars( 'plugins.plugin.action',
|
||||
array(
|
||||
'U_ACTION' => $action_url . '&action=activate',
|
||||
'L_ACTION' => l10n('Activate'),
|
||||
)
|
||||
);
|
||||
$template->assign_block_vars( 'plugins.plugin.action',
|
||||
array(
|
||||
'U_ACTION' => $action_url . '&action=uninstall',
|
||||
'L_ACTION' => l10n('Uninstall'),
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$template->assign_block_vars( 'plugins.plugin.action',
|
||||
array(
|
||||
'U_ACTION' => $action_url . '&action=install',
|
||||
'L_ACTION' => l10n('Install'),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$missing_plugin_ids = array_diff(
|
||||
array_keys($db_plugins_by_id), array_keys($fs_plugins)
|
||||
);
|
||||
foreach( $missing_plugin_ids as $plugin_id )
|
||||
{
|
||||
$template->assign_block_vars( 'plugins.plugin',
|
||||
array(
|
||||
'NAME' => $plugin_id,
|
||||
'VERSION' => $db_plugins_by_id[$plugin_id]['version'],
|
||||
'DESCRIPTION' => "ERROR: THIS PLUGIN IS MISSING BUT IT IS INSTALLED! UNINSTALL IT NOW !",
|
||||
'CLASS' => ($num++ % 2 == 1) ? 'row2' : 'row1',
|
||||
)
|
||||
);
|
||||
$action_url = $my_base_url.'&plugin='.$plugin_id;
|
||||
$template->assign_block_vars( 'plugins.plugin.action',
|
||||
array(
|
||||
'U_ACTION' => $action_url . '&action=uninstall',
|
||||
'L_ACTION' => l10n('Uninstall'),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
$template->assign_var_from_handle('ADMIN_CONTENT', 'plugins');
|
||||
?>
|
||||
|
|
|
@ -71,4 +71,5 @@ define('SEARCH_TABLE', $prefixeTable.'search');
|
|||
define('USER_MAIL_NOTIFICATION_TABLE', $prefixeTable.'user_mail_notification');
|
||||
define('TAGS_TABLE', $prefixeTable.'tags');
|
||||
define('IMAGE_TAG_TABLE', $prefixeTable.'image_tag');
|
||||
define('PLUGINS_TABLE', $prefixeTable.'plugins');
|
||||
?>
|
||||
|
|
|
@ -67,6 +67,10 @@ function add_event_handler($event, $func, $priority=50, $accepted_args=1)
|
|||
}
|
||||
|
||||
|
||||
/* Triggers an event and calls all registered event handlers
|
||||
* @param string $event name of the event
|
||||
* @param mixed $data data to pass to handlers
|
||||
*/
|
||||
function trigger_event($event, $data=null)
|
||||
{
|
||||
global $pwg_event_handlers;
|
||||
|
@ -123,32 +127,53 @@ function trigger_event($event, $data=null)
|
|||
|
||||
|
||||
|
||||
|
||||
function get_active_plugins($runtime = true)
|
||||
/* Returns an array of plugins defined in the database
|
||||
* @param string $state optional filter on this state
|
||||
* @param string $id optional returns only data about given plugin
|
||||
*/
|
||||
function get_db_plugins($state='', $id='')
|
||||
{
|
||||
global $conf;
|
||||
if ($conf['disable_plugins'] and $runtime)
|
||||
$query = '
|
||||
SELECT * FROM '.PLUGINS_TABLE;
|
||||
if (!empty($state) or !empty($id) )
|
||||
{
|
||||
return array();
|
||||
$query .= '
|
||||
WHERE 1=1';
|
||||
if (!empty($state))
|
||||
{
|
||||
$query .= '
|
||||
AND state="'.$state.'"';
|
||||
}
|
||||
if (!empty($id))
|
||||
{
|
||||
$query .= '
|
||||
AND id="'.$id.'"';
|
||||
}
|
||||
}
|
||||
if (empty($conf['active_plugins']))
|
||||
{
|
||||
return array();
|
||||
}
|
||||
return explode(',', $conf['active_plugins']);
|
||||
|
||||
$result = pwg_query($query);
|
||||
$plugins = array();
|
||||
while ($row = mysql_fetch_array($result))
|
||||
{
|
||||
array_push($plugins, $row);
|
||||
}
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
|
||||
/*loads all the plugins on startup*/
|
||||
function load_plugins()
|
||||
{
|
||||
$plugins = get_active_plugins();
|
||||
global $conf;
|
||||
if ($conf['disable_plugins'])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$plugins = get_db_plugins('active');
|
||||
foreach( $plugins as $plugin)
|
||||
{
|
||||
if (!empty($plugin))
|
||||
{
|
||||
include_once( PHPWG_PLUGINS_PATH.$plugin.'/index.php' );
|
||||
}
|
||||
@include_once( PHPWG_PLUGINS_PATH.$plugin['id'].'/index.php' );
|
||||
}
|
||||
trigger_event('plugins_loaded');
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('page_banner','<h
|
|||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('history_admin','false','keep a history of administrator visits on your website');
|
||||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('history_guest','true','keep a history of guest visits on your website');
|
||||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('login_history','true','keep a history of user logins on your website');
|
||||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('active_plugins','','activated plugins');
|
||||
-- Notification by mail
|
||||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('nbm_send_mail_as','','Send mail as param value for notification by mail');
|
||||
INSERT INTO phpwebgallery_config (param,value,comment) VALUES ('nbm_send_detailed_content','true','Send detailed content for notification by mail');
|
||||
|
|
60
install/db/34-database.php
Normal file
60
install/db/34-database.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | PhpWebGallery - a PHP based picture gallery |
|
||||
// | Copyright (C) 2002-2003 Pierrick LE GALL - pierrick@phpwebgallery.net |
|
||||
// | Copyright (C) 2003-2005 PhpWebGallery Team - http://phpwebgallery.net |
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | branch : BSF (Best So Far)
|
||||
// | file : $RCSfile$
|
||||
// | last update : $Date: 2005-09-21 00:04:57 +0200 (mer, 21 sep 2005) $
|
||||
// | last modifier : $Author: plg $
|
||||
// | revision : $Revision: 870 $
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | 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 |
|
||||
// | |
|
||||
// | This program is distributed in the hope that it will be useful, but |
|
||||
// | WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||
// | General Public License for more details. |
|
||||
// | |
|
||||
// | You should have received a copy of the GNU General Public License |
|
||||
// | along with this program; if not, write to the Free Software |
|
||||
// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
|
||||
// | USA. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
if (!defined('PHPWG_ROOT_PATH'))
|
||||
{
|
||||
die('Hacking attempt!');
|
||||
}
|
||||
|
||||
$upgrade_description = 'Add #plugins and remove active_plugins from #config';
|
||||
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | Upgrade content |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
$query = '
|
||||
DELETE FROM '.PREFIX_TABLE.'config WHERE param="active_plugins"';
|
||||
pwg_query($query);
|
||||
|
||||
$query = '
|
||||
CREATE TABLE '.PREFIX_TABLE.'plugins (
|
||||
`id` varchar(64) binary NOT NULL default "",
|
||||
`state` enum("inactive","active") NOT NULL default "inactive",
|
||||
`version` varchar(64) NOT NULL default "0",
|
||||
PRIMARY KEY (`id`)
|
||||
);';
|
||||
pwg_query($query);
|
||||
|
||||
|
||||
echo
|
||||
"\n"
|
||||
.'"'.$upgrade_description.'"'.' ended'
|
||||
."\n"
|
||||
;
|
||||
|
||||
?>
|
|
@ -178,6 +178,18 @@ CREATE TABLE `phpwebgallery_images` (
|
|||
KEY `images_i1` (`storage_category_id`)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
--
|
||||
-- Table structure for table `phpwebgallery_plugins`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `phpwebgallery_plugins`;
|
||||
CREATE TABLE `phpwebgallery_plugins` (
|
||||
`id` varchar(64) binary NOT NULL default '',
|
||||
`state` enum('inactive','active') NOT NULL default 'inactive',
|
||||
`version` varchar(64) NOT NULL default '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
--
|
||||
-- Table structure for table `phpwebgallery_rate`
|
||||
--
|
||||
|
|
|
@ -95,6 +95,7 @@ $lang['Groups'] = 'Groups';
|
|||
$lang['Guests'] = 'Guests';
|
||||
$lang['History'] = 'History';
|
||||
$lang['Informations'] = 'Informations';
|
||||
$lang['Install'] = 'Install';
|
||||
$lang['Interface theme'] = 'Interface theme';
|
||||
$lang['Language'] = 'Language';
|
||||
$lang['Link all category elements to a new category'] = 'Link all category elements to a new category';
|
||||
|
@ -172,6 +173,7 @@ $lang['Tag selection'] = 'Tag selection';
|
|||
$lang['Take selected elements out of caddie'] = 'Take selected elements out of caddie';
|
||||
$lang['The %d following tags were deleted : %s'] = 'The %d following tags were deleted : %s';
|
||||
$lang['Unable to check for upgrade since allow_url_fopen is disabled.'] = 'Unable to check for upgrade since allow_url_fopen is disabled.';
|
||||
$lang['Uninstall'] = 'Uninstall';
|
||||
$lang['Use default sort order']='Use the default image sort order (defined in the configuration file)';
|
||||
$lang['User comments validation'] = 'User comments validation';
|
||||
$lang['Users'] = 'Users';
|
||||
|
|
|
@ -95,6 +95,7 @@ $lang['Groups'] = 'Groupes';
|
|||
$lang['Guests'] = 'Invités';
|
||||
$lang['History'] = 'Historique';
|
||||
$lang['Informations'] = 'Informations';
|
||||
$lang['Install'] = 'Installer';
|
||||
$lang['Interface theme'] = 'Theme de l\'interface';
|
||||
$lang['Language'] = 'Langue';
|
||||
$lang['Link all category elements to a new category'] = 'Associer tous les éléments de la catégorie à une nouvelle catégorie';
|
||||
|
@ -172,6 +173,7 @@ $lang['Tag selection'] = 'S
|
|||
$lang['Take selected elements out of caddie'] = 'Sortir les éléments sélectionnés du panier';
|
||||
$lang['The %d following tags were deleted : %s'] = 'Les %d tags suivants ont été supprimés : %s';
|
||||
$lang['Unable to check for upgrade since allow_url_fopen is disabled.'] = 'Impossible de connaître la dernière version cat la fonction allow_url_fopen est désactivée.';
|
||||
$lang['Uninstall'] = 'Désinstaller';
|
||||
$lang['Use default sort order']='Utiliser l\'ordre de tri des images par défaut (défini dans le fichier de configuration)';
|
||||
$lang['User comments validation'] = 'Validation des commentaires utilisateur';
|
||||
$lang['Users'] = 'Utilisateurs';
|
||||
|
|
|
@ -22,7 +22,11 @@
|
|||
<td>{plugins.plugin.NAME}</td>
|
||||
<td>{plugins.plugin.VERSION}</td>
|
||||
<td>{plugins.plugin.DESCRIPTION}</td>
|
||||
<td><a href="{plugins.plugin.U_ACTION}">{plugins.plugin.L_ACTION}</a></td>
|
||||
<td>
|
||||
<!-- BEGIN action -->
|
||||
<a href="{plugins.plugin.action.U_ACTION}">{plugins.plugin.action.L_ACTION}</a>
|
||||
<!-- END action -->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END plugin -->
|
||||
</table>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue