From a9263bc9030d13ba61ed61917ed4effbf9200f69 Mon Sep 17 00:00:00 2001
From: Christopher York
Date: Sun, 23 Feb 2014 21:23:16 -0600
Subject: [PATCH 01/26] Pushing the new v3.x.x series WHMCS WebsitePanel module
---
WebsitePanel.WHMCSModule/changelog.html | 26 +
WebsitePanel.WHMCSModule/changelog.log | 6 -
WebsitePanel.WHMCSModule/how-to-upgrade.html | 14 +
.../includes/hooks/websitepanel_addons.php | 137 --
.../includes/hooks/websitepanel_sync.php | 114 --
.../addons/websitepanel_addons/hooks.php | 128 ++
.../websitepanel_addons.php | 390 +++---
.../addons/websitepanel_sync/hooks.php | 136 ++
.../websitepanel_sync/websitepanel_sync.php | 148 ++-
.../servers/websitepanel/clientarea.tpl | 2 +
.../servers/websitepanel/enterpriseserver.php | 502 ++++++++
.../servers/websitepanel/functions.php | 146 +++
.../english.php} | 102 +-
.../websitepanel/websitepanel.class.php | 438 -------
.../websitepanel/websitepanel.functions.php | 197 ---
.../servers/websitepanel/websitepanel.php | 1097 ++++++++++-------
WebsitePanel.WHMCSModule/readme.txt | 19 -
17 files changed, 1895 insertions(+), 1707 deletions(-)
create mode 100644 WebsitePanel.WHMCSModule/changelog.html
delete mode 100644 WebsitePanel.WHMCSModule/changelog.log
create mode 100644 WebsitePanel.WHMCSModule/how-to-upgrade.html
delete mode 100644 WebsitePanel.WHMCSModule/includes/hooks/websitepanel_addons.php
delete mode 100644 WebsitePanel.WHMCSModule/includes/hooks/websitepanel_sync.php
create mode 100644 WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
create mode 100644 WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/hooks.php
create mode 100644 WebsitePanel.WHMCSModule/modules/servers/websitepanel/clientarea.tpl
create mode 100644 WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
create mode 100644 WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
rename WebsitePanel.WHMCSModule/modules/servers/websitepanel/{websitepanel.errorcodes.php => lang/english.php} (57%)
delete mode 100644 WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.class.php
delete mode 100644 WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.functions.php
delete mode 100644 WebsitePanel.WHMCSModule/readme.txt
diff --git a/WebsitePanel.WHMCSModule/changelog.html b/WebsitePanel.WHMCSModule/changelog.html
new file mode 100644
index 00000000..6a313d0a
--- /dev/null
+++ b/WebsitePanel.WHMCSModule/changelog.html
@@ -0,0 +1,26 @@
+
+
+
+
+WHMCS WebsitePanel Server Module / Addons
+
+
+
+WHMCS WebsitePanel Server Module
+DO NOT CONTACT WHMCS FOR SUPPORT WITH THIS MODULE
+Changelog / Updates
+2-23-2014 (v3.0.2)
+
+ - HTTP communications now support GZIP compression (enabled by default)
+
+2-22-2014 (v3.0.1)
+
+ - Usage calculation now works with multiple WebsitePanel ES installations
+ - Large amount of refactoring
+ - Addon hooks (WSP Addons and WSP Client Sync) now execute only when the addon is activated
+ - Improved logging
+ - Client area "one-click login" is now a POST form instead of a link. Credentials are no longer passed through a standard HREF link
+ - Admin area "one-click login" is now a link directly to the WebsitePanel account rather than a login link to WebsitePanel
+
+
+
diff --git a/WebsitePanel.WHMCSModule/changelog.log b/WebsitePanel.WHMCSModule/changelog.log
deleted file mode 100644
index e570fc7b..00000000
--- a/WebsitePanel.WHMCSModule/changelog.log
+++ /dev/null
@@ -1,6 +0,0 @@
-04/16/2013 (1.3)
-* Some refactoring done
-
-01/29/2013 (1.2)
-* WebsitePanel addons no longer require the storage of the Enterprise Server credentials
-* Error codes are not properly resolved to their text responses
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/how-to-upgrade.html b/WebsitePanel.WHMCSModule/how-to-upgrade.html
new file mode 100644
index 00000000..272d88e0
--- /dev/null
+++ b/WebsitePanel.WHMCSModule/how-to-upgrade.html
@@ -0,0 +1,14 @@
+WHMCS WebsitePanel Server Module / Addons
+WHMCS WebsitePanel Server Module
+DO NOT CONTACT WHMCS FOR SUPPORT WITH THIS MODULE
+Update from the previous available version... (The version prior to 2-23-2014)
+
+ - Backup all files / directories before deleting!!
+ - Delete includes/hooks/websitepanel_sync.php
+ - Delete includes/hooks/websitepanel_addons.php
+ - Delete modules/servers/websitepanel
+ - Delete modules/addons/websitepanel_sync
+ - Delete modules/addons/websitepanel_addons
+ - Upload the contents of the ZIP to your WHMCS root
+
+All new files should have version 3.x.x listed in their headers
diff --git a/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_addons.php b/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_addons.php
deleted file mode 100644
index b8a0fe99..00000000
--- a/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_addons.php
+++ /dev/null
@@ -1,137 +0,0 @@
- 0)
- {
- // Include the WebsitePanel ES Class
- require_once(ROOTDIR . '/modules/servers/websitepanel/websitepanel.class.php');
- require_once(ROOTDIR . '/modules/servers/websitepanel/websitepanel.functions.php');
-
- // Retrieve the WebsitePanel Addons module settings
- $modSettings = websitepanel_addons_GetSettings();
- $srvSettings = websitepanel_GetServerSettings();
- if (empty($modSettings['serverhost']) || empty($modSettings['serverport']) || empty($srvSettings['username']) || empty($srvSettings['password']))
- {
- // The module is disabled or has not yet been configured - stop
- return;
- }
-
- // Get the associated WebsitePanel username from WHMCS
- $results = select_query('tblhosting', 'username', array('id' => $params['serviceid']));
- $username = mysql_fetch_array($results);
- $username = $username['username'];
- if (empty($username))
- {
- // The username is required - if missing we cannot continue
- return;
- }
-
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($srvSettings['username'], $srvSettings['password'], $modSettings['serverhost'], $modSettings['serverport'], (($modSettings['serversecured']) == 'on' ? TRUE : FALSE));
-
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return;
- }
-
- // Get the user's current WebsitePanel hosting space Id (Hosting Plan)
- $package = $wsp->getUserPackages($user['UserId']);
- $packageId = $package['PackageId'];
- if (empty($packageId))
- {
- return;
- }
-
- // Get the associated WebsitePanel addon id
- $results = select_query('mod_wspaddons', 'wsp_id,is_ipaddress', array('whmcs_id' => $params['addonid']));
- $addon = mysql_fetch_array($results);
- $addonPlanId = $addon['wsp_id'];
- $addonIsIpAddress = $addon['is_ipaddress'];
-
- // Add the Addon Plan to the customer's WebsitePanel package / hosting space
- $results = $wsp->addPackageAddonById($packageId, $addonPlanId);
-
- // Check the results to verify that the addon has been successfully allocated
- if ($results['Result'] > 0)
- {
- // If this addon is an IP address addon - attempt to randomly allocate an IP address to the customer's hosting space
- if ($addonIsIpAddress)
- {
- $wsp->packageAllocateIpAddress($packageId);
- }
- }
- }
-}
-
-/* Addon Activation - WebsitePanel */
-add_hook('AddonActivation', 1, 'websitepanel_addons_AddonActivation');
-
-/* Addon Activation - WebsitePanel */
-add_hook('AddonActivated', 1, 'websitepanel_addons_AddonActivation');
-
-/**
- * websitepanel_addons_GetSettings
- *
- * @access public
- * @return array
- */
-function websitepanel_addons_GetSettings()
-{
- $settings = array();
-
- // Retrieve the settings from the modules configuration table
- $results = select_query('tbladdonmodules', 'setting,value', array('module' => 'websitepanel_addons'));
- while (($row = mysql_fetch_array($results)) != false)
- {
- $settings[$row[0]] = $row[1];
- }
- return $settings;
-}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_sync.php b/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_sync.php
deleted file mode 100644
index d9129b62..00000000
--- a/WebsitePanel.WHMCSModule/includes/hooks/websitepanel_sync.php
+++ /dev/null
@@ -1,114 +0,0 @@
- 0)
- {
- // Include the WebsitePanel ES Class
- require_once(ROOTDIR . '/modules/servers/websitepanel/websitepanel.class.php');
- require_once(ROOTDIR . '/modules/servers/websitepanel/websitepanel.functions.php');
-
- // Retrieve the WebsitePanel Addons module settings
- $modSettings = websitepanel_sync_GetSettings();
- $srvSettings = websitepanel_GetServerSettings();
- if (empty($modSettings['serverhost']) || empty($modSettings['serverport']) || empty($srvSettings['username']) || empty($srvSettings['password']))
- {
- // The module is disabled or has not yet been configured - stop
- return;
- }
-
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($srvSettings['username'], $srvSettings['password'], $modSettings['serverhost'], $modSettings['serverport'], (($modSettings['serversecured']) == 'on' ? TRUE : FALSE));
-
- // Get all WSP users with the old email
- $items = (array)$wsp->getUsersPagedRecursive(1, 'Email', $params['olddata']['email'], 0, 0, '');
-
- // Load / parse the XML response
- $xml = simplexml_load_string($items['any']);
- $rootPath = $xml->NewDataSet;
-
- // Total number of elements to update
- $total = $rootPath->Table->Column1;
-
- // Begin updating WebsitePanel accounts
- for ($i = 0; $i < $total; $i++)
- {
- // Set the current root element and get the users details from WebsitePanel
- // We cannot use the details provided by the get_users_paged_recursive method as it does not return all the required elements to fully update the user
- $currentRoot = $rootPath->Table1->$i;
- $userDetails = (array)$wsp->getUserByUsername($currentRoot->Username);
-
- // Update the current user
- $wsp->updateUserDetails($userDetails['RoleId'], (($userDetails['RoleId'] == 2) ? 'Reseller' : 'User'), $userDetails['StatusId'], $userDetails['Status'], $userDetails['LoginStatusId'], $userDetails['LoginStatus'], $userDetails['FailedLogins'], $userDetails['UserId'], $userDetails['OwnerId'], $userDetails['Created'], $userDetails['Changed'], $userDetails['IsDemo'], $userDetails['IsPeer'], $currentRoot->Comments, $userDetails['Username'], $userDetails['Password'], $params['firstname'], $params['lastname'], $params['email'], $params['phonenumber'], $params['postcode'], '', '', '', '', $params['country'], $params['address1'] . (!empty($params['address2']) ? " {$params['address2']}" : ''), $params['city'], $params['state'], TRUE, $params['companyname'], (($userDetails['RoleId'] == 2) ? TRUE : FALSE));
-
- // Add log entry to client log
- logactivity("WebsitePanel Sync - Account {$currentRoot->Username} contact details updated successfully", $params['userid']);
- }
- }
-}
-
-/* Update Client Contact Details - WebsitePanel */
-add_hook('ClientEdit', 1, 'websitepanel_sync_ClientEdit');
-
-/**
- * websitepanel_addons_GetSettings
- *
- * @access public
- * @return array
- */
-function websitepanel_sync_GetSettings()
-{
- $settings = array();
-
- // Retrieve the settings from the modules configuration table
- $results = select_query('tbladdonmodules', 'setting,value', array('module' => 'websitepanel_sync'));
- while (($row = mysql_fetch_array($results)) != false)
- {
- $settings[$row['setting']] = $row['value'];
- }
- return $settings;
-}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
new file mode 100644
index 00000000..5982c6c6
--- /dev/null
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
@@ -0,0 +1,128 @@
+ 0)
+ {
+ // Get the results of the query
+ $row = mysql_fetch_assoc($result);
+
+ // Start processing the users addon
+ $username = $row['username'];
+ $serverUsername = $row['serverusername'];
+ $serverPassword = decrypt($row['serverpassword']);
+ $serverPort = $row['configoption6'];
+ $serverHost = empty($row['serverhostname']) ? $row['serverip'] : $row['serverhostname'];
+ $serverSecure = $row['serversecure'] == 'on' ? TRUE : FALSE;
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the UserId
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot allocate addon for unknown user");
+ }
+
+ // Get the user's package details from WebsitePanel - We need the PackageId
+ $package = $wsp->getUserPackages($user['UserId']);
+ $packageId = $package['PackageId'];
+
+ // Get the associated WebsitePanel addon id
+ $results = select_query('mod_wspaddons', 'wsp_id,is_ipaddress', array('whmcs_id' => $addonId));
+ $addon = mysql_fetch_array($results);
+ $addonPlanId = $addon['wsp_id'];
+ $addonIsIpAddress = $addon['is_ipaddress'];
+
+ // Add the Addon Plan to the customer's WebsitePanel package / hosting space
+ $results = $wsp->addPackageAddonById($packageId, $addonPlanId);
+
+ // Check the results to verify that the addon has been successfully allocated
+ if ($results['Result'] > 0)
+ {
+ // If this addon is an IP address addon - attempt to randomly allocate an IP address to the customer's hosting space
+ if ($addonIsIpAddress)
+ {
+ $wsp->allocatePackageIPAddresses($packageId);
+ }
+
+ // Add log entry to client log
+ logactivity("WebsitePanel Addon - Account {$username} addon successfully completed - Addon ID: {$addonId}", $userId);
+ }
+ else
+ {
+ // Add log entry to client log
+ throw new Exception("Unknown", $results['Result']);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "websitepanel_addons_AddonActivation Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+ }
+ }
+}
+
+/* Addon Activation - WebsitePanel */
+add_hook('AddonActivation', 1, 'websitepanel_addons_AddonActivation');
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
index d71cb9b7..05b3e7ef 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
@@ -1,197 +1,195 @@
- 'WebsitePanel Addons Automation',
- 'description' => 'Automates WHMCS product addons with WebsitePanel',
- 'version' => '1.2',
- 'author' => 'Christopher York',
- 'fields' => array('serverhost' => array('FriendlyName', 'Enterprise Server Host', 'Type' => 'text', 'Size' => 25, 'Description' => 'Enterprise Server hostname / IP address', 'Default' => '127.0.0.1'),
- 'serverport' => array('FriendlyName', 'Enterprise Server Port', 'Type' => 'text', 'Size' => 4, 'Description' => 'Enterprise Server port', 'Default' => 9002),
- 'serversecured' => array('FriendlyName', 'Use Secured Connection', 'Type' => 'yesno', 'Description' => 'Tick to use SSL secured connection'),
- )
- );
- return $configarray;
-}
-
-/**
- * websitepanel_addons_activate
- *
- * @access public
- * @return array
- */
-function websitepanel_addons_activate()
-{
- // Create the WebsitePanel Addons table
- $query = "CREATE TABLE `mod_wspaddons` (
- `whmcs_id` int(11) NOT NULL,
- `wsp_id` int(11) NOT NULL,
- `is_ipaddress` bit(1) NOT NULL DEFAULT 0,
- PRIMARY KEY (`whmcs_id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
- $result = full_query($query);
-
- // Check the results to verify that the table has been created properly
- if (!$result)
- {
- return array('status' => 'error', 'description' => 'There was an error while activating the module');
- }
- else
- {
- return array('status' => 'success', 'description' => 'The module has been activated successfully');
- }
-}
-
-/**
- * websitepanel_addons_deactivate
- *
- * @access public
- * @return array
- */
-function websitepanel_addons_deactivate()
-{
- // Drop the WebsitePanel Addons table
- $result = full_query('DROP TABLE `mod_wspaddons`');
-
- // Check the results to verify that the table has been created properly
- if (!$result)
- {
- return array('status' => 'error', 'description' => 'There was an error while deactiviting the module');
- }
- else
- {
- return array('status' => 'success', 'description' => 'The module has been deactivated successfully');
- }
-}
-
-/**
- * websitepanel_addons_upgrade
- *
- * @param $vars array
- * @access public
- * @return array
- */
-function websitepanel_addons_upgrade($vars)
-{
-
- $version = $vars['version'];
-
- // Adjust the table name and remove the WebsitePanel credentials
- if ($version < 1.2)
- {
- full_query('RENAME TABLE `tblwspaddons` TO `mod_wspaddons`');
- full_query("DELETE FROM `tbladdonmodules` WHERE `module` = 'websitepanel_addons' AND `setting` = 'username'");
- full_query("DELETE FROM `tbladdonmodules` WHERE `module` = 'websitepanel_addons' AND `setting` = 'password'");
- }
-}
-
-/**
- * websitepanel_addons_output
- *
- * @access public
- * @return mixed
- */
-function websitepanel_addons_output($params)
-{
- // Delete the requested WebsitePanel addon
- if (isset($_GET['action']) && $_GET['action'] == 'delete')
- {
- delete_query('mod_wspaddons', array('whmcs_id' => $_GET['id']));
- }
-
- // Add the requested WebsitePanel addon
- if ($_POST && isset($_POST['action']) && $_POST['action'] == 'add')
- {
- // Sanity check to make sure the WHMCS addon ID exists
- $results = select_query('tbladdons', 'id', array('id' => $_POST['whmcs_id']));
- if (mysql_num_rows($results) > 0)
- {
- $results = select_query('mod_wspaddons', 'whmcs_id', array('whmcs_id' => $_POST['whmcs_id']));
- if (mysql_num_rows($results) > 0)
- {
- echo 'Duplicate WHMCS Addon ID. The WHMCS Addon ID Is Assigned To Another WebsitePanel Addon.
';
- }
- else
- {
- insert_query('mod_wspaddons', array('whmcs_id' => $_POST['whmcs_id'], 'wsp_id' => $_POST['wsp_id'], 'is_ipaddress' => $_POST['is_ipaddress']));
- }
- }
- else
- {
- echo 'WHMCS Addon Not Found! Check The ID And Try Again.
';
- }
- }
-
- // Get all the assigned addons and display them to the user
- $results = full_query('SELECT a.name AS `name`, a.id AS `whmcs_id`, w.wsp_id AS `wsp_id` FROM `tbladdons` AS a, `mod_wspaddons` AS w WHERE w.whmcs_id = a.id');
-
- // Build the table / data grid
- echo '';
- echo '
';
- echo 'Addon Name | WHMCS ID | WebsitePanel ID | |
';
- if (mysql_num_rows($results) > 0)
- {
- while (($row = mysql_fetch_array($results)) != false)
- {
- echo "{$row['name']} | {$row['whmcs_id']} | {$row['wsp_id']} | Delete |
";
- }
- }
- else
- {
- echo 'No Addon Data Found |
';
- }
- echo '
';
-
- // Build the add addon form
- echo 'Add WebsitePanel Addon
';
- echo "
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
new file mode 100644
index 00000000..5cf56d31
--- /dev/null
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
@@ -0,0 +1,502 @@
+_username = $username;
+ $this->_password = $password;
+ $this->_host = $host;
+ $this->_port = $port;
+ $this->_secured = $secured;
+ $this->_caching = $caching;
+ $this->_compression = $compression;
+ }
+
+ /**
+ * Executes the "CreateUserWizard" method
+ *
+ * @param array $params CreateUserWizard method parameters
+ * @throws Exception
+ * @return int
+ */
+ public function createUserWizard($params)
+ {
+ try
+ {
+ return $this->execute('esPackages.asmx', 'CreateUserWizard', $params)->CreateUserWizardResult;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("ChangeUserStatus Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "UpdateUser" method
+ *
+ * @access private
+ * @param array $params
+ * @throws Exception
+ * @return int
+ */
+ public function updateUserDetails($params)
+ {
+ try
+ {
+ return $this->execute('esUsers.asmx', 'UpdateUser', array('user' => $params))->UpdateUserResult;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("UpdateUser Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "DeleteUser" method
+ *
+ * @access public
+ * @param int $userId User's WebsitePanel userId
+ * @throws Exception
+ * @return int
+ */
+ public function deleteUser($userId)
+ {
+ try
+ {
+ return $this->execute('esUsers.asmx', 'DeleteUser', array('userId' => $userId))->DeleteUserResult;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("ChangeUserStatus Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "GetUserByUsername" method
+ *
+ * @access public
+ * @param string $username Websitepanel username
+ * @throws Exception
+ * @return array
+ */
+ public function getUserByUsername($username)
+ {
+ try
+ {
+ return $this->convertArray($this->execute('esUsers.asmx', 'GetUserByUsername', array('username' => $username))->GetUserByUsernameResult);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("GetUserByUsername Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()})", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "ChangeUserStatus" method
+ *
+ * @access public
+ * @param int $userId User's WebsitePanel userId
+ * @param string $status Account status (Active, Suspended, Cancelled, Pending)
+ * @throws Exception
+ * @return int
+ */
+ public function changeUserStatus($userId, $status)
+ {
+ try
+ {
+ return $this->execute('esUsers.asmx', 'ChangeUserStatus', array('userId' => $userId, 'status' => $status))->ChangeUserStatusResult;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("ChangeUserStatus Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "ChangeUserPassword" method
+ *
+ * @access public
+ * @param int $userId User's WebsitePanel userId
+ * @param string $password User's new password
+ * @throws Exception
+ * @return int
+ */
+ public function changeUserPassword($userId, $password)
+ {
+ try
+ {
+ return $this->execute('esUsers.asmx', 'ChangeUserPassword', array('userId' => $userId, 'password' => $password))->ChangeUserPasswordResult;
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("ChangeUserPassword Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "GetMyPackages" method
+ *
+ * @access public
+ * @param int $userId User's WebsitePanel userId
+ * @throws Exception
+ * @return array
+ */
+ public function getUserPackages($userId)
+ {
+ try
+ {
+ return $this->convertArray($this->execute('esPackages.asmx', 'GetMyPackages', array('userId' => $userId))->GetMyPackagesResult->PackageInfo);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("GetMyPackages Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "GetUsersPagedRecursive" method
+ *
+ * @access public
+ * @param unknown $userId Users's WebsitePanel userId
+ * @param unknown $filterColumn Column value to filter against
+ * @param unknown $filterValue Filter value
+ * @param unknown $statusId Users's account status id
+ * @param unknown $roleId User's account role id
+ * @param unknown $sortColumn Column value to sort against
+ * @param number $startRow Start value
+ * @param string $maximumRows Maximum rows to return
+ * @throws Exception
+ * @return object
+ */
+ public function getUsersPagedRecursive($userId, $filterColumn, $filterValue, $statusId, $roleId, $sortColumn, $startRow = 0, $maximumRows = PHP_INT_MAX)
+ {
+ try
+ {
+ $result = (array)$this->execute('esUsers.asmx', 'GetUsersPagedRecursive', array('userId' => $userId, 'filterColumn' => $filterColumn, 'filterValue' => $filterValue, 'statusId' => $statusId, 'roleId' => $roleId, 'sortColumn' => $sortColumn, 'startRow' => $startRow, 'maximumRows' => $maximumRows))->GetUsersPagedRecursiveResult;
+ return $this->convertArray($result['any'], TRUE);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("GetUsersPagedRecursive Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "UpdatePackageLiteral" method
+ *
+ * @access public
+ * @param int $packageId Package's WebsitePanel packageId
+ * @param int $statusId Package's status id
+ * @param int $planId Package's WebsitePanel planid
+ * @param string $purchaseDate Package's purchase date
+ * @param string $packageName Package's name
+ * @param string $packageComments Package's comments
+ * @throws Exception
+ * @return array
+ */
+ public function updatePackageLiteral($packageId, $statusId, $planId, $purchaseDate, $packageName, $packageComments)
+ {
+ try
+ {
+ return $this->convertArray($this->execute('esPackages.asmx', 'UpdatePackageLiteral', array('packageId' => $packageId, 'statusId' => $statusId, 'planId' => $planId, 'purchaseDate' => $purchaseDate, 'packageName' => $packageName, 'packageComments' => $packageComments))->UpdatePackageLiteralResult);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("UpdatePackageLiteral Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "AddPackageAddonById" method
+ *
+ * @access public
+ * @param unknown $packageId WebsitePanel package Id
+ * @param unknown $addonPlanId WebsitePanel addon id
+ * @param number $quantity Number of addons to add :)
+ * @throws Exception
+ * @return array
+ */
+ public function addPackageAddonById($packageId, $addonPlanId, $quantity = 1)
+ {
+ try
+ {
+ return $this->convertArray($this->execute('esPackages.asmx', 'AddPackageAddonById', array('packageId' => $packageId, 'addonPlanId' => $addonPlanId, 'quantity' => $quantity))->AddPackageAddonByIdResult);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("AddPackageAddonById Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "GetPackageBandwidth" method
+ *
+ * @access public
+ * @param unknown $packageId WebsitePanel package id
+ * @param unknown $startDate Calculation start date
+ * @param unknown $endDate Calculation end date
+ * @throws Exception
+ * @return object
+ */
+ public function getPackageBandwidthUsage($packageId, $startDate, $endDate)
+ {
+ try
+ {
+ $result = (array)$this->execute('esPackages.asmx', 'GetPackageBandwidth', array('packageId' => $packageId, 'startDate' => $startDate, 'endDate' => $endDate))->GetPackageBandwidthResult;
+ return $this->convertArray($result['any'], TRUE);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("GetPackageBandwidth Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "GetPackageDiskspace" method
+ *
+ * @access public
+ * @param unknown $packageId WebsitePanel package id
+ * @throws Exception
+ * @return object
+ */
+ public function getPackageDiskspaceUsage($packageId)
+ {
+ try
+ {
+ $result = (array)$this->execute('esPackages.asmx', 'GetPackageDiskspace', array('packageId' => $packageId))->GetPackageDiskspaceResult;
+ return $this->convertArray($result['any'], TRUE);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("GetPackageDiskspace Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the "AllocatePackageIPAddresses" method
+ *
+ * @access public
+ * @param int $packageId WebsitePanel package id
+ * @param string $groupName WebsitePanel IP address group name
+ * @param string $pool WebsitePanel IP address pool
+ * @param int $addressesNumber Number of IP addresses to allocate
+ * @param string $allocateRandom Allocate randomly
+ * @throws Exception
+ * @return object
+ */
+ public function allocatePackageIPAddresses($packageId, $groupName = 'Web', $pool = 'WebSites', $addressesNumber = 1, $allocateRandom = TRUE)
+ {
+ try
+ {
+ return $this->convertArray($this->execute('esServers.asmx', 'AllocatePackageIPAddresses', array('packageId' => $packageId, 'groupName' => $groupName, 'pool' => $pool, 'addressesNumber' => $addressesNumber, 'allocateRandom' => $allocateRandom))->AllocatePackageIPAddressesResult);
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("AllocatePackageIPAddresses Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}", $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Converts the WebsitePanel error code to a friendly human-readable message
+ *
+ * @access public
+ * @param int $code WebsitePanel error code
+ * @return string
+ */
+ public static function getFriendlyError($code)
+ {
+ $errors = array(-100 => 'Username not available, already in use',
+ -101 => 'Username not found, invalid username',
+ -102 => 'User\'s account has child accounts',
+ -300 => 'Hosting package could not be found',
+ -301 => 'Hosting package has child hosting spaces',
+ -501 => 'The sub-domain belongs to an existing hosting space that does not allow sub-domains to be created',
+ -502 => 'The domain or sub-domain exists in another hosting space / user account',
+ -511 => 'Instant alias is enabled, but not configured',
+ -601 => 'The website already exists on the target hosting space or server',
+ -700 => 'The email domain already exists on the target hosting space or server',
+ -1100 => 'User already exists');
+
+ // Find the error and return it, else a general error will do!
+ if (array_key_exists($code, $errors))
+ {
+ return $errors[$code];
+ }
+ else
+ {
+ return "An unknown error occured (Code: {$code}). Please reference WebsitePanel BusinessErrorCodes for further information";
+ }
+ }
+
+ /**
+ * Executes the request on the Enterprise Server and returns the results
+ *
+ * @param unknown $service
+ * @param unknown $method
+ * @param unknown $params
+ * @throws Exception
+ */
+ private function execute($service, $method, $params)
+ {
+ // Set the Enterprise Server full URL
+ $host = (($this->_secured) ? 'https' : 'http') . "://{$this->_host}:{$this->_port}/{$service}?WSDL";
+ try
+ {
+ // Create the SoapClient
+ $client = new SoapClient($host, array('login' => $this->_username, 'password' => $this->_password, 'compression' => (($this->_compression) ? (SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP) : ''), 'cache_wsdl' => ($this->_caching) ? 1 : 0));
+
+ // Execute the request and process the results
+ return call_user_func(array($client, $method), $params);
+ }
+ catch (SoapFault $e)
+ {
+ throw new Exception("SOAP Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()})");
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("General Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()})");
+ }
+ }
+
+ /**
+ * Converts an object or an XML string to an array
+ *
+ * @access private
+ * @param mixed $value Object or an XML string
+ * @param boolean $loadXml Loads the string into the SimpleXML object
+ * @return array
+ */
+ private function convertArray($value, $loadXml = FALSE)
+ {
+ // This is silly, but it works, and it works very well for what we are doing :)
+ return json_decode(json_encode(($loadXml ? simplexml_load_string($value) : $value)), TRUE);
+ }
+}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
new file mode 100644
index 00000000..49c3b40e
--- /dev/null
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
@@ -0,0 +1,146 @@
+ (int)$_SESSION['adminid']));
+ $results = mysql_fetch_assoc($result);
+ $selectedLanguage = !empty($results['language']) ? $results['language'] : 'english';
+ }
+
+ // Load the language file
+ $languageFile = dirname(__FILE__) . "/lang/{$selectedLanguage}.php";
+ if (file_exists($languageFile))
+ {
+ require_once($languageFile);
+ }
+ else
+ {
+ // Load the default (English) language file
+ require_once(dirname(__FILE__) . '/lang/english.php');
+ }
+
+ // Process the module language entries
+ if (is_array($_MOD_LANG))
+ {
+ foreach ($_MOD_LANG as $key => $value)
+ {
+ if (empty($_LANG[$key]))
+ {
+ $_LANG[$key] = $value;
+ }
+ }
+ }
+
+ // Add to the template
+ if (isset($smarty))
+ {
+ $smarty->assign('LANG', $_LANG);
+ }
+
+ return $_MOD_LANG;
+}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.errorcodes.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/lang/english.php
similarity index 57%
rename from WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.errorcodes.php
rename to WebsitePanel.WHMCSModule/modules/servers/websitepanel/lang/english.php
index 5bde4a57..6d4d1809 100644
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.errorcodes.php
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/lang/english.php
@@ -1,60 +1,42 @@
- 'User already exists',
- -101 => 'User not found',
- -102 => 'User has child user accounts',
- -300 => 'Hosting package could not be found',
- -301 => 'Hosting package has child hosting spaces',
- -501 => 'The sub-domain belongs to an existing hosting space that does not allow sub-domains to be created',
- -502 => 'The domain or sub-domain exists within another hosting space',
- -511 => 'Instant alias is enabled, but not configured',
- -601 => 'The website already exists on the target hosting space',
- -700 => 'The email domain already exists on the target hosting space',
- -1100 => 'User already exists');
- return $esErrorCodes;
-}
\ No newline at end of file
+_esUsername = $esUsername;
- $this->_esPassword = $esPassword;
- $this->_esServerUrl = $esServerUrl;
- $this->_esServerPort = $esServerPort;
- $this->_esUseSsl = $useSsl;
- }
-
- /**
- * WebsitePanel::CreateAccount()
- *
- * @param string $username Account username
- * @param string $password Account password
- * @param string $roleId Account role id
- * @param string $firstName Account holders firstname
- * @param string $lastName Account holders lastname
- * @param string $email Account holders email address
- * @param string $planId WebsitePanel plan id
- * @param integer $parentPackageId Parent space / package id
- * @param string $domainName Account domain name
- * @param string $hostName Website hostname (if createWebsite is TRUE)
- * @param bool $htmlMail Send HTML email
- * @param bool $sendAccountLetter Send WebsitePanel "Account Summary" letter
- * @param bool $sendPackageLetter Send WebsitePanel "Hosting Space Summary" letter
- * @param bool $createPackage Create hostingspace / package on user creation
- * @param bool $tempDomain Create temporary domain on hostingspace / package creation
- * @param bool $createWebSite Create Website on hostingspace / package creation
- * @param bool $createFtpAccount Create FTP account on hostingspace / package creation
- * @param string $ftpAcountName FTP account name to create (if createFtpAccount is TRUE)
- * @param bool $createMailAccount Create default mail account on hostingspace / package creation
- * @param bool $createZoneRecord Create domain DNS zone record (if createMailAccount OR createWebSite are TRUE)
- * @return int
- */
- public function createUserWizard($username, $password, $roleId, $firstName, $lastName, $email, $planId, $parentPackageId, $domainName, $hostName, $htmlMail = TRUE, $sendAccountLetter = TRUE, $sendPackageLetter = TRUE, $createPackage = TRUE, $tempDomain = FALSE, $createWebSite = FALSE, $createFtpAccount = FALSE, $ftpAcountName = '', $createMailAccount = FALSE, $createZoneRecord = FALSE)
- {
- $params = array();
- foreach (get_defined_vars() as $key => $value)
- {
- if ($key == 'params')
- continue;
-
- $params[$key] = $value;
- }
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'CreateUserWizard', $params)->CreateUserWizardResult;
- }
-
- /**
- * WebsitePanel::UpdateUserDetails()
- *
- * @access public
- * @param int $RoleId Account role id
- * @param string $Role Account role
- * @param int $StatusId Account status id
- * @param string $Status Account status
- * @param int $UserId Account user id
- * @param int $OwnerId Account owner id
- * @param string $Created Account creation date
- * @param string $Changed Account changed date
- * @param bool $IsDemo Demo account
- * @param bool $IsPeer Peer account
- * @param string $Comments Account comments
- * @param string $Username Account username
- * @param string $Password Account password
- * @param string $FirstName Account holders firstname
- * @param string $LastName Account holders lastname
- * @param string $Email Account holders email address
- * @param string $PrimaryPhone Account holders phone number
- * @param string $Zip Account holders postal code
- * @param string $InstantMessenger Account holders IM
- * @param string $Fax Account holders fax number
- * @param string $SecondaryPhone Account holders secondary phone number
- * @param string $SecondaryEmail Account holders secondary email
- * @param string $Country Account holders country
- * @param string $Address Account holders physical address
- * @param string $City Account holders city
- * @param string $State Account holders state
- * @param bool $HtmlMail Send HTML email
- * @param string $CompanyName Account holders Company name
- * @param bool $EcommerceEnabled Ecommerce enabled
- * @return void
- */
- public function updateUserDetails($RoleId, $Role, $StatusId, $Status, $LoginStatusId, $LoginStatus, $FailedLogins, $UserId, $OwnerId, $Created, $Changed, $IsDemo, $IsPeer, $Comments, $Username, $Password, $FirstName, $LastName, $Email, $PrimaryPhone, $Zip, $InstantMessenger, $Fax, $SecondaryPhone, $SecondaryEmail, $Country, $Address, $City, $State, $HtmlMail, $CompanyName, $EcommerceEnabled)
- {
- $params = array();
- foreach (get_defined_vars() as $key => $value)
- {
- if ($key == 'params')
- continue;
-
- $params[$key] = $value;
- }
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_USERS, 'UpdateUser', array('user' => $params))->UpdateUserResult;
- }
-
- /**
- * WebsitePanel::DeleteUser()
- *
- * @access public
- * @param int $userid User id
- * @return int
- */
- public function deleteUser($userId)
- {
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_USERS, 'DeleteUser', array('userId' => $userId))->DeleteUserResult;
- }
-
- /**
- * WebsitePanel::GetUserByUsername()
- *
- * @access public
- * @param string $username Username
- * @return array
- */
- public function getUserByUsername($username)
- {
- return (array)$this->executeServerMethod(WebsitePanel::SERVICEFILE_USERS, 'GetUserByUsername', array('username' => $username))->GetUserByUsernameResult;
- }
-
- /**
- * WebsitePanel::ChangeUserStatus()
- *
- * @param int $userId User id
- * @param string $status Account status (Active, Suspended, Cancelled, Pending)
- * @return int
- */
- public function changeUserStatus($userId, $status)
- {
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_USERS, 'ChangeUserStatus', array('userId' => $userId, 'status' => $status))->ChangeUserStatusResult;
- }
-
- /**
- * WebsitePanel::ChangeUserPassword()
- *
- * @access public
- * @param int $userId User id
- * @param string $password New password
- * @return int
- */
- public function changeUserPassword($userId, $password)
- {
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_USERS, 'ChangeUserPassword', array('userId' => $userId, 'password' => $password))->ChangeUserPasswordResult;
- }
-
- /**
- * WebsitePanel::GetUserPackages()
- *
- * @access public
- * @param int $userid User id
- * @return array
- */
- public function getUserPackages($userid)
- {
- return (array)$this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'GetMyPackages', array('userId' => $userid))->GetMyPackagesResult->PackageInfo;
- }
-
- /**
- * WebsitePanel::getUsersPagedRecursive()
- *
- * @param int $userId User id
- * @param string $filterColumn Column name to filter on
- * @param string $filterValue Filter value
- * @param int $statusId Status id
- * @param int $roleId Role id
- * @param string $sortColumn Column name to sort on
- * @param int $startRow Row to start at
- * @param int $maximumRows Maximum rows to return
- */
- public function getUsersPagedRecursive($userId, $filterColumn, $filterValue, $statusId, $roleId, $sortColumn, $startRow = 0, $maximumRows = 999)
- {
- $params = array();
- foreach (get_defined_vars() as $key => $value)
- {
- if ($key == 'params')
- continue;
-
- $params[$key] = $value;
- }
- return $this->executeServerMethod(WebSitePanel::SERVICEFILE_USERS, 'GetUsersPagedRecursive', $params)->GetUsersPagedRecursiveResult;
- }
-
- /**
- * WebsitePanel::UpdatePackageLiteral()
- *
- * @access public
- * @param int $packageId Package id
- * @param int $statusId Status id
- * @param int $planId Plan id
- * @param string $purchaseDate Purchase date
- * @param string $packageName Package name
- * @param string $packageComments Package comments
- * @return array
- */
- public function updatePackageLiteral($packageId, $statusId, $planId, $purchaseDate, $packageName, $packageComments)
- {
- $params = array();
- foreach (get_defined_vars() as $key => $value)
- {
- if ($key == 'params')
- continue;
-
- $params[$key] = $value;
- }
- return (array)$this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'UpdatePackageLiteral', $params)->UpdatePackageLiteralResult;
- }
-
- /**
- * WebsitePanel::addPackageAddonById()
- *
- * @access public
- * @param mixed $packageId Package id
- * @param mixed $addonPlanId Addon plan od
- * @param integer $quantity Quantity
- * @return array
- */
- public function addPackageAddonById($packageId, $addonPlanId, $quantity = 1)
- {
- return (array)$this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'AddPackageAddonById', array('packageId' => $packageId, 'addonPlanId' => $addonPlanId, 'quantity' => $quantity))->AddPackageAddonByIdResult;
- }
-
- /**
- * WebsitePanel::GetSpaceBandwidthUsage()
- *
- * @access public
- * @param int $packageId Package id
- * @param string $startDate Start date
- * @param string $endDate Ending date
- * @return object
- */
- public function getSpaceBandwidthUsage($packageId, $startDate, $endDate)
- {
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'GetPackageBandwidth', array('packageId' => $packageId, 'startDate' => $startDate, 'endDate' => $endDate))->GetPackageBandwidthResult;
- }
-
- /**
- * WebsitePanel::GetSpaceDiskspaceUsage()
- *
- * @access private
- * @param int $packageId Package Id
- * @return object
- */
- public function getSpaceDiskspaceUsage($packageId)
- {
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_PACKAGES, 'GetPackageDiskspace', array('packageId' => $packageId))->GetPackageDiskspaceResult;
- }
-
- /**
- * WebsitePanel::packageAllocateIpAddress()
- *
- * @param mixed $packageId Package id
- * @param mixed $groupName Group name
- * @param mixed $pool Address pool
- * @param integer $addressesNumber Number of IP addresses
- * @param bool $allocateRandom Allocate IP addresses randomly
- * @return object
- */
- public function packageAllocateIpAddress($packageId, $groupName = WebsitePanel::IPADDRESS_POOL_WEB, $pool = WebsitePanel::IPADDRESS_GROUP_WEBSITES, $addressesNumber = 1, $allocateRandom = TRUE)
- {
- $params = array();
- foreach (get_defined_vars() as $key => $value)
- {
- if ($key == 'params')
- continue;
-
- $params[$key] = $value;
- }
- return $this->executeServerMethod(WebsitePanel::SERVICEFILE_SERVERS, 'AllocatePackageIPAddresses', $params)->AllocatePackageIPAddressesResult;
- }
-
- /**
- * Executes the requested Enterprise Server method / parameters and returns the results
- *
- * @access private
- * @param string $serviceFile Enterprise Server service filename
- * @param string $serviceMethod Enterprise Server service method name
- * @param array $methodParameters Method parameters
- * @throws Exception
- * @return object
- */
- private function executeServerMethod($serviceFile, $serviceMethod, $methodParameters = array())
- {
- $esUrl = (($this->_esUseSsl ? "https" : "http") . "://{$this->_esServerUrl}:{$this->_esServerPort}/{$serviceFile}?WSDL");
- $soapParams = array('login' => $this->_esUsername,
- 'password' => $this->_esPassword,
- 'cache_wsdl' => WSDL_CACHE_NONE // WSDL caching is an annoying nightmare - we will disable it
- );
- try
- {
- $client = new SoapClient($esUrl, $soapParams);
- $result = $client->$serviceMethod($methodParameters);
- if (is_soap_fault($result))
- {
- throw new Exception($result->faultstring);
- }
- return $result;
- }
- catch (Exception $e)
- {
- throw new Exception($e->getMessage());
- }
- }
-}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.functions.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.functions.php
deleted file mode 100644
index 32e0eb6b..00000000
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.functions.php
+++ /dev/null
@@ -1,197 +0,0 @@
-getSpaceBandwidthUsage($packageId, $startDate, date('Y-m-d', time()));
- return websitepanel_CalculateUsage($result, WebsitePanel::USAGE_BANDWIDTH);
- }
- catch (Exception $e)
- {
- // Do nothing, just catch the Exception to keep PHP from exploding :)
- }
-}
-
-/**
- * websitepanel_CalculateDiskspaceUsage()
- *
- * @access public
- * @param mixed $params
- * @param mixed $packageId
- * @return int
- */
-function websitepanel_CalculateDiskspaceUsage($params, $packageId)
-{
- // Create the ASPnix websitepanel_EnterpriseServer class object
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- try
- {
- $result = $wsp->getSpaceDiskspaceUsage($packageId);
- return websitepanel_CalculateUsage($result, WebsitePanel::USAGE_DISKSPACE);
- }
- catch (Exception $e)
- {
- // Do nothing, just catch the Exception to keep PHP from exploding :)
- }
-}
-
-/**
- * websitepanel_CalculateUsage()
- *
- * @access public
- * @param mixed $result
- * @param int $usageType
- * @return int
- */
-function websitepanel_CalculateUsage($result, $usageType)
-{
- // Process results
- $xml = simplexml_load_string($result->any);
- $total = 0;
- if (count($xml->NewDataSet->Table) > 0)
- {
- foreach ($xml->NewDataSet->Table as $table)
- {
- switch ($usageType)
- {
- case WebsitePanel::USAGE_BANDWIDTH:
- $total = $total + $table[0]->MegaBytesTotal;
- break;
-
- case WebsitePanel::USAGE_DISKSPACE:
- $total = $total + $table[0]->Diskspace;
- break;
-
- default:
- $total = $total + $table[0]->MegaBytesTotal;
- break;
- }
- }
- }
- return $total;
-}
-
-/**
- * websitepanel_GetServerSettings
- *
- * @access public
- * @return array
- */
-function websitepanel_GetServerSettings()
-{
- $settings = array('username' => '', 'password' => '');
-
- // Retrieve the settings from the modules configuration table
- $results = select_query('tblservers', 'username,password', array('type' => 'websitepanel'));
- if (mysql_num_rows($results) != 0)
- {
- $row = mysql_fetch_array($results, MYSQL_ASSOC);
- $settings['username'] = $row['username'];
- $settings['password'] = decrypt($row['password']);
- }
- return $settings;
-}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
index f322d709..4cae0ae7 100644
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
@@ -1,465 +1,634 @@
- array( 'Type' => 'text', 'Size' => 25, 'Description' => 'Package Name'),
- 'Web Space Quota' => array( 'Type' => 'text', 'Size' => 5, 'Description' => 'MB'),
- 'Bandwidth Limit' => array( 'Type' => 'text', 'Size' => 5, 'Description' => 'MB'),
- 'Plan ID' => array( 'Type' => 'text', 'Size' => 4, 'Description' => 'WebsitePanel hosting plan id'),
- 'Parent Space ID' => array( 'Type' => 'text', 'Size' => 3, 'Description' => '* SpaceID that all accounts are created under', 'Default' => 1),
- 'Enterprise Server Port' => array( 'Type' => 'text', 'Size' => 5, 'Description' => '* Required', 'Default' => 9002),
- 'Different Potal URL' => array( 'Type' => 'yesno', 'Description' => 'Tick if portal address is different to server address'),
- 'Portal URL' => array( 'Type' => 'text', 'Size' => 25, 'Description' => 'Portal URL, with http://, no trailing slash' ),
- 'Send Account Summary Email' => array( 'Type' => 'yesno', 'Description' => 'Tick to send the "Account Summary" letter' ),
- 'Send Hosting Space Summary Email' => array( 'Type' => 'yesno', 'Description' => 'Tick to send the "Hosting Space Summary" letter'),
- 'Create Mail Account' => array( 'Type' => 'yesno', 'Description' => 'Tick to create mail account' ),
- 'Create FTP Account' => array( 'Type' => 'yesno', 'Description' => 'Tick to create FTP account' ),
- 'Create Temporary Domain' => array( 'Type' => 'yesno', 'Description' => 'Tick to create a temporary domain' ),
- 'Send HTML Email' => array( 'Type' => 'yesno', 'Description' => 'Tick enable HTML email from WebsitePanel' ),
- 'Create Website' => array( 'Type' => 'yesno', 'Description' => 'Tick to create Website' ),
- 'Count Bandwidth / Diskspace' => array( 'Type' => 'yesno', 'Description' => 'Tick to update diskpace / bandwidth in WHMCS'),
- 'Default Pointer' => array( 'Type' => 'text', 'Size' => 25, 'Description' => 'The default pointer (hostname) to use when creating a Website' ),
- 'Create DNS Zone' => array( 'Type' => 'yesno', 'Description' => 'Tick to create domain DNS zone' )
- );
- return $configarray;
-}
-
-/**
- * websitepanel_CreateAccount()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_CreateAccount($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $username = $params['username'];
- $password = $params['password'];
- $accountId = $params['accountid'];
- $packageId = $params['packageid'];
- $domain = $params['domain'];
- $packageType = $params['type'];
- $clientsDetails = $params['clientsdetails'];
-
- // WebsitePanel API parameters
- $planId = $params['configoption4'];
- $parentPackageId = $params['configoption5'];
- $roleId = ($packageType == 'reselleraccount') ? 2 : 3;
- $htmlMail = ($params['configoption14'] == 'on') ? TRUE : FALSE;
- $sendAccountLetter = ($params['configoption9'] == 'on') ? TRUE : FALSE;
- $sendPackageLetter = ($params['configoption10'] == 'on') ? TRUE : FALSE;
- $createMailAccount = ($params['configoption11'] == 'on') ? TRUE : FALSE;
- $createTempDomain = ($params['configoption13'] == 'on') ? TRUE : FALSE;
- $createFtpAccount = ($params['configoption12'] == 'on') ? TRUE : FALSE;
- $createWebsite = ($params['configoption15'] == 'on') ? TRUE : FALSE;
- $websitePointerName = $params['configoption17'];
- $createZoneRecord = ($params['configoption18'] == 'on') ? TRUE : FALSE;
-
- try
- {
- // Attempt to create the WSP user account and his / her package / hosting space
- $result = $wsp->createUserWizard($username, $password, $roleId, $clientsDetails['firstname'], $clientsDetails['lastname'], $clientsDetails['email'], $planId, $parentPackageId, $domain, $websitePointerName, $htmlMail, $sendAccountLetter, $sendPackageLetter, TRUE, $createTempDomain, $createWebsite, $createFtpAccount, $username, $createMailAccount, $createZoneRecord);
- if ($result >= 0)
- {
- // Grab the user's details from WebsitePanel
- $user = $wsp->getUserByUsername($username);
-
- // Update the user's account with his / her WHMCS contact details
- $wsp->updateUserDetails($user['RoleId'], $user['Role'], $user['StatusId'], $user['Status'], $user['LoginStatusId'], $user['LoginStatus'], $user['FailedLogins'], $user['UserId'], $user['OwnerId'], $user['Created'], $user['Changed'], $user['IsDemo'], $user['IsPeer'], $user['Comments'], $username, $password, $clientsDetails['firstname'], $clientsDetails['lastname'], $clientsDetails['email'], $clientsDetails['phonenumber'], $clientsDetails['postcode'], '', '', '', '', $clientsDetails['country'], $clientsDetails['address1'], $clientsDetails['city'], $clientsDetails['state'], $htmlMail, $clientsDetails['companyname'], (($roleId == 2) ? TRUE : FALSE));
-
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_TerminateAccount()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_TerminateAccount($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $username = $params['username'];
-
- try
- {
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return "The specified user {$username} does not exist";
- }
- else
- {
- // Attempt to delete the users account and package / hosting space
- $result = $wsp->deleteUser($user['UserId']);
- if ($result >= 0)
- {
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_SuspendAccount()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_SuspendAccount($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $username = $params['username'];
-
- try
- {
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return "The specified user {$username} does not exist";
- }
- else
- {
- // Attempt to suspend the users account and package / hosting space
- $result = $wsp->changeUserStatus($user['UserId'], WebsitePanel::USERSTATUS_SUSPENDED);
- if ($result >= 0)
- {
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_UnsuspendAccount()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_UnsuspendAccount($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $username = $params['username'];
-
- try
- {
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return "The specified user {$username} does not exist";
- }
- else
- {
- // Attempt to activate the users account and package / hosting space
- $result = $wsp->changeUserStatus($user['UserId'], WebsitePanel::USERSTATUS_ACTIVE);
- if ($result >= 0)
- {
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_ChangePassword()
- *
- * @access public
- * @param array $params
- * @return int
- */
-function websitepanel_ChangePassword($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $serviceId = $params['serviceid'];
- $username = $params['username'];
- $password = $params['password'];
- $userId = $params['clientsdetails']['userid'];
-
- try
- {
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return "The specified user {$username} does not exist";
- }
- else
- {
- // Attempt to change the user's account password
- $result = $wsp->changeUserPassword($user['UserId'], $password);
- if ($result >= 0)
- {
- // Log this action for logging / tracking purposes incase the client complains we have record of what went on and why
- logActivity("Control Panel Password Updated - Service ID: {$serviceId}", $userId);
-
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_ChangePackage()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_ChangePackage($params)
-{
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $params['serverip'], $params['configoption6'], $params['serversecure']);
-
- // WHMCS server parameters & package parameters
- $username = $params['username'];
-
- // WebsitePanel API parameters
- $planId = $params['configoption4'];
- $packageName = $params['configoption1'];
-
- try
- {
- // Grab the user's details from WebsitePanel in order to get the user's id
- $user = $wsp->getUserByUsername($username);
- if (empty($user))
- {
- return "The specified user {$username} does not exist";
- }
- else
- {
- // Get the user's current WebsitePanel hosting space Id (Hosting Plan)
- $package = $wsp->getUserPackages($user['UserId']);
- $packageId = $package['PackageId'];
-
- // Update the user's package
- $result = $wsp->updatePackageLiteral($packageId, $package['StatusId'], $planId, $package['PurchaseDate'], $packageName, '');
- $result = $result['Result'];
- if ($result >= 0)
- {
- // Success - Alert WHMCS
- return 'success';
- }
- else
- {
- // Failed - Alert WHMCS of the returned Enterprise Server error code
- return websitepanel_GetErrorMessage($result);
- }
- }
- }
- catch (Exception $e)
- {
- return $e->getMessage();
- }
-}
-
-/**
- * websitepanel_LoginLink()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_LoginLink($params)
-{
- echo "Login to Control Panel (One-Click Login)";
-}
-
-/**
- * websitepanel_ClientArea()
- *
- * @access public
- * @param array $params
- * @return string
- */
-function websitepanel_ClientArea($params)
-{
- return "Login to Control Panel (One-Click Login)";
-}
-
-/**
- * websitepanel_UsageUpdate()
- *
- * @access public
- * @param array $params
- * @return void
- */
-function websitepanel_UsageUpdate($params)
-{
- // WHMCS server parameters & package parameters
- $serverid = $params['serverid'];
- $serverip = $params['serverip'];
-
- // Query for all active or suspended users under the specified server
- $query = full_query("SELECT `username`, `packageid`, `regdate` FROM `tblhosting` WHERE `server` = {$serverid} AND `domainstatus` IN ('Active', 'Suspended') AND `username` <> ''");
- while (($row = mysql_fetch_array($query)) != false)
- {
- try
- {
- // Start processing the specified users usage
- $username = $row['username'];
- $packageId = $row['packageid'];
-
- // Get the packages ConfigOptions
- $packageQuery = select_query('tblproducts', 'configoption2,configoption3,configoption6,configoption16', array('id' => $packageId, 'configoption16' => 'on'));
- $cfgOptions = mysql_fetch_array($packageQuery);
- $params['configoption6'] = $cfgOptions['configoption6'];
-
- // Diskspace and Bandwidth limits for this package
- $diskLimit = $cfgOptions['configoption2'];
- $bwidthLimit = $cfgOptions['configoption3'];
-
- // Create the WebsitePanel object instance
- $wsp = new WebsitePanel($params['serverusername'], $params['serverpassword'], $serverip, $params['configoption6'], $params['serversecure']);
-
- // Get the specified user details
- // Get the Package id of the user's package
- $user = $wsp->getUserByUsername($username);
- $package = $wsp->getUserPackages($user['UserId']);
-
- // Gather the bandwidth / diskspace usage stats
- $bandwidthUsage = websitepanel_CalculateBandwidthUsage($params, $package['PackageId'], websitepanel_CreateBandwidthDate($row['regdate']));
- $diskSpaceUsage = websitepanel_CalculateDiskspaceUsage($params, $package['PackageId']);
-
- // Update the package details
- update_query('tblhosting', array('diskusage' => $diskSpaceUsage, 'disklimit' => $diskLimit, 'bwusage' => $bandwidthUsage, 'bwlimit' => $bwidthLimit, 'lastupdate' => date('Y-m-d H:i:s')), array('username' => $username, 'server' => $serverid));
- }
- catch (Exception $e)
- {
- // Nothing
- }
- }
+ array( 'Type' => 'text', 'Size' => 25, 'Description' => 'Package Name'),
+ 'Web Space Quota' => array( 'Type' => 'text', 'Size' => 5, 'Description' => 'MB'),
+ 'Bandwidth Limit' => array( 'Type' => 'text', 'Size' => 5, 'Description' => 'MB'),
+ 'WebsitePanel Plan ID' => array( 'Type' => 'text', 'Size' => 4, 'Description' => 'WebsitePanel hosting plan id'),
+ 'Parent Space ID' => array( 'Type' => 'text', 'Size' => 3, 'Description' => '* SpaceID that all accounts are created under', 'Default' => 1),
+ 'Enterprise Server Port' => array( 'Type' => 'text', 'Size' => 5, 'Description' => '* Required', 'Default' => 9002),
+ 'Different Potal URL' => array( 'Type' => 'yesno', 'Description' => 'Tick if portal address is different to server address'),
+ 'Portal URL' => array( 'Type' => 'text', 'Size' => 25, 'Description' => 'Portal URL, with http(s)://, no trailing slash' ),
+ 'Send Account Summary Email' => array( 'Type' => 'yesno', 'Description' => 'Tick to send the "Account Summary" letter' ),
+ 'Send Hosting Space Summary Email' => array( 'Type' => 'yesno', 'Description' => 'Tick to send the "Hosting Space Summary" letter'),
+ 'Create Mail Account' => array( 'Type' => 'yesno', 'Description' => 'Tick to create mail account' ),
+ 'Create FTP Account' => array( 'Type' => 'yesno', 'Description' => 'Tick to create FTP account' ),
+ 'Create Temporary Domain' => array( 'Type' => 'yesno', 'Description' => 'Tick to create a temporary domain' ),
+ 'Send HTML Email' => array( 'Type' => 'yesno', 'Description' => 'Tick enable HTML email from WebsitePanel' ),
+ 'Create Website' => array( 'Type' => 'yesno', 'Description' => 'Tick to create Website' ),
+ 'Count Bandwidth / Diskspace' => array( 'Type' => 'yesno', 'Description' => 'Tick to update diskpace / bandwidth in WHMCS'),
+ 'Default Pointer' => array( 'Type' => 'text', 'Size' => 25, 'Description' => 'The default pointer (hostname) to use when creating a Website' ),
+ 'Create DNS Zone' => array( 'Type' => 'yesno', 'Description' => 'Tick to create domain DNS zone'));
+}
+
+/**
+ * Creates the WebsitePanel user account and package
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_CreateAccount($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $password = $params['password'];
+ $domain = $params['domain'];
+ $packageType = $params['type'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ // WebsitePanel API parameters
+ $planId = $params['configoption4'];
+ $parentPackageId = $params['configoption5'];
+ $roleId = ($packageType == 'reselleraccount') ? 2 : 3;
+ $htmlMail = ($params['configoption14'] == 'on') ? TRUE : FALSE;
+ $sendAccountLetter = ($params['configoption9'] == 'on') ? TRUE : FALSE;
+ $sendPackageLetter = ($params['configoption10'] == 'on') ? TRUE : FALSE;
+ $createMailAccount = ($params['configoption11'] == 'on') ? TRUE : FALSE;
+ $createTempDomain = ($params['configoption13'] == 'on') ? TRUE : FALSE;
+ $createFtpAccount = ($params['configoption12'] == 'on') ? TRUE : FALSE;
+ $createWebsite = ($params['configoption15'] == 'on') ? TRUE : FALSE;
+ $websitePointerName = $params['configoption17'];
+ $createZoneRecord = ($params['configoption18'] == 'on') ? TRUE : FALSE;
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Create the user's new account using the CreateUserWizard method
+ $userParams = array('parentPackageId' => $parentPackageId,
+ 'username' => $username,
+ 'password' => $password,
+ 'roleId' => $roleId,
+ 'firstName' => $clientsDetails['firstname'],
+ 'lastname' => $clientsDetails['lastname'],
+ 'email' => $clientsDetails['email'],
+ 'secondaryEmail' => '',
+ 'htmlMail' => $htmlMail,
+ 'sendAccountLetter' => $sendAccountLetter,
+ 'createPackage' => TRUE,
+ 'planId' => $planId,
+ 'sendPackageLetter' => $sendPackageLetter,
+ 'domainName' => $domain,
+ 'tempDomain' => $createTempDomain,
+ 'createWebSite' => $createWebsite,
+ 'createFtpAccount' => $createFtpAccount,
+ 'ftpAccountName' => $username,
+ 'createMailAccount' => $createMailAccount,
+ 'hostName' => $websitePointerName,
+ 'createZoneRecord' => $createZoneRecord);
+
+ // Execute the CreateUserWizard method
+ $result = $wsp->createUserWizard($userParams);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Get the newly created user's details from WebsitePanel so we can update the account details completely
+ $user = $wsp->getUserByUsername($username);
+
+ // Update the user's account details using the previous details + WHMCS's details (address, city, state etc.)
+ $userParams = array('RoleId' => $roleId,
+ 'Role' => $user['Role'],
+ 'StatusId' => $user['StatusId'],
+ 'Status' => $user['Status'],
+ 'LoginStatusId' => $user['LoginStatusId'],
+ 'LoginStatus' => $user['LoginStatus'],
+ 'FailedLogins' => $user['FailedLogins'],
+ 'UserId' => $user['UserId'],
+ 'OwnerId' => $user['OwnerId'],
+ 'IsPeer' => $user['IsPeer'],
+ 'Created' => $user['Created'],
+ 'Changed' => $user['Changed'],
+ 'IsDemo' => $user['IsDemo'],
+ 'Comments' => $user['Comments'],
+ 'LastName' => $clientsDetails['lastname'],
+ 'Username' => $username,
+ 'Password' => $password,
+ 'FirstName' => $clientsDetails['firstname'],
+ 'Email' => $clientsDetails['email'],
+ 'PrimaryPhone' => $clientsDetails['phonenumber'],
+ 'Zip' => $clientsDetails['postcode'],
+ 'InstantMessenger' => '',
+ 'Fax' => '',
+ 'SecondaryPhone' => '',
+ 'SecondaryEmail' => '',
+ 'Country' => $clientsDetails['country'],
+ 'Address' => $clientsDetails['address1'],
+ 'City' => $clientsDetails['city'],
+ 'State' => $clientsDetails['state'],
+ 'HtmlMail' => $htmlMail,
+ 'CompanyName' => $clientsDetails['companyname'],
+ 'EcommerceEnabled' => (($roleId == 2) ? TRUE : FALSE),
+ 'SubscriberNumber' => '');
+
+ // Execute the UpdateUserDetails method
+ $wsp->updateUserDetails($userParams);
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "CreateAccount Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Terminates the WebsitePanel user account and package
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_TerminateAccount($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot terminate account for unknown user");
+ }
+
+ // Attempt the delete the users account
+ $result = $wsp->deleteUser($user['UserId']);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "TerminateAccount Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Suspends the WebsitePanel user account and package
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_SuspendAccount($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot suspend account for unknown user");
+ }
+
+ // Change the user's account and package account status
+ $result = $wsp->changeUserStatus($user['UserId'], websitepanel_EnterpriseServer::USERSTATUS_SUSPENDED);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "SuspendAccount Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Unsuspends the WebsitePanel user account and package
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_UnsuspendAccount($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot unsuspend account for unknown user");
+ }
+
+ // Change the user's account and package account status
+ $result = $wsp->changeUserStatus($user['UserId'], websitepanel_EnterpriseServer::USERSTATUS_ACTIVE);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "UnsuspendAccount Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Changes the WebsitePanel user account password
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_ChangePassword($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $password = $params['password'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot change account password for unknown user");
+ }
+
+ // Change the user's account password
+ $result = $wsp->changeUserPassword($user['UserId'], $password);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "ChangePassword Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Changes the WebsitePanel user hosting package
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_ChangePackage($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+ $serviceId = $params['serviceid'];
+
+ // WebsitePanel API parameters
+ $planId = $params['configoption4'];
+ $packageName = $params['configoption1'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot change package for unknown user");
+ }
+
+ // Get the user's package details from WebsitePanel - We need the PackageId
+ $package = $wsp->getUserPackages($user['UserId']);
+
+ // Update the user's WebsitePanel package
+ $result = $wsp->updatePackageLiteral($package['PackageId'], $package['StatusId'], $planId, $package['PurchaseDate'], $packageName, $package['PackageComments']);
+ if ($result < 0)
+ {
+ // Something went wrong
+ throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
+ }
+
+ // Notify success
+ return 'success';
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "ChangePackage Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+}
+
+/**
+ * Updates the WHMCS service's usage details from WebsitePanel
+ *
+ * @param aray $params WHMCS parameters
+ * @throws Exception
+ */
+function websitepanel_UsageUpdate($params)
+{
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $serverId = $params['serverid'];
+ $userId = 0;
+ $serviceId = 0;
+
+ // Query for WebsitePanel user accounts assigned to this server
+ // Only services that have packages that have "Tick to update diskpace / bandwidth in WHMCS" enabled
+ $result = full_query("SELECT h.id AS serviceid, h.userid AS userid, h.username AS username, h.regdate AS regdate, p.configoption2 AS configoption2, p.configoption3 AS configoption3, p.configoption6 AS configoption6 FROM `tblhosting` AS h, `tblproducts` AS p WHERE h.server = {$serverId} AND h.packageid = p.id AND p.configoption16 = 'on' AND h.domainstatus IN ('Active', 'Suspended')");
+ while (($row = mysql_fetch_array($result)) != false)
+ {
+ // Start processing the users usage
+ $username = $row['username'];
+ $userId = $row['userid'];
+ $serviceId = $row['serviceid'];
+ $serverPort = $row['configoption6'];
+
+ // Diskspace and Bandwidth limits for this package
+ $diskLimit = $row['configoption2'];
+ $bwidthLimit = $row['configoption3'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot calculate usage for unknown user");
+ }
+
+ // Get the user's package details from WebsitePanel - We need the PackageId
+ $package = $wsp->getUserPackages($user['UserId']);
+
+ // Gather the bandwidth / diskspace usage stats
+ $bwidthUsage = websitepanel_CalculateUsage($wsp->getPackageBandwidthUsage($package['PackageId'], websitepanel_CreateBandwidthDate($row['regdate']), date('Y-m-d', time())), websitepanel_EnterpriseServer::USAGE_BANDWIDTH);
+ $diskUsage = websitepanel_CalculateUsage($wsp->getPackageDiskspaceUsage($package['PackageId']), websitepanel_EnterpriseServer::USAGE_DISKSPACE);
+
+ // Update WHMCS's service details
+ update_query('tblhosting', array('diskusage' => $diskUsage, 'disklimit' => $diskLimit, 'bwusage' => $bwidthUsage, 'bwlimit' => $bwidthLimit, 'lastupdate' => 'now()'), array('id' => $serviceId));
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "UsageUpdate Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+ }
+ }
+}
+
+/**
+ * Returns the WebsitePanel one-click login link
+ *
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return string
+ */
+function websitepanel_LoginLink($params)
+{
+ // WHMCS does not return the full hosting account details, we will query for what we need
+ $result = select_query('tblhosting', 'domainstatus', array('id' => $params['serviceid']));
+ $results = mysql_fetch_array($result);
+
+ // Display the link only if the account is Active or Suspended
+ if (in_array($results['domainstatus'], array('Active', 'Suspended')))
+ {
+ // WHMCS server parameters & package parameters
+ $serverUsername = $params['serverusername'];
+ $serverPassword = $params['serverpassword'];
+ $serverPort = $params['configoption6'];
+ $serverHost = empty($params['serverhostname']) ? $params['serverip'] : $params['serverhostname'];
+ $serverSecure = $params['serversecure'];
+ $username = $params['username'];
+ $serviceId = $params['serviceid'];
+ $clientsDetails = $params['clientsdetails'];
+ $userId = $clientsDetails['userid'];
+
+ try
+ {
+ // Create the WebsitePanel Enterprise Server Client object instance
+ $wsp = new websitepanel_EnterpriseServer($serverUsername, $serverPassword, $serverHost, $serverPort, $serverSecure);
+
+ // Get the user's details from WebsitePanel - We need the userid
+ $user = $wsp->getUserByUsername($username);
+ if (empty($user))
+ {
+ throw new Exception("User {$username} does not exist - Cannot display LoginLink for non-existing user");
+ }
+
+ // Load the client area language file
+ $LANG = websitepanel_LoadClientLanguage();
+
+ // Print the link
+ echo "{$LANG['websitepanel_adminarea_gotowebsitepanelaccount']}
";
+ }
+ catch (Exception $e)
+ {
+ // Error message to log / return
+ $errorMessage = "LoginLink Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+
+ // Log to WHMCS
+ logactivity($errorMessage, $userId);
+
+ // Notify failure - Houston we have a problem!
+ return $errorMessage;
+ }
+ }
+}
+
+/**
+ * Client Area module output for the customer's "My Services" service output
+ *
+ * @access public
+ * @param array $params WHMCS parameters
+ * @throws Exception
+ * @return array
+ */
+function websitepanel_ClientArea($params)
+{
+ // WHMCS server parameters & package parameters
+ $username = $params['username'];
+ $password = $params['password'];
+
+ // Load the client area language file
+ websitepanel_LoadClientLanguage();
+
+ // Return template information
+ return array('templatefile' => 'clientarea', 'vars' => array('websitepanel_url' => $params['configoption8'], 'username' => $username, 'password' => $password));
}
\ No newline at end of file
diff --git a/WebsitePanel.WHMCSModule/readme.txt b/WebsitePanel.WHMCSModule/readme.txt
deleted file mode 100644
index 631f8816..00000000
--- a/WebsitePanel.WHMCSModule/readme.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-Extract the "includes" and "modules" directories into the root of your WHMCS installation
-Overwrite any files that already exists
-
-To enable addon automation...
-1. WHMCS Admin -> Setup -> Addon Modules -> "WebsitePanel Addons Automation" -> Activate
-2. Select "Configure"
-3. Enter your WebsitePanel Enterprise Server details
-4. WHMCS Admin -> Addons -> "WebsitePanel Addons"
-5. Enter your addons here. You can get the WHMCS addon ID by clicking the edit icon on the product addon and extracting the &id=x from the URL (x being the WHMCS Addon ID)
- You can retrieve your WebsitePanel addon ID by clicking the hosting addon and extracting the PlanID=x from the URL (x being the WebsitePanel Addon ID)
-6. If this is a Dedicated IP address addon, check the Dedicated IP Address box to allow the module to auto-allocate an IP address.
-
-What does not work?
-- Quantities, only a single addon => addon can be allocated
-- Terminating / Suspending Addons, I've ran out of time on what I can do currently
-- When an IP address is allocated WebsitePanel does not return back what IP was allocated, so WHMCS is not updated which what IP has been allocated to his / her package at this time.
-- A single user => single package is the only way the WebsitePanel server module works. I have no plans on making this work any other way.
-
-DO NOT CONTACT WHMCS FOR SUPPORT WITH THIS MODULE - THIS IS NOT DEVELOPED BY WHMCS AND HAS NO AFFILIATION WITH WHMCS OR WHMCS.COM.
\ No newline at end of file
From 9a3fc09abc85fffe870929c4af9ddcac90786d25 Mon Sep 17 00:00:00 2001
From: Christopher York
Date: Sun, 23 Feb 2014 21:25:37 -0600
Subject: [PATCH 02/26] Woops, missed the updated version number on the WHMCS
addon modules.
---
.../modules/addons/websitepanel_addons/websitepanel_addons.php | 2 +-
.../modules/addons/websitepanel_sync/websitepanel_sync.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
index 05b3e7ef..56d3d780 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
@@ -50,7 +50,7 @@ function websitepanel_addons_config()
{
return array('name' => 'WebsitePanel Addons Automation',
'description' => 'Automates WHMCS product addons with WebsitePanel Addons',
- 'version' => '3.0.1',
+ 'version' => '3.0.2',
'author' => 'Christopher York');
}
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
index a1375e4d..6947a376 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
@@ -48,7 +48,7 @@ function websitepanel_sync_config()
{
return array('name' => 'WebsitePanel Sync Automation',
'description' => 'Syncs WHMCS client details / contact changes with WebsitePanel accounts',
- 'version' => '3.0.1',
+ 'version' => '3.0.2',
'author' => 'Christopher York');
}
From 3d1bb47f3547ee6e0cfe8a1da03fe3b91d3b084b Mon Sep 17 00:00:00 2001
From: Christopher York
Date: Wed, 23 Apr 2014 23:21:29 -0500
Subject: [PATCH 03/26] Added: WHMCS module log debug calls. See
http://docs.whmcs.com/Troubleshooting_Module_Problems
---
WebsitePanel.WHMCSModule/changelog.html | 8 ++
.../addons/websitepanel_addons/hooks.php | 2 +-
.../websitepanel_addons.php | 4 +-
.../addons/websitepanel_sync/hooks.php | 2 +-
.../websitepanel_sync/websitepanel_sync.php | 4 +-
.../servers/websitepanel/enterpriseserver.php | 2 +-
.../servers/websitepanel/functions.php | 2 +-
.../servers/websitepanel/websitepanel.php | 84 +++++++++++++++++--
8 files changed, 92 insertions(+), 16 deletions(-)
diff --git a/WebsitePanel.WHMCSModule/changelog.html b/WebsitePanel.WHMCSModule/changelog.html
index 6a313d0a..e2b32726 100644
--- a/WebsitePanel.WHMCSModule/changelog.html
+++ b/WebsitePanel.WHMCSModule/changelog.html
@@ -9,6 +9,14 @@
WHMCS WebsitePanel Server Module
DO NOT CONTACT WHMCS FOR SUPPORT WITH THIS MODULE
Changelog / Updates
+2-24-2014 (v3.0.3)
+
+ - Added: WHMCS module log debug calls.
+
+
+
2-23-2014 (v3.0.2)
- HTTP communications now support GZIP compression (enabled by default)
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
index 5982c6c6..bde9b7d4 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/hooks.php
@@ -37,7 +37,7 @@ require_once(ROOTDIR. '/modules/servers/websitepanel/enterpriseserver.php');
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel_EnterpriseServer
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
* @final
*/
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
index 56d3d780..89a04ea1 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_addons/websitepanel_addons.php
@@ -35,7 +35,7 @@
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel_EnterpriseServer
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
* @final
*/
@@ -50,7 +50,7 @@ function websitepanel_addons_config()
{
return array('name' => 'WebsitePanel Addons Automation',
'description' => 'Automates WHMCS product addons with WebsitePanel Addons',
- 'version' => '3.0.2',
+ 'version' => '3.0.3',
'author' => 'Christopher York');
}
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/hooks.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/hooks.php
index 279f0d30..aee3f71b 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/hooks.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/hooks.php
@@ -37,7 +37,7 @@ require_once(ROOTDIR. '/modules/servers/websitepanel/enterpriseserver.php');
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
*/
diff --git a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
index 6947a376..b5bc3ef0 100644
--- a/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
+++ b/WebsitePanel.WHMCSModule/modules/addons/websitepanel_sync/websitepanel_sync.php
@@ -34,7 +34,7 @@
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
*/
@@ -48,7 +48,7 @@ function websitepanel_sync_config()
{
return array('name' => 'WebsitePanel Sync Automation',
'description' => 'Syncs WHMCS client details / contact changes with WebsitePanel accounts',
- 'version' => '3.0.2',
+ 'version' => '3.0.3',
'author' => 'Christopher York');
}
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
index 5cf56d31..b3bd4afb 100644
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/enterpriseserver.php
@@ -35,7 +35,7 @@
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel_EnterpriseServer
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
* @final
*/
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
index 49c3b40e..fdf0cc28 100644
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/functions.php
@@ -34,7 +34,7 @@
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
*/
diff --git a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
index 4cae0ae7..03ec01b4 100644
--- a/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
+++ b/WebsitePanel.WHMCSModule/modules/servers/websitepanel/websitepanel.php
@@ -38,7 +38,7 @@ require_once(ROOTDIR. '/modules/servers/websitepanel/functions.php');
* @link http://www.websitepanel.net/
* @access public
* @name websitepanel
- * @version 3.0.2
+ * @version 3.0.3
* @package WHMCS
*/
@@ -143,6 +143,9 @@ function websitepanel_CreateAccount($params)
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
+
// Get the newly created user's details from WebsitePanel so we can update the account details completely
$user = $wsp->getUserByUsername($username);
@@ -195,6 +198,9 @@ function websitepanel_CreateAccount($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
+
// Notify failure - Houston we have a problem!
return $errorMessage;
}
@@ -240,6 +246,9 @@ function websitepanel_TerminateAccount($params)
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
+
// Notify success
return 'success';
}
@@ -251,6 +260,9 @@ function websitepanel_TerminateAccount($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
+
// Notify failure - Houston we have a problem!
return $errorMessage;
}
@@ -296,6 +308,9 @@ function websitepanel_SuspendAccount($params)
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
+
// Notify success
return 'success';
}
@@ -307,6 +322,9 @@ function websitepanel_SuspendAccount($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
+
// Notify failure - Houston we have a problem!
return $errorMessage;
}
@@ -351,6 +369,9 @@ function websitepanel_UnsuspendAccount($params)
// Something went wrong
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
// Notify success
return 'success';
@@ -362,6 +383,9 @@ function websitepanel_UnsuspendAccount($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
// Notify failure - Houston we have a problem!
return $errorMessage;
@@ -408,6 +432,9 @@ function websitepanel_ChangePassword($params)
// Something went wrong
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
// Notify success
return 'success';
@@ -419,6 +446,9 @@ function websitepanel_ChangePassword($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
// Notify failure - Houston we have a problem!
return $errorMessage;
@@ -472,6 +502,9 @@ function websitepanel_ChangePackage($params)
throw new Exception('Fault: ' . websitepanel_EnterpriseServer::getFriendlyError($result), $result);
}
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $result);
+
// Notify success
return 'success';
}
@@ -482,6 +515,9 @@ function websitepanel_ChangePackage($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
// Notify failure - Houston we have a problem!
return $errorMessage;
@@ -507,7 +543,7 @@ function websitepanel_UsageUpdate($params)
// Query for WebsitePanel user accounts assigned to this server
// Only services that have packages that have "Tick to update diskpace / bandwidth in WHMCS" enabled
- $result = full_query("SELECT h.id AS serviceid, h.userid AS userid, h.username AS username, h.regdate AS regdate, p.configoption2 AS configoption2, p.configoption3 AS configoption3, p.configoption6 AS configoption6 FROM `tblhosting` AS h, `tblproducts` AS p WHERE h.server = {$serverId} AND h.packageid = p.id AND p.configoption16 = 'on' AND h.domainstatus IN ('Active', 'Suspended')");
+ $result = full_query("SELECT h.id AS serviceid, h.userid AS userid, h.username AS username, h.regdate AS regdate, p.configoption2 AS configoption2, p.configoption3 AS configoption3, p.configoption6 AS configoption6 FROM `tblhosting` AS h, `tblproducts` AS p WHERE h.server = 17 AND h.packageid = p.id AND p.configoption16 = 'on' AND h.domainstatus IN ('Active', 'Suspended')");
while (($row = mysql_fetch_array($result)) != false)
{
// Start processing the users usage
@@ -541,12 +577,18 @@ function websitepanel_UsageUpdate($params)
// Update WHMCS's service details
update_query('tblhosting', array('diskusage' => $diskUsage, 'disklimit' => $diskLimit, 'bwusage' => $bwidthUsage, 'bwlimit' => $bwidthLimit, 'lastupdate' => 'now()'), array('id' => $serviceId));
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $package);
}
catch (Exception $e)
{
// Error message to log / return
$errorMessage = "UsageUpdate Fault: (Code: {$e->getCode()}, Message: {$e->getMessage()}, Service ID: {$serviceId})";
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
+
// Log to WHMCS
logactivity($errorMessage, $userId);
}
@@ -565,9 +607,10 @@ function websitepanel_LoginLink($params)
// WHMCS does not return the full hosting account details, we will query for what we need
$result = select_query('tblhosting', 'domainstatus', array('id' => $params['serviceid']));
$results = mysql_fetch_array($result);
-
+ $params['domainstatus'] = $results['domainstatus'];
+
// Display the link only if the account is Active or Suspended
- if (in_array($results['domainstatus'], array('Active', 'Suspended')))
+ if (in_array($params['domainstatus'], array('Active', 'Suspended')))
{
// WHMCS server parameters & package parameters
$serverUsername = $params['serverusername'];
@@ -589,7 +632,7 @@ function websitepanel_LoginLink($params)
$user = $wsp->getUserByUsername($username);
if (empty($user))
{
- throw new Exception("User {$username} does not exist - Cannot display LoginLink for non-existing user");
+ throw new Exception("User {$username} does not exist - Cannot display account link for unknown user");
}
// Load the client area language file
@@ -597,6 +640,9 @@ function websitepanel_LoginLink($params)
// Print the link
echo "{$LANG['websitepanel_adminarea_gotowebsitepanelaccount']}
";
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $user);
}
catch (Exception $e)
{
@@ -605,9 +651,9 @@ function websitepanel_LoginLink($params)
// Log to WHMCS
logactivity($errorMessage, $userId);
-
- // Notify failure - Houston we have a problem!
- return $errorMessage;
+
+ // Log the module call
+ websitepanel_logModuleCall(__FUNCTION__, $params, $e->getMessage());
}
}
}
@@ -631,4 +677,26 @@ function websitepanel_ClientArea($params)
// Return template information
return array('templatefile' => 'clientarea', 'vars' => array('websitepanel_url' => $params['configoption8'], 'username' => $username, 'password' => $password));
+}
+
+/**
+ * Logs all module calls to the WHMCS module debug logger
+ *
+ * @access public
+ * @param string $function
+ * @param mixed $params
+ * @param mixed $response
+ */
+function websitepanel_logModuleCall($function, $params, $response)
+{
+ // Get the module name
+ $callerData = explode('_', $function);
+ $module = $callerData[0];
+ $action = $callerData[1];
+
+ // Replacement variables
+ $replacementVars = array('');
+
+ // Log the call with WHMCS
+ logModuleCall($module, $action, $params, $response, '', $replacementVars);
}
\ No newline at end of file
From 4d44baa8172cfa1f09e10ebe87d38007a4d614cc Mon Sep 17 00:00:00 2001
From: Olov Karlsson
Date: Wed, 22 Oct 2014 21:34:14 +0200
Subject: [PATCH 04/26] First commit of SNI and CCS support in the IIS80
Provider. Only small changes to IIS70 to allow inheritance.
---
WebsitePanel/Database/update_db.sql | 42 ++
.../Microsoft.Web.Administration.dll | Bin 0 -> 143360 bytes
.../Common/ConfigurationModuleService.cs | 2 +-
.../SSL/SSLModuleService.cs | 2 +-
.../WebsitePanel.Providers.Web.IIs80/IIs80.cs | 132 ++++-
.../SSL/SSLModuleService80.cs | 508 ++++++++++++++++++
.../WebsitePanel.Providers.Web.IIs80.csproj | 17 +-
.../ProviderControls/IIS70_Settings.ascx | 34 ++
.../ProviderControls/IIS70_Settings.ascx.cs | 19 +
.../IIS70_Settings.ascx.designer.cs | 109 +++-
.../WebsitePanel/WebSitesEditSite.ascx.cs | 23 +-
11 files changed, 841 insertions(+), 47 deletions(-)
create mode 100644 WebsitePanel/Lib/References/Microsoft/Windows2012/Microsoft.Web.Administration.dll
create mode 100644 WebsitePanel/Sources/WebsitePanel.Providers.Web.IIs80/SSL/SSLModuleService80.cs
diff --git a/WebsitePanel/Database/update_db.sql b/WebsitePanel/Database/update_db.sql
index fe2f171b..98849b0c 100644
--- a/WebsitePanel/Database/update_db.sql
+++ b/WebsitePanel/Database/update_db.sql
@@ -5374,4 +5374,46 @@ AS
RETURN @Result
END
+GO
+
+
+
+-- IIS80 Provider update for SNI and CCS support
+-- Add default serviceproperties for all existing IIS80 Services (if any). These properties are used as markers in the IIS70 Controls in WebPortal to know the version of the IIS Provider
+declare c cursor read_only for
+select ServiceID from Services where ProviderID in(select ProviderID from Providers where ProviderName='IIS80')
+
+declare @ServiceID int
+
+open c
+
+fetch next from c
+into @ServiceID
+
+while @@FETCH_STATUS = 0
+begin
+ if not exists(select null from ServiceProperties where ServiceID = @ServiceID and PropertyName = 'sslccscommonpassword')
+ insert into ServiceProperties(ServiceID, PropertyName, PropertyValue)
+ values(@ServiceID, 'sslccscommonpassword', '')
+
+ if not exists(select null from ServiceProperties where ServiceID = @ServiceID and PropertyName = 'sslccsuncpath')
+ insert into ServiceProperties(ServiceID, PropertyName, PropertyValue)
+ values(@ServiceID, 'sslccsuncpath', '')
+
+ if not exists(select null from ServiceProperties where ServiceID = @ServiceID and PropertyName = 'ssluseccs')
+ insert into ServiceProperties(ServiceID, PropertyName, PropertyValue)
+ values(@ServiceID, 'ssluseccs', 'False')
+
+ if not exists(select null from ServiceProperties where ServiceID = @ServiceID and PropertyName = 'ssluseccs')
+ insert into ServiceProperties(ServiceID, PropertyName, PropertyValue)
+ values(@ServiceID, 'sslusesni', 'False')
+
+ fetch next from c
+ into @ServiceID
+end
+
+close c
+
+deallocate c
+
GO
\ No newline at end of file
diff --git a/WebsitePanel/Lib/References/Microsoft/Windows2012/Microsoft.Web.Administration.dll b/WebsitePanel/Lib/References/Microsoft/Windows2012/Microsoft.Web.Administration.dll
new file mode 100644
index 0000000000000000000000000000000000000000..7415f557f3efdcf7c1e59bdd713175ac7d35c63e
GIT binary patch
literal 143360
zcmeFa37k~L)jnK(Z};tfY|2VE~iw{e8>tbCjxk
zPMtb+>eQ)Ib?a7DAG_=lWh$lo`1kqeN=_c%h}J2J~8o2=9nLlptMv%jGROO_(>oDg@u|4ScDA{~u{!1K+Fh$9Gdh1=Q}xm3rkQpDGZn
zQkOiS)M3R+9UoI_n~U3DH2R>27F{=N)6nnUa9MisfJGC({oe9_-1nV#p6Yn+_jf)W
z`_2gkw@06j99Mhg#zF61{*xK!U$yMBXHFcy=<@IGFqgjY&`3XZR0WTgvY$LXGitM-6!@hh};o9IZ~x=`{pC{?_9I<
z$-wSWM{PKK;X#IdkW%4z`AAbWK~TJ1N46sHSJ-vHCt^te8+N18h8+XK8H6A;7(v+D
zMmiym*KP%cKYoz0P2xp1A}fi7f(?@33_*sti#@+j?DKtMi+z1@SlTD{mOim>^@*)C
z`rYL*g^ijN?YD1_R>DFZ}o{C
z9_Yc>v!{cMG**ZF@#~Cq5OG75Vb}v<6V6IQSw?D=Pgxlz=;XL6$AZmaus_&M#7JW$
z$luWD=4e5Vu$8C)2UwtGr7mG+lGGO(_T?U+bHN1D0VKsoySYz6?zmF@Vc%zgKi2Ld
zM%QE%lsH6FB-f(`&MJ^~9tHc?*_}hdw!b}$X6uq&uz#|`ZaZu_haf|2vD`c^&^4>JLfPo`T2fpP8vi_XO8$rx+7a@h6n{g1w}jPTfHo^K
ztkl;i7ZWv!_&rV&tf>Jf4E_o-GIo3daSAo;b44)!KmRh0(WcB9)Tz|
z62Tcp-I$8#g%PAzIHQ0FTMdPF2{P$qtHOci!ky$EpKaI^km8Eg2GOWnLmdnmpsMl@(IDeZzrd7VV3Cj@~yPe?wui*y=5P
zj;r5BW&2<&e5Zjg)=hJI7`hKmiIC+qLsF6RUeRV>a?a~5XO17`oc;y4?^j+JufmXw
zb`}rRL!vOuB8IBEX;dFgTj~j;Q81~9*@qC>dYPiBKG}@(tb)$PZ4=hDA7ydQlqT$A
z$4eAjyBK%uJON?c>RLo(z2WQt4J_~qM9F5TL3^Y`#M(uQ6x>93XH>Gp4>!yV9iQRP
zh~r&mRA{&=1Do|DRnX-lEp;@y75Z1M{q|+Y`Q;ad@+BC~hDl``#=nbFln2TJ>}g@C
zMyWC3WmGnlJToi!#HWGqOO+=*DvzvhVyS-@ISlN|mks5IX
zvh4cvX^F5L2cUqZyl3}wCqlI8>
z{`iqb@_cB_#*|5qNbzwRW7=?77Sr$AE1eiOUE404q=I*Z%}+%pF*X_A;FL?O5HSNf15TxfewU
zFH?;IE%|l$jQK+)1zzAkYSYKiUqakMo9tGCfVGH(5pkzF1FskBFVo*drhW6l-aR^-razlUF
z@}@|6iGD#RiKjd^qBjhK3jG?gqP@cnLwn|(iX4fEVL5z|{27vI;`muc8jT)qh%r|@zUE;NRghwS6=q-)dAYNY1@YA7^Pn0bXW1T-q>
zEWmS7XeUc(m~FVc9nMdkg-9wcGxbp9rnr?JE0rdn0ta3)Tu@YBtP68Fc84M%dl6FJ
z2*o1->cuA?UK>^AbX5km`V6&AZTVDxUfq{g7lQ${N}c`%JgTML*+P?k4X{@|C;f;x
zX)I`&%~cqzT9SgZ4P!z%;ZmfgmI5e;Z?)XXrD&SP7?EI_ajxp+{xUR33^jF=^6DN5
z?y;7ETJ&8WaP?bMR^YZs0o6hEL;|AWaw`rE%68oU!)v&p*eoD2)=z
zuyQ%&X`RM7FE`LbfBvr47sha!r|B$52<3WnUfnNF1d?;!ygns!-W~8g873NOhRA0^
zudsgkGqrSu$--c628MA{JGXETd#`lXL%TiuiYaw6=g)i5vJ#&rkzs|>>NbQS$!%>H%Bw5pjPoy==!P_GW0>7)g?-UdKoqr+L(ZO
zgR|SZWEZw7n|yJ@vR9J#zB)<^646m;2dARk{?boqQHk-XRfy>KsXB8fryknBFb~YQ
z6_J>+>k=$yH88_^(05Sb`gNs;e>9ynprur6-MEc0hHKGB=MaeEch(VyH(%l$e?EYz
zq2z{Z5Z4OX!|_`CB@UjmjbwjVq700t-Od!~)vu=pQa`-l^71HTk(270&&9pzbl+f@#BjSm+I8o~X2(ZS2$+_h7|dCP;bK3@JjJcDLp0Z=23m+*HUB;N`UIA9AGf{?(Udo9s@f7%m
zy_gZxiy54qFk=j6SCZK_GJ~a(8S!NHlPoiMCa!}BMti30%fz3uM6E6nF};Wap&rV{
z8woMlHxm0oh>&~9pj#rQ$6LfzMjD;V-w<_Ygvn9hAoK7H@j8|71aPTRJI1ITdWx0W
z&f39-Sv%rcyUCtR(%fh`zT3igt3m9;b>VfR3(Oez=&c(ywa*vIuoX=%0Fzs-s=Bba
z>szgA$>9QdFO|ADQ{HT|jSA-|k}`&*S5wlnC@DHGB_$qm^~XENB*--wa@B^7q#o0-Dr5fS`_=J&$
zOZ7Lz3?q3a^6cJ$RE}|BdnCAb63mc7)5(}M3>;9j_x^Gdvg!Ai)Gf%e_e%@?BJ;r2
zd3o;0<#}qqd4}flJe3H`|T&Ffl8)ub`LQJ3U^^Jo==#Vj1$Ss+y6_9AOOIdofd_0hJAPr9ETGmWoR0+iCOrNx3|b+u
z(Tl)+4O;mF=IDNTP~DVKmX@aXxQ6Ky`lPI(lvC{(iL71g&`qc;VtTo~L0O^`pl<4%
zKIRm}gQ&l#xR{$U+g~?o%06981DL?YM6(`-8T#@tEG^%|z{gF@3IKzZv4t2@7X7jw
z{DdsgY?p|b{4oQZQduIY3hRLhYPKE$v$A83FBl^y8&
z7@OgYgc)Nndy>qyp)+}7Gx6E6S(qJYY!;#ejm;#|bDW-IGsaUSiA0Pc(GMumc1mRF
zQHuENcuGG1kBz4oWJFTNko1R?^ejs1j;F+<6MAD3Wc9|1{|}F2dDeOatUYf^i80Q`mU2*pGHhCpZ2gvlrj%hvK)=Nz_TfK1>sk80@4t$^K=~{rzkGP>8syWX
zlRNPIoBn$27b%y8^eT7W|HX1!zDT(&B)?qvC`=Wl=QBpnf15r3JobD{0XUN;o<6E8
zE1zdF#N?Zd%J~O7=O4}ll{?RUNmeV}g(>%YX3CGx}|CSMH5
z`c#$(i!Cz4AjaHDC_8rlK1<|v6k>XHlwffWYv^MRf`!d9_csR_1P(psAk5I$9E3&X
zw+-qsIa|6XA~C&0T$&~FG(t=-jimM>BBmG7l^#*nKM|AjKX-WP^39PGH)47*d2TNv
zVtNsM;2A`TWP`USUTPB
zDG(|e`Wr?Vb`fBFgdA!l
zi~G466Sjpxy$)7n1nu7;cZL~sws-~KIf@itEwEtJqYNzA+94KkDqedei!Q;it0>1<
zsHDNP8b~IR9Al(eEvE=YU?GA;cmvXr$Y|JM;0(JsFJzT;#he5l<8D!&erNR|SFY|O
zjPxkhJ(jIIRzQ*sJu06eg{DJgS=WpD;K@o*I9)4NShD@+6&B6uVh>nh7KKj$b`5or
zZXnd*L5-}Rr5AgRG$C|LQR-H-S=il2<)OZsLuL746;fVU7gH_SIcS@1_YKem8V&FG
zy^_OjqfXzuQMIzQ5Z*KGWbfKym5Pn3nVakKR(Lqi9PhP5x`~yv-4c5?W}DZTG9}93
z+c6rGVO+lZ%m5Gm8C4lFLAwb>o`r_Ah&eN@>QN)%xJ0+RIE(cfUm>E$I0X$J-RqtaH*R|)&}v?px{Cyy21V09)igR5S1Ec
zB52ybMccy?FdMUyO+d?*Id7lbU!YlW>Xw6EgYPbapriA<2-!)8@#Qj93B6zp^rC%e
zK;f-ad^`7AEe2=3tY{RfWg^}XmFY+my-xYK6R;2hq%KD>XP6GHs-f6)
zas$%>Bh9j95HOt)NY6*;9Hv7v)VZ8`d5j`VpjmQxO{Wq#>6!SLfkpl7H%TnJDs)uz
zfh&OxV;4d-D8Vm?2;Yoih;-cz+FlP@O@u7i++bM&r;t%{4fRbX+1%{g^iTQa93Zb%
z;&;mOcwl{xwMhkl;}$aeDrp+qCOp>I}Jd0$!XJRWJ$1(gG7i7*3A9!bw(O%rPyv}q^<)xLqMY-i=X=x
zxNkjT`U54fSTKNn#t~W0*FX-MLfNxJ8Y4}P&OjK68Ss}NLy1)qED4nql!QwP?Qwf$
z25;84GFyh-v@ez?+NHkdUJ#5W
zn8Ns_ZE3gERcaKgVbx@&AjLAcbH{I)+~MQT+AU`K`LtC%Xkpx}H|A^kG|?V3W@aY&
z`7=i{3UD6|xieo&&wSN7pUKTnS#kWCX@pArndv$|23ml@%xsBehX>$*E~UiM#gr6e
zj*;|muDpPH3FTq32pgG##IQBf4}=f@?#90w{DXxn%&VyfBTcc5G^KV9f}VhA&1*j8pCA5HFv;dVUT@%$Mq(C&wBnUN-=L{P^l
zo%CpLels}~Y8oecJPyc)27ce31SuSxxIouL{aLzmGN248x(o4r-?DFpFvH$3(nPs-
z8dkA5w?Qif`(!4bB{%^w&k`8+WHMaqrLRx9#3jvF!hpF$7p>qN9$eSc?QIhzKh-=dr#ec@~{(W
zJ{^U1l>7#=>G@De@;N-4vSBmK-CU%0LGd;H;pex4=XkKK2v=YxnYF0GU_+py-0E(L
z7S+$uIg1PGMue~#<#vdKEtHJ~1@6Yl;$VT;ooofE!_F{T4^BBW6jnq6VnyNl5axzX
zH}Ked`9L?Oxxt`on^rtRVf6kmzgpL!ZId>?8nR8G&D%53(zJLn&beR%m%bEHQRwXp
z(K&r#Y}`nAr!AJTc)wNfHbhy7=Ucmru}39@JsCGc^kHwfM=f^u5(8}T4a6KGnZ<4@a3ICXcd`GIrw5kO??iWSz#YbOT<`?(_M4;=%x5|%Hf9Jo=(buhQXWl
z`zqpz+4>m)dnRbY{<03DQ$t!G(xgoXJ*@Bl4@Gd<0rYX1|xjLU?)rd3r3}n
z^}qvQ8!rzdd;?q(Vf%0t=iH5A;_zkc_S|zK-QRS+LA3s6|9$X<(PK==^;~*NbWkDI
z{nYEcLgd_o?9ROi3hVn}v(kNd!tR=z5e)-)KjMb>x=YHirFA3vusw|C=QcuP(ijdFhL@N!l@Mn;oYH%;#|irM{`%S7M0k#+Fk1(x=3^
zRfu0h$#i;9h1ftRV)%B~#`7#3`#)*nh3J4Yt&nVwcWBwg1!C0fY~J3k3NQUcq#HJO
z#CKPA7Z#vLXo=rIzby&{$3NU-RETSPkE`Jew2d3st-WY%uKzVcw!`|C4VH6KuCV;E
zCTl}ws&ugC^^lRKb8RS-4W)kPF`7v+23UOD#(S2x2dqk*HTUx8^)$a8404m#WmMjg
zMw;=~Z=>CvfI-bpRT
zwNM|fSB!x!4i}p}2`SjKttohr1YH0tPdPZX9s*@ZW~3SNV?WvHlSp%oG$k@@9B##H
z3w$qk+3VcfsZvRH;P)&n3klv7iLrxJLw@JnUUZO+b9RmoM@V}e1ihFJ&!J&zkopdw
z3|xvoT3A2C49ZKdY~C(1(nN>+a<0mG2z1zPTo|-Qze=H*<~$4}1tDNMj}W5o!W>Oa
z5q${NcY!ae?_XxvkKzfoE46d?9{R1qZYF&XNeyqHmtxZ_-ecTeS`qio?eT^yC`tMf
zS~$yiCy8Z?CR=)vD~sPTDU2s4=9M`hKp?kY2*Ybhu2*_Ap4gF%G3m+*$--raffWv27sRyYAvt_w1!hotJjYS4l!E@Cy9-f>t7_*Kw?N4_
zZExC(dechV)qocUPw0mEOy>x;1E=G1W|OP#+NUtqbSpuJ{q9=8shT`{#*>LgnxX59
za`{^F^G)fMkBLT_AsbMOGF{gJH`1YO_zTxo48L7*7@lF!6
zq3-}wszFjbQxHH*0X9Pb^C6RR)=Ix%z84ART+pc?A{)le1Xr5^Oa#yYAEeKm9Fx^0
z4tq?ZzFq>OOgplnPabT#eR48V3WKTpARKhb)R6NF5K8CM8OOiG@|fiOlCnUd1We~;
zLSBEGBl;AoUjeW6`3jz(&-?q(=c`Ea^f~Fk`ph!kN#Z#EVGZgq*vzp~&+kzF`T-&5
zH7PMsW;qYA(53=idSN%-F*|jU;ZVW~>509bGPv)IL50n7VX#za3#nnQ|4Q**qY2>k
zUzQyIq3fLdnG`f*Y|S7{y^i8CU4&C_@Npv_%?zZFlb90Y``UkjfsszT*e#5C56Fh#OUM;sfiBbt7)DoHxP0RGAuyuMKboz7PIY
zskcy_)F1E|-W^3Db_|cs2MAIhA}Fl7un9%U0p^SO6m&jf+MCF=op8h|!s$QD`EO#o
zi5&y%?&&>PRn(Q1nW2l?LiXFB;EPp5jTwpx!}QgtGdD!^-_D>uXzr=xgLxW5ZWrxe
zvBwwPgEQLQ6~P2P&R8hw4J(0la(UvZ82z3#PF?gPPalKSy+6~>jfTkucGzz{e1Iiu
z0(cjWlqp@1k*2}v9qfbo`K5j*r5BO#fdFGPWrzms-H^S0ULH9;Yhhviy^ZIwv2fwnt6_$fya`h1)7e!~`&nk4
zi#`Vu38F3z{VpK=c%jstJz!{|yYxc9U#6TtL)?ZMqe7&68l12cCux@R7bJCKscRA9
zH>gV2XBDgZ33`2n{FecL!v5tIhC_?;O18>;y5wrZv>+}Eb~`OU5lX@C`PJO})gdZA
z&nwq2XM7h9u;~{vL
zCMsH>k7aKrkw#(VPR_#Vj8>8xnDW@2K}t)XL#G6~5Y`PB+9dx7!7>E8tFRmx#NwcI
z(7Xg`p8*h99i&PU-WlIx)a`%LXW2M*tw-u&=*;ZIEZ=ZjI&qS2@g5E2{FN1|A6nr2
z4G%c8Y5yIu)l`9#-O#00vE|f4$MqrCO}~`yrn`e~`+0I1%2uP6dOl=;I=aD5C0
zY#OOoKqc<7er6!qg7oBO1RB%iW-cVZbP9a>W>SLHc#nH~_{;7T
z&dh8D7-%TMt1LyxYy*hrC-{7(Jd6C9?aZa?ht@f;NL{Urz*f(V;_Ix$Pd~GZWWItxbc-?5_iyQz5>U5{d+9cd>aE+=Mw&w5zOgg~
zl3F*^YQRW~Txm*Vze@$;rjIo1<|$N{l`XSp!$_HZHcU?@qSM|DBc-4;3YTjb>athE
z5G4&0)z)4YUMfSL?jU$e3M2IohL~sOGk{)$$(;oD9$@Ux5lEUoq;I527q#(;oy!yg
z#h=iz@UeYJ!Fsi%*amch_$I1)douY7*%ShU}#|N%9GO
zLg@8c?w^rCc+UXulSw1K(C>^Q5!O+#$ts+=6CAmY;&<0>M8izXJ>IXzHnMk#tQAt$eE{lE~n4dLxEQEoZE2P%IN#d`sNJ%1*Uzh)m)
z!$)epAE`xhiot9r7KM2Cgb103f#VX{{4i6lPY4hoFjoU+=WOJ}8xO_Jt)F}Yb>Jc9
zyAbojB*u7O_yp3Xu$?l@JL5L=9y)rTA
zsZiQYsG8U$`pz|sQkWQ9&N#%uzG%nnixz7XQVO>2v6x&1@1a0?TtdXj1MKWxAZcez
zbQ*=HtPy?<)yXhXldvK}(mm+Rb;lQA60cV3yu
zQzp&}8W3|{;1V6{5y@OTyn8h|tsO&iT2*!_nq?Oe@o#!}pA19+N#@BBnmZ*=1jPE<
zu0G3I^M%TR!@HkV!J;#K3SJ3Wo+B3%a&1P^P)%&q<4Rp2mBn^G7D>Qs)DFuuwa
zBMGmV{TKzD2qJ94t6(&_wJQWOfLN?FI{d`F64FOcD7YdEy$#Jolq<2(<_%;ud%m3issGsbO`b{qs
z>hv!UrMUxRR`#e96$8aVG1XKIIt}C06SM=*`jLy*@9DkrQ4An#4r21pxPW*dZ2;{-
z`vDp6N1*R!yTE>4)D}}mqT1AM7T|rC_GfBPC}e;rvY;q2WFgvlh@?Ak7Xtu%FUNZ?
z(W$q9&Jd?lxMK?@pm%KPjNbx9>JySNK_|0@!u4Tt#SWG?5o;8_f1{CXeu(#J;3)SDB
zS5fCWX%Le1+Vx;IfoVCfAZM@dk3*2;tzFMWPtgki`D0n``!hxZmT5W%>q6=KI8SMn
zGAW~@wMjWm=i4X^Z%Tsy6`u9(IjqN!NzfSzCP9vdQV$BdlLCl-{Tz$A*9lj;rO8c{
zgj>Icfh@xe&^uJXq#nu_W_e`>Wv`W#rPZCsu4D8Q92dBPpwt?cW!h{ojQImx7P;zZ
z@fsr;0q@Jl-j&;^3GyT~a}wtD2Q(}D1GFN2lAv=3%U)@uiF6fB
zyy@KO3MEu`?ThN}e5zaXfnEE`a(g}%_cQfb{5|rD7v)}moTv}jl+7qS>R
z_ksdVd}ylc%DIzeYhUS?
zKC})zVbiwfOYY0%*DQm=QdoJ8g9_^y{k)9VwBb{=4gZsJhz<7=QES9`0O_=05yrD&
z0X!QfkY~farS5SSBx(=9%X&9%B5On~-(a2e9x&W@T%gx`c*9c=L|B;i%L6PMo6qG?
zmpC}XOnr~DH=K^3)sJKv=Buy-nb$JLe6<2BQXr8Fn>nu~o^)t#p<@g>{JI>rPmvCj
zGSU%GIv9x1F$P^F=|+*ROw$ohIy9HiF$P@~=^$^|F4uI#lMWRYI>w-@CLK2IhwTbY
zM?7CRrLKrkmgIvIlYESkFV1|}$uIZMQeNW8A0ATZ7=vy&>2T^G%nM~mM?C3ZoG3fi2JTH8oaDzk_PI`=ICN%U@C9JOftTFPN(o
zda@w7?Ic&zpOEg1d{mN)Z4yMQoM-o9Q(-#K5xFiuPX!LlJqfH%`l?RixvHO2Lc9v%
zx^mGhgpk}^YnuZb;2&x%U8f==mW%U&G`Nb4*n34rx<^GudVfVmjJ6^peYYYbUA-bh
zOSC6YuN=oq!83{WsYub=;A~2UibVf{-(Dmo(r~B)Gx;v$bEiUXBlvgC<|OX>tfQIy
zJeNK|r*Ok7Z3JiPw5|M%l>AH=#zG&*2HG~(gWbLW+g^&I_(~kxw{2tD-s?N~h`M1n
zSmpN}g8Q=V5unerZCyTA{5^iWFL?L1p8N=A`y$7TjY2PWAw9C!1MJ%wYomUlMc--bc{i_
zfOJQauD_-u9_6D+0)radflW~)O~2!>N*B_ad0CN)E|H!weVvc{+gohm@de>WbZ*1
z1_vRR>=p4&HGIX_^CUzHe)rHYKq%!fw;GI5jfJenQB(?ykV8K4R33buRF^U6mXK~r
z-cU}w_(ZOc;F|>Q8@D`9pzj+e2p8#K@Xo()91aKmL)N4?mg~D4+}ea#3Ii&V>-_wwI{1j*S!ZkBhCLn3ogoPHI~k^0TtM|?;RT#M?oscR
zp1t3Z&W*l!81iq)<$ob7OR5WH^p--)&per2o;P!Ow(d92)?A*Ca(OQ6GY|KeVans$
zRNh^GM}xfR&9hkTeB&9DcO_qAF?~n&D#2u)Srv0`X4SRoO9r!sImR{2hxscp#q3#D
z-D*vfZ?Zjei*<4i&%dfFbrx-teF5uiuH1R|2#Va@$Mg_-a-Psl@jN-324+R``izL?
zuPy2h;l9DW`v&evJci}NMbkoH)BC*_pWWLj17V-{;$yLE7CIANG2me34u~OD*-HzL
zMoRoq3=$2*jYrVW)c4uWMoQfzIe;YRkNg!CQTr51gHz1h?URMSTEcasBB4;^48RMFk)31G)(D
znXp&WJ{1)(>|;>39``X?N%l1?g|r}D-%#P*(>MpT!%6}T1tmd!>B>+rhX)8J+=KgY
z2!|n8fTflM9lSSM!|F?1q~F291#T*K4h4zq~+Um0@dQdD!DK2Ee@AJ3@X&-ZMsajyhj!=J^k_!ZXt)OL|;9*B4X`oa@no`qFKRmJr~
zjDo7A_IxCWjB=6Wy2gTFXeiDaMf^9(DLWh_!Ottu#n$8g>H4x@Byd$Eu&&X>wzx>3
zYDqaRjIc((&nqy0NeMX)>EBj@bHLnbD^?-H$-`iuQRf5-YBl(yMcw19N*;*UOyD=kAU-
zWiG3|e~-(}ae+02g;tk0ySTpF8shygToa0MXFu13y4U4o@3w~5
z;_Q(lHl{!_Vl&2worE|&rbWb)4pShZV+^_!>GG#V#PefhRCvhN*)vV+>~aiK?E{
z3F80XJYIVGLwz5IaqC;^U>Y)EBPRa~1c=_r60uXl3}J`=*4?B30)qq1k)@Pg_m)*w
zd(ycD|C{ADA^!o(Wg)%Fo!3{nr=Z^6I{~kyK0@vH&RC~c*^)ur$gMr^TxDVT<>$4|
zg`OOGl8)OXiOGG=yTwc2HQQ}HV)CckXuYSiL@VHqI4372|C=9(xR+AffyarMd`{wf
z_%2>T|B-x`T=9t(3j(_j_2$6*#Wt&xpzEtR_lpSq=bjW&mAvcfm(%lXJ(1yRBS|v;
z;sX{ieRBCu_L{xnHJ*ek-D(iis~Y38L})FkD>1!@j>!^X>Lo|5Y0bJxscLH$6#WyK6V
zUQ4gSO&WB-*<0ve;4e7wXZte5>s0#kqrIDhvr-Y0>pgOLU=OMDE)T@a<~oWxoNo^H
zO?Z?QIWB6PL|cVH{1+7A{^G{TdiX?}nLMkg&PH`j`#$8tNc#;$y?%pY1?Vw9q~!SS
z2gO+;t>2pyo`Q5efx}GFpSl(tWcs}`
z?lJnm?C)eyfc)RdAZ$8h5_f+mW0+AG6Ug|#(f8%cx?N1)2!
z*7)io_iF^*pG2JuDxOQ>jT#niK9ThzZo4r3x!P!b;El{5(2s-h$)p|H;z!E$i0!AT
zFmJYjY3%}vh7E6wF|GX>l2aWd#9+)-GUDl1ABOgN?(un{CkDRpRS)Ctwm~-e%?xPR
z+J%Ll!Gtp|_E;6QG3
z>mqL6C-U+#-@Z%CmG}L;Ji5Gn=gOD!$-I1Q+a<_+0d!{SUGDnV9ZD5``;yleUW^Wu
zC@=+>R~0v~O*Vx(HefWvmKu(_^Y9B&z|>VWm78*(qj`Mt2sEV}R74i8yWE8`c1pX9
zMUF|>GwkxOwK4S!Pl9bJLw4I^O(AMnSYdh%t%Z(?a`X=|VG~goia}y1n3q^ptbbss
z+=`Y5>zg9@T|3@1fP;(ZtgnJcA`rz%yhK4F0>mbYj(dOV8XJvhF@A`234b>aKYE2l
z?(paz79>zymHXR%-aGt-lpE(WM+dFCx{>%{E359z(P1lg6J#csoQ}$3NmY(3RW`~m
zUome#q;oFi4*F0W@AK5>mN7NvS`@zpMeT0Z6rG-Nk!e)R!@Ov52)^fZ2%MN0%(X=W
zYEK;|O5{q+g4AYE;4;jheTG|W7bSO7vR~-6%tO?;lav|eK=OY}oBP!js3WY$t-olY
zP{Jbj%qvwNt($ZMS}YCOiwiDlt7$4Jh+c=EzP_%op!&L^`YFIwkD6rEO>(wK^^7Ra
zfy_Yt>1;i$1+4xhv~U44EJbd_RZFicG8=GA#H?yEf^oCD$q4Bw7;Mtd^)+<~*0wAD-sd?B4XI0h^07PcilX!k6kw}U{zl?FAi+=sPUeVg+??eA{K>Q0gFw;
zJ-UWmBwos#cz}fSWWG2Rvgmm?Ue&@*hHYvkyXYo46>O-rl4oXtVk>!p2P8rmQE51C
z#SSv6S5)Ii^VXS(Qr&}f52Ill_A4ZWH*q})Qc7uWGv0nQ!O
zO&l3E>PC(ZAXpkV>Y7Iv8+EHkhVZnsF1!Sosl*&f%v8ZdU0qmluoX?#+hzxl^AYALb;PZ=l(%t(c+t
zGxs3yCx!(Q#R2(YJndg#WxS3yb1zcsOL1kSJOr%@dB6!%u>S*v=#Mp~>mkFHeaKLl
zd^0PN{h<_{z>l<`R%c+sQx!=46SzX0;*}d4vDZ;tYpi?4Y7@j`S-GpCM
zVRkD~W7VC}SZvjeZVXwmhateQVfYObrgh`^GN^~W(H+hmz;@4sj8pT!qW5JPxIJrnbw!n5OTAxd|EB92wW=G-;Q86B?Xw|U>YRNGi1PKha9bVbp5x^1
z%p*IrjGR+p-G;wsC}+Vjeqc1N%3w^xnd4{%Lxta}(aQQCsugd9!Fbg+%**}!NXh(o
zFYCir&=mH6i@+emdV5G6i#HCut6)!FMG^aF&aOlsG8@jeV?|VJ1Q+}jO7WQKvJDZj
z5fQN!?J5M=pRNV`3yLF`x-*)N2&QV0R1^&FJ9B8SjER8}zi#b_zq%<@QC{#SXBAnb
z7NM%Cjz2YwPmZ|I4vXdWyLD0?GHly@-XZegfS8Z%Yx}Ua)blR
zLVGH2kmNy34wZ${cV&~iOXW=LxoBL^^ok+@yf6gxj^cuNq=34eItCmNv%mif|KiXy
zUSQXMJAa-E%kaOzxS(Q{qDnsm7C544X#C>Lr(Iq
z#A8bS;)0n)kSTzFn3MB9SGoUdA!3dr8}N_)=`8%?cZzS~-;?+^;fDx9>ARu0==6u=
znT{wt;cj`RiwRqkpk^h`Cp-ZI1nB9Zc52Kiy--TA-iU#~IyLQ6$4IaL_C%#hS-Lis#ot0=n*+PW=r0-`&
z{m@UgvYRD}-2V6HY;VFG5w{|b#?IL>(9$^EtT{nVR8!S7tUt}bH_ju~jdi8&Tfh&%
zLf#;@aZpYEnXZ$|uG{FQv*Wf2dw2|5;GZ7Kjefo~8sDFxUOnJ#8Q{dvyUEdU1Mmd%
zz}g$N9(D2d#VDgbh^=SDGCzz)4+{ifGk)#!0bEFc+Gol|1gLxhVB*hE_XI8>K-Ckt
zlmOLF;4%Vi0vr#Nc?iF#V)9!I7f@eW5~#utvG>Od^N@e(gFKl(5fEuI1NFk
z#=9jQ4R8CXYAlOb{g=*UFFFzbxY82GC*rZNTMIN{zPSuW&qciD>nJ~cH6?i%0Wow5
zqMFPjc#500i9CfoZHR?O>Ix8n^G`6gpmlkk!A19wk4k9Uw`-C=1x=!}Zw9XYfp*{26HquidE+
zEy$gD#Mn?i771C8o{7Y`i4X0SNQ_R@J2i36ZKN;fo9s`a``k8X?K{vlL)v%jG0fAj
zp4xvW1A7lgyFy5g%Y()B!vex~KOpj`@Y@GKxaU$i{~U!{!T(00>kJ3;&~OT-!Y`i%
zAS*PtTn7P*@agy^`*Ps%JG}N4h@)>8(XTT9glv9u@BIEP*X%35-^g5xG?@dVu@$@z
z?24swx^GkVLMs~NGfOms8Crq1L+D-3k(xMd2V{mFN6)$vGt7z%yN)T1kYTqoqZxKn
zMC?Y0*ntqS>wub_1`)dpknAW}%wd;I@Q5H{R~j@fp(u9kx>%9R(q(u^DXPA#kK%Dc
z5ZSeGIO)e{kq1ZR_$)#h;zqS(hB#3zo*{lz%V>xz)q+YJ82qhEbtCl0yJ|3Ap=U)i
zUjtLdHy~zLV@I-mP_Oi5Q_i2@U0U*B?2$Y-x_NF!Y%!RbOa)cnJ6&<(+xyz!!5^DSl>{$x_LM^z|Uf1}R4P8wL&ot`{yS?ufnY*~Jp+kG8*uqNPKNuvuoS0NHN
z9>$%oC0O_fIk3I(_4@KwN#<4wtT2BY%w*AD9)S3chmZ##*ujND9vZc4h@cj_|LFCK
zT`1o!1Co2=Dnt#nsd5r|Z;jZ1SP2*l<>*l1%y-EZXU`ZX2I|;BcY=))<)GJwF|Y_3
zuE>(C+QHPOs>ydQ7C_obqy>Vpgt32HuYjTu7)#jhjTCnx$iwv#Dc4TXiNd%uTd#TJ
zL;*a=8R(YlfktaFu|q?<#Ye^HqhONZT?COP85^*nmpNQd2r~>f}9HwJ+G5>TF%#IKMUJbR}C_Q`x8at<9@ilZn{+REv{rZ(Es&Eox1s+MJHVMmLV+
ze~H-iuJ+EZWb0uatzDhT=JrJF_^y@hZ7s*NZeHk|+S+m0%JJiy$F__eH)>+jnAVXK
zCJx!Vu&$2Iw)L%E=?hwM1s^u7QBwRJKXnHGe{0LCD=x2i=dxb)zy85*-hF@ETYvkw`jsE=>9?Rn)vlhRhTiu=b;(I@^&i5#gMlH;HF
z+D(W4_~uIv|Mil)k9qHoNei!i;peSas2AGq`1_D{^e+{~Ki=Q}0~6nyQT_{f#z7#3
zYl76Xh?|j)FMjGD_;(J(!o`tt*V_P09G8RUzku6{JeS}ZqoMr0jy(9_rY20qG;#xu
zSF~ZQVaeg_WEQJzJ}Mp;)O_GDcPNoMbhg@bb2aT{DSPK30g
z+yhI5)iibF7h`uI$zB1gL#IiNS4p@A?-(7}5ARiCI%G;pMOGtSH$W@YoWy?){#B!u
z#_W&5EM)FP`=v0|rDDBe4nxMqzPZnp_E>;Stthb#Cm`GKv`XPKmx}9VB5IB5f&e)!
z&j%5$;5d}h4qJdHP{TAi4I&Bh7Q~cVmvAJWyF?_+m(;YL6`u(ntC$0F8=H{TR^{7=R;8lwA*MnK1^`eVbSKbfV`ar44Ad)X{Mej^S!GA9Z7Y
zXjOM0!?Y6sG($~P=t9W80A%c2WWm}T4;=fzVo{0L`W_9{fn^*uVc%>P*hO{~BhrUS1-350y>x(ixP+R;F
z%%&}dEGwZ5YtpG%^e+1XRO8P=xlE5h;@QywbS12
zY-;OR6Kh%9oNR9C#Gp4RmL7>?PDA&{V`Xcsb^V6U&5gdJ_9Anf_E<;r`qtRLSKi_K
zX0*O5)fq#PD_c7^wYGM|M#Y*tR>j7R$rHNKH-z;WytcD*LuznrWn0H8sgSC@VIeu?&04td_yt+wwp6UGV`FoB+bWl^r8U_JEklQ`v9-;q7`3;O0%SQf`i7As
zRJyutO;=JYJ0+8h7}&8@Vopap^mOp?(C_+ID7d{fm12%mXR@oM(@6^3)lOFjik(ev
z8az9w{>z
zu2Uk$B6{Q}!!@m)VBgWY34~Ht*6L^qp4ETM+0g209sJlnsjdwhoFs+Yv=%MZ97~B<
z+9E-d&D!XjEUYNfe_*Z$^_e->FJ>_v#?-}5mXyw>n*ps0nL9VfdRujRR7UJNJvx&>
zOSQCjrP?;4#>=PXcVzS~SWnih(b!Vit`0G)P87Nrw(o38#iq7&wr$iFN!7Nsw8rek
zQx^;u&3AQlpno*_t`tL9j5+TrWL$;Ztt<7!Hno-=D3`No!J_GsH`VEE*wDJFk)$bT
z??B@GJG_&gT#xy(+}YQA__F=0V5Nip@rCq2#zOdoC2UT%#yk;Yv1u%c<^|QFgCfmS
zjZe3JTe7pOxgA|KiH_wYH~%MYxL)jNznpSLH)r8mnD{2$E_+)^V=OimePnHGGE1|*
zc{4pLObv}qWu)l2*@GH6C0{Umnh!5x{Nvj3M!pP<9XWpVcwzz=WR-dhFY`mT;_gtq
zppQ8n&kH(}U?y|=6U%*S3KpA(oUlM$@2*V^IqHO?XCU7_fD`!0GGto2vy$xahY6Bz
z@#ha6QGj)S^-o_DFJ)orc*j+D;@=Md9E_13r^oaf1mN3<$G`96AI3*n-zGit@!ul+
z!}~LSo=nLa1ZhseKfD?WEom@33vI&1bFHEP|M0>ocyb<7(okZl<4X=hxT)ksgy+ih
zrGmdoFt^I{JrX`5;j{E=A+t;Gx|40p=
zAFE~faV^W5-=Fxu#~Mn)YUl9n!!31ff~kLNWH@9*LrIC+JYv-dOYIp+nxBp%{?C(`
zYwF}fCR^&K)A;-UYObo=*@))pHA;TwtlXMdgdaYQd7BODr|#WafHp
z#rIZN>Q~JSe5B81sy2eVY>{F
z@Gyrh*y6DDFLIdrb>J=akc3Y=Y`d2nO8X~=a{4wfwM@cs8?I_wqvqp7&BH!j%K8nY
z+$P~g67G`lp$*j4vl6~9dHbD4&Kpi6w{uQouInV+C1G)r?J_1w{0s?~O1Lh`dhJLu
z*Y$WVQx7I5<8<54lGM*Df-g!jHJ+-){Yp&;!|J3IEo!Uaccs3!BCK|&hOdjN-vA#~
zC7u5Q<%-V9otD}n;a4Oq=%UQkUDVYBT{m=DD!P%J`){O8j>mIY9kr3=F4}lOM_8={
zKCId$JVV0sCHxLTOZ{-;VW9uHJlmVd`7nfG_3QfW!^0}QiLG$?CffP$H(k}no=!i&
zkZ~#Ye2s-yAjB2*`2v}0fbwA(eGQ-@+$lFnZAV&4pnht$q+J6jjNim;73e-mi{hP%
zTz)7}6(;xO@(VyExK(hAKp#t54SvauY05Vizv2|b_Yy+2fQr;0b&Eg^E_9zjGX)x|
zo)YLp7kXKsv_QkuI|5zpLZ1Rs>H$EN@P`2arRrHpOAtbNF9M3<+gBYRrG6`vW7Tk|
zqEvkZD1fhF!+imyeTsjT*c&&>XM#&PJZwNs0Yomt3{oDeCirefg~k}Ok-AJR5=uuX
zm#M{4`XxfSTrHD)cL;Q{S|!jk0OO&fBv8A0P-^?GK!s1yM7vOv=XI!nDGP@6#KtJi!c^wkBZ5<4E>6b)_>Xr~K(MWBmZ=n8=@aiQA<
zy3Bt(%uy42KAQjY*4-jD4@Qs-j>?_%})*9u6`#_
zI6&x5^#_4$f$ma&7HEb*_o#n~22Tc5r0!RrOIixhB>YZv&|tgol(Yv_p>cDmUwth=
zEj^&h03mjvD6UEU1J;C?@B;ymB-PV*a-lr63`O@eM_KkxzJMrJ?lcx
z3-p2uy(Z9$F7&ZLueeY_0eQaRLIVYQ%Y_aR=+`bZMxfuj&=i3_a-q2bed$}y#`-1+
zv>DJ`HPyFNpsz^UOkcY|R|+)C*Co)+0v+w!D$qSHbeTZUxzM!&y#Z*xI>vX0K!2CC
zIllYhmP%D=A?t9g?-6smw*^=s4eFX3lFY@cqa%QHMiuaC?+m;QO&~nG4DyK+gzt
zBA`WTp>H=Jr8)ss0(!}$Wt}HHm%7lEF7zu&yQT2K!hkx_r=b^J=yj80&O0vj8`A*K
zk5PK1TIc)34ExHI53eNy)h0kpivX%rnJ~$p|sifq=!cpo`UlmNNR9&{7
z`5yJv2~>?aX#k_xAc2+%^q6l5#schN>}1;GzM+1+tf^N34OBmLp~+qO(BC(DP(+>F
z?+)yKy`bMW)VysSmA`}=Ijgc8;boP-L3m5$?-4#w`60r`DnCZ}T;*R8{-W}q2;Z!F
z0><(8s;3YZRzHKVruun=SJf0i{kKc_{hA^?|3t#KYAW#jK}`)pO*wAhFY$cbK=d~^
zd|}{cc>cve4zX_z3?TeqU;)Cv4J<|&Jcy-K9#n>B9qx<%;)7oS&4-5!#;)HZhfnt}
z+BSFikqDPbc#4E;5&BeeIJsRt{A4`eGMtj%F`Tt~Z1_rGo)^q-1oN?kr1yBPNxTT2
z?GkQ4=u_JhFS|TFnhR@71BT<8bJ{+IAQ^Lm^D9PTXKig1&
zTrUaDn-c!9fhB&5&}+ME8yA4)j>aVjztu>dPdA=~=hquqqu)wsj#vpyWJEi{kt0q+
zIBi51!o?#t2O{dy5$EAqxBh#h4zbX_qp0%_YU=U)K;=;gKd4C|e6Q(TdHxQ6q!bN2a}iJxVke>u@8@MzwgSd@K!
ze_}NI{67<81(vFu1a76Zlj3-;n>5{Tslg0O8z&VQcvoVonkx8XCxO3OG6|eZTO@VU
zByd*eOaf1Jtx$erQmkO!wnsrfZ`(@-CaE7I
z{BSbc?jI6PJrt$8z2>uN|14az&6@ttLJw0q{c~XE4rN#=&p$nc&j-o#Arg*}@GuEy
zNH|Br6C^xE!gdKaO1MqJuS$5Ogf~feyM*^i_(45Oc>>R#BtMUk1gEa5d0wjlIqT4rnq&CL?tE8!0$d{M&RN%)C`
zMKeh`RKm#;9w*^S3D-+_Izo^0xic>S{iPD#DB-;lK8nzzd2!|?pm|-w-%9uqLNE36
znO7pU;K(bpm^F#(fSEg#;Y(=Q%SN#^iZC@E*4{iJO3*eaKBkKGkp}
zZB>VN9`$2j9y;ndgiju|8=>|^|2T^N>dB)%1EzQuX&PpYD~81ie#)$wcz)=p*;&jf
zv%ZSwHM1^8*foozj2^ANI_nBxuAg-cLOrH@XV&$2eoDeOX5E5k?K8C>_;40|%O|t`
ziShB-WBB~~G3;M!=gcpew=ILPC;zMFtN`ZTIV}jEoU;buGjrMz{(R192tS%bUz!`m
z=54!hF8$lrCHw(G&%gD~J8#?R$5BgqoUNblFU1HV;kbbug?~GrdG{dHV?jinvtVXv
zL|rc7Z3{L-dxI8UTI%V4>x?H$7j661LWUR1bBlzhoxrj3DhVG)IB(kxiwcZI+wNO5
zzhu$2$0Yo@gzrj7JuKSxxjdII=5t)asS>s$9HnkrJU$XpcQ2+N{_*0;5l@B_mi{8L
zXj`*{9TIj*c$S3MN%)9_KaucN3Ez`YEo0tdgjio%_CE**Fm%1v;AMYB>J$l2l5n$x
zmq~b+gg=q+eS}!UT=q|dq2=ULy8JVQwabk%OATEfKsaJ~I2%q~J|AmY$1SI4?pTVt
zlrC9LU%gK7XG(Y@!_udSFMUH&)iPjQ+5WP;xNOn3(i0ihBgA^+iR5h<-R3dzeCvG
z#pi7jR&4UccgHw?J-hmA{qDyY^ol^2hi>WjEkIR+n09&SJN+I3bdU@Epx@(whPlx5
z{hq=XheKWHzXh7_LKUSx=k
zWt64cg$@_!FD}$3PA0-Y`5Cy36lh22lSrYB*hx$Ca9Iv9s^TUxq3w*`ufh@Gr*H68dh1LQ}aJwle
zi>vzMZS7EjE>|^GjxVCF73gdguiA(=+`C+8Y}J`~_kPmNcWhP6SE(vxqvP3XMb-V-
zMtHbDJJedFRjCC!4Q=$IuSTuZX`#C6a$l|LbfNLp(*T|2LdPMkzq-PORw1pw`nn5k
zM%n=NwhNtwv;pcb0&NdnQ2nZJpc+um8l4@wzWOad2Me?#w5$4EKyzH^vFZ;1b!tex
zT3rI@YJq;NK0*lt)dK=;SAP?pyIm+;LoT1XP@;xh4o$GU?V&%{FyBQ2?Fd=5Re)}G
zp$dV%?Lr3&^q!k0Rsu06;YsE!tBM`&s7!GLxObhc`#wE?}&52B#lRy)#&
zsgHRaNbGYWAT?4$mqd>+2B`@eQWw_F1hmtIF0Gw|&i;-IU0r*>I#~V7g>I-_XdJAH
zxSbw0aYOA=K+^^Kk?$LThN@)({m%Dn?FwUAw!=6?g?Qi$Tz+5M
zY1ryaf!^_bTD#d8uFlg?!SMbE1G?BvThf0kpxfNEbNg5M>eT}-^lblgje7OCK<7w*
zuUAhCr2THadS0Nb)awK80p%|QV!wS5(0c-HSDy}e*hr``O_XfADjxWR(WqV#Xa}_P
zlrcgzVDqn1KT_YQeb5-CCJA)5svG#CZ?u{r(4}hlz-Nsn^*fy=vW!uG%tEgkW7Hmj
zcKjdi-aMeHYKtFVXP}BaQ%WTq~4OUuKnwFLJ%CeW1y}qBd_P&=3V(@TZLJJsI6h0TeY$WQ6IzlwTiI^(=@||!E!M1j~a2^r^0eD{mroQRvqmj
z*bi&nl2%>8b{lqYt6pG-4BOsnfIXB>7`CU?FtGE69c&e14I>|U2B6yYb*luhW`|g?N38^!VRSLAN7)Iq%&^|Alfl*-HoSEzm_L6#1a-)4J;6?-
z-iFO=Jr#_9*_(9*t!IGk7FJ11TE~F>XxRO&JA$?A&z6<66Zs|5WWzocpDTq`)9>Q*
z8^i2q&SCff$$|WYtrw)N)wDPIZLmGU*k=uX75;T$)vkALQjvB%I&hBa-o18lWn?b?J{V`-CNecQCM$I{;o8`CD)PNp{vo82ZAtjl1X!&2zR
z(J;dvfo>da7N$!*p0*hskI(V6$1onBGX(UJU-KDyJ0-)GU$k5JnAy&tYJLr
zGHB{hT{0eZnY2`x9$A^R(=Z-cnRM1L9$A?bJ&e;;5|6A*N*AVEDU+rOt9C`T4Y4w*
zz_1=|6Tq%FYy@-@Xr*D-LN|f#H>|AfetRNqGi+7c17I&3_7u`hqW27Y1?eWy7lwTZ
z-DEmv*jeZ%)1QXfF=5se3XUiIRl8cmw6dpAJHvWGHT;Uy2h}o
zm>pn6hHZ*Twy&inhV73@1v@27x5P9$>$Yr{Y)``jMmqPW+ogi#8ulV|(`lh$2cVly
z#|--nx-9z9uhH&6Z>yFhXxq-Wb7rdafZDT8)oHF
zmSG>pwz6}n)Ua=3lkGfOYS?eFsbD`E7SKM-nnh}a&b?LpR`x7vZdi}@HTG=kU|2%?
z9bf|t%YbeUB^x#ex;d0(*p2N&tbCeh*lq0-z-~5d9nxJ#s||Y!>8_*AhV6rHF8$rG
zBhbyIw+#CpJ`3nm!+wL$0{Yf4-ww8;kbXC;Z3jQF#v{2#y5)-~R9Lm^j}Bp05p^^y
z3cq_$L<0>Q+%e25rg4VNfUcNk7*^G>y`zLm4ZE*n7qD9ltLfOwF^{$jtE7Fxz7)o}
zhuQP!oUx?NAyz5bqjXz`bxr_lW>~k*vm9l#(6EHgbHRc}Ys-m9S5CQx6(e0a{l~Cn
zo##0!s1cTI9zFNKvVuAat8_ioISg!|VOxcbHTi-~T!uXu;
zLB~zhZanLlZFbyDsUG&EqmuZSwX|-BV<{aH#>do`9n0vL(H)Q6?^sS}J?wzvHo|ur
zBpsjquB3LtDk-DOe#h-JQ<(1eJ7}KPxi)tB+;JzZH0gGCISsbmun)WZ70&hw8FWAKJ~D*
z&KC%WGW=D#wqLc;`4Y7hR^@u-s>i{~4SV;h5bGscEUemf_Nu3#+azqY%j*6d*lwe1
z+kGck(@ge>GitD1)L&Q?rF5^sx7+6$HdELtVXKFgbl>CLO*BEuLN`J84~jNyRrlAO
z|DbYV)%0lhr<{8zXrkmmd%7P0+h7>)lK0RK!wz+S*ZDH-^RSPdd+DDZcHH?Yedb}`
zIA5b5g;mlg-G6evLHADL9M;hH-T!dDO^1Y4lBd8fMaQ?jKQ-Fz(x{d_JNqt#esD!>l7T->^2qZa0k2y^qj(!&Zg%^EpDt4C~SJ
z7IlQaHEdwd!O;DonOqM!O3km)rRKIiN->6U9X_F9!niCcKF4Uau&@1im;50&TK37i*6BXSsjV=*7d=j$4ddPCaq6#`AMZZDq>+a4?(<8^5XQML
z^!buz3afUNq3$QC*sxnrzmv4su(hIFZrBsT)*7~3*e1gc3fpejr^5bW*muI-HjG>M
zBz?-Gnv5$quh)@x5;LIZ53OeMhk-3NjIW%0M-Ld5Ai6DvWlFj?HKXf#zvA;9{b<-t
zz57^asAHCHiMxeOH;h~23>66Dvb^hamW~M1>*v4dj4<6U|Dp@R*0_G^ebnb)6f{G8
zqLsb?i}tW_)(^B)*a?ek`y;K^@i6fBIdu?+P6g`z#|6`z08Qf-M+C;0wKYlZ1C@hd%~
znbiGPdeShi`>(XqSaK`BK=cXHOf*$ztKm+xMW$rztLA(=i)m2PCpse
zqi3@HyZqd!J~|cq{!SBeYU^-`W(wmv%=f)SB_4L8?;mtnTcQq2eg8w>XdT5~eFxa^
zT=t@S^H0jqOlo9VGYsPzSyrL3OjwnxvTvfxww^X@Mc*(yKC;`e
zM})m**mHeb;n}Bmg{`62`X;#?R&*YhrJDGN=VP50wwgZco8t1ZF3e(GC5806#^tgc
zvo-71Z?>zEwcfD!exw;lN!_1???I5$nFTZtlMqx-P<2hrgIHCa9yu8lu;o
zxAuSC)zRuFI&S$7Tya*3NyoZw)|_ItWZmbk9@ar&Ut6r}Wkt+$>x$IXR;FgI`}=?6
z>T6vq>}%_B)S<7n(qkFx*Vo$QVO{Eqilxdq2R(D9SfRqW)L-~rW5o)qc0E4uYrku)afUr3tU@!|J+KDP
z=l#tvUazmURtc-5*I+r#+HKf-!oD$g)zRuxB=(4R5hOHYE
zVr5(7G^57{)ljxILs%8Ahb2IluXQMA4b8OX3FCFJiT_M%p-FdpWT=0RwZ*XGk!}2E
zSw+u6MJ*
zjVN_3+m2G-;MQ4Ovc(?8Io#}FoI|CX`Ekjr+|1$}mb#fA=TOTyhg*zJAAzdfmXcpB
z9`Jf@rhwQdKUUW>|vaHElUrr@?xyp=&?)>UG2sA3~-ZM=e(}>!~UDyEb#5Y+5V4t
z*q30Bdl;V&ZgDed2@earI5^D03(rhBlZO=g*RpGdgm~%ZfZ?sdh
z3-+{!Z2_zCF!r+D!(2l{EPSuS_-sD(G5=@XOlHS(9>#6W-x<`SfZKYfhs6x7!E?24
zFK4gX=3mSBF6l0hj$3!PhjHtAm~-sVJ^u1q8ky7A4jt^X*JH`)Uh%N>(EYW{Su*sX
z|30_Qxftp8d)SJhANarSVfPO`?*FETZ5jHy^KCbik$lj@_}=F`9=3bvY5#wEm~ZI!
z{)gO5dh=Zm<92!9%>q}2X8Rv@Gv@)+??VqeHuPuzk3Edk@FDd`(?gR3G}GrxQv=R=
zbUdej@G#!L|L9@7e?RADPOke;ZWg$BScp~2Rt}pH@UvSd^*irj+<*UeGqlpIfD0bR
z<-F)&>7fe)Y8lVl-#t2>x3!FC+$E2W=j|UJ#`E?+9>(+bPY>gHOUgZ~9zz{04|@Sj
zdDsWTssn5f<5qHb81KWK9>&)MY8mhCeLOne+t)I_LQu>2nm{e%{d+Cry}hs7i?pZ9
z%>wzVgTIIMj$ai}%SOeASpgm$UxTP+d@Z4tag7>zEV)L39(GfFg2nG_l65KY*7y*s
ziHAK5U66;p09~+$9e@r$cVNH
z+cqA??b6o6xLsm2likGo0qqQX-P)g!soHzk>4YNzwe07FF9SNNXgSYXi|1{=3+SZA
z3ga`?auuhhnscyzBSWk%s=%-$VK*5zRoE)S3WPmk*dk#&3|l#}o$8|AFzo)3n;Uge
zpBT1HbYB|wlB7Ft*qfsBUC8ZJ?fO7iq+ut8bv5j~uwjNdMscar4QnJUSD3C}7gZ*V
z&v3Ul>Z+D_bT!mXEi>u(PFgp0zhQhQt($tv%knC9ijQhQpnlDUW
zeeJF8HSENw?;7=1xIKYCuB}gCAJtNr-evVwU4-#2D=4t9TIFG3f%x{IW~fn6V1IQ=
zr^BABRp0=1L0BcmOAQTBxi?6u>3C#=IUTPu4C56g
zLCr9XSB(T!Xc(^=398yKp8bhxjbS|d6V*1uxaEhd7Y*ZX7DZR?Iz*9z0E
zo2>E;JQCa
z0ZC5=rm5JQbw9);Z3i1?*wCcs!77ARx-yeq0(($cm8&FaFW7#=s*=L2H1&>QtCB*%
zJ``5%dN^r6EdMZUd(r{0*d?4_rRyc}IZjx$i=UxGeunY$FKKFnuu3{0<$Tw$ucc(T
z2!+2&@*Tsa?rvE7FBe=9p(
z{o7cUkEx*y74$cqAK&ZAP_e@J3RYt)Lk;k-<*p2sVi;e~nxJMHwqy*mO2dwZzZW<`
zt@W^v11G9yJZ!mZvN|lRlGY>lsj5pA=f0L+81s4HR5eFS`cHIzU0
ztH9~Xbqo8{<19;sdRT&$rJ_BoSK}Ef&cpgQo~inH*s#XAYNRl}3O>5=Y?ZBbF2Ce<
zDqrOb(|2X_)jVM*tgz(Kjq}w~VLUg(?73={^M={3#gV-
zyKEKOh@T@XR*hCL(>+$KnhC2U9%scWO_=VnVl_h;
zub(}dl&CVzuwwRWGEY6?VevRidDg?mHd&}X^stFd7OP7hHnT~kN?pmhSHeq4lUvny
z!uUAdqsblWg3*04{^llks^r_X<>~QDo2*i~hFt_(tyUQplG0AyrT!GQmb#~G19RP>
z(~U_9v+hz$g;nF&_&(TfVXG-WWqFe|>Vnbno%*{~%R4!pelqSJ^{Oy#i5gm~-WFEv
z;&HZC{bU%Av$ZO66{o9q@pxIQx){deWvv=&7>|&3YL2i<;{IEw)*HtCw@$rl828^g
zrB<^~o%=fFFRa>iYf6p1PPH`b!IT|fR~hzPO0s>g8gAHYDXCyv4Eqqe`_yj3zJcyO
z6@8a3_3zN#uX-ESI5ib)gD{=@18R%WO-~K69#AhCR*;$i_NHOir-oVU)hC9nN^NDY
zSKk}Ht?*Bx(hg3VmK0&&NR6oPMPTkt%VKv9F-%@M9?l&wX
zZ3oy6!@8us)MSHt-LSrCd%=zxmXP*Fla1<(VX0~Ffc?j?>1l_XJfec`)@3P9`vfe;
zuqA29_9oTGu+?d)V55cUcG;{_jgH%8vzlobx65W#Vi?!&QS~>&xPFhSdkp&%IXtGe
z7}g{`6|C7kTo%s374*1@5mxEym)sfO{BRa@0e!}!Un
zt*XQ@zB2TL+GZF(KmUZ|D^ib==fRUC)EYR=A|J5{e$_5jcAu{8q3+{@cBv&EHX&%Yy4%CDg7&E6!dBx+lDR>zsH6A0)6Eas
zufF%Nxj}C#)-0jbGd|9^Iq0BaYomY9xFzTvH=E&`xiaXV>Vl-xcexKK>j6&r8iizr
zS%*|(VY)XDsZe3nu0EOf1RYXC3`@=ofn};;*9x0y81E?$sS3kpw!R*&tR4bZh-&$;XT-c=oh@d$Y~=v~!aST((#`C`y}>M>z!=!?v4>V5U4VfF-Op%1c`
zH54aoykVn-~Q#`?0g1=PXcvzF*uT`IoT30nO
zH27OJ*sxzFv<&`EO%|rl-Oj2RMt5k^(BQMGz{5raf3J=i_S2+s!9S|)k7%FGCuano
zQ{NbtJ9%>OPpaJ}t;?O97yPT*;$icGFR6mfT30do#$a3$epIvS$<@KOood*M$+v+m
z6tLl)xk|{j$=ede=&Kh7yH-bXM>x1
zbeAUY3~uIOkyG9ZZs}oHgSD~05ubYIw6}w{vRBd8Z!_OQHmzxI
zyTq_F*IwJSuf4&rKdznCw7$%_y$uqtHqAw>@fglkIbwQ6Y5v?nk_LU^?SZ
z9#lp`SuAfPSgXbNK}Eudg+<~q9#qs*;u_;6K3;GVP~rQi>{BEAbmzsE?s&P_=u&y3
z&T$d6wkhGQVtKnD#}&Tm%Kq>1LM^Gav1qec>a_9)euID25ZqSGSL-gK$7
z{dtjlmrb{iJ%iI~)TzQm;%)iYacv(ZsWfUoI{w#b|Np9O`#-m#_lW8)sRjWRB}-iU
z$(DG&$QOIX+0r(aT3ah|&`Pn?eNj826x}0o?{Qe03%(ES@#*%!xhr~1NR1##2FQB@rrAkTDeVH@|0Mz
z+*@MN^O9EEFe-XMB<}UZini#DY%5yHbANclP8OH;4YA}W0WIRyNl}Qzb(wVu-5%Jc
z4YY0>-Pd|Pdaop1#Dh-TOX7cR$*Y+~+Fq}X1OJCq|BHV;N_Ab5F7v77-X-TXM^UC&
zYHbDL%C!7VGkI;~zP4z&$hpUDc^=Y1n7a|y;?0ae+z8EKOGgex;}^7nib8W#v`t+hx8zw(Me&H+
zlp+$?z#cY`%&KQ29z0s@VX@dWAHd$a0TtdE#PM+wpA57q2k4*$KquZ7#54UqpfA4r
zXrWZqQmWhG$EI~a2W|u=5k5ur^TDUM6O3&w8ZsT)yI18v7s&XsAF^=qxERlqeahUJ?c8bo1#8In}&(!
zv4YytgHl?qyPlhAxw`x;nVRcHMTI?0z5cK5kz>8IbN}bO{%_NIm-$L@T}PuuAKhR|
z0HO{5-tf-#6uH0*v*U|04tJYrSXYE}w25u1C
z1XPe{Kdi;N?a9|2zfvvO{>o)z?KUsZ?znfE*=A>MFh=z&rc_!R4Eg^>3sb|j_FkR5
z>%zIa$6mFq>`xky`ir~QA4b9tsVFs`&u@6!I4I7;ZL7vh_|dVxn*t%SU^wYhv|
z#)fxYOpdVsDVJ9vy>?xcI7@IW$Yb;a;(d@3kMctA<7NkuYl&Vbdy52Rh6f#whXlt>
zk
z&K_Zac&=?>%j`_O_o3{wp}Jq*8vpN-cqz|c`*H7uxouC&*{L4?T0TxZTxtKSJ?mX?
z7AzGl6nlMc!2PbMe%z+2Y;J+t5on8z;|lVIEmgEdt7q~0`HU4A{s_s!z-
z*mp$!wcrne=a5#>MZ|6LpUq<_0_dbTpe3V=k<$(oiMJmfGm1tUOSWMxk5Zg*8hak4
zO8nTyQtu_X42seu?Gz6xnt?dZJVkPyV7cHzpdwCXQzhaK*(W;9sPoniLXKC~u9395
z4Bn~!cM1DfX6|@!U4dOOk-bXB{>+SNnJXgx5u|ndD1Q!Dptps9!p(V0Mcv#{e
z7jo`ybYj2nL?1ZuM&cIuS;r{wD2hY~rqOm);1r;5
zNWR490TZl6z*csp#Fq=M7Q9bzqu^G-XMnBj7lAeS%^Z%u1#BI16xcrGTVT052dtrs
zz)FXuIPRyBBu?$-1~pqwBj<(!v*)&ImJfM5VA0$j&Dy}SU$Y*-1gpO~Gk0>cB*jmp
z>^5onU0%C!pRuA@K5SZtETP|7Pr-6o;j^K~h~FJ{jQIPk$K<`!$B5rGeGKm+ZUp2v
zeVvkCJ1=i0JP&U*bY8ArUlPwpm8<9n_~G|dpAzjUYFu<45`JsfDYQV5I;uio#Lnnpyl_
z#Af25nZ?(`PtoF{Ww7DrD2~BC3&%*c;mok0lftW2@V%*C)##Wo_6BDo%l}{ZKj}~9bN3F{OoQ(?$fA<
z7im;+Y(%h~Q5+ZHM`tiH`>RRC-SHK{^5UzZy`>n>)r#j%Qid+#vy1ref~QZ4pyhV%
zBCXT|HvC;S#$1d0DA(c^E3nw6z~YuH7j3-#Y4ME_N8x!{M4IhiawqV33u5xQ;$u|*;@r2`lB9@|sn?)|QxP=!<
zNh-z9a$s)ceZaYqC+Je|(#Vx41KwgGC0r>bTqz}72|w3`-7P(JxA<8vmg~iGy;!an
z%k^TpSu8h;TP$~r<^0lL9st9_J_s(n8Z)P{`MAUr6fGY9%{iYxw*wDwAiyP&LdTpx+Ioo
z#pfw$`Lp89p?IvGvW}I!2Yc={hvHVXz*!moBPD&t{QlCHIS})x4mni^{(w
z$<<%o+`oAguYpOSy%oRZJKnx(-i;CQ_SN$;qvGum^QJ^?wKM0OJZAc>CDAQfPmkH$Q3+%2}=kDISx96!*&@#ba`~lwpwKejlWGUSA8#_X9eS
zZ|TXXPWW#0nW%DU|8g1goCQ+m#Rj>|i^YDa*e@0P#bUo$?ET~^+?7)P
zy9L(^ZWi1oxI=Ka;A?`GgVz>6$EMOTK^(84V37nn_LV*v8{yz%Xd98Ip%(ocGmdT?
zVg0LgX_GX0w`7F%YiX~>2gTcA!DE7_1kVbtmljwrtJ5p$`|v^S*Gqe@m)2M>E97~p
zmmg6X-l6CSF3W-q*LuCIFk7j4S#J9tj-h3X+xK@QmsJ8sMlAYAhA82xIr>%~-`Li6)NRPCU5%?nFCD}
zB}lb@K}B4e<1n!LTUSeJ{ss0+Z}f`gQ=pNod8U|_Op
z0nAifhE<&PO-392fdjK2RR|A9Wfxu?=FkrYn0@%_X1B|v)fbHx|U>BRq*4^fE
z_O`iH{cLWlVImnWl4P6fk}8r+kxUkwYb9;2;2hBwh_+lL3q`U-;!7pIQsS#5ez#cO
zD}FYJe6z^6hbT8q
zb;{ZI-
zUIaFBc4>m1a;^goa`LFobaJUCJGoTXI=NIcoLuHy=l&oU&2hdBEN~tQYD^_a>qq6V
z_oIcd^rOYjm|#Cz;_L`q>g)Xy>^{MrI_$GA#J)3B+a1Mw}wAVPlZC+)w
z%_f^|HX*(`be~AxvSX}oo1YYUjDzJd4z@`|JUBeb!P+Fp1J=prIU?T#dDj;EB)(7V
zqn&IM?PQx6=L%=5s2C^bxXCGH@L@dZ^Ql!8W&4U}U-rDu_c>RJW1sH|=Y^GFs8054$kne9(1^fZX_M0T_CP*UMZW75pgDaeg
zZTE?GpZGi}@{_PEY&f&7)WF(OG6Nn!x!o(TDwwuvIo
z5lIdt&0}(mg#DZh;IR?Yh_Nb=ag*T5KpuxVjWtReHrB`zy9tkN1$9+JI;ihGntuakyP8noLVl$TH2v!Mh5=?5j%hk43zTj5Dw}3ad`c~qdqIS7{
zXq5zP+d5z3%YkvNzirE@{9<;wmbB>vT;3)h*tYd@;O;hCCH|Jg_jcrUqq6(lMuDAQ
z0Ty>YK7=L1hBEG*!82&@44y&9C4OAuGZu0yZCH3sz~>z{NW7aB8q(W}3K@d1$!Z+(
zC_IJmfQcet~cT@srSfg>V}DTZA)^
zeUHx{5Y8d~GjzWo{wu;o#DB-<9|(U!XW5NIYy_tr9^z|Uqr$YK%ifY1qcao}AM?}5-0`d;|#19@M_`y&iQ
zd@w$TLY{~)3h^X-j>YFVe5N3z!6pNI0>UJODG1jfOhd>*$VSLP$b;={gnY#3A`~KB
zF+S%Zlp$UL-2%iH;qyj>n-Q;sybAGK5tf6m0KXmKPQ+J3wg#W~Agn{U58(mG9z=K;
zVI%A|LG~!Z`8>Dz-#dN3_hR3=ko|JBK!k3FXQtS$oJuMKR(}p>@9pA0DlMJ
z5W;&1A0ixu{8NO_5WYY-0o#)ZUm<=P`fm|GgZTFdKOlY%vY!!t1;2>!JHj6be2iwO@YDm#75cE?nzvh0n+E*%qIV<1-qcTc}I3^k&)3(i_id
zc4Mad~SGI_zmGz;p@Vm3jaOaitvdDhzO1t6p?^mqDzXH7LgxO9Pzh^
zjS;szqVA5mKWaC%
zm+o#H7dpP#@wbl7PE9*??KHGga;KS{=60Ij=?462
z__9tLJDu$GQ>Q;Vo$VYE7aJEB*CVcP+~ByxxTLtLaXE1jXj6PItw%mu9#4bf>N$P`
z5?9pY&|Cd*XQMyP7YE{rx?y;xZ#;HIDfs>L3E0tOQ4?Ho528X`_r9J&@C&%1IEM{TR
za{Gt|V0SeT=Tw8~3;ZVMm$>?V61jhcEAOW%fxe+c`i_Rv_k=gD;~M-q8bv?jE$Y8e
z68(zn@Rw*T*;X=sMs6JW<9*|etW;`hrBQRd3%rGuf#2$!fH$K}q;_~)c6)0wb;rA~
zhgef-xOEMU#9N5RS|#{B;!?WCDx(>Aqi>#7L36G7RD$>9F18lp7l;?p3JY(-wQj&K
z4lky=tsChf>n7T0-Ar4oCA8J5#Fe#M@T2-)3wdBG^fAkl-Z262V&qw+g;4ctX(CoYQs{EDx(j
z*H?wD1l|$08rWW~1FjES4}3Cg18`T^X5ikit-v?KYJeYw@f&fHBiLq!;5@X8-(pugLoYZm+a97xR;74JP0B4B&
z7O{V>d
zfN1uRDmWpUYcVr=G33S3mB1UKZvoyCeH(B^^eP}q2;AG|CEy!v*yf!!dx4*};k>>R
ziQSgv|9kG2=Xg4XOZBVZ{jT@n&A%N>A_cp)y26&xXW!tW#`
z<73%oaEBlV(U=Y#U*H#t_{0wEXI2MJRVMMjiS|yx4IMbgtrFiQcu*uCc4T-*)7*
z&Q6T&I`xC(>P|y|PsDMmf5hE^_&XB+GHwmxXC(e(9NYgY_^040>t1L>y0E;pU@yTj
zUD(gKuD(vf@05=R>RrgWu8a5d>V)gSGee2t|BrkCwm@J=sIHpzh6&!V(}a?8zfX
zkFi~0vmx0Ph99>l?{SsVi_=c)#g^Ae{CdGyyIPJkc(Y{zo`osdy_2K!V
zu4cI|o4X9j(C!M$0D9L~+Z6WXes^205zG7QS*G=W9+!nC^=Hr53D)%g0TOo$ug3*Z
zw}*p>?+W_>=$+RW1J6M6m0-s~yn5)gy(PYX5PR<6=jTuK&q0i*266k0AIx}-;H<%Y
zAYU^$$%8M8TuZ#;?owR?OC9exXg1=#1=CtCM0{7+V&E48dG-5BP+J!EWUbrgHm~@-
zi0fVp9>U&Y1$D3W8^Yz^IfT>h8^UEiDEP793Bi8}UJ}&WenZ(Ze(1AEb#ITIK)s5(
zbLSl0@uP_A66)3RUqf*y#)|C&{3wjevp1{};%<9R`%zdlBs$gDVO+v$@c0#;T=Wdm
zv(I~$={cxppPpHImgyNfEq*OL6vT7w^gPw`QI}l%yefh7N)*fx%uZl?cTJ!0s>^=F
zcPAXISE}Zt*h5>vs{{uNjuf0QijOKy#+PWTn5%kQ399RqpXUIp}?S>E;19&~+m9qW2nEFP-rd3aiWS-Pur4)W{A
z{u{Vr?C-$uEvqrnrm@^s+s1MWEc5q)QYD;a?CY6t6
zy;3=sVS*`wkEC#Vq=UgY?VUNULPdknHM`_;!&!qhX+~>Lge8=@4;7@7nN2k^H
ztSLn!IgdW;&_~8yVSVdaYI%AFmuIfv1DTJ*=IKn9ydZNk{Y>YT_EK*Pd#9lmo&u#|K>lYFo-D>$I>#8DI8#^TgJ)3`VgW!G
z&eIim6QDoN(G{d2z(&*@7)W8j#yD43uxJ4c!uh&_U29-doU7v-BoqT|hI4g=G#!AU
z)Cm}dy@A4+epg@wT?LG!9>5mV3m8RxfUR&wui&dcFdAp`3LXao+v03q!EZdu-wvqY
zJrUR*XY&eqjDma+&gAj@n&4oZ&0E+7jD=(ifOrBOpZIPQ(4v>{spv()
zUHG){I}p1d*)6ySOkpqhGUWS#3VXs=5Pw7PE!qdkn?U>;HS~(!7CZ<&era9s5WNNY
zKLy`~6@EEa@B`=-y${5bnAlw^It;|Gg+j0BW5J`ix^K}Zg2!N`up;6e2GM<9o!hZBK#J`5#!oHO8TRH*BH-g_G1%5{xi0`1l7Qf^x_ycSe{R@ci
zz95C7AAuHjuxAke320#_`#s|41^nh+Zs|Rql)eAV+>H{pW
z`T~or{=i~uAh5(544h~2s#$8q1Iw&LV7WB{SYeF<&bN|)3#_rgh1NLWA}a-Wy_E*M
z$;trUY)t?zu_ghlEWAq_?<>9rcq`s)t!SB*1ze7|M=QF`$^ovh@_={YZO@pARzC1<
zYcB8}ysKH!I=rJ<;jO>(fa|R?;DdMX*1;B^#oS&i%)(yao){Vf;c#pE8NAdn-
z%vP%kxE1e2#yqud1=irL$BMRFD}c{hw*#NE?gZ|zRs(lhYk)6W_W*ZW>wy2T?gQ?z
z9zc7(48&-+9t57Y9tM76Z3Lddn}0E4tw(|1TaN>Ow6+4z;hnq~lX&~CqF?ZyT}7Ah
zMqP|L>p9?mtmlEGUI1F^Mfg`h9GlcGphNuw*hsw$c_0wuO1*-36Tx7$50W6k=4wCU
z5$X+K3-uOkS^{wdQ3rr+@vd5nVu1MNS#=25S-l60Qy&1kstjvEr2ss6fj4%2F_A#fY+%Q;9M08EKnVQ^He8bDc)L%ajv=o
zE7VoM`KkwSf$9ZZsQLgGslLGL@oa~p8}MdAd@lj-AjD`_LxDG|c!eVu5XUK%2&_^g
zfYoXg@K%)sT&BhXm#cBW+f)j0g-QdiR2jhA)db)jY7+2HH3hgzT?1ULrUCC#S->?a
z8+fUC$f%mIo-~(zN@Ih4ud`MLQA65&18`L7;Ms)-5
z5p^SQle!t@-wed8QI&{422}L8szQ8=;1lW=NVW<-scwaQ8&J_xYB}&}wF3Bzx*hne
zx)b=ES`GZWS_6DR-2?lbKt(UBb-=yqKHw|r0pP3ZLEt|1Fz_|C5x8G%0=}*u1-_vk
z2fnGc0^d?k0^e3o0S~Ae;6c252haPf=Ya32=aI|%Kpdyk3&11lMc`4j3;2op2k=w%
zGHi|k6&+Wv08glWz%SK);A!;+@Ei3O@OyOtmj41O`a!*e_>Vx$XuKZ=M;!GYBtHxO
zqCSA+JP=15^T_7QCoFM*KHCLHm^(m0-&w!Tw1<+-m0Q%V{f&TVazySL+u#x>O
zFwi~&Y;1oIY-0Za46@GwgYBPzP3>QRA@;ApzV=1nK>KAo~yCVEa$t5E}<18VXc2
z%(j8?wiB3O`vMbfKWK*oF)M9;J@8075ID+i0{LhlW~Chr9Ak$7$J)(-Q|&Ndp&bD%
zvReR)?I_rn05O~F*1-988%P!aF?Z}3#1{cEckEciZvbNM*c}kR5s0~CcS3v#P*J5F
zhxp$Fm)cz+sS>=!z6$bcAZC-@1GvoY1<7(CW|Q3q@fASKCc7`r2LHto5zV&XW0iUrmAbA#ud0|gLd^z{qV>VI+05Mt|`G^MsFrCVl`rrk#oqvUU)k7uxaOA%
z26-DitrP(432h8zA|6BOkjLN&C6-KtWqXVe#uCWeQz>lPQ#rK#@GOuo@HpiC=u61^
z(dUqt;i(nIcbx&i_np4Le>(X)0ty#wD)5yNjyrsOfj&Mqb`jHk061F+)wXk;KJ;H~Cr-m;J-w-}NVnW2si1LW@5&n^jBUeN|
z9{GIadyyw1?G_;|5?f4YQQYE|7VBEp-i&%L
z>Z7QkF>_
z!T`)oYo8r35Nn3D-)=t8#t!xkJ45)bz8_~epkMC$dj`u_`1)qr?<0`O(4JKim|*Lrgc;TsT+
zCH&2G{fF~n>Kf7qh|+leun)zxhWfIum&tB&=3Z6b?k4BJv6svCX0VL^I7i$v!j3r<
zeHn@!as<9-(gEKy>3}_S2i*DUfZpwZZ=iI*KDq;SN9cjj6QLJEZ-hPwS0hwm=eQK%
zcHCjR0|CEWh~3{x?EO|@=eG*`zLj)0!aWFU5!NBxi*O&p{Rj^rtVehd;UR>F5gx%D
z+Jvwf;W6w0A4lN*ApiN(K(s+PO_onw`h0~=rOoF00;vk+l1LYMICo&6C;B1}eDjj$zrncceO7go2H
zOYL4Qm)X~}yx#eFRA1E%pO@NQ@AQkk-WiI}4xuN)FoYC@=?JeP976aE;Vgnr`^QyL
z`|F)gbsXTjv(o_AJqQnUy5xH&!Y7?C`QC%iks$Qr^sN@wtWhMfqjrrP<~A#YNq6
z3koPbudKMDG$*g@FHI@2xM)`X>b6V&RwrNi@PWmgoGm(`IK7iQ-dT`pkdS#}JsGS7(US^-@O_UtV6C
zKeM7duWkW6Vy`+joGv40PF`X56;f;Em3-978*-93C%+)q4DMRXx)~XDL&@u?{!98R
zs*5TLCuSE^)N7m(Zt<1UxqH7(i=}3l&q*vUEXgiKPrCb;h2yeIO7cp364r#v5@;RO
zT`I0*l~|maT{d@IULhuTVsb`e=447wP0UKnil2}8Vmy
zx1Y@XsZ($*yJ8c{j7uuQJTA(q*Zw+XL!F+X7dT3+C@U{6#MB*`pI4CUoktyoryHbw
z-M!}Sn@Oel<=Ha}^6Dtfy!G%JmR!P}+dA
zE6l6fS^i~{7Nf*U^9ww?8Es%
zowmMA(?E8{W>jf$MF}OA=4F@Xxkt|Myn?*h;8-7$v#(!7$;H{Z3E2fa!%FMSb9Xn
zsre;&IMC+tVVE-bI4!Xhcqq-!)#8+y*I_C{i35|{-Y~CYcv3DOr^^?KcXMzrE{&Jt
zEG6NPnVDahS6oqUB%VV&S8qO&rW9!r^6>IntM!)R+-zP2PAlA>UMgSBSQnZwNlGI_fBP=5y4aeY}4XEdQign5Ph50zUlPE8hTmy)4
zvXYa>Cs=v8rp{%iqiJG(X?aCXhneq4jvvi#`{HQcfUb5ltGNUsh6F#))Vg_I}s|
zm6f{>8|1aVNHM#mS77}u%x{iv
zX@geDc}26!=b$U{b9FP;&k7q@UQYf8Z^p7gk7n;`+BXp=KKZkFtD85XwA8dkT@zl;
z%G^6&X_neF(L~IO~}j1t|-e(MWan9L&@F4zusuBJKXCj8yKAR
zEG{dIxBkjQyaB7rJk=eymksT@>N>;P%a(h_Go%|H&X8UhrM_8!6w58nocu6tZI
z(5`OPWzBGf;aX=9qB$=gs&y>t57WBJIzzOfG1owW8#24B@c(^Snj>O*-Yj!D(|tOo
zw<_i|({#3$Wo7HDX=ah3tQ_wPsFa0m5zlS24a>@QUlS{XrPoy`&_cN^fk=Lt-qE{V
zn#G#0+0_x%x{{@{jyI9iDG5tV^33ena`MQcB%FZfWET`H%E0Pr)|4bZP2eMwMA%9f
z2zYfWb{R4sqG6ai1saq)EKp{$!O7KMMwsVG8
z<~k81*pHMiB1Al{F}}UK!c$&cm#WhR^%}c=LrdH00+dnRR&)=R`SrF$o^4K6Jq=$&
z!^wR49Cw{5s|*XUbWj7UoAhl%^4UB_Fi^EsRxyH^MFUPTekjpd3=o;xk1=m7Uh
zlvSp$hI6yU*Yz(VobfLxgF<(@utd(*i}OKXzU~iEd6$TN)V7%4mX<$K5b4a%FSDt
zG7A?(q{R9Ij=j>j&&X>FRMyR{#(5L=D&7t+-|KYq+Us=Fwdd-_mzHK@zUw_m9pAMC
zTc(mlnZ>vcEg%2In0+UeWh^SnnS*5z3y?WEtt;Ga@ukQcF+EXucPUAf*G3|MagPUF0`ch_Teql1U
zkTM(jj@