فهرست منبع

Merge pull request #1700 from causefx/v2-develop

version 2.1.496
causefx 4 سال پیش
والد
کامیت
d63b80a6a3
47فایلهای تغییر یافته به همراه1732 افزوده شده و 3488 حذف شده
  1. 2 0
      .gitignore
  2. 21 6
      api/classes/deluge.class.php
  3. 95 21
      api/classes/organizr.class.php
  4. 44 1
      api/config/default.php
  5. 9 9
      api/functions/auth-functions.php
  6. 324 0
      api/functions/option-functions.php
  7. 36 21
      api/functions/organizr-functions.php
  8. 5 5
      api/functions/sso-functions.php
  9. 19 84
      api/homepage/calendar.php
  10. 26 80
      api/homepage/couchpotato.php
  11. 52 101
      api/homepage/deluge.php
  12. 47 171
      api/homepage/emby.php
  13. 21 65
      api/homepage/healthchecks.php
  14. 24 72
      api/homepage/html.php
  15. 21 48
      api/homepage/jackett.php
  16. 51 85
      api/homepage/jdownloader.php
  17. 46 162
      api/homepage/jellyfin.php
  18. 40 136
      api/homepage/lidarr.php
  19. 8 19
      api/homepage/misc.php
  20. 21 61
      api/homepage/monitorr.php
  21. 98 188
      api/homepage/netdata.php
  22. 32 98
      api/homepage/nzbget.php
  23. 20 52
      api/homepage/octoprint.php
  24. 40 145
      api/homepage/ombi.php
  25. 19 59
      api/homepage/pihole.php
  26. 73 282
      api/homepage/plex.php
  27. 39 138
      api/homepage/qbittorrent.php
  28. 50 188
      api/homepage/radarr.php
  29. 63 146
      api/homepage/rtorrent.php
  30. 36 98
      api/homepage/sabnzbd.php
  31. 30 95
      api/homepage/sickrage.php
  32. 50 194
      api/homepage/sonarr.php
  33. 20 51
      api/homepage/speedtest.php
  34. 49 184
      api/homepage/tautulli.php
  35. 29 113
      api/homepage/trakt.php
  36. 29 90
      api/homepage/transmission.php
  37. 28 87
      api/homepage/unifi.php
  38. 27 114
      api/homepage/weather.php
  39. 11 0
      api/v2/routes/certificate.php
  40. 5 3
      api/vendor/kryptonit3/couchpotato/src/CouchPotato.php
  41. 6 4
      api/vendor/kryptonit3/sickrage/src/SickRage.php
  42. 5 3
      api/vendor/kryptonit3/sonarr/src/Sonarr.php
  43. 2 1
      css/dark.css
  44. 0 0
      css/dark.min.css
  45. 0 1
      index.php
  46. 52 7
      js/functions.js
  47. 7 0
      js/version.json

+ 2 - 0
.gitignore

@@ -77,6 +77,7 @@ Github.txt
 Demo.txt
 Dev.txt
 config/cacert.pem
+config/custom.pem
 config/config.php
 config/config*.bak.php
 config/users/
