diff --git a/.gitignore b/.gitignore index f3d01e2b6..94df708a1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ cachefile.dbm *.pyc .idea/* bazarr.pid +/venv diff --git a/bazarr/get_settings.py b/bazarr/get_settings.py index e02dbaada..ebe291686 100644 --- a/bazarr/get_settings.py +++ b/bazarr/get_settings.py @@ -37,7 +37,7 @@ def get_general_settings(): if cfg.has_option('general', 'path_mappings'): path_mappings = cfg.get('general', 'path_mappings') else: - path_mappings = [] + path_mappings = '[]' if cfg.has_option('general', 'log_level'): log_level = cfg.get('general', 'log_level') @@ -67,7 +67,7 @@ def get_general_settings(): if cfg.has_option('general', 'use_scenename'): use_scenename = cfg.getboolean('general', 'use_scenename') else: - use_scenename = False + use_scenename = True if cfg.has_option('general', 'use_postprocessing'): use_postprocessing = cfg.getboolean('general', 'use_postprocessing') @@ -92,7 +92,7 @@ def get_general_settings(): if cfg.has_option('general', 'path_mappings_movie'): path_mappings_movie = cfg.get('general', 'path_mappings_movie') else: - path_mappings_movie = [] + path_mappings_movie = '[]' if cfg.has_option('general', 'serie_default_enabled'): serie_default_enabled = cfg.getboolean('general', 'serie_default_enabled') @@ -137,7 +137,7 @@ def get_general_settings(): if cfg.has_option('general', 'use_embedded_subs'): use_embedded_subs = cfg.getboolean('general', 'use_embedded_subs') else: - use_embedded_subs = False + use_embedded_subs = True if cfg.has_option('general', 'only_monitored'): only_monitored = cfg.getboolean('general', 'only_monitored') @@ -151,20 +151,20 @@ def get_general_settings(): else: ip = '0.0.0.0' - port = '6768' + port = '6767' base_url = '/' - path_mappings = [] + path_mappings = '[]' log_level = 'INFO' branch = 'master' auto_update = True single_language = False minimum_score = '90' - use_scenename = False + use_scenename = True use_postprocessing = False postprocessing_cmd = '' use_sonarr = False use_radarr = False - path_mappings_movie = [] + path_mappings_movie = '[]' serie_default_enabled = False serie_default_language = [] serie_default_hi = 'False' @@ -173,7 +173,7 @@ def get_general_settings(): movie_default_hi = 'False' page_size = '25' minimum_score_movie = '70' - use_embedded_subs = False + use_embedded_subs = True only_monitored = False adaptive_searching = False @@ -366,7 +366,7 @@ def get_radarr_settings(): full_update = 'Dayly' else: ip = '127.0.0.1' - port = '8989' + port = '7878' base_url = '/' ssl = False apikey = '' diff --git a/bazarr/init.py b/bazarr/init.py index 515ceded3..8078a48a1 100644 --- a/bazarr/init.py +++ b/bazarr/init.py @@ -28,185 +28,6 @@ if os.path.exists(os.path.join(config_dir, 'log')) is False: config_file = os.path.normpath(os.path.join(config_dir, 'config/config.ini')) cfg = ConfigParser() -try: - # Open database connection - db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30) - c = db.cursor() - - # Get general settings from database table - c.execute("SELECT * FROM table_settings_general") - general_settings = c.fetchone() - c.execute('''SELECT * FROM table_settings_auth''') - auth_settings = c.fetchone() - c.execute('''SELECT * FROM table_settings_sonarr''') - sonarr_settings = c.fetchone() - c.execute('''SELECT * FROM table_settings_radarr''') - radarr_settings = c.fetchone() - - c.execute("DROP TABLE table_settings_general") - c.execute("DROP TABLE table_settings_auth") - c.execute("DROP TABLE table_settings_sonarr") - c.execute("DROP TABLE table_settings_radarr") - - # Close database connection - db.close() - - section = 'general' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', str(general_settings[0])) - cfg.set(section, 'port', str(general_settings[1])) - cfg.set(section, 'base_url', general_settings[2]) - cfg.set(section, 'path_mappings', general_settings[3]) - cfg.set(section, 'log_level', general_settings[4]) - cfg.set(section, 'branch', general_settings[5]) - cfg.set(section, 'auto_update', general_settings[6]) - cfg.set(section, 'single_language', general_settings[9]) - cfg.set(section, 'minimum_score', general_settings[10]) - cfg.set(section, 'use_scenename', general_settings[11]) - cfg.set(section, 'use_postprocessing', general_settings[12]) - cfg.set(section, 'postprocessing_cmd', general_settings[13]) - cfg.set(section, 'use_sonarr', general_settings[14]) - cfg.set(section, 'use_radarr', general_settings[15]) - cfg.set(section, 'path_mappings_movie', general_settings[16]) - cfg.set(section, 'serie_default_enabled', general_settings[17]) - cfg.set(section, 'serie_default_language', general_settings[18]) - cfg.set(section, 'serie_default_hi', general_settings[19]) - cfg.set(section, 'movie_default_enabled', general_settings[20]) - cfg.set(section, 'movie_default_language', general_settings[21]) - cfg.set(section, 'movie_default_hi', general_settings[22]) - cfg.set(section, 'page_size', general_settings[23]) - cfg.set(section, 'minimum_score_movie', general_settings[24]) - cfg.set(section, 'use_embedded_subs', general_settings[25]) - cfg.set(section, 'only_monitored', general_settings[26]) - - section = 'auth' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'enabled', auth_settings[0]) - cfg.set(section, 'username', auth_settings[1]) - cfg.set(section, 'password', auth_settings[2]) - - section = 'sonarr' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', sonarr_settings[0]) - cfg.set(section, 'port', str(sonarr_settings[1])) - cfg.set(section, 'base_url', sonarr_settings[2]) - cfg.set(section, 'ssl', sonarr_settings[3]) - cfg.set(section, 'apikey', sonarr_settings[4]) - cfg.set(section, 'full_update', sonarr_settings[5]) - - section = 'radarr' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', radarr_settings[0]) - cfg.set(section, 'port', str(radarr_settings[1])) - cfg.set(section, 'base_url', radarr_settings[2]) - cfg.set(section, 'ssl', radarr_settings[3]) - cfg.set(section, 'apikey', radarr_settings[4]) - cfg.set(section, 'full_update', radarr_settings[5]) - - with open(config_file, 'w+') as configfile: - cfg.write(configfile) - - - logging.info('BAZARR Config file succesfully migrated from database') - -except sqlite3.OperationalError: - if os.path.exists(config_file) is False: - cfg = ConfigParser() - - section = 'general' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', "0.0.0.0") - cfg.set(section, 'port', "6767") - cfg.set(section, 'base_url', "/") - cfg.set(section, 'path_mappings', '[]') - cfg.set(section, 'log_level', "INFO") - cfg.set(section, 'branch', "master") - cfg.set(section, 'auto_update', "True") - cfg.set(section, 'single_language', "False") - cfg.set(section, 'minimum_score', "90") - cfg.set(section, 'use_scenename', "False") - cfg.set(section, 'use_postprocessing', "False") - cfg.set(section, 'postprocessing_cmd', "False") - cfg.set(section, 'use_sonarr', "False") - cfg.set(section, 'use_radarr', "False") - cfg.set(section, 'path_mappings_movie', '[]') - cfg.set(section, 'serie_default_enabled', "False") - cfg.set(section, 'serie_default_language', '') - cfg.set(section, 'serie_default_hi', "False") - cfg.set(section, 'movie_default_enabled', "False") - cfg.set(section, 'movie_default_language', '') - cfg.set(section, 'movie_default_hi', "False") - cfg.set(section, 'page_size', "25") - cfg.set(section, 'minimum_score_movie', "70") - cfg.set(section, 'use_embedded_subs', "False") - cfg.set(section, 'only_monitored', "False") - cfg.set(section, 'adaptive_searching', "False") - - section = 'proxy' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'type', "None") - cfg.set(section, 'url', "") - cfg.set(section, 'port', "") - cfg.set(section, 'username', "") - cfg.set(section, 'password', "") - cfg.set(section, 'exclude', "localhost,127.0.0.1") - - section = 'auth' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'type', "None") - cfg.set(section, 'username', "") - cfg.set(section, 'password', "") - - section = 'sonarr' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', "127.0.0.1") - cfg.set(section, 'port', "8989") - cfg.set(section, 'base_url', "/") - cfg.set(section, 'ssl', "False") - cfg.set(section, 'apikey', "") - cfg.set(section, 'full_update', "Daily") - - section = 'radarr' - - if not cfg.has_section(section): - cfg.add_section(section) - - cfg.set(section, 'ip', "127.0.0.1") - cfg.set(section, 'port', "7878") - cfg.set(section, 'base_url', "/") - cfg.set(section, 'ssl', "False") - cfg.set(section, 'apikey', "") - cfg.set(section, 'full_update', "Daily") - - - with open(config_file, 'w+') as configfile: - cfg.write(configfile) - - logging.info('BAZARR Config file created successfully') try: # Get SQL script from file fd = open(os.path.join(os.path.dirname(__file__), 'create_db.sql'), 'r') diff --git a/bazarr/main.py b/bazarr/main.py index b21a54094..180c7e251 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -90,6 +90,7 @@ import ast import hashlib import time import urllib +from six import text_type from get_languages import load_language_in_db, language_from_alpha3 from get_providers import load_providers, get_providers, get_providers_auth @@ -226,6 +227,215 @@ def restart(): restart_file.close() +@route(base_url + 'wizard') +@custom_auth_basic(check_credentials) +def wizard(): + authorize() + db = sqlite3.connect(os.path.join(config_dir, 'db/bazarr.db'), timeout=30) + c = db.cursor() + settings_languages = c.execute("SELECT * FROM table_settings_languages ORDER BY name").fetchall() + settings_providers = c.execute("SELECT * FROM table_settings_providers ORDER BY name").fetchall() + c.close() + + settings_general = get_general_settings() + settings_sonarr = get_sonarr_settings() + settings_radarr = get_radarr_settings() + + return template('wizard', __file__=__file__, bazarr_version=bazarr_version, settings_general=settings_general, + settings_languages=settings_languages, settings_providers=settings_providers, + settings_sonarr=settings_sonarr, settings_radarr=settings_radarr, base_url=base_url) + + +@route(base_url + 'save_wizard', method='POST') +@custom_auth_basic(check_credentials) +def save_settings(): + authorize() + + conn = sqlite3.connect(os.path.join(config_dir, 'db/bazarr.db'), timeout=30) + c = conn.cursor() + + settings_general_ip = request.forms.get('settings_general_ip') + settings_general_port = request.forms.get('settings_general_port') + settings_general_baseurl = request.forms.get('settings_general_baseurl') + if settings_general_baseurl.endswith('/') is False: + settings_general_baseurl += '/' + settings_general_sourcepath = request.forms.getall('settings_general_sourcepath') + settings_general_destpath = request.forms.getall('settings_general_destpath') + settings_general_pathmapping = [] + settings_general_pathmapping.extend([list(a) for a in zip(settings_general_sourcepath, settings_general_destpath)]) + settings_general_sourcepath_movie = request.forms.getall('settings_general_sourcepath_movie') + settings_general_destpath_movie = request.forms.getall('settings_general_destpath_movie') + settings_general_pathmapping_movie = [] + settings_general_pathmapping_movie.extend( + [list(a) for a in zip(settings_general_sourcepath_movie, settings_general_destpath_movie)]) + settings_general_single_language = request.forms.get('settings_general_single_language') + if settings_general_single_language is None: + settings_general_single_language = 'False' + else: + settings_general_single_language = 'True' + settings_general_adaptive_searching = request.forms.get('settings_general_adaptive_searching') + if settings_general_adaptive_searching is None: + settings_general_adaptive_searching = 'False' + else: + settings_general_adaptive_searching = 'True' + settings_general_use_sonarr = request.forms.get('settings_general_use_sonarr') + if settings_general_use_sonarr is None: + settings_general_use_sonarr = 'False' + else: + settings_general_use_sonarr = 'True' + settings_general_use_radarr = request.forms.get('settings_general_use_radarr') + if settings_general_use_radarr is None: + settings_general_use_radarr = 'False' + else: + settings_general_use_radarr = 'True' + + cfg = ConfigParser() + + if not cfg.has_section('general'): + cfg.add_section('general') + + cfg.set('general', 'ip', text_type(settings_general_ip)) + cfg.set('general', 'port', text_type(settings_general_port)) + cfg.set('general', 'base_url', text_type(settings_general_baseurl)) + cfg.set('general', 'path_mappings', text_type(settings_general_pathmapping)) + cfg.set('general', 'log_level', text_type(get_general_settings()[4])) + cfg.set('general', 'branch', text_type(get_general_settings()[5])) + cfg.set('general', 'auto_update', text_type(get_general_settings()[6])) + cfg.set('general', 'single_language', text_type(settings_general_single_language)) + cfg.set('general', 'minimum_score', text_type(get_general_settings()[8])) + cfg.set('general', 'use_scenename', text_type(text_type(get_general_settings()[9]))) + cfg.set('general', 'use_postprocessing', text_type(get_general_settings()[10])) + cfg.set('general', 'postprocessing_cmd', text_type(get_general_settings()[11])) + cfg.set('general', 'use_sonarr', text_type(settings_general_use_sonarr)) + cfg.set('general', 'use_radarr', text_type(settings_general_use_radarr)) + cfg.set('general', 'path_mappings_movie', text_type(settings_general_pathmapping_movie)) + cfg.set('general', 'page_size', text_type(get_general_settings()[21])) + cfg.set('general', 'minimum_score_movie', text_type(get_general_settings()[22])) + cfg.set('general', 'use_embedded_subs', text_type(get_general_settings()[23])) + cfg.set('general', 'only_monitored', text_type(get_general_settings()[24])) + cfg.set('general', 'adaptive_searching', text_type(settings_general_adaptive_searching)) + + if not cfg.has_section('proxy'): + cfg.add_section('proxy') + + cfg.set('proxy', 'type', text_type(get_proxy_settings()[0])) + cfg.set('proxy', 'url', text_type(get_proxy_settings()[1])) + cfg.set('proxy', 'port', text_type(get_proxy_settings()[2])) + cfg.set('proxy', 'username', text_type(get_proxy_settings()[3])) + cfg.set('proxy', 'password', text_type(get_proxy_settings()[4])) + cfg.set('proxy', 'exclude', text_type(get_proxy_settings()[5])) + + if not cfg.has_section('auth'): + cfg.add_section('auth') + + cfg.set('auth', 'type', text_type(get_auth_settings()[0])) + cfg.set('auth', 'username', text_type(get_auth_settings()[1])) + cfg.set('auth', 'password', text_type(get_auth_settings()[2])) + + settings_sonarr_ip = request.forms.get('settings_sonarr_ip') + settings_sonarr_port = request.forms.get('settings_sonarr_port') + settings_sonarr_baseurl = request.forms.get('settings_sonarr_baseurl') + settings_sonarr_ssl = request.forms.get('settings_sonarr_ssl') + if settings_sonarr_ssl is None: + settings_sonarr_ssl = 'False' + else: + settings_sonarr_ssl = 'True' + settings_sonarr_apikey = request.forms.get('settings_sonarr_apikey') + + if not cfg.has_section('sonarr'): + cfg.add_section('sonarr') + + cfg.set('sonarr', 'ip', text_type(settings_sonarr_ip)) + cfg.set('sonarr', 'port', text_type(settings_sonarr_port)) + cfg.set('sonarr', 'base_url', text_type(settings_sonarr_baseurl)) + cfg.set('sonarr', 'ssl', text_type(settings_sonarr_ssl)) + cfg.set('sonarr', 'apikey', text_type(settings_sonarr_apikey)) + cfg.set('sonarr', 'full_update', text_type(get_sonarr_settings()[5])) + + settings_radarr_ip = request.forms.get('settings_radarr_ip') + settings_radarr_port = request.forms.get('settings_radarr_port') + settings_radarr_baseurl = request.forms.get('settings_radarr_baseurl') + settings_radarr_ssl = request.forms.get('settings_radarr_ssl') + if settings_radarr_ssl is None: + settings_radarr_ssl = 'False' + else: + settings_radarr_ssl = 'True' + settings_radarr_apikey = request.forms.get('settings_radarr_apikey') + if settings_radarr_apikey != '': + cfg.set('general', 'use_radarr', 'True') + else: + cfg.set('general', 'use_radarr', 'False') + + if not cfg.has_section('radarr'): + cfg.add_section('radarr') + + cfg.set('radarr', 'ip', text_type(settings_radarr_ip)) + cfg.set('radarr', 'port', text_type(settings_radarr_port)) + cfg.set('radarr', 'base_url', text_type(settings_radarr_baseurl)) + cfg.set('radarr', 'ssl', text_type(settings_radarr_ssl)) + cfg.set('radarr', 'apikey', text_type(settings_radarr_apikey)) + cfg.set('radarr', 'full_update', text_type(get_radarr_settings()[5])) + + settings_subliminal_providers = request.forms.getall('settings_subliminal_providers') + c.execute("UPDATE table_settings_providers SET enabled = 0") + for item in settings_subliminal_providers: + c.execute("UPDATE table_settings_providers SET enabled = '1' WHERE name = ?", (item,)) + + settings_subliminal_languages = request.forms.getall('settings_subliminal_languages') + c.execute("UPDATE table_settings_languages SET enabled = 0") + for item in settings_subliminal_languages: + c.execute("UPDATE table_settings_languages SET enabled = '1' WHERE code2 = ?", (item,)) + + settings_serie_default_enabled = request.forms.get('settings_serie_default_enabled') + if settings_serie_default_enabled is None: + settings_serie_default_enabled = 'False' + else: + settings_serie_default_enabled = 'True' + cfg.set('general', 'serie_default_enabled', text_type(settings_serie_default_enabled)) + + settings_serie_default_languages = str(request.forms.getall('settings_serie_default_languages')) + if settings_serie_default_languages == "['None']": + settings_serie_default_languages = 'None' + cfg.set('general', 'serie_default_language', text_type(settings_serie_default_languages)) + + settings_serie_default_hi = request.forms.get('settings_serie_default_hi') + if settings_serie_default_hi is None: + settings_serie_default_hi = 'False' + else: + settings_serie_default_hi = 'True' + cfg.set('general', 'serie_default_hi', text_type(settings_serie_default_hi)) + + settings_movie_default_enabled = request.forms.get('settings_movie_default_enabled') + if settings_movie_default_enabled is None: + settings_movie_default_enabled = 'False' + else: + settings_movie_default_enabled = 'True' + cfg.set('general', 'movie_default_enabled', text_type(settings_movie_default_enabled)) + + settings_movie_default_languages = str(request.forms.getall('settings_movie_default_languages')) + if settings_movie_default_languages == "['None']": + settings_movie_default_languages = 'None' + cfg.set('general', 'movie_default_language', text_type(settings_movie_default_languages)) + + settings_movie_default_hi = request.forms.get('settings_movie_default_hi') + if settings_movie_default_hi is None: + settings_movie_default_hi = 'False' + else: + settings_movie_default_hi = 'True' + cfg.set('general', 'movie_default_hi', text_type(settings_movie_default_hi)) + + with open(config_file, 'w+') as f: + cfg.write(f) + + logging.info('Config file created successfully') + + conn.commit() + c.close() + + configured() + redirect(base_url) + + @route(base_url + 'static/:path#.+#', name='static') @custom_auth_basic(check_credentials) def static(path): @@ -294,6 +504,8 @@ def redirect_root(): redirect(base_url + 'series') elif get_general_settings()[13] is True: redirect(base_url + 'movies') + elif os.path.exists(os.path.join(config_dir, 'config/config.ini')) is False: + redirect(base_url + 'wizard') else: redirect(base_url + 'settings') diff --git a/views/wizard.tpl b/views/wizard.tpl new file mode 100644 index 000000000..9f16e6d50 --- /dev/null +++ b/views/wizard.tpl @@ -0,0 +1,1282 @@ + + + + + + + + + + + + + + + + + + + Settings - Bazarr + + + + +
+
Loading...
+
+ + + +
+
+
+

Some fields are in error and you can't save settings until you have corrected them. Be sure to check in every tabs.

+
+
+
+ +
+
General
+
General settings
+
+
+
+ +
+
Subliminal
+
Subliminal settings
+
+
+
+ +
+
Sonarr
+
Sonarr settings
+
+
+
+ +
+
Radarr
+
Radarr settings
+
+
+
+
+
+
Start-Up
+
+
+
+
+ +
+
+
+
+ +
+
+
+ + + +
+ +
+
+ +
+
+
+
+ +
+
+
+ + + +
+ +
+
+ +
+
+
+ %if settings_general[2] == None: + % base_url = "/" + %else: + % base_url = settings_general[2] + %end + +
+
+ + + +
+
+
+ +
Path Mappings for shows
+
+
+ %import ast + %if settings_general[3] is not None: + % path_substitutions = ast.literal_eval(settings_general[3]) + %else: + % path_substitutions = [] + %end +
+
+ +
+
+
+

+ Path for Sonarr: +

+
+
+ +
+ +
+
+
+

+ Path for Bazarr: +

+
+
+ +
+ %for x in range(0, 5): + % path = [] + % try: + % path = path_substitutions[x] + % except IndexError: + % path = ["", ""] + % end +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ %end +
+
+ +
Path Mappings for movies
+
+
+ %import ast + %if settings_general[14] is not None: + % path_substitutions_movie = ast.literal_eval(settings_general[14]) + %else: + % path_substitutions_movie = [] + %end +
+
+ +
+
+
+

+ Path for Radarr: +

+
+
+ +
+ +
+
+
+

+ Path for Bazarr: +

+
+
+ +
+ %for x in range(0, 5): + % path_movie = [] + % try: + % path_movie = path_substitutions_movie[x] + % except IndexError: + % path_movie = ["", ""] + % end +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ %end +
+
+
+
+ +
+ + +
+
Subtitles providers
+
+
+

Be aware that the more providers you enable, the longer it will take everytime you search for a subtitles.

+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
Subtitles languages
+
+
+
+
+ +
+
+
+ + +
+
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+
+ +
Series default settings
+
+
+
+
+ +
+
+
+
+ + +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+
+
+ +
Movies default settings
+
+
+
+
+ +
+
+
+
+ + +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ +
Connection settings
+
+
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
Connection settings
+
+
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ % include('footer.tpl') + + + + + + + +