@@ -103,6 +104,7 @@ api/config/config*
 upgrade/*
 api/upgrade*
 api/functions/cert/cacert.pem
+api/functions/cert/custom.pem
 wallpaper.jpg
 strings.json
 css/themes/*.css

+ 21 - 6
api/classes/deluge.class.php

@@ -5,12 +5,28 @@ class deluge
 	private $ch;
 	private $url;
 	private $request_id;
+	public $options;
 	
-	public function __construct($host, $password)
+	public function __construct($host, $password, $options)
 	{
+		$verify = (isset($options['verify'])) ? $options['verify'] : false;
 		$this->url = $host . (substr($host, -1) == "/" ? "" : "/") . "json";
 		$this->request_id = 0;
 		$this->ch = curl_init($this->url);
+		switch (gettype($options['verify'])) {
+			case 'string':
+				$cert = $options['custom_cert'];
+				$verify = 2;
+				break;
+			case 'NULL':
+				$cert = $options['organizr_cert'];
+				$verify = 2;
+				break;
+			default:
+				$cert = $options['organizr_cert'];
+				$verify = false;
+				break;
+		}
 		$curl_options = array(
 			CURLOPT_RETURNTRANSFER => true,
 			CURLOPT_HTTPHEADER => array("Accept: application/json", "Content-Type: application/json"),
@@ -18,10 +34,9 @@ class deluge
 			CURLOPT_COOKIEJAR => "",
 			CURLOPT_CONNECTTIMEOUT => 10,
 			CURLOPT_TIMEOUT => 10,
-			CURLOPT_CAINFO => getCert(),
-			CURLOPT_SSL_VERIFYHOST => localURL($host) ? 0 : 2,
-			CURLOPT_SSL_VERIFYPEER => localURL($host) ? 0 : 2,
-			//CURLOPT_SSL_VERIFYPEER => false, THIS IS INSECURE!! However, deluge appears not to send intermediate certificates, so it can be necessary. Use with caution!
+			CURLOPT_CAINFO => $cert,
+			CURLOPT_SSL_VERIFYHOST => $verify,
+			CURLOPT_SSL_VERIFYPEER => $verify,
 		);
 		curl_setopt_array($this->ch, $curl_options);
 		//Log in and get cookies
@@ -569,4 +584,4 @@ class deluge
 		$this->request_id++;
 		return $result->result;
 	}
-}
+}

+ 95 - 21
api/classes/organizr.class.php

@@ -60,7 +60,7 @@ class Organizr
 	
 	// ===================================
 	// Organizr Version
-	public $version = '2.1.476';
+	public $version = '2.1.496';
 	// ===================================
 	// Quick php Version check
 	public $minimumPHP = '7.3';
@@ -328,7 +328,7 @@ class Organizr
 		}
 	}
 	
-	public function setResponse($responseCode = 200, $message = null, $data = null)
+	public function setResponse(int $responseCode = 200, string $message = null, $data = null)
 	{
 		switch ($responseCode) {
 			case 200:
@@ -340,9 +340,7 @@ class Organizr
 				$result = 'error';
 				break;
 		}
-		if ($result) {
-			$GLOBALS['api']['response']['result'] = $result;
-		}
+		$GLOBALS['api']['response']['result'] = $result;
 		if ($message) {
 			$GLOBALS['api']['response']['message'] = $message;
 		}
@@ -444,6 +442,7 @@ class Organizr
 	{
 		if ($this->config['gaTrackingID'] !== '') {
 			return '
+				<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
 				<script async src="https://www.googletagmanager.com/gtag/js?id=' . $this->config['gaTrackingID'] . '"></script>
 				<script>
 					window.dataLayer = window.dataLayer || [];
@@ -1325,7 +1324,7 @@ class Organizr
 		}
 		$approvedPath = 'plugins/images/userTabs/';
 		$removeImage = $approvedPath . pathinfo($image, PATHINFO_BASENAME);
-		if ($this->approvedFileExtension($removeImage)) {
+		if ($this->approvedFileExtension($removeImage, 'image')) {
 			if (file_exists(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . $removeImage)) {
 				$this->writeLog('success', 'Image Manager Function -  Deleted Image [' . pathinfo($image, PATHINFO_BASENAME) . ']', $this->user['username']);
 				$this->setAPIResponse(null, pathinfo($image, PATHINFO_BASENAME) . ' has been deleted', null);
@@ -1343,7 +1342,7 @@ class Organizr
 	public function uploadImage()
 	{
 		$filesCheck = array_filter($_FILES);
-		if (!empty($filesCheck) && $this->approvedFileExtension($_FILES['file']['name']) && strpos($_FILES['file']['type'], 'image/') !== false) {
+		if (!empty($filesCheck) && $this->approvedFileExtension($_FILES['file']['name'], 'image') && strpos($_FILES['file']['type'], 'image/') !== false) {
 			ini_set('upload_max_filesize', '10M');
 			ini_set('post_max_size', '10M');
 			$tempFile = $_FILES['file']['tmp_name'];
@@ -1845,6 +1844,7 @@ class Organizr
 	
 	public function getSettingsMain()
 	{
+		$certificateStatus = $this->hasCustomCert() ? '<span lang="en">Custom Certificate Loaded</span><br />Located at <span>' . $this->getCustomCert() . '</span>' : '<span lang="en">Custom Certificate not found - please upload below</span>';
 		return array(
 			'Settings Page' => array(
 				array(
@@ -1978,7 +1978,7 @@ class Organizr
 					'class' => 'ldapAuth ftpAuth switchAuth',
 					'label' => 'Host Address',
 					'value' => $this->config['authBackendHost'],
-					'placeholder' => 'http{s) | ftp(s) | ldap(s)://hostname:port'
+					'placeholder' => 'http(s) | ftp(s) | ldap(s)://hostname:port'
 				),
 				array(
 					'type' => 'input',
@@ -1988,15 +1988,6 @@ class Organizr
 					'value' => $this->config['authBaseDN'],
 					'placeholder' => 'cn=%s,dc=sub,dc=domain,dc=com'
 				),
-				array(
-					'type' => 'select',
-					'name' => 'ldapType',
-					'id' => 'ldapType',
-					'label' => 'LDAP Backend Type',
-					'class' => 'ldapAuth switchAuth',
-					'value' => $this->config['ldapType'],
-					'options' => $this->getLDAPOptions()
-				),
 				array(
 					'type' => 'input',
 					'name' => 'authBackendHostPrefix',
@@ -2027,9 +2018,18 @@ class Organizr
 					'type' => 'password',
 					'name' => 'ldapBindPassword',
 					'class' => 'ldapAuth switchAuth',
-					'label' => 'Password',
+					'label' => 'Bind Password',
 					'value' => $this->config['ldapBindPassword']
 				),
+				array(
+					'type' => 'select',
+					'name' => 'ldapType',
+					'id' => 'ldapType',
+					'label' => 'LDAP Backend Type',
+					'class' => 'ldapAuth switchAuth',
+					'value' => $this->config['ldapType'],
+					'options' => $this->getLDAPOptions()
+				),
 				array(
 					'type' => 'html',
 					'class' => 'ldapAuth switchAuth',
@@ -2432,7 +2432,54 @@ class Organizr
 					'value' => $this->config['otherPingRefresh'],
 					'options' => $this->timeOptions()
 				),
-			)
+			),
+			'Certificate' => array(
+				array(
+					'type' => 'html',
+					'label' => '',
+					'override' => 12,
+					'html' => '
+					<script>
+						let myDropzone = new Dropzone("#upload-custom-certificate", {
+							url: "api/v2/certificate/custom",
+							headers:{ "formKey": local("g","formKey") },
+							init: function() {
+								this.on("complete", function(file) {
+									if(file["status"] === "success"){
+										$(".custom-certificate-status").html("<span lang=\"en\">Custom Certificate Loaded</span>");
+									}else{
+										$(".custom-certificate-status").html("<span lang=\"en\">Error Saving file...</span>");
+									}
+								});
+							}
+						});
+					</script>
+					<div class="row">
+						<div class="col-lg-12">
+							<div class="panel panel-info">
+								<div class="panel-heading"><span lang="en">Notice</span></div>
+								<div class="panel-wrapper collapse in" aria-expanded="true">
+									<div class="panel-body">
+										<span lang="en">By default, Organizr uses certificates from https://curl.se/docs/caextract.html<br/>If you would like to use your own certificate, please upload it below.  You will then need to enable each homepage item to use it.</span>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+					<div class="row">
+						<div class="col-md-12">
+							<div class="white-box">
+								<h3 class="box-title m-b-0">Custom Certificate Status</h3>
+								<p class="text-muted m-b-30 custom-certificate-status">' . $certificateStatus . '</p>
+								<form action="#" class="dropzone dz-clickable" id="upload-custom-certificate">
+									<div class="dz-default dz-message"><span lang="en">Drop Certificate file here to upload</span></div>
+								</form>
+							</div>
+						</div>
+					</div>
+					'
+				)
+			),
 		);
 	}
 	
@@ -6085,6 +6132,33 @@ class Organizr
 		return ($this->checkValidCert($file)) ? $file : $file2;
 	}
 	
+	public function hasCustomCert()
+	{
+		return file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem');
+	}
+	
+	public function getCustomCert()
+	{
+		return ($this->hasCustomCert()) ? dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem' : false;
+	}
+	
+	public function uploadCert()
+	{
+		$filesCheck = array_filter($_FILES);
+		if (!empty($filesCheck) && $this->approvedFileExtension($_FILES['file']['name'], 'cert')) {
+			ini_set('upload_max_filesize', '10M');
+			ini_set('post_max_size', '10M');
+			$tempFile = $_FILES['file']['tmp_name'];
+			$targetPath = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR;
+			$targetFile = $targetPath . 'custom.pem';
+			$this->setAPIResponse(null, pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' has been uploaded', null);
+			return move_uploaded_file($tempFile, $targetFile);
+		} else {
+			$this->setAPIResponse('error', pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' is not approved to be uploaded', 403);
+			return false;
+		}
+	}
+	
 	public function plexJoinAPI($array)
 	{
 		$username = ($array['username']) ?? null;
@@ -6316,8 +6390,8 @@ class Organizr
 			function CBPFWTabs( el, options ) {
 				this.el = el;
 				this.options = extend( {}, this.options );
-		        extend( this.options, options );
-		        this._init();
+				extend( this.options, options );
+				this._init();
 			}
 		
 			CBPFWTabs.prototype.options = {

+ 44 - 1
api/config/default.php

@@ -38,14 +38,20 @@ return array(
 	'plexToken' => '',
 	'plexTabName' => '',
 	'plexAdmin' => '',
+	'plexDisableCertCheck' => false,
+	'plexUseCustomCertificate' => false,
 	'embyTabURL' => '',
 	'embyTabName' => '',
 	'embyURL' => '',
 	'embyToken' => '',
+	'embyDisableCertCheck' => false,
+	'embyUseCustomCertificate' => false,
 	'jellyfinTabName' => '',
 	'jellyfinURL' => '',
 	'jellyfinToken' => '',
 	'jellyfinSSOURL' => '',
+	'jellyfinUseCustomCertificate' => false,
+	'jellyfinDisableCertCheck' => false,
 	'plexID' => '',
 	'tautulliURL' => '',
 	'ombiURL' => '',
@@ -53,6 +59,8 @@ return array(
 	'ombiAlias' => false,
 	'ombiFallbackUser' => '',
 	'ombiFallbackPassword' => '',
+	'ombiDisableCertCheck' => false,
+	'ombiUseCustomCertificate' => false,
 	'overseerrURL' => '',
 	'overseerrToken' => '',
 	'overseerrFallbackUser' => '',
@@ -72,10 +80,14 @@ return array(
 	'sonarrToken' => '',
 	'sonarrSocksEnabled' => false,
 	'sonarrSocksAuth' => '999',
+	'sonarrDisableCertCheck' => false,
+	'sonarrUseCustomCertificate' => false,
 	'lidarrURL' => '',
 	'lidarrToken' => '',
 	'lidarrSocksEnabled' => false,
 	'lidarrSocksAuth' => '999',
+	'lidarrDisableCertCheck' => false,
+	'lidarrUseCustomCertificate' => false,
 	'radarrURL' => '',
 	'radarrUnmonitored' => false,
 	'radarrPhysicalRelease' => true,
@@ -84,19 +96,29 @@ return array(
 	'radarrToken' => '',
 	'radarrSocksEnabled' => false,
 	'radarrSocksAuth' => '999',
+	'radarrDisableCertCheck' => false,
+	'radarrUseCustomCertificate' => false,
 	'couchpotatoURL' => '',
 	'couchpotatoToken' => '',
+	'couchpotatoDisableCertCheck' => false,
+	'couchpotatoUseCustomCertificate' => false,
 	'sickrageURL' => '',
 	'sickrageToken' => '',
+	'sickrageDisableCertCheck' => false,
+	'sickrageUseCustomCertificate' => false,
 	'jdownloaderURL' => '',
 	'jdownloaderCombine' => false,
 	'jdownloaderRefresh' => '60000',
+	'jdownloaderUseCustomCertificate' => false,
+	'jdownloaderDisableCertCheck' => false,
 	'sabnzbdURL' => '',
 	'sabnzbdToken' => '',
 	'sabnzbdSocksEnabled' => false,
 	'sabnzbdSocksAuth' => '999',
 	'sabnzbdCombine' => false,
 	'sabnzbdRefresh' => '60000',
+	'sabnzbdDisableCertCheck' => false,
+	'sabnzbdUseCustomCertificate' => false,
 	'nzbgetURL' => '',
 	'nzbgetUsername' => '',
 	'nzbgetPassword' => '',
@@ -110,14 +132,17 @@ return array(
 	'transmissionHideSeeding' => false,
 	'transmissionHideCompleted' => false,
 	'transmissionCombine' => false,
-	'transmissionDisableCertCheck' => false,
 	'transmissionRefresh' => '60000',
+	'transmissionUseCustomCertificate' => false,
+	'transmissionDisableCertCheck' => true,
 	'delugeURL' => '',
 	'delugePassword' => '',
 	'delugeHideSeeding' => false,
 	'delugeHideCompleted' => false,
 	'delugeCombine' => false,
 	'delugeRefresh' => '60000',
+	'delugeDisableCertCheck' => false,
+	'delugeUseCustomCertificate' => false,
 	'qBittorrentURL' => '',
 	'qBittorrentUsername' => '',
 	'qBittorrentPassword' => '',
@@ -128,6 +153,7 @@ return array(
 	'qBittorrentCombine' => false,
 	'qBittorrentApiVersion' => '1',
 	'qBittorrentDisableCertCheck' => false,
+	'qBittorrentUseCustomCertificate' => false,
 	'qBittorrentRefresh' => '60000',
 	'qBittorrentSocksEnabled' => false,
 	'qBittorrentSocksAuth' => '999',
@@ -141,12 +167,15 @@ return array(
 	'rTorrentReverseSorting' => false,
 	'rTorrentCombine' => false,
 	'rTorrentDisableCertCheck' => false,
+	'rTorrentUseCustomCertificate' => false,
 	'rTorrentLimit' => '200',
 	'rTorrentRefresh' => '60000',
 	'homepageJackettEnabled' => false,
 	'homepageJackettAuth' => '1',
 	'jackettURL' => '',
 	'jackettToken' => '',
+	'jackettUseCustomCertificate' => false,
+	'jackettDisableCertCheck' => false,
 	'homepageJackettBackholeDownload' => false,
 	'homepageCalendarEnabled' => false,
 	'homepageCalendarAuth' => '4',
@@ -217,6 +246,8 @@ return array(
 	'homepageHealthChecksAuth' => '1',
 	'homepageHealthChecksShowDesc' => false,
 	'homepageHealthChecksShowTags' => false,
+	'healthChecksDisableCertCheck' => false,
+	'healthChecksUseCustomCertificate' => false,
 	'homepageOrdercustomhtml' => '1',
 	'homepageOrdercustomhtmlTwo' => '2',
 	'homepageOrdertransmission' => '3',
@@ -352,6 +383,8 @@ return array(
 	'unifiPassword' => '',
 	'unifiSiteName' => '',
 	'unifiCookie' => '',
+	'unifiUseCustomCertificate' => false,
+	'unifiDisableCertCheck' => true,
 	'homepageUnifiEnabled' => false,
 	'homepageUnifiAuth' => '1',
 	'homepageUnifiRefresh' => '600000',
@@ -382,6 +415,8 @@ return array(
 	'tautulliFriendlyName' => true,
 	'tautulliSocksEnabled' => false,
 	'tautulliSocksAuth' => '999',
+	'tautulliUseCustomCertificate' => false,
+	'tautulliDisableCertCheck' => false,
 	'homepagePiholeEnabled' => false,
 	'homepagePiholeAuth' => '1',
 	'homepagePiholeRefresh' => '10000',
@@ -395,15 +430,21 @@ return array(
 	'monitorrHeaderToggle' => true,
 	'monitorrHeader' => 'Monitorr',
 	'monitorrCompact' => false,
+	'monitorrDisableCertCheck' => false,
+	'monitorrUseCustomCertificate' => false,
 	'homepageSpeedtestEnabled' => false,
 	'homepageSpeedtestAuth' => '1',
 	'homepageSpeedtestRefresh' => '1800000',
 	'speedtestURL' => '',
 	'speedtestHeaderToggle' => true,
 	'speedtestHeader' => 'Speedtest',
+	'speedtestDisableCertCheck' => false,
+	'speedtestUseCustomCertificate' => false,
 	'homepageNetdataEnabled' => false,
 	'homepageNetdataRefresh' => '10000',
 	'homepageNetdataAuth' => '1',
+	'netdataDisableCertCheck' => false,
+	'netdataUseCustomCertificate' => false,
 	'netdataURL' => '',
 	'netdata1Title' => '',
 	'netdata1Chart' => '',
@@ -476,6 +517,8 @@ return array(
 	'octoprintToken' => '',
 	'octoprintHeaderToggle' => true,
 	'octoprintHeader' => 'Octoprint',
+	'octoprintDisableCertCheck' => false,
+	'octoprintUseCustomCertificate' => false,
 	'githubMenuLink' => true,
 	'organizrSupportMenuLink' => true,
 	'organizrDocsMenuLink' => true,

+ 9 - 9
api/functions/auth-functions.php

@@ -12,9 +12,9 @@ trait AuthFunctions
 			foreach ($ldapServers as $key => $value) {
 				// Calculate parts
 				$digest = parse_url(trim($value));
-				$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : 'ldap'));
-				$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
-				$port = (isset($digest['port']) ? $digest['port'] : (strtolower($scheme) == 'ldap' ? 389 : 636));
+				$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
+				$host = ($digest['host'] ?? ($digest['path'] ?? ''));
+				$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
 				// Reassign
 				$ldapHosts[] = $host;
 				if ($i == 0) {
@@ -86,9 +86,9 @@ trait AuthFunctions
 			foreach ($ldapServers as $key => $value) {
 				// Calculate parts
 				$digest = parse_url(trim($value));
-				$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : 'ldap'));
-				$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
-				$port = (isset($digest['port']) ? $digest['port'] : (strtolower($scheme) == 'ldap' ? 389 : 636));
+				$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
+				$host = ($digest['host'] ?? ($digest['path'] ?? ''));
+				$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
 				// Reassign
 				$ldapHosts[] = $host;
 				$ldapServersNew[$key] = $scheme . '://' . $host . ':' . $port; // May use this later
@@ -133,11 +133,11 @@ trait AuthFunctions
 					//return $user;
 					//return $user->getUserPrincipalName();
 					//return $user->getGroups(['cn']);
-					$this->setAPIResponse('success', 'LDAP connection successful', 200);
+					$this->setResponse(200, 'LDAP connection successful');
 					return true;
 				} else {
 					// Failed.
-					$this->setAPIResponse('error', 'Username/Password Failed to authenticate', 401);
+					$this->setResponse(401, 'Username/Password Failed to authenticate');
 					return false;
 				}
 			} catch (\Adldap\Auth\BindException $e) {
@@ -538,4 +538,4 @@ trait AuthFunctions
 		}
 	}
 	
-}
+}

+ 324 - 0
api/functions/option-functions.php

@@ -2,6 +2,330 @@
 
 trait OptionsFunction
 {
+	public function settingsOptionGroup($options = [])
+	{
+		$settings = [];
+		foreach ($options as $option) {
+			$optionType = $option[0] ? $option[0] : false;
+			$optionName = $option[1] ? $option[1] : null;
+			$optionExtras = $option[2] ? $option[2] : [];
+			$setting = $this->settingsOption($optionType, $optionName, $optionExtras);
+			array_push($settings, $setting);
+		}
+		return $settings;
+	}
+	
+	public function settingsOption($type, $name = null, $extras = null)
+	{
+		$type = strtolower(str_replace('-', '', $type));
+		$setting = [
+			'name' => $name,
+			'value' => $this->config[$name]
+		];
+		switch ($type) {
+			case 'enable':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Enable',
+				];
+				break;
+			case 'auth':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Minimum Authentication',
+					'options' => $this->groupOptions
+				];
+				break;
+			case 'refresh':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Refresh Seconds',
+					'options' => $this->timeOptions()
+				];
+				break;
+			case 'combine':
+			case 'combine-downloader':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Add to Combined Downloader',
+				];
+				break;
+			case 'test':
+				$settingMerge = [
+					'type' => 'button',
+					'label' => '',
+					'icon' => 'fa fa-flask',
+					'class' => 'pull-right',
+					'text' => 'Test Connection',
+					'attr' => 'onclick="testAPIConnection(\'' . $name . '\')"'
+				];
+				break;
+			case 'url':
+				$settingMerge = [
+					'type' => 'input',
+					'label' => 'URL',
+					'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
+					'placeholder' => 'http(s)://hostname:port'
+				];
+				break;
+			case 'multipleurl':
+				$settingMerge = [
+					'type' => 'select2',
+					'class' => 'select2-multiple',
+					'id' => $name . '-select',
+					'label' => 'Multiple URL\'s',
+					'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
+					'placeholder' => 'http(s)://hostname:port',
+					'options' => $this->makeOptionsFromValues($this->config[$name]),
+					'settings' => '{tags: true, selectOnClose: true, closeOnSelect: true}',
+				];
+				break;
+			case 'multiple':
+				$settingMerge = [
+					'type' => 'select2',
+					'class' => 'select2-multiple',
+					'id' => $name . '-select',
+					'label' => 'Multiple Values\'s',
+					'options' => $this->makeOptionsFromValues($this->config[$name]),
+					'settings' => '{tags: true, selectOnClose: true, closeOnSelect: true}',
+				];
+				break;
+			case 'username':
+				$settingMerge = [
+					'type' => 'input',
+					'label' => 'Username',
+				];
+				break;
+			case 'password':
+				$settingMerge = [
+					'type' => 'password',
+					'label' => 'Password',
+				];
+				break;
+			case 'passwordalt':
+				$settingMerge = [
+					'type' => 'password-alt',
+					'label' => 'Password',
+				];
+				break;
+			case 'apikey':
+			case 'token':
+				$settingMerge = [
+					'type' => 'password-alt',
+					'label' => 'API Key/Token',
+				];
+				break;
+			case 'multipleapikey':
+			case 'multipletoken':
+				$settingMerge = [
+					'type' => 'select2',
+					'class' => 'select2-multiple',
+					'id' => $name . '-select',
+					'label' => 'Multiple API Key/Token\'s',
+					'options' => $this->makeOptionsFromValues($this->config[$name]),
+					'settings' => '{tags: true, theme: "default password-alt", selectOnClose: true, closeOnSelect: true}',
+				];
+				break;
+			case 'socks':
+				$settingMerge = [
+					'type' => 'html',
+					'override' => 12,
+					'label' => '',
+					'html' => '
+						<div class="panel panel-default">
+							<div class="panel-wrapper collapse in">
+								<div class="panel-body">' . $this->socksHeadingHTML($name) . '</div>
+							</div>
+						</div>'
+				];
+				break;
+			case 'about':
+				$settingMerge = [
+					'type' => 'html',
+					'override' => 12,
+					'label' => '',
+					'html' => '
+						<div class="panel panel-default">
+							<div class="panel-wrapper collapse in">
+								<div class="panel-body">
+									<h3 lang="en">' . ucwords($name) . ' Homepage Item</h3>
+									<p lang="en">' . $extras["about"] . '</p>
+								</div>
+							</div>
+						</div>'
+				];
+				break;
+			case 'title':
+				$settingMerge = [
+					'type' => 'input',
+					'label' => 'Title',
+					'help' => 'Sets the title of this homepage module',
+				];
+				break;
+			case 'toggletitle':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Toggle Title',
+					'help' => 'Shows/hides the title of this homepage module'
+				];
+				break;
+			case 'disablecertcheck':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Disable Certificate Check',
+				];
+				break;
+			case 'usecustomcertificate':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Use Custom Certificate',
+				];
+				break;
+			case 'hideseeding':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Hide Seeding',
+				];
+			case 'hidecompleted':
+				$settingMerge = [
+					'type' => 'switch',
+					'label' => 'Hide Completed',
+				];
+				break;
+			case 'limit':
+				$settingMerge = [
+					'type' => 'number',
+					'label' => 'Item Limit',
+				];
+				break;
+			case 'mediasearchserver':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Media Search Server',
+					'options' => $this->mediaServerOptions()
+				];
+				break;
+			case 'imagecachequality':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Image Cache Quality',
+					'options' => [
+						[
+							'name' => 'Low',
+							'value' => '.5'
+						],
+						[
+							'name' => '1x',
+							'value' => '1'
+						],
+						[
+							'name' => '2x',
+							'value' => '2'
+						],
+						[
+							'name' => '3x',
+							'value' => '3'
+						]
+					]
+				];
+				break;
+			case 'blank':
+				$settingMerge = [
+					'type' => 'blank',
+					'label' => '',
+				];
+				break;
+			case 'plexlibraryexclude':
+				$settingMerge = [
+					'type' => 'select2',
+					'class' => 'select2-multiple',
+					'id' => $name . '-exclude-select',
+					'label' => 'Libraries to Exclude',
+					'options' => $extras['options']
+				];
+				break;
+			// HTML ITEMS
+			case 'precodeeditor':
+				$settingMerge = [
+					'type' => 'textbox',
+					'class' => 'hidden ' . $name . 'Textarea',
+					'label' => '',
+				];
+				break;
+			case 'codeeditor':
+				$settingMerge = [
+					'type' => 'html',
+					'override' => 12,
+					'label' => 'Custom Code',
+					'html' => '<button type="button" class="hidden save' . $name . 'Textarea btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="' . $name . 'Editor" style="height:300px">' . htmlentities($this->config[$name]) . '</div>'
+				];
+				break;
+			// CALENDAR ITEMS
+			case 'calendarstart':
+				$settingMerge = [
+					'type' => 'number',
+					'label' => '# of Days Before'
+				];
+				break;
+			case 'calendarend':
+				$settingMerge = [
+					'type' => 'number',
+					'label' => '# of Days After'
+				];
+				break;
+			case 'calendarstartingday':
+			case 'calendarstartday':
+			case 'calendarstart':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Start Day',
+					'options' => $this->daysOptions()
+				];
+				break;
+			case 'calendardefaultview':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Default View',
+					'options' => $this->calendarDefaultOptions()
+				];
+				break;
+			case 'calendartimeformat':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Time Format',
+					'options' => $this->timeFormatOptions()
+				];
+				break;
+			case 'calendarlocale':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Locale',
+					'options' => $this->calendarLocaleOptions()
+				];
+				break;
+			case 'calendarlimit':
+				$settingMerge = [
+					'type' => 'select',
+					'label' => 'Items Per Day',
+					'options' => $this->limitOptions()
+				];
+				break;
+			default:
+				$settingMerge = [
+					'type' => strtolower($type),
+					'label' => ''
+				];
+				break;
+		}
+		$setting = array_merge($settingMerge, $setting);
+		if ($extras) {
+			if (gettype($extras) == 'array') {
+				$setting = array_merge($setting, $extras);
+			}
+		}
+		return $setting;
+	}
+	
 	public function makeOptionsFromValues($values = null)
 	{
 		$formattedValues = [];

+ 36 - 21
api/functions/organizr-functions.php

@@ -177,20 +177,29 @@ trait OrganizrFunctions
 		return (substr($s, -1, 1) == '\\') ? $s : $s . '\\';
 	}
 	
-	public function approvedFileExtension($filename)
+	public function approvedFileExtension($filename, $type = 'image')
 	{
 		$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
-		switch ($ext) {
-			case 'gif':
-			case 'png':
-			case 'jpeg':
-			case 'jpg':
-			case 'svg':
-				return true;
-				break;
-			default:
-				return false;
+		if ($type == 'image') {
+			switch ($ext) {
+				case 'gif':
+				case 'png':
+				case 'jpeg':
+				case 'jpg':
+				case 'svg':
+					return true;
+				default:
+					return false;
+			}
+		} elseif ($type == 'cert') {
+			switch ($ext) {
+				case 'pem':
+					return true;
+				default:
+					return false;
+			}
 		}
+		
 	}
 	
 	public function getImages()
@@ -343,7 +352,7 @@ trait OrganizrFunctions
 				'value' => '2'
 			),
 			array(
-				'name' => 'First IPA',
+				'name' => 'Free IPA',
 				'value' => '3'
 			),
 		);
@@ -701,21 +710,27 @@ trait OrganizrFunctions
 		return strtr($link, $variables);
 	}
 	
-	public function requestOptions($url, $override = false, $timeout = null, $extras = null)
+	public function requestOptions($url, $timeout = null, $override = false, $customCertificate = false, $extras = null)
 	{
 		$options = [];
-		if ($extras) {
-			if (gettype($extras) == 'array') {
-				$options = array_merge($options, $extras);
-			}
-		}
 		if (is_numeric($timeout)) {
-			$timeout = $timeout / 1000;
+			if ($timeout >= 1000) {
+				$timeout = $timeout / 1000;
+			}
 			$options = array_merge($options, array('timeout' => $timeout));
 		}
+		if ($customCertificate) {
+			if ($this->hasCustomCert()) {
+				$options = array_merge($options, array('verify' => $this->getCustomCert(), 'verifyname' => false));
+			}
+		}
 		if ($this->localURL($url, $override)) {
-			$options = array_merge($options, array('verify' => false));
-			
+			$options = array_merge($options, array('verify' => false, 'verifyname' => false));
+		}
+		if ($extras) {
+			if (gettype($extras) == 'array') {
+				$options = array_merge($options, $extras);
+			}
 		}
 		return $options;
 	}

+ 5 - 5
api/functions/sso-functions.php

@@ -94,7 +94,7 @@ trait SSOFunctions
 				"Pw" => $password
 			);
 			$endpoint = '/Users/authenticatebyname';
-			$options = $this->requestOptions($url, false, 60000);
+			$options = $this->requestOptions($url, 60000);
 			$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
 			if ($response->success) {
 				$token = json_decode($response->body, true);
@@ -129,7 +129,7 @@ trait SSOFunctions
 				"plexToken" => $oAuthToken
 			);
 			$endpoint = ($oAuthToken) ? '/api/v1/Token/plextoken' : '/api/v1/Token';
-			$options = $this->requestOptions($url, false, 60000);
+			$options = $this->requestOptions($url, 60000);
 			$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
 			if ($response->success) {
 				$token = json_decode($response->body, true)['access_token'];
@@ -173,7 +173,7 @@ trait SSOFunctions
 						"token" => $plexToken,
 						"remember_me" => 1,
 					);
-					$options = $this->requestOptions($url, false, 60000);
+					$options = $this->requestOptions($url, 60000);
 					$response = Requests::post($url . '/auth/signin', $headers, $data, $options);
 					if ($response->success) {
 						$qualifiedURL = $this->qualifyURL($url, true);
@@ -208,7 +208,7 @@ trait SSOFunctions
 				"authToken" => $oAuthToken
 			);
 			$endpoint = '/api/v1/auth/plex';
-			$options = $this->requestOptions($url, false, 60000);
+			$options = $this->requestOptions($url, 60000);
 			$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
 			if ($response->success) {
 				$user = json_decode($response->body, true); // not really needed yet
@@ -252,7 +252,7 @@ trait SSOFunctions
 				'token' => $oAuthToken
 			);
 			$endpoint = ($oAuthToken) ? '/api/login/plex_login' : '/api/login';
-			$options = $this->requestOptions($url, false, 60000);
+			$options = $this->requestOptions($url, 60000);
 			$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
 			if ($response->success) {
 				$user = json_decode($response->body, true)['user'];

+ 19 - 84
api/homepage/calendar.php

@@ -14,91 +14,26 @@ trait CalendarHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageCalendarEnabled',
-						'label' => 'Enable iCal',
-						'value' => $this->config['homepageCalendarEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageCalendarAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageCalendarAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'input',
-						'name' => 'calendariCal',
-						'label' => 'iCal URL\'s',
-						'value' => $this->config['calendariCal'],
-						'placeholder' => 'separate by comma\'s'
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'number',
-						'name' => 'calendarStart',
-						'label' => '# of Days Before',
-						'value' => $this->config['calendarStart'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'number',
-						'name' => 'calendarEnd',
-						'label' => '# of Days After',
-						'value' => $this->config['calendarEnd'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					)
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageCalendarEnabled'),
+					$this->settingsOption('auth', 'homepageCalendarAuth'),
+					$this->settingsOption('multiple-url', 'calendariCal', ['label' => 'iCal URL\'s']),
+				],
+				'Misc Options' => [
+					$this->settingsOption('calendar-start', 'calendarStart'),
+					$this->settingsOption('calendar-end', 'calendarEnd'),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	

+ 26 - 80
api/homepage/couchpotato.php

@@ -15,86 +15,31 @@ trait CouchPotatoHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageCouchpotatoEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageCouchpotatoEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageCouchpotatoAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageCouchpotatoAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'couchpotatoURL',
-						'label' => 'URL',
-						'value' => $this->config['couchpotatoURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'couchpotatoToken',
-						'label' => 'Token',
-						'value' => $this->config['couchpotatoToken']
-					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					)
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageCouchpotatoEnabled'),
+					$this->settingsOption('auth', 'homepageCouchpotatoAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('multiple-url', 'couchpotatoURL'),
+					$this->settingsOption('multiple-token', 'couchpotatoToken'),
+					$this->settingsOption('disable-cert-check', 'couchpotatoDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'couchpotatoUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('calendar-start', 'calendarStart'),
+					$this->settingsOption('calendar-end', 'calendarEnd'),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -132,7 +77,8 @@ trait CouchPotatoHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['couchpotatoURL'], $this->config['couchpotatoToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\CouchPotato\CouchPotato($value['url'], $value['token']);
+				$options = $this->requestOptions($value['url'], 60, $this->config['couchpotatoDisableCertCheck'], $this->config['couchpotatoUseCustomCertificate']);
+				$downloader = new Kryptonit3\CouchPotato\CouchPotato($value['url'], $value['token'], null, null, $options);
 				$calendar = $this->formatCouchCalendar($downloader->getMediaList(array('status' => 'active,done')), $key);
 			} catch (Exception $e) {
 				$this->writeLog('error', 'Radarr Connect Function - Error: ' . $e->getMessage(), 'SYSTEM');

+ 52 - 101
api/homepage/deluge.php

@@ -14,103 +14,53 @@ trait DelugeHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'custom' => '
-				<div class="row">
-                    <div class="col-lg-12">
-                        <div class="panel panel-info">
-                            <div class="panel-heading">
-								<span lang="en">Notice</span>
-                            </div>
-                            <div class="panel-wrapper collapse in" aria-expanded="true">
-                                <div class="panel-body">
-									<ul class="list-icons">
-                                        <li><i class="fa fa-chevron-right text-danger"></i> <a href="https://github.com/idlesign/deluge-webapi/tree/master/dist" target="_blank">Download Plugin</a></li>
-                                        <li><i class="fa fa-chevron-right text-danger"></i> Open Deluge Web UI, go to "Preferences -> Plugins -> Install plugin" and choose egg file.</li>
-                                        <li><i class="fa fa-chevron-right text-danger"></i> Activate WebAPI plugin </li>
-                                    </ul>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-				</div>
-				',
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageDelugeEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageDelugeEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageDelugeAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageDelugeAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'delugeURL',
-						'label' => 'URL',
-						'value' => $this->config['delugeURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password',
-						'name' => 'delugePassword',
-						'label' => 'Password',
-						'help' => 'Note that using a blank password might not work correctly.',
-						'value' => $this->config['delugePassword']
+			'settings' => [
+				'FYI' => [
+					$this->settingsOption('html', null, ['override' => 12, 'html' => '
+						<div class="row">
+							<div class="col-lg-12">
+								<div class="panel panel-info">
+									<div class="panel-heading">
+										<span lang="en">Notice</span>
+									</div>
+									<div class="panel-wrapper collapse in" aria-expanded="true">
+										<div class="panel-body">
+											<ul class="list-icons">
+												<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://github.com/idlesign/deluge-webapi/tree/master/dist" target="_blank">Download Plugin</a></li>
+												<li><i class="fa fa-chevron-right text-danger"></i> Open Deluge Web UI, go to "Preferences -> Plugins -> Install plugin" and choose egg file.</li>
+												<li><i class="fa fa-chevron-right text-danger"></i> Activate WebAPI plugin </li>
+											</ul>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>']
 					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'delugeHideSeeding',
-						'label' => 'Hide Seeding',
-						'value' => $this->config['delugeHideSeeding']
-					), array(
-						'type' => 'switch',
-						'name' => 'delugeHideCompleted',
-						'label' => 'Hide Completed',
-						'value' => $this->config['delugeHideCompleted']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'delugeRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['delugeRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'delugeCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['delugeCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing. Note that using a blank password might not work correctly.'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'deluge\')"'
-					),
-				)
-			)
-		);
+				],
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageDelugeEnabled'),
+					$this->settingsOption('auth', 'homepageDelugeAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'delugeURL'),
+					$this->settingsOption('password', 'delugePassword', ['help' => 'Note that using a blank password might not work correctly.']),
+					$this->settingsOption('disable-cert-check', 'delugeDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'delugeUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('hide-seeding', 'delugeHideSeeding'),
+					$this->settingsOption('hide-completed', 'delugeHideCompleted'),
+					$this->settingsOption('refresh', 'delugeRefresh'),
+					$this->settingsOption('combine', 'delugeCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing. Note that using a blank password might not work correctly.']),
+					$this->settingsOption('test', 'deluge'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -121,7 +71,8 @@ trait DelugeHomepageItem
 			return false;
 		}
 		try {
-			$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']));
+			$options = $this->requestOptions($this->config['delugeURL'], $this->config['delugeRefresh'], $this->config['delugeDisableCertCheck'], $this->config['delugeUseCustomCertificate'], ['organizr_cert' => $this->getCert(), 'custom_cert' => $this->getCustomCert()]);
+			$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']), $options);
 			$torrents = $deluge->getTorrents(null, 'comment, download_payload_rate, eta, hash, is_finished, is_seed, message, name, paused, progress, queue, state, total_size, upload_payload_rate');
 			$this->setAPIResponse('success', 'API Connection succeeded', 200);
 			return true;
@@ -166,11 +117,11 @@ trait DelugeHomepageItem
 				<div id="' . __FUNCTION__ . '">
 					' . $loadingBox . '
 					<script>
-		                // homepageOrderdeluge
-		                ' . $builder . '
-		                homepageDownloader("deluge", "' . $this->config['delugeRefresh'] . '");
-		                // End homepageOrderdeluge
-	                </script>
+						// homepageOrderdeluge
+						' . $builder . '
+						homepageDownloader("deluge", "' . $this->config['delugeRefresh'] . '");
+						// End homepageOrderdeluge
+					</script>
 				</div>
 				';
 		}

+ 47 - 171
api/homepage/emby.php

@@ -14,166 +14,44 @@ trait EmbyHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageEmbyEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageEmbyEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageEmbyAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageEmbyAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'embyURL',
-						'label' => 'URL',
-						'value' => $this->config['embyURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'embyToken',
-						'label' => 'Token',
-						'value' => $this->config['embyToken']
-					)
-				),
-				'Active Streams' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageEmbyStreams',
-						'label' => 'Enable',
-						'value' => $this->config['homepageEmbyStreams']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageEmbyStreamsAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageEmbyStreamsAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageShowStreamNames',
-						'label' => 'User Information',
-						'value' => $this->config['homepageShowStreamNames']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageShowStreamNamesAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageShowStreamNamesAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageStreamRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageStreamRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Recent Items' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageEmbyRecent',
-						'label' => 'Enable',
-						'value' => $this->config['homepageEmbyRecent']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageEmbyRecentAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageEmbyRecentAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'number',
-						'name' => 'homepageRecentLimit',
-						'label' => 'Item Limit',
-						'value' => $this->config['homepageRecentLimit'],
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRecentRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageRecentRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'homepageEmbyLink',
-						'label' => 'Emby Homepage Link URL',
-						'value' => $this->config['homepageEmbyLink'],
-						'help' => 'Available variables: {id} {serverId}'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'embyTabName',
-						'label' => 'Emby Tab Name',
-						'value' => $this->config['embyTabName'],
-						'placeholder' => 'Only use if you have Emby in a reverse proxy'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'embyTabURL',
-						'label' => 'Emby Tab WAN URL',
-						'value' => $this->config['embyTabURL'],
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'cacheImageSize',
-						'label' => 'Image Cache Size',
-						'value' => $this->config['cacheImageSize'],
-						'options' => array(
-							array(
-								'name' => 'Low',
-								'value' => '.5'
-							),
-							array(
-								'name' => '1x',
-								'value' => '1'
-							),
-							array(
-								'name' => '2x',
-								'value' => '2'
-							),
-							array(
-								'name' => '3x',
-								'value' => '3'
-							)
-						)
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'emby\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageEmbyEnabled'),
+					$this->settingsOption('auth', 'homepageEmbyAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'embyURL'),
+					$this->settingsOption('token', 'embyToken'),
+					$this->settingsOption('disable-cert-check', 'embyDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'),
+				],
+				'Active Streams' => [
+					$this->settingsOption('enable', 'homepageEmbyStreams'),
+					$this->settingsOption('auth', 'homepageEmbyStreamsAuth'),
+					$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
+					$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
+					$this->settingsOption('refresh', 'homepageStreamRefresh'),
+				],
+				'Recent Items' => [
+					$this->settingsOption('enable', 'homepageEmbyRecent'),
+					$this->settingsOption('auth', 'homepageEmbyRecentAuth'),
+					$this->settingsOption('limit', 'homepageRecentLimit'),
+					$this->settingsOption('refresh', 'homepageRecentRefresh'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('input', 'homepageEmbyLink', ['label' => 'Emby Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
+					$this->settingsOption('input', 'embyTabName', ['label' => 'Emby Tab Name', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
+					$this->settingsOption('input', 'embyTabURL', ['label' => 'Emby Tab WAN URL', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
+					$this->settingsOption('image-cache-quality', 'cacheImageSize'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'emby'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -189,9 +67,9 @@ trait EmbyHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['embyURL']);
 		$url = $url . "/Users?api_key=" . $this->config['embyToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
 		try {
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$this->setAPIResponse('success', 'API Connection succeeded', 200);
 				return true;
@@ -297,9 +175,9 @@ trait EmbyHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['embyURL']);
 		$url = $url . '/Sessions?api_key=' . $this->config['embyToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
 		try {
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$emby = json_decode($response->body, true);
@@ -328,7 +206,7 @@ trait EmbyHomepageItem
 			return false;
 		}
 		$url = $this->qualifyURL($this->config['embyURL']);
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
 		$username = false;
 		$showPlayed = false;
 		$userId = 0;
@@ -340,7 +218,7 @@ trait EmbyHomepageItem
 			}
 			// Get A User
 			$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
-			$response = Requests::get($userIds, array(), $options);
+			$response = Requests::get($userIds, [], $options);
 			if ($response->success) {
 				$emby = json_decode($response->body, true);
 				foreach ($emby as $value) { // Scan for admin user
@@ -358,7 +236,7 @@ trait EmbyHomepageItem
 				$this->setAPIResponse('error', 'Emby Error Occurred', 500);
 				return false;
 			}
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$emby = json_decode($response->body, true);
@@ -392,19 +270,17 @@ trait EmbyHomepageItem
 			return false;
 		}
 		$url = $this->qualifyURL($this->config['embyURL']);
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, 60, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
 		$username = false;
 		$showPlayed = false;
 		$userId = 0;
 		try {
-			
-			
 			if (isset($this->user['username'])) {
 				$username = strtolower($this->user['username']);
 			}
 			// Get A User
 			$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
-			$response = Requests::get($userIds, array(), $options);
+			$response = Requests::get($userIds, [], $options);
 			if ($response->success) {
 				$emby = json_decode($response->body, true);
 				foreach ($emby as $value) { // Scan for admin user
@@ -422,7 +298,7 @@ trait EmbyHomepageItem
 				$this->setAPIResponse('error', 'Emby Error Occurred', 500);
 				return false;
 			}
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$emby = json_decode($response->body, true);

+ 21 - 65
api/homepage/healthchecks.php

@@ -14,71 +14,27 @@ trait HealthChecksHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageHealthChecksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageHealthChecksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageHealthChecksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageHealthChecksAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'healthChecksURL',
-						'label' => 'URL',
-						'value' => $this->config['healthChecksURL'],
-						'help' => 'URL for HealthChecks API',
-						'placeholder' => 'HealthChecks API URL'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'healthChecksToken',
-						'label' => 'Token',
-						'value' => $this->config['healthChecksToken']
-					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'healthChecksTags',
-						'label' => 'Tags',
-						'value' => $this->config['healthChecksTags'],
-						'help' => 'Pull only checks with this tag - Blank for all',
-						'placeholder' => 'Multiple tags using CSV - tag1,tag2'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageHealthChecksRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageHealthChecksRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageHealthChecksShowDesc',
-						'label' => 'Show Description',
-						'value' => $this->config['homepageHealthChecksShowDesc'],
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageHealthChecksShowTags',
-						'label' => 'Show Tags',
-						'value' => $this->config['homepageHealthChecksShowTags'],
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageHealthChecksEnabled'),
+					$this->settingsOption('auth', 'homepageHealthChecksAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'healthChecksURL'),
+					$this->settingsOption('multiple-token', 'healthChecksToken'),
+					$this->settingsOption('disable-cert-check', 'healthChecksDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'healthChecksUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('multiple', 'healthChecksTags', ['label' => 'Tags', 'help' => 'Pull only checks with this tag - Blank for all', 'placeholder' => 'Multiple tags using CSV - tag1,tag2']),
+					$this->settingsOption('refresh', 'homepageHealthChecksRefresh'),
+					$this->settingsOption('switch', 'homepageHealthChecksShowDesc', ['label' => 'Show Description']),
+					$this->settingsOption('switch', 'homepageHealthChecksShowTags', ['label' => 'Show Tags']),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -135,7 +91,7 @@ trait HealthChecksHomepageItem
 			$url = $this->qualifyURL($this->config['healthChecksURL']) . '/' . $tags;
 			try {
 				$headers = array('X-Api-Key' => $token);
-				$options = ($this->localURL($url)) ? array('verify' => false) : array('verify' => $this->getCert());
+				$options = $this->requestOptions($url, $this->config['homepageHealthChecksRefresh'], $this->config['healthChecksDisableCertCheck'], $this->config['healthChecksUseCustomCertificate']);
 				$response = Requests::get($url, $headers, $options);
 				if ($response->success) {
 					$healthResults = json_decode($response->body, true);

+ 24 - 72
api/homepage/html.php

@@ -14,41 +14,19 @@ trait HTMLHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageCustomHTMLoneEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageCustomHTMLoneEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageCustomHTMLoneAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageCustomHTMLoneAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Code' => array(
-					array(
-						'type' => 'textbox',
-						'name' => 'customHTMLone',
-						'class' => 'hidden customHTMLoneTextarea',
-						'label' => '',
-						'value' => $this->config['customHTMLone'],
-					),
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => 'Custom HTML/JavaScript',
-						'html' => '<button type="button" class="hidden savecustomHTMLoneTextarea btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customHTMLoneEditor" style="height:300px">' . htmlentities($this->config['customHTMLone']) . '</div>'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageCustomHTMLoneEnabled'),
+					$this->settingsOption('auth', 'homepageCustomHTMLoneAuth'),
+				],
+				'Code' => [
+					$this->settingsOption('pre-code-editor', 'customHTMLone'),
+					$this->settingsOption('code-editor', 'customHTMLone'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -64,45 +42,19 @@ trait HTMLHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
-			'name' => 'CustomHTML-2',
-			'enabled' => strpos('personal,business', $this->config['license']) !== false,
-			'image' => 'plugins/images/tabs/custom2.png',
-			'category' => 'Custom',
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageCustomHTMLtwoEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageCustomHTMLtwoEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageCustomHTMLtwoAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageCustomHTMLtwoAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Code' => array(
-					array(
-						'type' => 'textbox',
-						'name' => 'customHTMLtwo',
-						'class' => 'hidden customHTMLtwoTextarea',
-						'label' => '',
-						'value' => $this->config['customHTMLtwo'],
-					),
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => 'Custom HTML/JavaScript',
-						'html' => '<button type="button" class="hidden savecustomHTMLtwoTextarea btn btn-info btn-circle pull-right m-r-5 m-l-10"><i class="fa fa-save"></i> </button><div id="customHTMLtwoEditor" style="height:300px">' . htmlentities($this->config['customHTMLtwo']) . '</div>'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageCustomHTMLtwoEnabled'),
+					$this->settingsOption('auth', 'homepageCustomHTMLtwoAuth'),
+				],
+				'Code' => [
+					$this->settingsOption('pre-code-editor', 'customHTMLtwo'),
+					$this->settingsOption('code-editor', 'customHTMLtwo'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	

+ 21 - 48
api/homepage/jackett.php

@@ -14,51 +14,24 @@ trait JackettHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJackettEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageJackettEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageJackettAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageJackettAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'jackettURL',
-						'label' => 'URL',
-						'value' => $this->config['jackettURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'jackettToken',
-						'label' => 'Token',
-						'value' => $this->config['jackettToken']
-					)
-				),
-				'Options' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJackettBackholeDownload',
-						'label' => 'Prefer black hole download',
-						'help' => 'Prefer black hole download link instead of direct/magnet download',
-						'value' => $this->config['homepageJackettBackholeDownload']
-					)
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageJackettEnabled'),
+					$this->settingsOption('auth', 'homepageJackettAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'jackettURL'),
+					$this->settingsOption('token', 'jackettToken'),
+					$this->settingsOption('disable-cert-check', 'jackettDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'jackettUseCustomCertificate'),
+				],
+				'Options' => [
+					$this->settingsOption('switch', 'homepageJackettBackholeDownload', ['label' => 'Prefer black hole download', 'help' => 'Prefer black hole download link instead of direct/magnet download']),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -115,8 +88,8 @@ trait JackettHomepageItem
 		$apiURL = $this->qualifyURL($this->config['jackettURL']);
 		$endpoint = $apiURL . '/api/v2.0/indexers/all/results?apikey=' . $this->config['jackettToken'] . '&Query=' . urlencode($query);
 		try {
-			$headers = array();
-			$options = array('timeout' => 120);
+			$headers = [];
+			$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
 			$response = Requests::get($endpoint, $headers, $options);
 			if ($response->success) {
 				$apiData = json_decode($response->body, true);
@@ -146,8 +119,8 @@ trait JackettHomepageItem
 		$endpoint = $apiURL . $url;
 		error_log($endpoint);
 		try {
-			$headers = array();
-			$options = array('timeout' => 120);
+			$headers = [];
+			$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
 			$response = Requests::get($endpoint, $headers, $options);
 			if ($response->success) {
 				$apiData = json_decode($response->body, true);

+ 51 - 85
api/homepage/jdownloader.php

@@ -14,84 +14,50 @@ trait JDownloaderHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'custom' => '
-				<div class="row">
-                    <div class="col-lg-12">
-                        <div class="panel panel-info">
-                            <div class="panel-heading">
-								<span lang="en">Notice</span>
-                            </div>
-                            <div class="panel-wrapper collapse in" aria-expanded="true">
-                                <div class="panel-body">
-									<ul class="list-icons">
-                                        <li><i class="fa fa-chevron-right text-danger"></i> <a href="https://pypi.org/project/myjd-api/" target="_blank">Download [myjd-api] Module</a></li>
-                                        <li><i class="fa fa-chevron-right text-danger"></i> Add <b>/api/myjd</b> to the URL if you are using <a href="https://pypi.org/project/FeedCrawler/" target="_blank">FeedCrawler</a></li>
-                                    </ul>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-				</div>
-				',
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJdownloaderEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageJdownloaderEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageJdownloaderAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageJdownloaderAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'jdownloaderURL',
-						'label' => 'URL',
-						'value' => $this->config['jdownloaderURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'jdownloaderRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['jdownloaderRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'jdownloaderCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['jdownloaderCombine']
+			'settings' => [
+				'FYI' => [
+					$this->settingsOption('html', null, ['override' => 12, 'html' => '
+						<div class="row">
+							<div class="col-lg-12">
+								<div class="panel panel-info">
+									<div class="panel-heading">
+										<span lang="en">Notice</span>
+									</div>
+									<div class="panel-wrapper collapse in" aria-expanded="true">
+										<div class="panel-body">
+											<ul class="list-icons">
+												<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://pypi.org/project/myjd-api/" target="_blank">Download [myjd-api] Module</a></li>
+												<li><i class="fa fa-chevron-right text-danger"></i> Add <b>/api/myjd</b> to the URL if you are using <a href="https://pypi.org/project/FeedCrawler/" target="_blank">FeedCrawler</a></li>
+											</ul>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>']
 					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'jdownloader\')"'
-					),
-				)
-			)
-		);
+				],
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageJdownloaderEnabled'),
+					$this->settingsOption('auth', 'homepageJdownloaderAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'jdownloaderURL'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('disable-cert-check', 'jdownloaderDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'jdownloaderUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('refresh', 'jdownloaderRefresh'),
+					$this->settingsOption('combine', 'jdownloaderCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'jdownloader'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -103,8 +69,8 @@ trait JDownloaderHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['jdownloaderURL']);
 		try {
-			$options = $this->requestOptions($this->config['jdownloaderURL'], false, $this->config['jdownloaderRefresh']);
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$this->setAPIResponse('success', 'API Connection succeeded', 200);
 				return true;
@@ -152,11 +118,11 @@ trait JDownloaderHomepageItem
 				<div id="' . __FUNCTION__ . '">
 					' . $loadingBox . '
 					<script>
-		                // homepageOrderjdownloader
-		                ' . $builder . '
-		                homepageDownloader("jdownloader", "' . $this->config['jdownloaderRefresh'] . '");
-		                // End homepageOrderjdownloader
-	                </script>
+						// homepageOrderjdownloader
+						' . $builder . '
+						homepageDownloader("jdownloader", "' . $this->config['jdownloaderRefresh'] . '");
+						// End homepageOrderjdownloader
+					</script>
 				</div>
 				';
 		}
@@ -169,8 +135,8 @@ trait JDownloaderHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['jdownloaderURL']);
 		try {
-			$options = $this->requestOptions($this->config['jdownloaderURL'], false, $this->config['jdownloaderRefresh']);
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$temp = json_decode($response->body, true);
 				$packages = $temp['packages'];

+ 46 - 162
api/homepage/jellyfin.php

@@ -15,159 +15,43 @@ trait JellyfinHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJellyfinEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageJellyfinEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageJellyfinAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageJellyfinAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'jellyfinURL',
-						'label' => 'URL',
-						'value' => $this->config['jellyfinURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'jellyfinToken',
-						'label' => 'Token',
-						'value' => $this->config['jellyfinToken']
-					)
-				),
-				'Active Streams' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJellyfinStreams',
-						'label' => 'Enable',
-						'value' => $this->config['homepageJellyfinStreams']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageJellyStreamsAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageJellyStreamsAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageShowStreamNames',
-						'label' => 'User Information',
-						'value' => $this->config['homepageShowStreamNames']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageShowStreamNamesAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageShowStreamNamesAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageStreamRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageStreamRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Recent Items' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageJellyfinRecent',
-						'label' => 'Enable',
-						'value' => $this->config['homepageJellyfinRecent']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageJellyfinRecentAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageJellyfinRecentAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'number',
-						'name' => 'homepageRecentLimit',
-						'label' => 'Item Limit',
-						'value' => $this->config['homepageRecentLimit'],
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRecentRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageRecentRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'homepageJellyfinLink',
-						'label' => 'Jellyfin Homepage Link URL',
-						'value' => $this->config['homepageJellyfinLink'],
-						'help' => 'Available variables: {id} {serverId}'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'jellyfinTabName',
-						'label' => 'Jellyfin Tab Name',
-						'value' => $this->config['jellyfinTabName'],
-						'placeholder' => 'Only use if you have Jellyfin in a reverse proxy'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'cacheImageSize',
-						'label' => 'Image Cache Size',
-						'value' => $this->config['cacheImageSize'],
-						'options' => array(
-							array(
-								'name' => 'Low',
-								'value' => '.5'
-							),
-							array(
-								'name' => '1x',
-								'value' => '1'
-							),
-							array(
-								'name' => '2x',
-								'value' => '2'
-							),
-							array(
-								'name' => '3x',
-								'value' => '3'
-							)
-						)
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'jellyfin\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageJellyfinEnabled'),
+					$this->settingsOption('auth', 'homepageJellyfinAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'jellyfinURL'),
+					$this->settingsOption('token', 'jellyfinToken'),
+					$this->settingsOption('disable-cert-check', 'jellyfinDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'jellyfinUseCustomCertificate'),
+				],
+				'Active Streams' => [
+					$this->settingsOption('enable', 'homepageJellyfinStreams'),
+					$this->settingsOption('auth', 'homepageJellyStreamsAuth'),
+					$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
+					$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
+					$this->settingsOption('refresh', 'homepageStreamRefresh'),
+				],
+				'Recent Items' => [
+					$this->settingsOption('enable', 'homepageJellyfinRecent'),
+					$this->settingsOption('auth', 'homepageJellyfinRecentAuth'),
+					$this->settingsOption('limit', 'homepageRecentLimit'),
+					$this->settingsOption('refresh', 'homepageRecentRefresh'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('input', 'homepageJellyfinLink', ['label' => 'Jellyfin Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
+					$this->settingsOption('input', 'jellyfinTabName', ['label' => 'Jellyfin Tab Name', 'placeholder' => 'Only use if you have Jellyfin in a reverse proxy']),
+					$this->settingsOption('image-cache-quality', 'cacheImageSize'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'jellyfin'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -183,9 +67,9 @@ trait JellyfinHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['jellyfinURL']);
 		$url = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, null, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
 		try {
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body);
 				if (is_array($json) || is_object($json)) {
@@ -297,9 +181,9 @@ trait JellyfinHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['jellyfinURL']);
 		$url = $url . '/Sessions?api_key=' . $this->config['jellyfinToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
 		try {
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$jellyfin = json_decode($response->body, true);
@@ -328,7 +212,7 @@ trait JellyfinHomepageItem
 			return false;
 		}
 		$url = $this->qualifyURL($this->config['jellyfinURL']);
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
 		$username = false;
 		$showPlayed = false;
 		$userId = 0;
@@ -338,7 +222,7 @@ trait JellyfinHomepageItem
 			}
 			// Get A User
 			$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
-			$response = Requests::get($userIds, array(), $options);
+			$response = Requests::get($userIds, [], $options);
 			if ($response->success) {
 				$jellyfin = json_decode($response->body, true);
 				foreach ($jellyfin as $value) { // Scan for admin user
@@ -356,7 +240,7 @@ trait JellyfinHomepageItem
 				$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
 				return false;
 			}
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$jellyfin = json_decode($response->body, true);
@@ -390,7 +274,7 @@ trait JellyfinHomepageItem
 			return false;
 		}
 		$url = $this->qualifyURL($this->config['jellyfinURL']);
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
+		$options = $this->requestOptions($url, 60, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
 		$username = false;
 		$showPlayed = false;
 		$userId = 0;
@@ -400,7 +284,7 @@ trait JellyfinHomepageItem
 			}
 			// Get A User
 			$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
-			$response = Requests::get($userIds, array(), $options);
+			$response = Requests::get($userIds, [], $options);
 			if ($response->success) {
 				$jellyfin = json_decode($response->body, true);
 				foreach ($jellyfin as $value) { // Scan for admin user
@@ -418,7 +302,7 @@ trait JellyfinHomepageItem
 				$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
 				return false;
 			}
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$items = array();
 				$jellyfin = json_decode($response->body, true);

+ 40 - 136
api/homepage/lidarr.php

@@ -14,140 +14,41 @@ trait LidarrHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageLidarrEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageLidarrEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageLidarrAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageLidarrAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'lidarrURL',
-						'label' => 'URL',
-						'value' => $this->config['lidarrURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'lidarrToken',
-						'label' => 'Token',
-						'value' => $this->config['lidarrToken']
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('lidarr') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'lidarrSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['lidarrSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'lidarrSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['lidarrSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'number',
-						'name' => 'calendarStart',
-						'label' => '# of Days Before',
-						'value' => $this->config['calendarStart'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'number',
-						'name' => 'calendarEnd',
-						'label' => '# of Days After',
-						'value' => $this->config['calendarEnd'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'lidarr\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageLidarrEnabled'),
+					$this->settingsOption('auth', 'homepageLidarrAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('multiple-url', 'lidarrURL'),
+					$this->settingsOption('multiple-token', 'lidarrToken'),
+					$this->settingsOption('disable-cert-check', 'lidarrDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'lidarrUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'lidarr'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'lidarrSocksEnabled'),
+					$this->settingsOption('auth', 'lidarrSocksAuth'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('calendar-start', 'calendarStart'),
+					$this->settingsOption('calendar-end', 'calendarEnd'),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'lidarr'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -166,7 +67,8 @@ trait LidarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null . null, $options);
 				$results = $downloader->getRootFolder();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -246,7 +148,8 @@ trait LidarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
 				$results = $downloader->getQueue();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -279,7 +182,8 @@ trait LidarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
 				$results = $downloader->getCalendar($startDate, $endDate);
 				$result = json_decode($results, true);
 				if (is_array($result) || is_object($result)) {

+ 8 - 19
api/homepage/misc.php

@@ -14,26 +14,15 @@ trait MiscHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'YouTube' => array(
-					array(
-						'type' => 'input',
-						'name' => 'youtubeAPI',
-						'label' => 'Youtube API Key',
-						'value' => $this->config['youtubeAPI'],
-						'help' => 'Please make sure to input this API key as the organizr one gets limited'
-					),
-					array(
-						'type' => 'html',
-						'override' => 6,
-						'label' => 'Instructions',
-						'html' => '<a href="https://www.slickremix.com/docs/get-api-key-for-youtube/" target="_blank">Click here for instructions</a>'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'YouTube' => [
+					$this->settingsOption('token', 'youtubeAPI', ['label' => 'Youtube API Key', 'help' => 'Please make sure to input this API key as the organizr one gets limited']),
+					$this->settingsOption('html', null, ['override' => 6, 'label' => 'Instructions', 'html' => '<a href="https://www.slickremix.com/docs/get-api-key-for-youtube/" target="_blank">Click here for instructions</a>']),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	

+ 21 - 61
api/homepage/monitorr.php

@@ -14,66 +14,27 @@ trait MonitorrHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageMonitorrEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageMonitorrEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageMonitorrAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageMonitorrAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'monitorrURL',
-						'label' => 'URL',
-						'value' => $this->config['monitorrURL'],
-						'help' => 'URL for Monitorr. Please use the revers proxy URL i.e. https://domain.com/monitorr/.',
-						'placeholder' => 'http://domain.com/monitorr/'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageMonitorrRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageMonitorrRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'monitorrHeader',
-						'label' => 'Title',
-						'value' => $this->config['monitorrHeader'],
-						'help' => 'Sets the title of this homepage module',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'monitorrHeaderToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['monitorrHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'monitorrCompact',
-						'label' => 'Compact view',
-						'value' => $this->config['monitorrCompact'],
-						'help' => 'Toggles the compact view of this homepage module'
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageMonitorrEnabled'),
+					$this->settingsOption('auth', 'homepageMonitorrAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'monitorrURL', ['help' => 'URL for Monitorr. Please use the reverse proxy URL i.e. https://domain.com/monitorr/.', 'placeholder' => 'http://domain.com/monitorr/']),
+					$this->settingsOption('blank'),
+					$this->settingsOption('disable-cert-check', 'monitorrDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'monitorrUseCustomCertificate'),
+				],
+				'Options' => [
+					$this->settingsOption('refresh', 'homepageMonitorrRefresh'),
+					$this->settingsOption('switch', 'monitorrCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
+					$this->settingsOption('title', 'monitorrHeader'),
+					$this->settingsOption('toggle-title', 'monitorrHeaderToggle'),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -126,7 +87,7 @@ trait MonitorrHomepageItem
 		$url = $this->qualifyURL($this->config['monitorrURL']);
 		$dataUrl = $url . '/assets/php/loop.php';
 		try {
-			$options = $this->requestOptions($this->config['monitorrURL'], false, $this->config['homepageMonitorrRefresh']);
+			$options = $this->requestOptions($url, $this->config['homepageMonitorrRefresh'], $this->config['monitorrDisableCertCheck'], $this->config['monitorrUseCustomCertificate']);
 			$response = Requests::get($dataUrl, ['Token' => $this->config['organizrAPI']], $options);
 			if ($response->success) {
 				$html = html_entity_decode($response->body);
@@ -170,7 +131,6 @@ trait MonitorrHomepageItem
 					$ext = $ext[key(array_slice($ext, -1, 1, true))];
 					$imageUrl = $url . '/assets' . $image;
 					$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
-					$options = $this->requestOptions($this->config['monitorrURL'], false, $this->config['homepageMonitorrRefresh']);
 					$img = Requests::get($imageUrl, ['Token' => $this->config['organizrAPI']], $options);
 					if ($img->success) {
 						$base64 = 'data:image/' . $ext . ';base64,' . base64_encode($img->body);

+ 98 - 188
api/homepage/netdata.php

@@ -97,196 +97,99 @@ trait NetDataHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageNetdataEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageNetdataEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageNetdataAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageNetdataAuth'],
-						'options' => $this->groupSelect()
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'netdataURL',
-						'label' => 'URL',
-						'value' => $this->config['netdataURL'],
-						'help' => 'Please enter the local IP:PORT of your netdata instance'
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageNetdataEnabled'),
+					$this->settingsOption('auth', 'homepageNetdataAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'netdataURL'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('disable-cert-check', 'netdataDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'netdataUseCustomCertificate'),
+				],
+			]
+		];
 		for ($i = 1; $i <= 7; $i++) {
 			$homepageSettings['settings']['Chart ' . $i] = array(
-				array(
-					'type' => 'switch',
-					'name' => 'netdata' . $i . 'Enabled',
-					'label' => 'Enable',
-					'value' => $this->config['netdata' . $i . 'Enabled']
-				),
-				array(
-					'type' => 'blank',
-					'label' => ''
-				),
-				array(
-					'type' => 'input',
-					'name' => 'netdata' . $i . 'Title',
-					'label' => 'Title',
-					'value' => $this->config['netdata' . $i . 'Title'],
-					'help' => 'Title for the netdata graph'
-				),
-				array(
-					'type' => 'select',
-					'name' => 'netdata' . $i . 'Data',
-					'label' => 'Data',
-					'value' => $this->config['netdata' . $i . 'Data'],
-					'options' => $this->netdataOptions(),
-				),
-				array(
-					'type' => 'select',
-					'name' => 'netdata' . $i . 'Chart',
-					'label' => 'Chart',
-					'value' => $this->config['netdata' . $i . 'Chart'],
-					'options' => $this->netdataChartOptions(),
-				),
-				array(
-					'type' => 'select',
-					'name' => 'netdata' . $i . 'Colour',
-					'label' => 'Colour',
-					'value' => $this->config['netdata' . $i . 'Colour'],
-					'options' => $this->netdataColourOptions(),
-				),
-				array(
-					'type' => 'select',
-					'name' => 'netdata' . $i . 'Size',
-					'label' => 'Size',
-					'value' => $this->config['netdata' . $i . 'Size'],
-					'options' => $this->netdataSizeOptions(),
-				),
-				array(
-					'type' => 'blank',
-					'label' => ''
-				),
-				array(
-					'type' => 'switch',
-					'name' => 'netdata' . $i . 'lg',
-					'label' => 'Show on large screens',
-					'value' => $this->config['netdata' . $i . 'lg']
-				),
-				array(
-					'type' => 'switch',
-					'name' => 'netdata' . $i . 'md',
-					'label' => 'Show on medium screens',
-					'value' => $this->config['netdata' . $i . 'md']
-				),
-				array(
-					'type' => 'switch',
-					'name' => 'netdata' . $i . 'sm',
-					'label' => 'Show on small screens',
-					'value' => $this->config['netdata' . $i . 'sm']
-				),
+				$this->settingsOption('enable', 'netdata' . $i . 'Enabled'),
+				$this->settingsOption('blank'),
+				$this->settingsOption('input', 'netdata' . $i . 'Title', ['label' => 'Title', 'help' => 'Title for the netdata graph']),
+				$this->settingsOption('select', 'netdata' . $i . 'Data', ['label' => 'Data', 'options' => $this->netdataOptions()]),
+				$this->settingsOption('select', 'netdata' . $i . 'Chart', ['label' => 'Chart', 'options' => $this->netdataChartOptions()]),
+				$this->settingsOption('select', 'netdata' . $i . 'Colour', ['label' => 'Colour', 'options' => $this->netdataColourOptions()]),
+				$this->settingsOption('select', 'netdata' . $i . 'Size', ['label' => 'Size', 'options' => $this->netdataSizeOptions()]),
+				$this->settingsOption('blank'),
+				$this->settingsOption('switch', 'netdata' . $i . 'lg', ['label' => 'Show on large screens']),
+				$this->settingsOption('switch', 'netdata' . $i . 'md', ['label' => 'Show on medium screens']),
+				$this->settingsOption('switch', 'netdata' . $i . 'sm', ['label' => 'Show on small screens']),
 			);
 		}
 		$homepageSettings['settings']['Custom data'] = array(
-			array(
-				'type' => 'html',
-				'label' => '',
-				'override' => 12,
-				'html' => '
+			$this->settingsOption('html', null, ['label' => '', 'override' => 12, 'html' => '
 			<div>
-			    <p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select "Custom" in the data field for the chart.</p>
-			    <p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1 custom data source to RAM percentage:</p>
-			    <pre>{
-			    "1": {
-			        "url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
-			        "value": "result,0",
-			        "units": "%",
-			        "max": 100
-			    }
-			}</pre>
-			    <p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
-			    <table class="table table-striped">
-			        <thead>
-			            <tr>
-			                <th>Parameter</th>
-			                <th>Description</th>
-			                <th>Required</th>
-			            </tr>
-			        </thead>
-			        <tbody>
-			            <tr>
-			                <td>url</td>
-			                <td>Specifies the netdata API endpoint</td>
-			                <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
-			            </tr>
-			            <tr>
-			                <td>value</td>
-			                <td>Specifies the selector used to get the data form the netdata response</td>
-			                <td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
-			            </tr>
-			            <tr>
-			                <td>units</td>
-			                <td>Specifies the units shown in the graph/chart. Defaults to %</td>
-			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-			            </tr>
-			            <tr>
-			                <td>max</td>
-			                <td>Specifies the maximum possible value for the data. Defaults to 100</td>
-			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-			            </tr>
-			            <tr>
-			                <td>mutator</td>
-			                <td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be "/1000". These operations can be chained together by putting them in a comma-seprated format.</td>
-			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-			            </tr>
-			            <tr>
-			                <td>netdata</td>
-			                <td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
-			                <td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
-			            </tr>
-			        </tbody>
-			    </table>
-			</div>'
-			),
-			array(
-				'type' => 'html',
-				'name' => 'netdataCustomTextAce',
-				'class' => 'jsonTextarea hidden',
-				'label' => 'Custom definitions',
-				'override' => 12,
-				'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($this->config['netdataCustom']) . '</div>',
-			),
-			array(
-				'type' => 'textbox',
-				'name' => 'netdataCustom',
-				'class' => 'jsonTextarea hidden',
-				'id' => 'netdataCustomText',
-				'label' => '',
-				'value' => $this->config['netdataCustom'],
-			)
+				<p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select "Custom" in the data field for the chart.</p>
+				<p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1 custom data source to RAM percentage:</p>
+				<pre>
+{
+	"1": {
+		"url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
+		"value": "result,0",
+		"units": "%",
+		"max": 100
+	}
+}
+				</pre>
+				<p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
+				<table class="table table-striped">
+					<thead>
+						<tr>
+							<th>Parameter</th>
+							<th>Description</th>
+							<th>Required</th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr>
+							<td>url</td>
+							<td>Specifies the netdata API endpoint</td>
+							<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
+						</tr>
+						<tr>
+							<td>value</td>
+							<td>Specifies the selector used to get the data form the netdata response</td>
+							<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
+						</tr>
+						<tr>
+							<td>units</td>
+							<td>Specifies the units shown in the graph/chart. Defaults to %</td>
+							<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+						</tr>
+						<tr>
+							<td>max</td>
+							<td>Specifies the maximum possible value for the data. Defaults to 100</td>
+							<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+						</tr>
+						<tr>
+							<td>mutator</td>
+							<td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be "/1000". These operations can be chained together by putting them in a comma-seprated format.</td>
+							<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+						</tr>
+						<tr>
+							<td>netdata</td>
+							<td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
+							<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
+						</tr>
+					</tbody>
+				</table>
+			</div>']),
+			$this->settingsOption('html', 'netdataCustomTextAce', ['class' => 'jsonTextarea hidden', 'label' => 'Custom definitions', 'override' => 12, 'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($this->config['netdataCustom']) . '</div>']),
+			$this->settingsOption('textbox', 'netdataCustom', ['class' => 'jsonTextarea hidden', 'id' => 'netdataCustomText', 'label' => '']),
 		);
 		$homepageSettings['settings']['Options'] = array(
-			array(
-				'type' => 'select',
-				'name' => 'homepageNetdataRefresh',
-				'label' => 'Refresh Seconds',
-				'value' => $this->config['homepageNetdataRefresh'],
-				'options' => $this->timeOptions()
-			),
+			$this->settingsOption('refresh', 'homepageNetdataRefresh'),
 		);
 		return array_merge($homepageInformation, $homepageSettings);
 	}
@@ -337,7 +240,8 @@ trait NetDataHomepageItem
 		// Get Data
 		$dataUrl = $url . '/api/v1/data?chart=system.io&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json['latest_values'][0] / 1000;
@@ -357,7 +261,8 @@ trait NetDataHomepageItem
 		// Get Data
 		$dataUrl = $url . '/api/v1/data?chart=disk_space._&format=json&points=509&group=average&gtime=0&options=ms|jsonwrap|nonzero&after=-540&dimension=' . $dimension;
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json['result']['data'][0][1];
@@ -377,7 +282,8 @@ trait NetDataHomepageItem
 		// Get Data
 		$dataUrl = $url . '/api/v1/data?chart=system.net&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json['latest_values'][0] / 1000;
@@ -396,7 +302,8 @@ trait NetDataHomepageItem
 		$data = [];
 		$dataUrl = $url . '/api/v1/data?chart=system.cpu&format=array';
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json[0];
@@ -415,7 +322,8 @@ trait NetDataHomepageItem
 		$data = [];
 		$dataUrl = $url . '/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired';
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json['result'][0];
@@ -434,7 +342,8 @@ trait NetDataHomepageItem
 		$data = [];
 		$dataUrl = $url . '/api/v1/data?chart=system.swap&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used';
 		try {
-			$response = Requests::get($dataUrl);
+			$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$data['value'] = $json['result'][0];
@@ -481,7 +390,8 @@ trait NetDataHomepageItem
 				}
 				$dataUrl = $url . '/' . $custom['url'];
 				try {
-					$response = Requests::get($dataUrl);
+					$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
+					$response = Requests::get($dataUrl, [], $options);
 					if ($response->success) {
 						$json = json_decode($response->body, true);
 						if (!isset($custom['max']) || $custom['max'] == '') {

+ 32 - 98
api/homepage/nzbget.php

@@ -14,103 +14,37 @@ trait NZBGetHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageNzbgetEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageNzbgetEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageNzbgetAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageNzbgetAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'nzbgetURL',
-						'label' => 'URL',
-						'value' => $this->config['nzbgetURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'nzbgetUsername',
-						'label' => 'Username',
-						'value' => $this->config['nzbgetUsername']
-					),
-					array(
-						'type' => 'password',
-						'name' => 'nzbgetPassword',
-						'label' => 'Password',
-						'value' => $this->config['nzbgetPassword']
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('nzbget') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'nzbgetSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['nzbgetSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'nzbgetSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['nzbgetSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'nzbgetRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['nzbgetRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'nzbgetCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['nzbgetCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'nzbget\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageNzbgetEnabled'),
+					$this->settingsOption('auth', 'homepageNzbgetAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'nzbgetURL'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('username', 'nzbgetUsername'),
+					$this->settingsOption('password', 'nzbgetPassword'),
+					$this->settingsOption('disable-cert-check', 'nzbgetDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'nzbgetUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'nzbget'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'nzbgetSocksEnabled'),
+					$this->settingsOption('auth', 'nzbgetSocksAuth'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('refresh', 'nzbgetRefresh'),
+					$this->settingsOption('combine', 'nzbgetCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'nzbget'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -122,7 +56,7 @@ trait NZBGetHomepageItem
 		}
 		try {
 			$url = $this->qualifyURL($this->config['nzbgetURL']);
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, null, $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
 			$urlGroups = $url . '/jsonrpc/listgroups';
 			if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {
 				$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['nzbgetUsername'], $this->decrypt($this->config['nzbgetPassword']))));
@@ -193,7 +127,7 @@ trait NZBGetHomepageItem
 		}
 		try {
 			$url = $this->qualifyURL($this->config['nzbgetURL']);
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, $this->config['nzbgetRefresh'], $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
 			$urlGroups = $url . '/jsonrpc/listgroups';
 			$urlHistory = $url . '/jsonrpc/history';
 			if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {

+ 20 - 52
api/homepage/octoprint.php

@@ -14,58 +14,25 @@ trait OctoPrintHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageOctoprintEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageOctoprintEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageOctoprintAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageOctoprintAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'octoprintURL',
-						'label' => 'URL',
-						'value' => $this->config['octoprintURL'],
-						'help' => 'Enter the IP:PORT of your Octoprint instance e.g. http://octopi.local'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'octoprintToken',
-						'label' => 'API Key',
-						'value' => $this->config['octoprintToken'],
-						'help' => 'Enter your Octoprint API key, found in Octoprint settings page.'
-					),
-				),
-				'Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'octoprintHeader',
-						'label' => 'Title',
-						'value' => $this->config['octoprintHeader'],
-						'help' => 'Sets the title of this homepage module',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'octoprintToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['octoprintHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageOctoprintEnabled'),
+					$this->settingsOption('auth', 'homepageOctoprintAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'octoprintURL'),
+					$this->settingsOption('token', 'octoprintToken'),
+					$this->settingsOption('disable-cert-check', 'octoprintDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'octoprintUseCustomCertificate'),
+				],
+				'Options' => [
+					$this->settingsOption('title', 'octoprintHeader'),
+					$this->settingsOption('toggle-title', 'octoprintHeaderToggle'),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -123,7 +90,8 @@ trait OctoPrintHomepageItem
 			$dataUrl = $url . '/api/' . $endpoint;
 			try {
 				$headers = array('X-API-KEY' => $this->config['octoprintToken']);
-				$response = Requests::get($dataUrl, $headers);
+				$options = $this->requestOptions($url, $this->config['homepageOctoprintRefresh'], $this->config['octoprintDisableCertCheck'], $this->config['octoprintUseCustomCertificate']);
+				$response = Requests::get($dataUrl, $headers, $options);
 				if ($response->success) {
 					$json = json_decode($response->body, true);
 					$api['data'][$endpoint] = $json;

+ 40 - 145
api/homepage/ombi.php

@@ -15,148 +15,42 @@ trait OmbiHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageOmbiEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageOmbiEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageOmbiAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageOmbiAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'ombiURL',
-						'label' => 'URL',
-						'value' => $this->config['ombiURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'ombiToken',
-						'label' => 'Token',
-						'value' => $this->config['ombiToken']
-					),
-					array(
-						'type' => 'input',
-						'name' => 'ombiFallbackUser',
-						'label' => 'Ombi Fallback User',
-						'value' => $this->config['ombiFallbackUser'],
-						'help' => 'Organizr will request an Ombi User Token based off of this user credentials'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'ombiFallbackPassword',
-						'label' => 'Ombi Fallback Password',
-						'value' => $this->config['ombiFallbackPassword']
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'homepageOmbiRequestAuth',
-						'label' => 'Minimum Group to Request',
-						'value' => $this->config['homepageOmbiRequestAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select',
-						'name' => 'ombiTvDefault',
-						'label' => 'TV Show Default Request',
-						'value' => $this->config['ombiTvDefault'],
-						'options' => $this->ombiTvOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiLimitUser',
-						'label' => 'Limit to User',
-						'value' => $this->config['ombiLimitUser']
-					),
-					array(
-						'type' => 'number',
-						'name' => 'ombiLimit',
-						'label' => 'Item Limit',
-						'value' => $this->config['ombiLimit'],
-					),
-					array(
-						'type' => 'select',
-						'name' => 'ombiRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['ombiRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiAlias',
-						'label' => 'Use Ombi Alias Names',
-						'value' => $this->config['ombiAlias'],
-						'help' => 'Use Ombi Alias Names instead of Usernames - If Alias is blank, Alias will fallback to Username'
-					)
-				),
-				'Default Filter' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'ombiDefaultFilterAvailable',
-						'label' => 'Show Available',
-						'value' => $this->config['ombiDefaultFilterAvailable'],
-						'help' => 'Show All Available Ombi Requests'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiDefaultFilterUnavailable',
-						'label' => 'Show Unavailable',
-						'value' => $this->config['ombiDefaultFilterUnavailable'],
-						'help' => 'Show All Unavailable Ombi Requests'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiDefaultFilterApproved',
-						'label' => 'Show Approved',
-						'value' => $this->config['ombiDefaultFilterApproved'],
-						'help' => 'Show All Approved Ombi Requests'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiDefaultFilterUnapproved',
-						'label' => 'Show Unapproved',
-						'value' => $this->config['ombiDefaultFilterUnapproved'],
-						'help' => 'Show All Unapproved Ombi Requests'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'ombiDefaultFilterDenied',
-						'label' => 'Show Denied',
-						'value' => $this->config['ombiDefaultFilterDenied'],
-						'help' => 'Show All Denied Ombi Requests'
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'ombi\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageOmbiEnabled'),
+					$this->settingsOption('auth', 'homepageOmbiAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'ombiURL'),
+					$this->settingsOption('token', 'ombiToken'),
+					$this->settingsOption('username', 'ombiFallbackUser', ['label' => 'Ombi Fallback User', 'help' => 'Organizr will request an Ombi User Token based off of this user credentials']),
+					$this->settingsOption('password', 'ombiFallbackPassword', ['label' => 'Ombi Fallback Password',]),
+					$this->settingsOption('disable-cert-check', 'ombiDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'ombiUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('auth', 'homepageOmbiRequestAuth', ['label' => 'Minimum Group to Request']),
+					$this->settingsOption('select', 'ombiTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->ombiTvOptions()]),
+					$this->settingsOption('switch', 'ombiLimitUser', ['label' => 'Limit to User']),
+					$this->settingsOption('limit', 'ombiLimit'),
+					$this->settingsOption('refresh', 'ombiRefresh'),
+					$this->settingsOption('switch', 'ombiAlias', ['label' => 'Use Ombi Alias Names', 'help' => 'Use Ombi Alias Names instead of Usernames - If Alias is blank, Alias will fallback to Username']),
+				],
+				'Default Filter' => [
+					$this->settingsOption('switch', 'ombiDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Ombi Requests']),
+					$this->settingsOption('switch', 'ombiDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Ombi Requests']),
+					$this->settingsOption('switch', 'ombiDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Ombi Requests']),
+					$this->settingsOption('switch', 'ombiDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Ombi Requests']),
+					$this->settingsOption('switch', 'ombiDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Ombi Requests']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'ombi'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -176,7 +70,7 @@ trait OmbiHomepageItem
 		);
 		$url = $this->qualifyURL($this->config['ombiURL']);
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
 			$test = Requests::get($url . "/api/v1/Settings/about", $headers, $options);
 			if ($test->success) {
 				$this->setAPIResponse('success', 'API Connection succeeded', 200);
@@ -250,7 +144,7 @@ trait OmbiHomepageItem
 		$requests = array();
 		$url = $this->qualifyURL($this->config['ombiURL']);
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, $this->config['ombiRefresh'], $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
 			switch ($type) {
 				case 'movie':
 					$movie = Requests::get($url . "/api/v1/Request/movie", $headers, $options);
@@ -374,7 +268,7 @@ trait OmbiHomepageItem
 				break;
 		}
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false, 'timeout' => 30) : array('timeout' => 30);
+			$options = array('timeout' => 30);
 			if (isset($_COOKIE['Auth'])) {
 				$headers = array(
 					"Accept" => "application/json",
@@ -428,6 +322,7 @@ trait OmbiHomepageItem
 				$this->setAPIResponse('error', 'Could not contact TMDB', 422);
 				return false;
 			}
+			$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
 			$searchResponse = Requests::get($url . '/api/v1/Search/' . $type . '/' . urlencode($title), $headers, $options);
 			if ($searchResponse->success) {
 				$details = json_decode($searchResponse->body, true);
@@ -503,7 +398,7 @@ trait OmbiHomepageItem
 				break;
 		}
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false, 'timeout' => 30) : array('timeout' => 30);
+			$options = $this->requestOptions($url, 60, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
 			switch ($action) {
 				case 'approve':
 					$response = Requests::post($url . "/api/v1/Request/" . $type . "/approve", $headers, json_encode($data), $options);

+ 19 - 59
api/homepage/pihole.php

@@ -14,66 +14,26 @@ trait PiHoleHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePiholeEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepagePiholeEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagePiholeAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepagePiholeAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'piholeURL',
-						'label' => 'URL',
-						'value' => $this->config['piholeURL'],
-						'help' => 'Please make sure to use local IP address and port and to include \'/admin/\' at the end of the URL. You can add multiple Pi-holes by comma separating the URLs.',
-						'placeholder' => 'http(s)://hostname:port/admin/'
-					),
-				),
-				'Misc' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'piholeHeaderToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['piholeHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePiholeCombine',
-						'label' => 'Combine stat cards',
-						'value' => $this->config['homepagePiholeCombine'],
-						'help' => 'This controls whether to combine the stats for multiple pihole instances into 1 card.',
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'pihole\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepagePiholeEnabled'),
+					$this->settingsOption('auth', 'homepagePiholeAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'piholeURL', ['help' => 'Please make sure to use local IP address and port and to include \'/admin/\' at the end of the URL. You can add multiple Pi-holes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port/admin/']),
+				],
+				'Misc' => [
+					$this->settingsOption('toggle-title', 'piholeHeaderToggle'),
+					$this->settingsOption('switch', 'homepagePiholeCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple pihole instances into 1 card.']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'pihole'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	

+ 73 - 282
api/homepage/plex.php

@@ -15,279 +15,69 @@ trait PlexHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
+		$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
 		if ($this->config['plexID'] !== '' && $this->config['plexToken'] !== '') {
+			$libraryList = [];
 			$loop = $this->plexLibraryList('key')['libraries'];
 			foreach ($loop as $key => $value) {
-				$libraryList[] = array(
-					'name' => $key,
-					'value' => $value
-				);
+				$libraryList[] = ['name' => $key, 'value' => $value];
 			}
-		} else {
-			$libraryList = array(
-				array(
-					'name' => 'Refresh page to update List',
-					'value' => '',
-					'disabled' => true,
-				),
-			);
-		}
-		$homepageSettings = array(
-			'name' => 'Plex',
-			'enabled' => strpos('personal', $this->config['license']) !== false,
-			'image' => 'plugins/images/tabs/plex.png',
-			'category' => 'Media Server',
+		}
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePlexEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepagePlexEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagePlexAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepagePlexAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'plexURL',
-						'label' => 'URL',
-						'value' => $this->config['plexURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'blank',
-						'name' => '',
-						'label' => '',
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'plexToken',
-						'label' => 'Token',
-						'value' => $this->config['plexToken']
-					),
-					array(
-						'type' => 'button',
-						'label' => 'Get Plex Token',
-						'icon' => 'fa fa-ticket',
-						'text' => 'Retrieve',
-						'attr' => 'onclick="showPlexTokenForm(\'#homepage-Plex-form [name=plexToken]\')"'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'plexID',
-						'label' => 'Plex Machine',
-						'value' => $this->config['plexID']
-					),
-					array(
-						'type' => 'button',
-						'label' => 'Get Plex Machine',
-						'icon' => 'fa fa-id-badge',
-						'text' => 'Retrieve',
-						'attr' => 'onclick="showPlexMachineForm(\'#homepage-Plex-form [name=plexID]\')"'
-					),
-				),
-				'Active Streams' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePlexStreams',
-						'label' => 'Enable',
-						'value' => $this->config['homepagePlexStreams']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagePlexStreamsAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepagePlexStreamsAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageShowStreamNames',
-						'label' => 'User Information',
-						'value' => $this->config['homepageShowStreamNames']
-					),
-					array(
-						'type' => 'select2',
-						'class' => 'select2-multiple',
-						'id' => 'plex-stream-exclude-select',
-						'name' => 'homepagePlexStreamsExclude',
-						'label' => 'Libraries to Exclude',
-						'value' => $this->config['homepagePlexStreamsExclude'],
-						'options' => $libraryList
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageShowStreamNamesAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepageShowStreamNamesAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageStreamRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageStreamRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Recent Items' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePlexRecent',
-						'label' => 'Enable',
-						'value' => $this->config['homepagePlexRecent']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagePlexRecentAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepagePlexRecentAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select2',
-						'class' => 'select2-multiple',
-						'id' => 'plex-recent-exclude-select',
-						'name' => 'homepagePlexRecentExclude',
-						'label' => 'Libraries to Exclude',
-						'value' => $this->config['homepagePlexRecentExclude'],
-						'options' => $libraryList
-					),
-					array(
-						'type' => 'number',
-						'name' => 'homepageRecentLimit',
-						'label' => 'Item Limit',
-						'value' => $this->config['homepageRecentLimit'],
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRecentRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageRecentRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Media Search' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'mediaSearch',
-						'label' => 'Enable',
-						'value' => $this->config['mediaSearch']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'mediaSearchAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['mediaSearchAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'select2',
-						'class' => 'select2-multiple',
-						'id' => 'plex-search-exclude-select',
-						'name' => 'homepagePlexSearchExclude',
-						'label' => 'Libraries to Exclude',
-						'value' => $this->config['homepagePlexSearchExclude'],
-						'options' => $libraryList
-					),
-					array(
-						'type' => 'select',
-						'name' => 'mediaSearchType',
-						'label' => 'Media Server',
-						'value' => $this->config['mediaSearchType'],
-						'options' => $this->mediaServerOptions()
-					),
-				),
-				'Playlists' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagePlexPlaylist',
-						'label' => 'Enable',
-						'value' => $this->config['homepagePlexPlaylist']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagePlexPlaylistAuth',
-						'label' => 'Minimum Authorization',
-						'value' => $this->config['homepagePlexPlaylistAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'plexTabName',
-						'label' => 'Plex Tab Name',
-						'value' => $this->config['plexTabName'],
-						'placeholder' => 'Only use if you have Plex in a reverse proxy'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'plexTabURL',
-						'label' => 'Plex Tab WAN URL',
-						'value' => $this->config['plexTabURL'],
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'cacheImageSize',
-						'label' => 'Image Cache Size',
-						'value' => $this->config['cacheImageSize'],
-						'options' => array(
-							array(
-								'name' => 'Low',
-								'value' => '.5'
-							),
-							array(
-								'name' => '1x',
-								'value' => '1'
-							),
-							array(
-								'name' => '2x',
-								'value' => '2'
-							),
-							array(
-								'name' => '3x',
-								'value' => '3'
-							)
-						)
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageUseCustomStreamNames',
-						'label' => 'Use Tautulli custom names for users',
-						'value' => $this->config['homepageUseCustomStreamNames']
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'plex\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepagePlexEnabled'),
+					$this->settingsOption('auth', 'homepagePlexAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'plexURL'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('disable-cert-check', 'plexDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'plexUseCustomCertificate'),
+					$this->settingsOption('token', 'plexToken'),
+					$this->settingsOption('button', '', ['label' => 'Get Plex Token', 'icon' => 'fa fa-ticket', 'text' => 'Retrieve', 'attr' => 'onclick="showPlexTokenForm(\'#homepage-Plex-form [name=plexToken]\')"']),
+					$this->settingsOption('password-alt', 'plexID', ['label' => 'Plex Machine']),
+					$this->settingsOption('button', '', ['label' => 'Get Plex Machine', 'icon' => 'fa fa-id-badge', 'text' => 'Retrieve', 'attr' => 'onclick="showPlexMachineForm(\'#homepage-Plex-form [name=plexID]\')"']),
+				],
+				'Active Streams' => [
+					$this->settingsOption('enable', 'homepagePlexStreams'),
+					$this->settingsOption('auth', 'homepagePlexStreamsAuth'),
+					$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
+					$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
+					$this->settingsOption('refresh', 'homepageStreamRefresh'),
+					$this->settingsOption('plex-library-exclude', 'homepagePlexStreamsExclude', ['options' => $libraryList]),
+				],
+				'Recent Items' => [
+					$this->settingsOption('enable', 'homepagePlexRecent'),
+					$this->settingsOption('auth', 'homepagePlexRecentAuth'),
+					$this->settingsOption('plex-library-exclude', 'homepagePlexRecentExclude', ['options' => $libraryList]),
+					$this->settingsOption('limit', 'homepageRecentLimit'),
+					$this->settingsOption('refresh', 'homepageRecentRefresh'),
+				],
+				'Media Search' => [
+					$this->settingsOption('enable', 'mediaSearch'),
+					$this->settingsOption('auth', 'mediaSearchAuth'),
+					$this->settingsOption('plex-library-exclude', 'homepagePlexSearchExclude', ['options' => $libraryList]),
+					$this->settingsOption('media-search-server', 'mediaSearchType'),
+				],
+				'Playlists' => [
+					$this->settingsOption('enable', 'homepagePlexPlaylist'),
+					$this->settingsOption('auth', 'homepagePlexPlaylistAuth'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('input', 'plexTabName', ['label' => 'Plex Tab Name', 'placeholder' => 'Only use if you have Plex in a reverse proxy']),
+					$this->settingsOption('input', 'plexTabURL', ['label' => 'Plex Tab WAN URL', 'placeholder' => 'http(s)://domain.com/plex']),
+					$this->settingsOption('image-cache-quality', 'cacheImageSize'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('switch', 'homepageUseCustomStreamNames', ['label' => 'Use Tautulli custom names for users']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'plex'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -296,8 +86,8 @@ trait PlexHomepageItem
 		if (!empty($this->config['plexURL']) && !empty($this->config['plexToken'])) {
 			$url = $this->qualifyURL($this->config['plexURL']) . "/servers?X-Plex-Token=" . $this->config['plexToken'];
 			try {
-				$options = ($this->localURL($url)) ? array('verify' => false) : array();
-				$response = Requests::get($url, array(), $options);
+				$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+				$response = Requests::get($url, [], $options);
 				libxml_use_internal_errors(true);
 				if ($response->success) {
 					$this->setAPIResponse('success', 'API Connection succeeded', 200);
@@ -461,8 +251,8 @@ trait PlexHomepageItem
 		$resolve = true;
 		$url = $this->qualifyURL($this->config['plexURL']);
 		$url = $url . "/status/sessions?X-Plex-Token=" . $this->config['plexToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
-		$response = Requests::get($url, array(), $options);
+		$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+		$response = Requests::get($url, [], $options);
 		libxml_use_internal_errors(true);
 		if ($response->success) {
 			$items = array();
@@ -494,8 +284,8 @@ trait PlexHomepageItem
 		$urls['tv'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=2";
 		$urls['music'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=8";
 		foreach ($urls as $k => $v) {
-			$options = ($this->localURL($v)) ? array('verify' => false) : array();
-			$response = Requests::get($v, array(), $options);
+			$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+			$response = Requests::get($v, [], $options);
 			libxml_use_internal_errors(true);
 			if ($response->success) {
 				$items = array();
@@ -531,8 +321,8 @@ trait PlexHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['plexURL']);
 		$url = $url . "/playlists?X-Plex-Token=" . $this->config['plexToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
-		$response = Requests::get($url, array(), $options);
+		$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+		$response = Requests::get($url, [], $options);
 		libxml_use_internal_errors(true);
 		if ($response->success) {
 			$items = array();
@@ -579,8 +369,8 @@ trait PlexHomepageItem
 		$resolve = true;
 		$url = $this->qualifyURL($this->config['plexURL']);
 		$url = $url . "/library/metadata/" . $key . "?X-Plex-Token=" . $this->config['plexToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
-		$response = Requests::get($url, array(), $options);
+		$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+		$response = Requests::get($url, [], $options);
 		libxml_use_internal_errors(true);
 		if ($response->success) {
 			$items = array();
@@ -614,8 +404,8 @@ trait PlexHomepageItem
 		$resolve = true;
 		$url = $this->qualifyURL($this->config['plexURL']);
 		$url = $url . "/search?query=" . rawurlencode($query) . "&X-Plex-Token=" . $this->config['plexToken'];
-		$options = ($this->localURL($url)) ? array('verify' => false) : array();
-		$response = Requests::get($url, array(), $options);
+		$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
+		$response = Requests::get($url, [], $options);
 		libxml_use_internal_errors(true);
 		if ($response->success) {
 			$items = array();
@@ -836,7 +626,8 @@ trait PlexHomepageItem
 		$url = $this->qualifyURL($this->config['tautulliURL']);
 		$url .= '/api/v2?apikey=' . $this->config['tautulliApikey'];
 		$url .= '&cmd=get_users';
-		$response = Requests::get($url, [], []);
+		$options = $this->requestOptions($url, null, $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
+		$response = Requests::get($url, [], $options);
 		try {
 			$response = json_decode($response->body, true);
 			foreach ($response['response']['data'] as $user) {
@@ -845,7 +636,7 @@ trait PlexHomepageItem
 				}
 			}
 		} catch (Exception $e) {
-			$this->setAPIResponse('failure', null, 422, [$e->getMessage()]);
+			$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
 		}
 		$this->setAPIResponse('success', null, 200, $names);
 		return $names;

+ 39 - 138
api/homepage/qbittorrent.php

@@ -14,140 +14,41 @@ trait QBitTorrentHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageqBittorrentEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageqBittorrentEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageqBittorrentAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageqBittorrentAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'qBittorrentURL',
-						'label' => 'URL',
-						'value' => $this->config['qBittorrentURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'qBittorrentDisableCertCheck',
-						'label' => 'Disable Certificate Check',
-						'value' => $this->config['qBittorrentDisableCertCheck']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'qBittorrentApiVersion',
-						'label' => 'API Version',
-						'value' => $this->config['qBittorrentApiVersion'],
-						'options' => $this->qBittorrentApiOptions()
-					),
-					array(
-						'type' => 'input',
-						'name' => 'qBittorrentUsername',
-						'label' => 'Username',
-						'value' => $this->config['qBittorrentUsername']
-					),
-					array(
-						'type' => 'password',
-						'name' => 'qBittorrentPassword',
-						'label' => 'Password',
-						'value' => $this->config['qBittorrentPassword']
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('qbittorrent') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'qBittorrentSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['qBittorrentSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'qBittorrentSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['qBittorrentSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'qBittorrentHideSeeding',
-						'label' => 'Hide Seeding',
-						'value' => $this->config['qBittorrentHideSeeding']
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'qBittorrentHideCompleted',
-						'label' => 'Hide Completed',
-						'value' => $this->config['qBittorrentHideCompleted']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'qBittorrentSortOrder',
-						'label' => 'Order',
-						'value' => $this->config['qBittorrentSortOrder'],
-						'options' => $this->qBittorrentSortOptions()
-					), array(
-						'type' => 'switch',
-						'name' => 'qBittorrentReverseSorting',
-						'label' => 'Reverse Sorting',
-						'value' => $this->config['qBittorrentReverseSorting']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'qBittorrentRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['qBittorrentRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'qBittorrentCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['qBittorrentCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'qbittorrent\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageqBittorrentEnabled'),
+					$this->settingsOption('auth', 'homepageqBittorrentAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'qBittorrentURL'),
+					$this->settingsOption('select', 'qBittorrentApiVersion', ['label' => 'API Version', 'options' => $this->qBittorrentApiOptions()]),
+					$this->settingsOption('username', 'qBittorrentUsername'),
+					$this->settingsOption('password', 'qBittorrentPassword'),
+					$this->settingsOption('disable-cert-check', 'qBittorrentDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'qBittorrentUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'qbittorrent'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'qBittorrentSocksEnabled'),
+					$this->settingsOption('auth', 'qBittorrentSocksAuth'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('hide-seeding', 'qBittorrentHideSeeding'),
+					$this->settingsOption('hide-completed', 'qBittorrentHideCompleted'),
+					$this->settingsOption('select', 'qBittorrentSortOrder', ['label' => 'Order', 'options' => $this->qBittorrentSortOptions()]),
+					$this->settingsOption('switch', 'qBittorrentReverseSorting', ['label' => 'Reverse Sorting']),
+					$this->settingsOption('refresh', 'qBittorrentRefresh'),
+					$this->settingsOption('combine', 'qBittorrentCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'qbittorrent'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -163,8 +64,8 @@ trait QBitTorrentHomepageItem
 		$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
 		$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
 		try {
-			$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentRefresh']);
-			$response = Requests::post($url, array(), $data, $options);
+			$options = $this->requestOptions($this->config['qBittorrentURL'], null, $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
+			$response = Requests::post($url, [], $data, $options);
 			$reflection = new ReflectionClass($response->cookies);
 			$cookie = $reflection->getProperty("cookies");
 			$cookie->setAccessible(true);
@@ -255,8 +156,8 @@ trait QBitTorrentHomepageItem
 		$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
 		$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
 		try {
-			$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentRefresh']);
-			$response = Requests::post($url, array(), $data, $options);
+			$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentRefresh'], $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
+			$response = Requests::post($url, [], $data, $options);
 			$reflection = new ReflectionClass($response->cookies);
 			$cookie = $reflection->getProperty("cookies");
 			$cookie->setAccessible(true);
@@ -289,7 +190,7 @@ trait QBitTorrentHomepageItem
 					}
 					$api['content']['queueItems'] = $torrents;
 					$api['content']['historyItems'] = false;
-					$api['content'] = isset($api['content']) ? $api['content'] : false;
+					$api['content'] = $api['content'] ?? false;
 					$this->setAPIResponse('success', null, 200, $api);
 					return $api;
 				}

+ 50 - 188
api/homepage/radarr.php

@@ -14,192 +14,51 @@ trait RadarrHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageRadarrEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageRadarrEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRadarrAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageRadarrAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'radarrURL',
-						'label' => 'URL',
-						'value' => $this->config['radarrURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'radarrToken',
-						'label' => 'Token',
-						'value' => $this->config['radarrToken']
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('radarr') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'radarrSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['radarrSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'radarrSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['radarrSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Queue' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageRadarrQueueEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageRadarrQueueEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRadarrQueueAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageRadarrQueueAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageRadarrQueueCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['homepageRadarrQueueCombine']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageRadarrQueueRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageRadarrQueueRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Calendar' => array(
-					array(
-						'type' => 'number',
-						'name' => 'calendarStart',
-						'label' => '# of Days Before',
-						'value' => $this->config['calendarStart'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'number',
-						'name' => 'calendarEnd',
-						'label' => '# of Days After',
-						'value' => $this->config['calendarEnd'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'radarrUnmonitored',
-						'label' => 'Show Unmonitored',
-						'value' => $this->config['radarrUnmonitored']
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'radarrPhysicalRelease',
-						'label' => 'Show Physical Release',
-						'value' => $this->config['radarrPhysicalRelease']
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'radarrDigitalRelease',
-						'label' => 'Show Digital Release',
-						'value' => $this->config['radarrDigitalRelease']
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'radarrCinemaRelease',
-						'label' => 'Show Cinema Releases',
-						'value' => $this->config['radarrCinemaRelease']
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'radarr\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageRadarrEnabled'),
+					$this->settingsOption('auth', 'homepageRadarrAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('multiple-url', 'radarrURL'),
+					$this->settingsOption('multiple-token', 'radarrToken'),
+					$this->settingsOption('disable-cert-check', 'radarrDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'radarrUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'radarr'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'radarrSocksEnabled'),
+					$this->settingsOption('auth', 'radarrSocksAuth'),
+				],
+				'Queue' => [
+					$this->settingsOption('enable', 'homepageRadarrQueueEnabled'),
+					$this->settingsOption('auth', 'homepageRadarrQueueAuth'),
+					$this->settingsOption('combine', 'homepageRadarrQueueCombine'),
+					$this->settingsOption('refresh', 'homepageRadarrQueueRefresh'),
+				],
+				'Calendar' => [
+					$this->settingsOption('calendar-start', 'calendarStart'),
+					$this->settingsOption('calendar-end', 'calendarEnd'),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+					$this->settingsOption('switch', 'radarrUnmonitored', ['label' => 'Show Unmonitored']),
+					$this->settingsOption('switch', 'radarrPhysicalRelease', ['label' => 'Show Physical Releases']),
+					$this->settingsOption('switch', 'radarrDigitalRelease', ['label' => 'Show Digital Releases']),
+					$this->settingsOption('switch', 'radarrCinemaRelease', ['label' => 'Show Cinema Releases']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'radarr'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -218,7 +77,8 @@ trait RadarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
 				$results = $downloader->getRootFolder();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -317,7 +177,8 @@ trait RadarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr');
+				$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
 				$results = $downloader->getQueue();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -350,7 +211,8 @@ trait RadarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr');
+				$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
 				$results = $downloader->getCalendar($startDate, $endDate, $this->config['radarrUnmonitored']);
 				$result = json_decode($results, true);
 				if (is_array($result) || is_object($result)) {

+ 63 - 146
api/homepage/rtorrent.php

@@ -16,148 +16,65 @@ trait RTorrentHomepageItem
 			return $homepageInformation;
 		}
 		$xmlStatus = (extension_loaded('xmlrpc')) ? 'Installed' : 'Not Installed';
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'FYI' => array(
-					array(
-						'type' => 'html',
-						'label' => '',
-						'override' => 12,
+			'settings' => [
+				'FYI' => [
+					$this->settingsOption('html', null, ['label' => '', 'override' => 12,
 						'html' => '
 						<div class="row">
-						    <div class="col-lg-12">
-						        <div class="panel panel-info">
-						            <div class="panel-heading">
-						                <span lang="en">ATTENTION</span>
-						            </div>
-						            <div class="panel-wrapper collapse in" aria-expanded="true">
-						                <div class="panel-body">
-						                	<h4 lang="en">This module requires XMLRPC</h4>
-						                    <span lang="en">Status: [ <b>' . $xmlStatus . '</b> ]</span>
-						                    <br/></br>
-						                    <span lang="en">
-						                    	<h4><b>Note about API URL</b></h4>
-						                    	Organizr appends the url with <code>/RPC2</code> unless the URL ends in <code>.php</code><br/>
-						                    	<h5>Possible URLs:</h5>
-						                    	<li>http://localhost:8080</li>
-						                    	<li>https://domain.site/xmlrpc.php</li>
-						                    	<li>https://seedbox.site/rutorrent/plugins/httprpc/action.php</li>
-						                    </span>
-						                </div>
-						            </div>
-						        </div>
-						    </div>
+							<div class="col-lg-12">
+								<div class="panel panel-info">
+									<div class="panel-heading">
+										<span lang="en">ATTENTION</span>
+									</div>
+									<div class="panel-wrapper collapse in" aria-expanded="true">
+										<div class="panel-body">
+											<h4 lang="en">This module requires XMLRPC</h4>
+											<span lang="en">Status: [ <b>' . $xmlStatus . '</b> ]</span>
+											<br/></br>
+											<span lang="en">
+												<h4><b>Note about API URL</b></h4>
+												Organizr appends the url with <code>/RPC2</code> unless the URL ends in <code>.php</code><br/>
+												<h5>Possible URLs:</h5>
+												<li>http://localhost:8080</li>
+												<li>https://domain.site/xmlrpc.php</li>
+												<li>https://seedbox.site/rutorrent/plugins/httprpc/action.php</li>
+											</span>
+										</div>
+									</div>
+								</div>
+							</div>
 						</div>
 						'
-					)
-				),
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepagerTorrentEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepagerTorrentEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepagerTorrentAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepagerTorrentAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'rTorrentURL',
-						'label' => 'URL',
-						'value' => $this->config['rTorrentURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'rTorrentURLOverride',
-						'label' => 'rTorrent API URL Override',
-						'value' => $this->config['rTorrentURLOverride'],
-						'help' => 'Only use if you cannot connect.  Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port/xmlrpc'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'rTorrentUsername',
-						'label' => 'Username',
-						'value' => $this->config['rTorrentUsername']
-					),
-					array(
-						'type' => 'password',
-						'name' => 'rTorrentPassword',
-						'label' => 'Password',
-						'value' => $this->config['rTorrentPassword']
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'rTorrentDisableCertCheck',
-						'label' => 'Disable Certificate Check',
-						'value' => $this->config['rTorrentDisableCertCheck']
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'rTorrentHideSeeding',
-						'label' => 'Hide Seeding',
-						'value' => $this->config['rTorrentHideSeeding']
-					), array(
-						'type' => 'switch',
-						'name' => 'rTorrentHideCompleted',
-						'label' => 'Hide Completed',
-						'value' => $this->config['rTorrentHideCompleted']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'rTorrentSortOrder',
-						'label' => 'Order',
-						'value' => $this->config['rTorrentSortOrder'],
-						'options' => $this->rTorrentSortOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'rTorrentRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['rTorrentRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'number',
-						'name' => 'rTorrentLimit',
-						'label' => 'Item Limit',
-						'value' => $this->config['rTorrentLimit'],
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'rTorrentCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['rTorrentCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'rtorrent\')"'
-					),
-				)
-			)
-		);
+					]),
+				],
+				'Enable' => [
+					$this->settingsOption('enable', 'homepagerTorrentEnabled'),
+					$this->settingsOption('auth', 'homepagerTorrentAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'rTorrentURL'),
+					$this->settingsOption('input', 'rTorrentURLOverride', ['label' => 'rTorrent API URL Override', 'help' => 'Only use if you cannot connect.  Please make sure to use local IP address and port - You also may use local dns name too.', 'placeholder' => 'http(s)://hostname:port/xmlrpc']),
+					$this->settingsOption('username', 'rTorrentUsername'),
+					$this->settingsOption('password', 'rTorrentPassword'),
+					$this->settingsOption('disable-cert-check', 'rTorrentDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'rTorrentUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('hide-seeding', 'rTorrentHideSeeding'),
+					$this->settingsOption('hide-completed', 'rTorrentHideCompleted'),
+					$this->settingsOption('select', 'rTorrentSortOrder', ['label' => 'Order', 'options' => $this->rTorrentSortOptions()]),
+					$this->settingsOption('limit', 'rTorrentLimit'),
+					$this->settingsOption('refresh', 'rTorrentRefresh'),
+					$this->settingsOption('combine', 'rTorrentCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'rtorrent'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -172,13 +89,13 @@ trait RTorrentHomepageItem
 			$extraPath = (strpos($this->config['rTorrentURL'], '.php') !== false) ? '' : '/RPC2';
 			$extraPath = (empty($this->config['rTorrentURLOverride'])) ? $extraPath : '';
 			$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $extraPath;
-			$options = ($this->localURL($url, $this->config['rTorrentDisableCertCheck'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, null, $this->config['rTorrentDisableCertCheck'], $this->config['rtorrentUseCustomCertificate']);
 			if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
 				$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
 				$options = array_merge($options, $credentials);
 			}
 			$data = xmlrpc_encode_request("system.listMethods", null);
-			$response = Requests::post($url, array(), $data, $options);
+			$response = Requests::post($url, [], $data, $options);
 			if ($response->success) {
 				$methods = xmlrpc_decode(str_replace('i8>', 'i4>', $response->body));
 				if (count($methods) !== 0) {
@@ -227,11 +144,11 @@ trait RTorrentHomepageItem
 				<div id="' . __FUNCTION__ . '">
 					' . $loadingBox . '
 					<script>
-		                // homepageOrderrTorrent
-		                ' . $builder . '
-		                homepageDownloader("rTorrent", "' . $this->config['rTorrentRefresh'] . '");
-		                // End homepageOrderrTorrent
-	                </script>
+						// homepageOrderrTorrent
+						' . $builder . '
+						homepageDownloader("rTorrent", "' . $this->config['rTorrentRefresh'] . '");
+						// End homepageOrderrTorrent
+					</script>
 				</div>
 				';
 		}
@@ -278,7 +195,7 @@ trait RTorrentHomepageItem
 			$extraPath = (strpos($this->config['rTorrentURL'], '.php') !== false) ? '' : '/RPC2';
 			$extraPath = (empty($this->config['rTorrentURLOverride'])) ? $extraPath : '';
 			$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $extraPath;
-			$options = ($this->localURL($url, $this->config['rTorrentDisableCertCheck'])) ? array('verify' => false) : array();
+			$options = $this->requestOptions($url, $this->config['rTorrentRefresh'], $this->config['rTorrentDisableCertCheck'], $this->config['rtorrentUseCustomCertificate']);
 			if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
 				$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
 				$options = array_merge($options, $credentials);
@@ -312,7 +229,7 @@ trait RTorrentHomepageItem
 				"d.custom4=",
 				"d.custom5=",
 			), array());
-			$response = Requests::post($url, array(), $data, $options);
+			$response = Requests::post($url, [], $data, $options);
 			if ($response->success) {
 				$torrentList = xmlrpc_decode(str_replace('i8>', 'string>', $response->body));
 				if (is_array($torrentList)) {

+ 36 - 98
api/homepage/sabnzbd.php

@@ -15,97 +15,35 @@ trait SabNZBdHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSabnzbdEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageSabnzbdEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSabnzbdAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageSabnzbdAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'sabnzbdURL',
-						'label' => 'URL',
-						'value' => $this->config['sabnzbdURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'sabnzbdToken',
-						'label' => 'Token',
-						'value' => $this->config['sabnzbdToken']
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('sabnzbd') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'sabnzbdSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['sabnzbdSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'sabnzbdSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['sabnzbdSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'sabnzbdRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['sabnzbdRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'sabnzbdCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['sabnzbdCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'sabnzbd\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageSabnzbdEnabled'),
+					$this->settingsOption('auth', 'homepageSabnzbdAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'sabnzbdURL'),
+					$this->settingsOption('token', 'sabnzbdToken'),
+					$this->settingsOption('disable-cert-check', 'sabnzbdDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'sabnzbdUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'sabnzbd'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'sabnzbdSocksEnabled'),
+					$this->settingsOption('auth', 'sabnzbdSocksAuth'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('refresh', 'sabnzbdRefresh'),
+					$this->settingsOption('combine', 'sabnzbdCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'sabnzbd'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -115,8 +53,8 @@ trait SabNZBdHomepageItem
 			$url = $this->qualifyURL($this->config['sabnzbdURL']);
 			$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
 			try {
-				$options = ($this->localURL($url)) ? array('verify' => false) : array();
-				$response = Requests::get($url, array(), $options);
+				$options = $this->requestOptions($url, null, $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
+				$response = Requests::get($url, [], $options);
 				if ($response->success) {
 					$data = json_decode($response->body, true);
 					$status = 'success';
@@ -195,8 +133,8 @@ trait SabNZBdHomepageItem
 		$url = $this->qualifyURL($this->config['sabnzbdURL']);
 		$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$api['content']['queueItems'] = json_decode($response->body, true);
 			}
@@ -232,8 +170,8 @@ trait SabNZBdHomepageItem
 		$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=pause&value=' . $target . '&' : 'mode=pause';
 		$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$api['content'] = json_decode($response->body, true);
 			}
@@ -256,8 +194,8 @@ trait SabNZBdHomepageItem
 		$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=resume&value=' . $target . '&' : 'mode=resume';
 		$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
 		try {
-			$options = ($this->localURL($url)) ? array('verify' => false) : array();
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->success) {
 				$api['content'] = json_decode($response->body, true);
 			}

+ 30 - 95
api/homepage/sickrage.php

@@ -14,100 +14,33 @@ trait SickRageHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSickrageEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageSickrageEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSickrageAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageSickrageAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'sickrageURL',
-						'label' => 'URL',
-						'value' => $this->config['sickrageURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'sickrageToken',
-						'label' => 'Token',
-						'value' => $this->config['sickrageToken']
-					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'sickrage\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageSickrageEnabled'),
+					$this->settingsOption('auth', 'homepageSickrageAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'sickrageURL'),
+					$this->settingsOption('token', 'sickrageToken'),
+					$this->settingsOption('disable-cert-check', 'sickrageDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'sickrageUseCustomCertificate'),
+				],
+				'Calendar' => [
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'sickrage'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -126,7 +59,8 @@ trait SickRageHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token']);
+				$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
+				$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
 				$results = $downloader->sb();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -192,7 +126,8 @@ trait SickRageHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token']);
+				$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
+				$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
 				$sickrageFuture = $this->formatSickrageCalendarWanted($downloader->future(), $key);
 				$sickrageHistory = $this->formatSickrageCalendarHistory($downloader->history("100", "downloaded"), $key);
 				if (!empty($sickrageFuture)) {

+ 50 - 194
api/homepage/sonarr.php

@@ -14,199 +14,52 @@ trait SonarrHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'docs' => 'https://docs.organizr.app/books/setup-features/page/sonarr',
 			'debug' => true,
-			'settings' => array(
-				'About' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">
-										<h3 lang="en">Sonarr Homepage Item</h3>
-										<p lang="en">This item allows access to Sonarr\'s calendar data and aggregates it to Organizr\'s calendar.  Along with that you also have the Downloader function that allow access to Sonarr\'s queue.  The last item that is included is the API SOCKS function which acts as a middleman between API\'s which is useful if you are not port forwarding or reverse proxying Sonarr.</p>
-									</div>
-								</div>
-							</div>'
-					),
-				),
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSonarrEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageSonarrEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSonarrAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageSonarrAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'select2',
-						'class' => 'select2-multiple',
-						'id' => 'sonarrURL-select',
-						'name' => 'sonarrURL',
-						'label' => 'Sonarr URL',
-						'value' => $this->config['sonarrURL'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port',
-						'options' => $this->makeOptionsFromValues($this->config['sonarrURL']),
-						'settings' => '{tags: true, selectOnClose: true, closeOnSelect: true}',
-					),
-					array(
-						'type' => 'select2',
-						'class' => 'select2-multiple',
-						'id' => 'sonarrToken-select',
-						'name' => 'sonarrToken',
-						'label' => 'Sonarr Token',
-						'value' => $this->config['sonarrToken'],
-						'options' => $this->makeOptionsFromValues($this->config['sonarrToken']),
-						'settings' => '{tags: true, theme: "default password-alt, selectOnClose: true, closeOnSelect: true"}',
-					)
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('sonarr') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'sonarrSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['sonarrSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'sonarrSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['sonarrSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Queue' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSonarrQueueEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageSonarrQueueEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSonarrQueueAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageSonarrQueueAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSonarrQueueCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['homepageSonarrQueueCombine']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSonarrQueueRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageSonarrQueueRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Calendar' => array(
-					array(
-						'type' => 'number',
-						'name' => 'calendarStart',
-						'label' => '# of Days Before',
-						'value' => $this->config['calendarStart'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'number',
-						'name' => 'calendarEnd',
-						'label' => '# of Days After',
-						'value' => $this->config['calendarEnd'],
-						'placeholder' => ''
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'sonarrUnmonitored',
-						'label' => 'Show Unmonitored',
-						'value' => $this->config['sonarrUnmonitored']
-					)
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'sonarr\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'About' => [
+					$this->settingsOption('about', 'Sonarr', ['about' => 'This item allows access to Sonarr\'s calendar data and aggregates it to Organizr\'s calendar.  Along with that you also have the Downloader function that allow access to Sonarr\'s queue.  The last item that is included is the API SOCKS function which acts as a middleman between API\'s which is useful if you are not port forwarding or reverse proxying Sonarr.']),
+				],
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageSonarrEnabled'),
+					$this->settingsOption('auth', 'homepageSonarrAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('multiple-url', 'sonarrURL'),
+					$this->settingsOption('multiple-token', 'sonarrToken'),
+					$this->settingsOption('disable-cert-check', 'sonarrDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'sonarrUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'sonarr'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'sonarrSocksEnabled'),
+					$this->settingsOption('auth', 'sonarrSocksAuth'),
+				],
+				'Queue' => [
+					$this->settingsOption('enable', 'homepageSonarrQueueEnabled'),
+					$this->settingsOption('auth', 'homepageSonarrQueueAuth'),
+					$this->settingsOption('combine', 'homepageSonarrQueueCombine'),
+					$this->settingsOption('refresh', 'homepageSonarrQueueRefresh'),
+				],
+				'Calendar' => [
+					$this->settingsOption('calendar-start', 'calendarStart'),
+					$this->settingsOption('calendar-end', 'calendarEnd'),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+					$this->settingsOption('switch', 'sonarrUnmonitored', ['label' => 'Show Unmonitored']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'sonarr'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -225,7 +78,8 @@ trait SonarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
 				$results = $downloader->getRootFolder();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -324,7 +178,8 @@ trait SonarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr');
+				$options = $this->requestOptions($value['url'], $this->config['homepageSonarrQueueRefresh'], $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
+				$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
 				$results = $downloader->getQueue();
 				$downloadList = json_decode($results, true);
 				if (is_array($downloadList) || is_object($downloadList)) {
@@ -357,7 +212,8 @@ trait SonarrHomepageItem
 		$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
 		foreach ($list as $key => $value) {
 			try {
-				$sonarr = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr');
+				$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
+				$sonarr = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
 				$sonarr = $sonarr->getCalendar($startDate, $endDate, $this->config['sonarrUnmonitored']);
 				$result = json_decode($sonarr, true);
 				if (is_array($result) || is_object($result)) {

+ 20 - 51
api/homepage/speedtest.php

@@ -14,57 +14,25 @@ trait SpeedTestHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'html',
-						'override' => 6,
-						'label' => 'Info',
-						'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running on your network.</p>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageSpeedtestEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageSpeedtestEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageSpeedtestAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageSpeedtestAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'speedtestURL',
-						'label' => 'URL',
-						'value' => $this->config['speedtestURL'],
-						'help' => 'Enter the IP:PORT of your speedtest instance e.g. http(s)://<ip>:<port>'
-					),
-				),
-				'Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'speedtestHeader',
-						'label' => 'Title',
-						'value' => $this->config['speedtestHeader'],
-						'help' => 'Sets the title of this homepage module',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'speedtestHeaderToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['speedtestHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('html', null, ['override' => 6, 'label' => 'Info', 'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running on your network.</p>']),
+					$this->settingsOption('enable', 'homepageSpeedtestEnabled'),
+					$this->settingsOption('auth', 'homepageSpeedtestAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'speedtestURL'),
+					$this->settingsOption('disable-cert-check', 'speedtestDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'speedtestUseCustomCertificate'),
+				],
+				'Options' => [
+					$this->settingsOption('title', 'speedtestHeader'),
+					$this->settingsOption('toggle-title', 'speedtestHeaderToggle'),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -115,9 +83,10 @@ trait SpeedTestHomepageItem
 		}
 		$api = [];
 		$url = $this->qualifyURL($this->config['speedtestURL']);
+		$options = $this->requestOptions($url, null, $this->config['speedtestDisableCertCheck'], $this->config['speedtestUseCustomCertificate']);
 		$dataUrl = $url . '/api/speedtest/latest';
 		try {
-			$response = Requests::get($dataUrl);
+			$response = Requests::get($dataUrl, [], $options);
 			if ($response->success) {
 				$json = json_decode($response->body, true);
 				$api['data'] = [

+ 49 - 184
api/homepage/tautulli.php

@@ -14,188 +14,53 @@ trait TautulliHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageTautulliEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageTautulliEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTautulliAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTautulliAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'tautulliHeader',
-						'label' => 'Title',
-						'value' => $this->config['tautulliHeader'],
-						'help' => 'Sets the title of this homepage module'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliHeaderToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['tautulliHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'tautulliURL',
-						'label' => 'URL',
-						'value' => $this->config['tautulliURL'],
-						'help' => 'URL for Tautulli API, include the IP, the port and the base URL (e.g. /tautulli/) in the URL',
-						'placeholder' => 'http://<ip>:<port>'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'tautulliApikey',
-						'label' => 'API Key',
-						'value' => $this->config['tautulliApikey']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTautulliRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageTautulliRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'API SOCKS' => array(
-					array(
-						'type' => 'html',
-						'override' => 12,
-						'label' => '',
-						'html' => '
-							<div class="panel panel-default">
-								<div class="panel-wrapper collapse in">
-									<div class="panel-body">' . $this->socksHeadingHTML('tautulli') . '</div>
-								</div>
-							</div>'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliSocksEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['tautulliSocksEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'tautulliSocksAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['tautulliSocksAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Library Stats' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliLibraries',
-						'label' => 'Libraries',
-						'value' => $this->config['tautulliLibraries'],
-						'help' => 'Shows/hides the card with library information.',
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTautulliLibraryAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTautulliLibraryAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Viewing Stats' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliPopularMovies',
-						'label' => 'Popular Movies',
-						'value' => $this->config['tautulliPopularMovies'],
-						'help' => 'Shows/hides the card with Popular Movies information.',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliPopularTV',
-						'label' => 'Popular TV',
-						'value' => $this->config['tautulliPopularTV'],
-						'help' => 'Shows/hides the card with Popular TV information.',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliTopMovies',
-						'label' => 'Top Movies',
-						'value' => $this->config['tautulliTopMovies'],
-						'help' => 'Shows/hides the card with Top Movies information.',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliTopTV',
-						'label' => 'Top TV',
-						'value' => $this->config['tautulliTopTV'],
-						'help' => 'Shows/hides the card with Top TV information.',
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTautulliViewsAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTautulliViewsAuth'],
-						'options' => $this->groupOptions
-					),
-				),
-				'Misc Stats' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliTopUsers',
-						'label' => 'Top Users',
-						'value' => $this->config['tautulliTopUsers'],
-						'help' => 'Shows/hides the card with Top Users information.',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliTopPlatforms',
-						'label' => 'Top Platforms',
-						'value' => $this->config['tautulliTopPlatforms'],
-						'help' => 'Shows/hides the card with Top Platforms information.',
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTautulliMiscAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTautulliMiscAuth'],
-						'options' => $this->groupOptions
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'tautulliFriendlyName',
-						'label' => 'Use Friendly Name',
-						'value' => $this->config['tautulliFriendlyName'],
-						'help' => 'Use the friendly name set in tautulli for users.',
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'tautulli\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageTautulliEnabled'),
+					$this->settingsOption('auth', 'homepageTautulliAuth'),
+				],
+				'Options' => [
+					$this->settingsOption('title', 'tautulliHeader'),
+					$this->settingsOption('toggle-title', 'tautulliHeaderToggle'),
+					$this->settingsOption('refresh', 'homepageTautulliRefresh'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'tautulliURL'),
+					$this->settingsOption('api-key', 'tautulliApikey'),
+					$this->settingsOption('disable-cert-check', 'tautulliDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'tautulliUseCustomCertificate'),
+				],
+				'API SOCKS' => [
+					$this->settingsOption('socks', 'tautulli'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('enable', 'tautulliSocksEnabled'),
+					$this->settingsOption('auth', 'tautulliSocksAuth'),
+				],
+				'Library Stats' => [
+					$this->settingsOption('switch', 'tautulliLibraries', ['label' => 'Libraries', 'help' => 'Shows/hides the card with library information.']),
+					$this->settingsOption('auth', 'homepageTautulliLibraryAuth'),
+				],
+				'Viewing Stats' => [
+					$this->settingsOption('switch', 'tautulliPopularMovies', ['label' => 'Popular Movies', 'help' => 'Shows/hides the card with Popular Movie information.']),
+					$this->settingsOption('switch', 'tautulliPopularTV', ['label' => 'Popular TV', 'help' => 'Shows/hides the card with Popular TV information.']),
+					$this->settingsOption('switch', 'tautulliTopMovies', ['label' => 'Top Movies', 'help' => 'Shows/hides the card with Top Movies information.']),
+					$this->settingsOption('switch', 'tautulliTopTV', ['label' => 'Top TV', 'help' => 'Shows/hides the card with Top TV information.']),
+					$this->settingsOption('auth', 'homepageTautulliViewsAuth'),
+				],
+				'Misc Stats' => [
+					$this->settingsOption('switch', 'tautulliTopUsers', ['label' => 'Top Users', 'help' => 'Shows/hides the card with Top Users information.']),
+					$this->settingsOption('switch', 'tautulliTopPlatforms', ['label' => 'Top Platforms', 'help' => 'Shows/hides the card with Top Platforms information.']),
+					$this->settingsOption('auth', 'homepageTautulliMiscAuth'),
+					$this->settingsOption('switch', 'tautulliFriendlyName', ['label' => 'Use Friendly Name', 'help' => 'Use the friendly name set in tautulli for users.']),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'tautulli'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -213,7 +78,7 @@ trait TautulliHomepageItem
 		$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
 		try {
 			$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
-			$options = $this->requestOptions($this->config['tautulliURL'], false, $this->config['homepageTautulliRefresh']);
+			$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
 			$homestats = Requests::get($homestatsUrl, [], $options);
 			if ($homestats->success) {
 				$this->setAPIResponse('success', 'API Connection succeeded', 200);
@@ -284,7 +149,7 @@ trait TautulliHomepageItem
 		$nowPlayingWidth = $this->getCacheImageSize('npw');
 		try {
 			$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
-			$options = $this->requestOptions($this->config['tautulliURL'], false, $this->config['homepageTautulliRefresh']);
+			$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
 			$homestats = Requests::get($homestatsUrl, [], $options);
 			if ($homestats->success) {
 				$homestats = json_decode($homestats->body, true);
@@ -306,7 +171,7 @@ trait TautulliHomepageItem
 				$this->cacheImage($url . '/images/platforms/' . $platform . '.svg', 'tautulli-' . $platform, 'svg');
 			}
 			$libstatsUrl = $apiURL . '&cmd=get_libraries_table';
-			$options = $this->requestOptions($this->config['tautulliURL'], false, $this->config['homepageTautulliRefresh']);
+			$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
 			$libstats = Requests::get($libstatsUrl, [], $options);
 			if ($libstats->success) {
 				$libstats = json_decode($libstats->body, true);

+ 29 - 113
api/homepage/trakt.php

@@ -14,15 +14,13 @@ trait TraktHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'docs' => 'https://docs.organizr.app/books/setup-features/page/trakt',
 			'debug' => true,
-			'settings' => array(
-				'About' => array(
-					array(
-						'type' => 'html',
+			'settings' => [
+				'About' => [
+					$this->settingsOption('html', null, [
 						'override' => 12,
-						'label' => '',
 						'html' => '
 							<div class="panel panel-default">
 								<div class="panel-wrapper collapse in">
@@ -36,112 +34,30 @@ trait TraktHomepageItem
 									</div>
 								</div>
 							</div>'
-					),
-				),
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageTraktEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageTraktEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTraktAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTraktAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'traktClientId',
-						'label' => 'Client Id',
-						'value' => $this->config['traktClientId'],
-						'help' => 'Please make sure to use local IP address and port - You also may use local dns name too.'
-					),
-					array(
-						'type' => 'password-alt',
-						'name' => 'traktClientSecret',
-						'label' => 'Client Secret',
-						'value' => $this->config['traktClientSecret']
-					),
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before clicking button'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-user',
-						'class' => 'pull-right',
-						'text' => 'Connect Account',
-						'attr' => 'onclick="openOAuth(\'trakt\')"'
-					),
-				),
-				'Calendar' => array(
-					array(
-						'type' => 'number',
-						'name' => 'calendarStartTrakt',
-						'label' => '# of Days Before',
-						'value' => $this->config['calendarStartTrakt'],
-						'placeholder' => '',
-						'help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API'
-					),
-					array(
-						'type' => 'number',
-						'name' => 'calendarEndTrakt',
-						'label' => '# of Days After',
-						'value' => $this->config['calendarEndTrakt'],
-						'placeholder' => '',
-						'help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarFirstDay',
-						'label' => 'Start Day',
-						'value' => $this->config['calendarFirstDay'],
-						'options' => $this->daysOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarDefault',
-						'label' => 'Default View',
-						'value' => $this->config['calendarDefault'],
-						'options' => $this->calendarDefaultOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarTimeFormat',
-						'label' => 'Time Format',
-						'value' => $this->config['calendarTimeFormat'],
-						'options' => $this->timeFormatOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLocale',
-						'label' => 'Locale',
-						'value' => $this->config['calendarLocale'],
-						'options' => $this->calendarLocaleOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarLimit',
-						'label' => 'Items Per Day',
-						'value' => $this->config['calendarLimit'],
-						'options' => $this->limitOptions()
-					),
-					array(
-						'type' => 'select',
-						'name' => 'calendarRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['calendarRefresh'],
-						'options' => $this->timeOptions()
-					)
-				)
-			)
-		);
+					]),
+				],
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageTraktEnabled'),
+					$this->settingsOption('auth', 'homepageTraktAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('input', 'traktClientId', ['label' => 'Client Id']),
+					$this->settingsOption('password-alt', 'traktClientSecret', ['label' => 'Client Secret']),
+					$this->settingsOption('blank'),
+					$this->settingsOption('button', '', ['label' => 'Please Save before clicking button', 'icon' => 'fa fa-user', 'class' => 'pull-right', 'text' => 'Connect Account', 'attr' => 'onclick="openOAuth(\'trakt\')"']),
+				],
+				'Calendar' => [
+					$this->settingsOption('calendar-start', 'calendarStartTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
+					$this->settingsOption('calendar-end', 'calendarEndTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
+					$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
+					$this->settingsOption('calendar-default-view', 'calendarDefault'),
+					$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
+					$this->settingsOption('calendar-locale', 'calendarLocale'),
+					$this->settingsOption('calendar-limit', 'calendarLimit'),
+					$this->settingsOption('refresh', 'calendarRefresh'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -186,7 +102,7 @@ trait TraktHomepageItem
 			'trakt-api-key' => $this->config['traktClientId']
 		];
 		$url = $this->qualifyURL('https://api.trakt.tv/calendars/my/shows/' . $startDate . '/' . $totalDays . '?extended=full');
-		$options = $this->requestOptions($url, false, $this->config['calendarRefresh']);
+		$options = $this->requestOptions($url, $this->config['calendarRefresh']);
 		try {
 			$response = Requests::get($url, $headers, $options);
 			if ($response->success) {

+ 29 - 90
api/homepage/transmission.php

@@ -14,94 +14,33 @@ trait TransmissionHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageTransmissionEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageTransmissionEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageTransmissionAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageTransmissionAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'transmissionURL',
-						'label' => 'URL',
-						'value' => $this->config['transmissionURL'],
-						'help' => 'Please do not included /web in URL.  Please make sure to use local IP address and port - You also may use local dns name too.',
-						'placeholder' => 'http(s)://hostname:port'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'transmissionDisableCertCheck',
-						'label' => 'Disable Certificate Check',
-						'value' => $this->config['transmissionDisableCertCheck']
-					),
-					array(
-						'type' => 'input',
-						'name' => 'transmissionUsername',
-						'label' => 'Username',
-						'value' => $this->config['transmissionUsername']
-					),
-					array(
-						'type' => 'password',
-						'name' => 'transmissionPassword',
-						'label' => 'Password',
-						'value' => $this->config['transmissionPassword']
-					)
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'transmissionHideSeeding',
-						'label' => 'Hide Seeding',
-						'value' => $this->config['transmissionHideSeeding']
-					), array(
-						'type' => 'switch',
-						'name' => 'transmissionHideCompleted',
-						'label' => 'Hide Completed',
-						'value' => $this->config['transmissionHideCompleted']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'transmissionRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['transmissionRefresh'],
-						'options' => $this->timeOptions()
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'transmissionCombine',
-						'label' => 'Add to Combined Downloader',
-						'value' => $this->config['transmissionCombine']
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'transmission\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageTransmissionEnabled'),
+					$this->settingsOption('auth', 'homepageTransmissionAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'transmissionURL', ['help' => 'Please do not included /web in URL.  Please make sure to use local IP address and port - You also may use local dns name too.']),
+					$this->settingsOption('blank'),
+					$this->settingsOption('username', 'transmissionUsername'),
+					$this->settingsOption('password', 'transmissionPassword'),
+					$this->settingsOption('disable-cert-check', 'transmissionDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'transmissionUseCustomCertificate'),
+				],
+				'Misc Options' => [
+					$this->settingsOption('hide-seeding', 'transmissionHideSeeding'),
+					$this->settingsOption('hide-completed', 'transmissionHideCompleted'),
+					$this->settingsOption('refresh', 'transmissionRefresh'),
+					$this->settingsOption('combine', 'transmissionCombine'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'transmission'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -115,8 +54,8 @@ trait TransmissionHomepageItem
 		$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
 		$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
 		try {
-			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionRefresh']);
-			$response = Requests::get($url, array(), $options);
+			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
+			$response = Requests::get($url, [], $options);
 			if ($response->headers['x-transmission-session-id']) {
 				$headers = array(
 					'X-Transmission-Session-Id' => $response->headers['x-transmission-session-id'],
@@ -203,7 +142,7 @@ trait TransmissionHomepageItem
 		$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
 		$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
 		try {
-			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionRefresh']);
+			$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
 			$response = Requests::get($url, array(), $options);
 			if ($response->headers['x-transmission-session-id']) {
 				$headers = array(

+ 28 - 87
api/homepage/unifi.php

@@ -14,90 +14,32 @@ trait UnifiHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageUnifiEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageUnifiEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageUnifiAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageUnifiAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'unifiURL',
-						'label' => 'URL',
-						'value' => $this->config['unifiURL'],
-						'help' => 'URL for Unifi',
-						'placeholder' => 'Unifi API URL'
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'input',
-						'name' => 'unifiUsername',
-						'label' => 'Username',
-						'value' => $this->config['unifiUsername'],
-						'help' => 'Username is case-sensitive',
-					),
-					array(
-						'type' => 'password',
-						'name' => 'unifiPassword',
-						'label' => 'Password',
-						'value' => $this->config['unifiPassword']
-					),
-					array(
-						'type' => 'input',
-						'name' => 'unifiSiteName',
-						'label' => 'Site Name (Not for UnifiOS)',
-						'value' => $this->config['unifiSiteName'],
-						'help' => 'Site Name - not Site ID nor Site Description',
-					),
-					array(
-						'type' => 'button',
-						'label' => 'Grab Unifi Site (Not for UnifiOS)',
-						'icon' => 'fa fa-building',
-						'text' => 'Get Unifi Site',
-						'attr' => 'onclick="getUnifiSite()"'
-					),
-				),
-				'Misc Options' => array(
-					array(
-						'type' => 'select',
-						'name' => 'homepageUnifiRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageUnifiRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-				'Test Connection' => array(
-					array(
-						'type' => 'blank',
-						'label' => 'Please Save before Testing'
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-flask',
-						'class' => 'pull-right',
-						'text' => 'Test Connection',
-						'attr' => 'onclick="testAPIConnection(\'unifi\')"'
-					),
-				)
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageUnifiEnabled'),
+					$this->settingsOption('auth', 'homepageUnifiAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('url', 'unifiURL'),
+					$this->settingsOption('blank'),
+					$this->settingsOption('disable-cert-check', 'unifiDisableCertCheck'),
+					$this->settingsOption('use-custom-certificate', 'unifiUseCustomCertificate'),
+					$this->settingsOption('username', 'unifiUsername', ['help' => 'Username is case-sensitive']),
+					$this->settingsOption('password', 'unifiPassword'),
+					$this->settingsOption('input', 'unifiSiteName', ['label' => 'Site Name (Not for UnifiOS)', 'help' => 'Site Name - not Site ID nor Site Description']),
+					$this->settingsOption('button', '', ['label' => 'Grab Unifi Site (Not for UnifiOS)', 'icon' => 'fa fa-building', 'text' => 'Get Unifi Site', 'attr' => 'onclick="getUnifiSite()"']),
+				],
+				'Misc Options' => [
+					$this->settingsOption('refresh', 'homepageUnifiRefresh'),
+				],
+				'Test Connection' => [
+					$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
+					$this->settingsOption('test', 'unifi'),
+				]
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -159,7 +101,7 @@ trait UnifiHomepageItem
 		}
 		$url = $this->qualifyURL($this->config['unifiURL']);
 		try {
-			$options = array('verify' => false, 'verifyname' => false, 'follow_redirects' => false);
+			$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
 			$data = array(
 				'username' => $this->config['unifiUsername'],
 				'password' => $this->decrypt($this->config['unifiPassword']),
@@ -210,7 +152,7 @@ trait UnifiHomepageItem
 		}
 		$api['content']['unifi'] = array();
 		$url = $this->qualifyURL($this->config['unifiURL']);
-		$options = array('verify' => false, 'verifyname' => false, 'follow_redirects' => true);
+		$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
 		$data = array(
 			'username' => $this->config['unifiUsername'],
 			'password' => $this->decrypt($this->config['unifiPassword']),
@@ -267,8 +209,7 @@ trait UnifiHomepageItem
 		}
 		$api['content']['unifi'] = array();
 		$url = $this->qualifyURL($this->config['unifiURL']);
-		$extras = array('verify' => false, 'verifyname' => false, 'follow_redirects' => true);
-		$options = $this->requestOptions($url, true, $this->config['homepageUnifiRefresh'], $extras);
+		$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
 		$data = array(
 			'username' => $this->config['unifiUsername'],
 			'password' => $this->decrypt($this->config['unifiPassword']),

+ 27 - 114
api/homepage/weather.php

@@ -14,114 +14,30 @@ trait WeatherHomepageItem
 		if ($infoOnly) {
 			return $homepageInformation;
 		}
-		$homepageSettings = array(
+		$homepageSettings = [
 			'debug' => true,
-			'settings' => array(
-				'Enable' => array(
-					array(
-						'type' => 'switch',
-						'name' => 'homepageWeatherAndAirEnabled',
-						'label' => 'Enable',
-						'value' => $this->config['homepageWeatherAndAirEnabled']
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageWeatherAndAirAuth',
-						'label' => 'Minimum Authentication',
-						'value' => $this->config['homepageWeatherAndAirAuth'],
-						'options' => $this->groupOptions
-					)
-				),
-				'Connection' => array(
-					array(
-						'type' => 'input',
-						'name' => 'homepageWeatherAndAirLatitude',
-						'label' => 'Latitude',
-						'value' => $this->config['homepageWeatherAndAirLatitude'],
-						'help' => 'Please enter full latitude including minus if needed'
-					),
-					array(
-						'type' => 'input',
-						'name' => 'homepageWeatherAndAirLongitude',
-						'label' => 'Longitude',
-						'value' => $this->config['homepageWeatherAndAirLongitude'],
-						'help' => 'Please enter full longitude including minus if needed'
-					),
-					array(
-						'type' => 'blank',
-						'label' => ''
-					),
-					array(
-						'type' => 'button',
-						'label' => '',
-						'icon' => 'fa fa-search',
-						'class' => 'pull-right',
-						'text' => 'Need Help With Coordinates?',
-						'attr' => 'onclick="showLookupCoordinatesModal()"'
-					),
-				),
-				'Options' => array(
-					array(
-						'type' => 'input',
-						'name' => 'homepageWeatherAndAirWeatherHeader',
-						'label' => 'Title',
-						'value' => $this->config['homepageWeatherAndAirWeatherHeader'],
-						'help' => 'Sets the title of this homepage module',
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageWeatherAndAirWeatherHeaderToggle',
-						'label' => 'Toggle Title',
-						'value' => $this->config['homepageWeatherAndAirWeatherHeaderToggle'],
-						'help' => 'Shows/hides the title of this homepage module'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageWeatherAndAirWeatherEnabled',
-						'label' => 'Enable Weather',
-						'value' => $this->config['homepageWeatherAndAirWeatherEnabled'],
-						'help' => 'Toggles the view module for Weather'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageWeatherAndAirAirQualityEnabled',
-						'label' => 'Enable Air Quality',
-						'value' => $this->config['homepageWeatherAndAirAirQualityEnabled'],
-						'help' => 'Toggles the view module for Air Quality'
-					),
-					array(
-						'type' => 'switch',
-						'name' => 'homepageWeatherAndAirPollenEnabled',
-						'label' => 'Enable Pollen',
-						'value' => $this->config['homepageWeatherAndAirPollenEnabled'],
-						'help' => 'Toggles the view module for Pollen'
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageWeatherAndAirUnits',
-						'label' => 'Unit of Measurement',
-						'value' => $this->config['homepageWeatherAndAirUnits'],
-						'options' => array(
-							array(
-								'name' => 'Imperial',
-								'value' => 'imperial'
-							),
-							array(
-								'name' => 'Metric',
-								'value' => 'metric'
-							)
-						)
-					),
-					array(
-						'type' => 'select',
-						'name' => 'homepageWeatherAndAirRefresh',
-						'label' => 'Refresh Seconds',
-						'value' => $this->config['homepageWeatherAndAirRefresh'],
-						'options' => $this->timeOptions()
-					),
-				),
-			)
-		);
+			'settings' => [
+				'Enable' => [
+					$this->settingsOption('enable', 'homepageWeatherAndAirEnabled'),
+					$this->settingsOption('auth', 'homepageWeatherAndAirAuth'),
+				],
+				'Connection' => [
+					$this->settingsOption('input', 'homepageWeatherAndAirLatitude', ['label' => 'Latitude', 'help' => 'Please enter full latitude including minus if needed']),
+					$this->settingsOption('input', 'homepageWeatherAndAirLongitude', ['label' => 'Longitude', 'help' => 'Please enter full longitude including minus if needed']),
+					$this->settingsOption('blank'),
+					$this->settingsOption('button', null, ['type' => 'button', 'label' => '', 'icon' => 'fa fa-search', 'class' => 'pull-right', 'text' => 'Need Help With Coordinates?', 'attr' => 'onclick="showLookupCoordinatesModal()"']),
+				],
+				'Options' => [
+					$this->settingsOption('title', 'homepageWeatherAndAirWeatherHeader'),
+					$this->settingsOption('toggle-title', 'homepageWeatherAndAirWeatherHeaderToggle'),
+					$this->settingsOption('enable', 'homepageWeatherAndAirWeatherEnabled', ['label' => 'Enable Weather', 'help' => 'Toggles the view module for Weather']),
+					$this->settingsOption('enable', 'homepageWeatherAndAirAirQualityEnabled', ['label' => 'Enable Air Quality', 'help' => 'Toggles the view module for Air Quality']),
+					$this->settingsOption('enable', 'homepageWeatherAndAirPollenEnabled', ['label' => 'Enable Pollen', 'help' => 'Toggles the view module for Pollen']),
+					$this->settingsOption('select', 'homepageWeatherAndAirUnits', ['label' => 'Unit of Measurement', 'options' => [['name' => 'Imperial', 'value' => 'imperial'], ['name' => 'Metric', 'value' => 'metric']]]),
+					$this->settingsOption('refresh', 'homepageWeatherAndAirRefresh'),
+				],
+			]
+		];
 		return array_merge($homepageInformation, $homepageSettings);
 	}
 	
@@ -175,8 +91,7 @@ trait WeatherHomepageItem
 				return false;
 			}
 			$url = $this->qualifyURL('https://api.mapbox.com/geocoding/v5/mapbox.places/' . urlencode($query) . '.json?access_token=pk.eyJ1IjoiY2F1c2VmeCIsImEiOiJjazhyeGxqeXgwMWd2M2ZydWQ4YmdjdGlzIn0.R50iYuMewh1CnUZ7sFPdHA&limit=5&fuzzyMatch=true');
-			$options = array('verify' => false);
-			$response = Requests::get($url, array(), $options);
+			$response = Requests::get($url);
 			if ($response->success) {
 				$this->setAPIResponse('success', null, 200, json_decode($response->body));
 				return json_decode($response->body);
@@ -200,11 +115,9 @@ trait WeatherHomepageItem
 		$apiURL = $this->qualifyURL('https://api.breezometer.com/');
 		$info = '&lat=' . $this->config['homepageWeatherAndAirLatitude'] . '&lon=' . $this->config['homepageWeatherAndAirLongitude'] . '&units=' . $this->config['homepageWeatherAndAirUnits'] . '&key=' . $this->config['breezometerToken'];
 		try {
-			$headers = array();
-			$options = array();
 			if ($this->config['homepageWeatherAndAirWeatherEnabled']) {
 				$endpoint = '/weather/v1/forecast/hourly?hours=120&metadata=true';
-				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				$response = Requests::get($apiURL . $endpoint . $info);
 				if ($response->success) {
 					$apiData = json_decode($response->body, true);
 					$api['content']['weather'] = ($apiData['error'] === null) ? $apiData : false;
@@ -213,7 +126,7 @@ trait WeatherHomepageItem
 			}
 			if ($this->config['homepageWeatherAndAirAirQualityEnabled']) {
 				$endpoint = '/air-quality/v2/current-conditions?features=breezometer_aqi,local_aqi,health_recommendations,sources_and_effects,dominant_pollutant_concentrations,pollutants_concentrations,pollutants_aqi_information&metadata=true';
-				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				$response = Requests::get($apiURL . $endpoint . $info);
 				if ($response->success) {
 					$apiData = json_decode($response->body, true);
 					$api['content']['air'] = ($apiData['error'] === null) ? $apiData : false;
@@ -222,7 +135,7 @@ trait WeatherHomepageItem
 			}
 			if ($this->config['homepageWeatherAndAirPollenEnabled']) {
 				$endpoint = '/pollen/v2/forecast/daily?features=plants_information,types_information&days=1&metadata=true';
-				$response = Requests::get($apiURL . $endpoint . $info, $headers, $options);
+				$response = Requests::get($apiURL . $endpoint . $info);
 				if ($response->success) {
 					$apiData = json_decode($response->body, true);
 					$api['content']['pollen'] = ($apiData['error'] === null) ? $apiData : false;

+ 11 - 0
api/v2/routes/certificate.php

@@ -0,0 +1,11 @@
+<?php
+$app->post('/certificate/custom', function ($request, $response, $args) {
+	$Organizr = ($request->getAttribute('Organizr')) ?? new Organizr();
+	if ($Organizr->qualifyRequest(1, true)) {
+		$Organizr->uploadCert();
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});

+ 5 - 3
api/vendor/kryptonit3/couchpotato/src/CouchPotato.php

@@ -11,13 +11,15 @@ class CouchPotato
     protected $apiKey;
     protected $httpAuthUsername;
     protected $httpAuthPassword;
+    public $options;
 
-    public function __construct($url, $apiKey, $httpAuthUsername = null, $httpAuthPassword = null)
+    public function __construct($url, $apiKey, $httpAuthUsername = null, $httpAuthPassword = null, $options = [])
     {
         $this->url = rtrim($url, '/\\'); // Example: http://127.0.0.1:5050 (no trailing forward-backward slashes)
         $this->apiKey = $apiKey;
         $this->httpAuthUsername = $httpAuthUsername;
-        $this->httpAuthPassword = $httpAuthPassword;
+	    $this->httpAuthPassword = $httpAuthPassword;
+	    $this->options = $options;
     }
 
     /**
@@ -1226,7 +1228,7 @@ class CouchPotato
      */
     protected function _request(array $params)
     {
-        $client = new Client();
+	    $client = new Client($this->options);
 
         if ( $params['type'] == 'get' ) {
             $url = $this->url . '/api/' . $this->apiKey . '/' . $params['uri'] . '?' . http_build_query($params['data']);

+ 6 - 4
api/vendor/kryptonit3/sickrage/src/SickRage.php

@@ -11,13 +11,15 @@ class SickRage
     protected $apiKey;
     protected $httpAuthUsername;
     protected $httpAuthPassword;
+    public $options;
 
-    public function __construct($url, $apiKey, $httpAuthUsername = null, $httpAuthPassword = null)
+    public function __construct($url, $apiKey, $httpAuthUsername = null, $httpAuthPassword = null, $options = [])
     {
         $this->url = rtrim($url, '/\\'); // Example: http://127.0.0.1:8081 (no trailing forward-backward slashes)
         $this->apiKey = $apiKey;
         $this->httpAuthUsername = $httpAuthUsername;
-        $this->httpAuthPassword = $httpAuthPassword;
+	    $this->httpAuthPassword = $httpAuthPassword;
+	    $this->options = $options;
     }
 
     /**
@@ -1258,7 +1260,7 @@ class SickRage
      */
     protected function _request(array $params)
     {
-        $client = new Client();
+	    $client = new Client($this->options);
 
         if ( $params['type'] == 'get' ) {
             $url = $this->url . '/api/' . $this->apiKey . '/?cmd=' . $params['uri'] . '&' . http_build_query($params['data']);
@@ -1273,4 +1275,4 @@ class SickRage
             return $client->get($url, $options);
         }
     }
-}
+}

+ 5 - 3
api/vendor/kryptonit3/sonarr/src/Sonarr.php

@@ -11,14 +11,16 @@ class Sonarr
     protected $apiKey;
     protected $httpAuthUsername;
     protected $httpAuthPassword;
+    public $options;
 
-    public function __construct($url, $apiKey, $type = 'sonarr', $httpAuthUsername = null, $httpAuthPassword = null)
+    public function __construct($url, $apiKey, $type = 'sonarr', $httpAuthUsername = null, $httpAuthPassword = null, $options = [])
     {
         $this->url = rtrim($url, '/\\'); // Example: http://127.0.0.1:8989 (no trailing forward-backward slashes)
 	    $this->apiKey = $apiKey;
 	    $this->type = strtolower($type);
         $this->httpAuthUsername = $httpAuthUsername;
-        $this->httpAuthPassword = $httpAuthPassword;
+	    $this->httpAuthPassword = $httpAuthPassword;
+	    $this->options = $options;
     }
 
     /**
@@ -596,7 +598,7 @@ class Sonarr
      */
     protected function _request(array $params)
     {
-        $client = new Client(['verify' => getCert()]);
+        $client = new Client($this->options);
         $options = [
             'headers' => [
                 'X-Api-Key' => $this->apiKey

+ 2 - 1
css/dark.css

@@ -3127,7 +3127,7 @@ ul.common li a:hover {
     display: table;
 }
 .vtabs .tabs-vertical {
-    width: 150px;
+    min-width: 150px;
     border-right: 1px solid rgba(120, 130, 140, 0.13);
     display: table-cell;
     vertical-align: top;
@@ -16172,6 +16172,7 @@ ul.pro-amenities li span i {
 @media (max-width: 480px) {
     .vtabs .tabs-vertical {
         width: auto;
+        min-width: 100px;
     }
     .stat-item {
         padding-right: 0;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
css/dark.min.css


+ 0 - 1
index.php

@@ -233,7 +233,6 @@ $Organizr = new Organizr();
 <script src='plugins/bower_components/overlayScrollbars/jquery.overlayScrollbars.min.js'></script>
 <script src='plugins/bower_components/custombox/dist/custombox.min.js'></script>
 <script src="js/arrive.min.js"></script>
-<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
 <script src="js/functions.js?v=<?php echo $Organizr->fileHash; ?>"></script>
 <script src="js/custom.min.js?v=<?php echo $Organizr->fileHash; ?>"></script>
 <script id="custom-theme-javascript"></script>

+ 52 - 7
js/functions.js

@@ -1693,19 +1693,20 @@ function copyHomepageJSON(item){
 	});
 }
 function homepageItemFormHTML(v){
-	let docs = (typeof v.docs == 'undefined') ? '' : `<small class="pl-5"><a class="btn btn-sm btn-primary waves-effect waves-light" href="${v.docs}" target="_blank"> <i class="icon-docs m-r-5"></i> <span lang="en">Support Docs</span></a></small>`;
+	let docs = (typeof v.docs == 'undefined') ? '' : `<small class="pull-right m-r-5"><a data-toggle="tooltip" title="Goto Support Doc" data-placement="bottom" class="btn btn-circle btn-primary waves-effect waves-light" href="${v.docs}" target="_blank"> <i class="fa-fw fa fa-question-circle"></i></a></small>`;
 	let debug = (typeof v.debug == 'undefined') ? false : true;
 	debug = (debug === true) ? (v.debug) : false;
-	debug = (debug === true) ? `<small class="pl-5"><a href="javascript:copyHomepageJSON('${v.name}')" class="btn btn-sm btn-info waves-effect waves-light copyHomepageJSON"> <i class="ti-clipboard m-r-5"></i> <span lang="en">Copy JSON</span></a></small>` : '';
+	debug = (debug === true) ? `<small class="pull-right m-r-5"><a data-toggle="tooltip" title="Copy JSON Settings" data-placement="bottom" href="javascript:copyHomepageJSON('${v.name}')" class="btn btn-circle btn-info waves-effect waves-light copyHomepageJSON"> <i class="fa-fw ti-clipboard"></i></a></small>` : '';
 	return `
 	<a id="editHomepageItemCall" href="#editHomepageItemDiv" class="hidden">homepage item</a>
 	<form id="homepage-`+v.name+`-form" class="white-popup mfp-with-anim homepageForm addFormTick">
 		<fieldset style="border:0;" class="col-md-10 col-md-offset-1">
             <div class="panel bg-org panel-info">
                 <div class="panel-heading">
-                    <span class="" lang="en">`+v.name+`</span>${docs}${debug}
-                    <button type="button" class="btn bg-org btn-circle close-popup pull-right close-editHomepageItemDiv"><i class="fa fa-times"></i> </button>
-                    <button id="homepage-`+v.name+`-form-save" onclick="submitSettingsForm('homepage-`+v.name+`-form', true)" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right hidden animated loop-animation rubberBand m-r-20" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save</span></button>
+                    <span class="" lang="en">`+v.name+`</span>
+                    <button data-toggle="tooltip" title="Close" data-placement="bottom"  type="button" class="btn btn-default btn-circle close-popup pull-right close-editHomepageItemDiv"><i class="fa fa-times"></i> </button>
+                    ${docs}${debug}
+                    <button data-toggle="tooltip" title="Save" data-placement="bottom" id="homepage-`+v.name+`-form-save" onclick="submitSettingsForm('homepage-`+v.name+`-form', true)" class="btn btn-success btn-circle waves-effect waves-light pull-right hidden animated loop-animation rubberBand m-r-5" type="button"><span class=""><i class="fa fa-save"></i></span></button>
                 </div>
                 <div class="panel-wrapper collapse in" aria-expanded="true">
                     <div class="bg-org">
@@ -4927,8 +4928,8 @@ function buildStreamItem(array,source){
 						<h3 class="box-title pull-left p-l-10 elip" style="width:90%">`+v.nowPlayingTitle+`</h3>
 						<h3 class="box-title pull-right vertical-middle" style="width:10%"><i class="icon-control-`+v.state+` fa-fw text-info" style=""></i></h3>
 						<div class="clearfix"></div>
-						<small class="pull-left p-l-10"><i class="`+icon+` fa-fw text-info"></i>`+v.nowPlayingBottom+`</small>
-						<small class="pull-right p-r-10">`+v.user+` <i class="icon-user"></i></small>
+						<small class="pull-left p-l-10 w-50 elip"><span class="pull-left"><i class="`+icon+` fa-fw text-info"></i>`+v.nowPlayingBottom+`</span></small>
+						<small class="pull-right p-r-10 w-50 elip"><span class="pull-right">`+v.user+` <i class="icon-user"></i></span></small>
 						<br>
 					</div>
 				</div>
@@ -10774,6 +10775,50 @@ function tabShit(){
 
 }
 
+function msToTime(s) {
+	let pad = (n, z = 2) => ('00' + n).slice(-z);
+	let hours = (pad(s/3.6e6|0) !== '00') ? pad(s/3.6e6|0) + ':' : '';
+	let mins = pad((s%3.6e6)/6e4 | 0) + ':';
+	let secs = pad((s%6e4)/1000|0);
+	let ms = pad(s%1000, 3);
+	if(ms >= '500'){ secs = pad(parseFloat(secs) + 1, 2); }
+	return hours+mins+secs;
+}
+
+function clickMenuItem(selector){
+	if($(selector).length >= 1){
+		$(selector).click();
+	}else{
+		$('body').arrive(selector, {onceOnly: true}, function() {
+			$(selector).click();
+		});
+	}
+
+}
+function shortcut(selectors = ''){
+	let timeout = 200;
+	if(typeof selectors == 'string') {
+		if(selectors == ''){
+			selectors = [];
+		}else{
+			switch (selectors){
+				case 'custom-cert':
+					selectors = ['#settings-main-system-settings-anchor','#settings-settings-main-anchor','a[href$="Certificate"]'];
+					break;
+				default:
+					selectors = ['#settings-main-system-settings-anchor'];
+
+			}
+		}
+	}
+	selectors.forEach(function(selector){
+		timeout = timeout + 200;
+		setTimeout(function(){
+			clickMenuItem(selector);
+		}, timeout);
+	});
+}
+
 function launch(){
 	console.info('https://docs.organizr.app/books/setup-features/page/organizr-20--%3E-21-migration-guide');
 	organizrConsole('API V2 API','If you see a 404 Error for api/v2/launch below this line, you have not setup the new location block... See URL above this line', 'error');

+ 7 - 0
js/version.json

@@ -411,5 +411,12 @@
     "new": "",
     "fixed": "Local URL being set as regual URL|Line spacing|updated Overseerr logo",
     "notes": "See update notes for version 2.1.472"
+  },
+  "2.1.496": {
+    "date": "2021-08-27 21:40",
+    "title": "Weekly Update - New Feature",
+    "new": "Self Signed SSL Cert option to all homepage items (#1177)|shortcut function for menu clicking - WIP",
+    "fixed": "move google js to load only when config value setup (#1699)|tooltips to homepage settings ui buttons|First IPA typo to Free IPA|typo and sort LDAP options|now playing spacing",
+    "notes": ""
   }
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است