├── Readme.md ├── changelog.txt ├── languages └── mrp-default.po ├── readme.txt ├── whmcs-mrp.php └── whmcs └── modules ├── addons └── whmcs_multisite │ └── whmcs_multisite.php └── servers └── whmcs_multisite └── whmcs_multisite.php /Readme.md: -------------------------------------------------------------------------------- 1 | # WHMCS MultiSite Provisioning 2 | 3 | **INACTIVE NOTICE: This plugin is unsupported by WPMUDEV, we've published it here for those technical types who might want to fork and maintain it for their needs.** 4 | 5 | ## WHMCS MU Provisioning for running, expanding and automating your hosting business using WHMCS with WordPress. 6 | 7 | ### WHMCS and Pro Sites 8 | 9 | Not sure what WHMCS is, but you still want to host websites? 10 | 11 | If you are not familiar with WHMCS but still want to sell websites then you might want to consider our [Pro Sites plugin](http://premium.wpmudev.org/project/pro-sites/).  Pro Sites is designed to let you sell premium sites with themes, plugins, space upgrades and other cool feature enhancements. You bill your clients monthly, quarterly or yearly, and its all automated! 12 | 13 | ### Want to learn more about WHMCS? 14 | 15 | _WHMCS is an all-in-one client management, billing & support solution for online businesses. Handling everything from signup to termination, WHMCS is a powerful business automation tool that puts you firmly in control - [WHMCS.com](http://www.whmcs.com/members/aff.php?aff=5995 "WHCMS All in on client Management, billing and support")_ 16 | 17 | With our latest release you can now provision websites direct from your WHMCS installation. Yup you read that right – you can sell WordPress websites in your WordPress Multisite installation direct from WHMCS. How awesome is that? It just oozes with goodness, doesn't it? 18 | 19 | [![WHMCS WordPress Provisioning Product](http://premium.wpmudev.org/wp-content/uploads/2012/03/Screen-Shot-2012-03-20-at-11.47.16-1024x455.png)](http://premium.wpmudev.org/wp-content/uploads/2012/03/Screen-Shot-2012-03-20-at-11.47.16.png) 20 | 21 | Sell domain names right on your network 22 | 23 | Setup your products, how much space your want your customers to have and let our WHMCS Multisite Provisioning do the rest. 24 | [![WHMCS WordPress Provisioning Product - Product Details](http://premium.wpmudev.org/wp-content/uploads/2012/03/Screen-Shot-2012-03-20-at-11.47.37-1024x449.png)](http://premium.wpmudev.org/wp-content/uploads/2012/03/Screen-Shot-2012-03-20-at-11.47.37.png) 25 | 26 | Automate hosting with WHMCS 27 | 28 | ### Bring WHMCS to Your Dashboard 29 | 30 | Do you want to have your WHMCS install show within your WordPress website as if it was designed to fit like a glove? Pop over to [WHMCS WordPress Integration project page](http://premium.wpmudev.org/project/whmcs-wordpress-integration/) for more information! 31 | 32 | [![WHMCS WordPress Integration](http://premium.wpmudev.org/wp-content/projects/263/listing-image.png?rand=26e6a0c0ba7c8551fc0484efd9c781f3)](http://premium.wpmudev.org/project/whmcs-wordpress-integration) Integrate seamlessly with WordPress 33 | 34 | With the WHMCS WordPress Integration plugin you are essentially embedding your WHMCS installation right into your WordPress site. The style, the design... it's beautiful. -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Plugin Name: WHMCS Multisite Provisioning 2 | Author: Arnold Bailey 3 | 4 | == Change Log == 5 | = 1.1.0.8 = 6 | - Fixed naming logic for duplicate blogs in sub-directory networks. 7 | 8 | = 1.1.0.7 = 9 | - Fixed POODLE vulnerability due to cURL SSL version 3 being forced. 10 | 11 | = 1.1.0.6 = 12 | - Added check for Free level name which was previously indicated by a blank field. 13 | 14 | = 1.1.0.5 = 15 | - Fix for Delete blog error. 16 | 17 | = 1.1.0.4 = 18 | - Fixed problem with default site numbering. 19 | - Since the password on Wordpress can not be read, if WHMCS finds the user email already existing on WP it returns a "PREVIOUSLY SET" PASSWORD. 20 | 21 | = 1.1.0.3 = 22 | - Added check for blank domains 23 | 24 | = 1.1.0.2 = 25 | * Fixed problem in add on module creating table. 26 | 27 | = 1.1.0.1 = 28 | * Added an ob_end_clean around the the plugin code to make sure that Debug messages 29 | from Wordpress and other plugins don't get passed back in the WHMCS response, destroying the json structure, 30 | * Added debug setting checkbox which turns off blocking the debug messages sent back to WHMCS. 31 | They can then appear in the WHMCS Modules Log. 32 | * No longer assign level 1 if Pro-Sites field is empty. 33 | 34 | = 1.1 = 35 | * Added Pro_Sites support. 36 | 37 | = 1.0.9 = 38 | * Changed the client WordPress login link to be the Admin login. 39 | 40 | = 1.0.8 = 41 | * Fixed problem were global $base is sometimes returned as null 42 | * Fixed problem with site name setting if the custom field title is used 43 | * Fixed subdirectory installs so properly add count on duplicating an existing site. 44 | 45 | = 1.0.7 = 46 | * Fixed problem with duplicate user login names. 47 | 48 | = 1.0.6= 49 | * Username not being split from the email address correctly 50 | 51 | = 1.0.5 = 52 | * Fixed problem doing automatic login from WHMCS server button 53 | 54 | = 1.0.4 = 55 | * Added Provision Server module logging to support debugging the WHMCS side. 56 | 57 | = 1.0.3 = 58 | * Checked blog.dir creation. 59 | * Added dashboard notification 60 | 61 | = 1.0.2 = 62 | * Changed disabled to readonly on product fields because disabled fields aren't returned on edit wiping them out. 63 | 64 | = 1.0.1 = 65 | * Changed blog.dir permission to the correct 0755 octal value. 66 | 67 | = 1.0 = 68 | * Initial public release -------------------------------------------------------------------------------- /languages/mrp-default.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: WHMCS Multisite Provisioning\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2014-05-30 20:56-0500\n" 6 | "PO-Revision-Date: 2014-05-30 20:56-0500\n" 7 | "Last-Translator: Arnold Bailey \n" 8 | "Language-Team: Arnold Bailey \n" 9 | "Language: en\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Poedit-KeywordsList: __;_e\n" 14 | "X-Poedit-Basepath: .\n" 15 | "X-Poedit-SourceCharset: UTF-8\n" 16 | "X-Generator: Poedit 1.6.5\n" 17 | "X-Poedit-SearchPath-0: ..\n" 18 | 19 | #: ../whmcs-mrp.php:34 20 | msgid "" 21 | "The WHMCS Multisite Provisioning plugin is only compatible with WordPress " 22 | "Multisite." 23 | msgstr "" 24 | 25 | #: ../whmcs-mrp.php:150 26 | msgid "WHMCS Provisioning" 27 | msgstr "" 28 | 29 | #: ../whmcs-mrp.php:196 30 | msgid "This request was not from an authorized site:" 31 | msgstr "" 32 | 33 | #: ../whmcs-mrp.php:199 34 | msgid "You do not have permission to access this function." 35 | msgstr "" 36 | 37 | #: ../whmcs-mrp.php:201 38 | msgid "You can't create an empty site." 39 | msgstr "" 40 | 41 | #: ../whmcs-mrp.php:226 42 | msgid "No valid action request." 43 | msgstr "" 44 | 45 | #: ../whmcs-mrp.php:255 46 | msgid "Domain name is empty!" 47 | msgstr "" 48 | 49 | #: ../whmcs-mrp.php:260 50 | msgid "Is not a valid domain name, alphanumeric and \"-\" only" 51 | msgstr "" 52 | 53 | #: ../whmcs-mrp.php:268 54 | #, php-format 55 | msgid "" 56 | "The following words are reserved for use by WordPress functions and cannot " 57 | "be used as blog names: %s" 58 | msgstr "" 59 | 60 | #: ../whmcs-mrp.php:311 61 | msgid "Missing or invalid site address." 62 | msgstr "" 63 | 64 | #: ../whmcs-mrp.php:315 65 | msgid "Missing email address." 66 | msgstr "" 67 | 68 | #: ../whmcs-mrp.php:320 69 | msgid "Invalid email address." 70 | msgstr "" 71 | 72 | #: ../whmcs-mrp.php:371 73 | msgid "There was an error creating the user." 74 | msgstr "" 75 | 76 | #: ../whmcs-mrp.php:385 77 | msgid "PREVIOUSLY SET" 78 | msgstr "" 79 | 80 | #: ../whmcs-mrp.php:437 81 | #, php-format 82 | msgid "" 83 | "New site created by %1s\n" 84 | "\n" 85 | "Address: %2s\n" 86 | "Name: %3s" 87 | msgstr "" 88 | 89 | #: ../whmcs-mrp.php:438 90 | #, php-format 91 | msgid "[%s] New Site Created" 92 | msgstr "" 93 | 94 | #: ../whmcs-mrp.php:472 ../whmcs-mrp.php:533 95 | msgid "Requesting Pro-sites which is not active on this site." 96 | msgstr "" 97 | 98 | #: ../whmcs-mrp.php:480 ../whmcs-mrp.php:540 99 | msgid "No Levels defined in Pro-Sites." 100 | msgstr "" 101 | 102 | #: ../whmcs-mrp.php:550 103 | msgid "Invalid Pro Sites Level name in changepackage." 104 | msgstr "" 105 | 106 | #: ../whmcs-mrp.php:593 107 | msgid "domain not found when trying to suspend" 108 | msgstr "" 109 | 110 | #: ../whmcs-mrp.php:621 111 | msgid "domain not found when trying to unsuspend" 112 | msgstr "" 113 | 114 | #: ../whmcs-mrp.php:651 115 | msgid "domain not found when trying to terminate" 116 | msgstr "" 117 | 118 | #: ../whmcs-mrp.php:702 ../whmcs-mrp.php:708 119 | msgid "WHMCS Multisite Provisioning" 120 | msgstr "" 121 | 122 | #: ../whmcs-mrp.php:714 123 | msgid "Remote WHMCS host:" 124 | msgstr "" 125 | 126 | #: ../whmcs-mrp.php:719 127 | msgid "Authentication Token:" 128 | msgstr "" 129 | 130 | #: ../whmcs-mrp.php:725 131 | msgid "Authorized IP Addresses:" 132 | msgstr "" 133 | 134 | #: ../whmcs-mrp.php:726 135 | msgid "(one per line)" 136 | msgstr "" 137 | 138 | #: ../whmcs-mrp.php:727 139 | msgid "Leave blank to disable IP filtering" 140 | msgstr "" 141 | 142 | #: ../whmcs-mrp.php:734 143 | msgid "Debug:" 144 | msgstr "" 145 | 146 | #: ../whmcs-mrp.php:742 147 | msgid "" 148 | "This plugin prevents sending debug messages generated by other plugins to " 149 | "WHMCS." 150 | msgstr "" 151 | 152 | #: ../whmcs-mrp.php:743 153 | msgid "" 154 | "Check debug to allow these message to pass to WHMCS so they can appear in " 155 | "the WHMCS Modules Log there." 156 | msgstr "" 157 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WHMCS MultiSite Provisioning === 2 | Contributors: Arnold Bailey 3 | Tags: WHMCS, hosting, multisite, support, billing, integration, provisioning 4 | Requires at least: 3.0 and Multisite 5 | Tested up to: 3.3.1 6 | Stable tag: 1.0 7 | 8 | This plugin allows provisioning of blogs on a Wordpress multi-site installation from external WHMCS packages and billing system. 9 | Includes provisioning for Subdomain, Subdirectory or Domain Mapping Wordpress Multisite installs. 10 | 11 | == Description == 12 | 13 | This plugin allows provisioning of blogs on a Wordpress multi-site installation from external WHMCS packages and billing system. 14 | Includes provisioning for Subdomain, Subdirectory or Domain Mapping Wordpress Multisite installs. 15 | 16 | The plugin's /whmcs directory contains modules to be installed on WHMCS to communicate with this plugin. 17 | 18 | The current version Is from WHMCS to Wordpress only. Some fields will sync to WHMCS automatically such as username, domain, path. Changes to these are locked out at WHMCS admin. 19 | Passwords may be chaged from WHMCS Admin but they DO NOT feed back from Wordpress to WHMCS. Wordpress passwords are hashed and can't be read in clear. 20 | 21 | == Installation of Wordpress Plugin == 22 | 23 | 1. Upload the 'whmcs-multisite-provisioning' folder to the '/wp-content/plugins/' directory 24 | 2. Network activate the plugin on the Network Admin of your multi-site Wordpress installation through the Network Admin's 'Plugins' menu in WordPress. 25 | 3. On the settings page currently nothing is required to be set. 26 | 4. If you set allowed IP addresses they must contain the IP of your WHMCS server. Blank turns off IP filtering. 27 | 5. Other fields are non-functional to be added in the future. 28 | 6. That's all the config necessary. 29 | 30 | == Installation of WHMCS Modules == 31 | 32 | Copy the whmcs/Modules directory over your Modules directory in your WHMCS installation. 33 | This adds a Server Module named "whmcs_multisite" and an Addon module named "whmcs_multisite". 34 | 35 | IMPORTANT: Create an Administrator in WHMCS who will authorize Multisite products. This is necessary to authorize Wordpress to update your 36 | service information after a site is created. If not filled in the Product creation will fail. 37 | The only important part is that the username has administartor level permission to create the product. 38 | 39 | == WHMCS Server Module configuration == 40 | 1. In WHMCS admin go to Setup | Servers and Add New Server 41 | 2. Give the Server a name meaningful to you. 42 | 3. Enter the Hostname = primary domain of the Wordpress install you want to control (mydomain.tld) 43 | 4. Enter Monthly cost 44 | 5. Set the Type to whmcs_multisite 45 | 6. Set the Username to a Super Admin username on your Wordpress site. 46 | 7. Set the Password to the Super Admin password. 47 | 8. Save. 48 | 9. In Servers Create New Group and add this server to it. 49 | 50 | == WHMCS Addon Module configuration == 51 | 1. In WHMCS admin go to Setup | Addon Modules 52 | 2. Activate the WHMCS Multisite Module 53 | 3. Set Access Control as appropriate for your administrators. 54 | 55 | == If your Wordpress Install is Subdomain or Subdirectory ONLY, no Domain Mapping == 56 | Create a Product 57 | 1. Create a Product Group to Hold the new products 58 | 2. In WHMCS admin go to Setup | Product/Services and Add New Product 59 | 3. Create a Product and Set the Product Type to Hosting Account. 60 | 4. Set the product Group to the one created above. 61 | 5. Give the Product a meaningful name 62 | 6. On the Details tab UNTICK Require domain. Other fields as you prefer 63 | 7. On the Modules Settings Tab Select whmcs_multisite as the Module name 64 | 8. Select the Server Group defined above. 65 | 9. Set a default blog title. This can be edited by the user in Wordpress after blog is created ("My New Blog"). 66 | 10. Set a default Blog Domain. If you're not using Custom fields(see below) this is the default domain name that will be used with numbers appended. 67 | 11. Set the default role of the User that may be created for this product. Usually "administrator". 68 | 12. Set the Web Space Quota or leave blank for Wordpress default value. 69 | 13. Tick the two custom field names if you are using custom fields to define the Title and Domain. 70 | 14. Set the Radio buttons for when the product setup will occur. 71 | 15. On the Custom fields Tab, create two custom fields. 72 | 16. First custom field must be named "Domain", as a Textbox Validation "|^([a-zA-Z0-9-])+$|i" (without quotes), Required field, Show on Order Form. 73 | 17. Second custom field must be named "Title", as a Textbox Validation "|^([a-zA-Z0-9- ])+$|i" (without quotes), Required field, Show on Order Form. 74 | 18. Rest of the Product fields are Admin's choice. 75 | 76 | == If your Wordpress Install Offers Domain Mapping as well as subdomain/subdirectory installs == 77 | Create a Product = 78 | 1. Create a Product Group to Hold the new products 79 | 2. In WHMCS admin go to Setup | Product/Services and Add New Product 80 | 3. Create a Product and Set the Product Type to Hosting Account. 81 | 4. Set the Product Group to the one created above. 82 | 5. Give the Product a meaningful name 83 | 6. On the Details Tab TICK Require domain. Other fields as you prefer 84 | 7. On the Modules Settings Tab Select whmcs_multisite as the Module name 85 | 8. Select the Server Group defined above. 86 | 9. Set a default blog title. This can be edited by the user in Wordpress after blog is created ("My New Blog"). 87 | 10. Set a default Blog Domain. If you're not using Custom fields(see below) this is the default domain name that will be used with numbers appended. 88 | 11. UNTICK the two custom field names. 89 | 12. Set the default role of the User that may be created for this product. Usually "administrator". 90 | 13. Set the Web Space Quota or leave blank for Wordpress default value. 91 | 14. Set the Radio buttons for when the product setup will occur. 92 | 15. Do not create custom fields. 93 | 16. If you want to offer subdomain/subdirectory installs as well as Domain Mapping = 94 | 17. On the Other Tab fill in the Subdomains Option with your sites primary domain ('.mysite.com") Note the leading dot. 95 | 18. Rest of the Product fields are Admin's choice. 96 | 97 | == Known Issues == 98 | 1. If a user does not already exist on Wordpress as determined by matching the WHMCS Clients email 99 | with the Wordpress users email, a new user will be created using the WHMCS Client's email address and 100 | the portion before the '@' as the users name. If you are Accepting Orders in Admin before creation you 101 | can change the default username there. Once the username is created it cannot be changed. 102 | 103 | 2. If the WHMCS clients email address already exists as a user in Wordpress the Wordpress user account 104 | will override what you may put in WHMCS. Wordpress user names can not be changed and this is enforced in WHMCS. 105 | 106 | 3. WHMCS generates random passwords when creating a service. If a new Wordpress user is 107 | created as a result this random password is the password for the new user. If the user already exists 108 | in Wordpress WHMCS still generates a random password but it does NOT change the Wordpress password. 109 | WHMCS can change the password on Wordpress but Wordpress password changes will not be feed back to WHMCS. 110 | 111 | 4. Each WHMCS service creates different random passwords so they will not match if a Client has more than one 112 | service at the same Wordpress site. 113 | 114 | == Change log == 115 | 116 | See separate changelog.txt 117 | 118 | -------------------------------------------------------------------------------- /whmcs-mrp.php: -------------------------------------------------------------------------------- 1 | domain_mapping_active = class_exists('domain_map') || function_exists('dm_sunrise_warning'); 69 | if($this->domain_mapping_active){ 70 | // Hook up the domain mapping table 71 | $this->db =& $wpdb; 72 | 73 | if(!empty($this->db->dmtable)) { 74 | $this->dmt = $this->db->dmtable; 75 | } else { 76 | if(defined('DM_COMPATIBILITY')) { 77 | if(!empty($this->db->base_prefix)) { 78 | $this->db->dmtable = $this->db->base_prefix . 'domain_mapping'; 79 | } else { 80 | $this->db->dmtable = $this->db->prefix . 'domain_mapping'; 81 | } 82 | } else { 83 | if(!empty($this->db->base_prefix)) { 84 | $this->db->dmtable = $this->db->base_prefix . 'domain_map'; 85 | } else { 86 | $this->db->dmtable = $this->db->prefix . 'domain_map'; 87 | } 88 | } 89 | } 90 | } 91 | 92 | register_activation_hook(__FILE__,array($this,'on_activate')); 93 | register_deactivation_hook(__FILE__,array($this,'on_deactivate')); 94 | 95 | add_action('init', array($this,'on_init')); 96 | add_action('admin_init', array($this,'on_admin_init')); 97 | add_action('network_admin_menu', array($this,'on_network_admin_menu')); 98 | 99 | add_filter('query_vars', array($this,'on_query_vars')); 100 | add_action('parse_request', array($this,'on_parse_request')); 101 | } 102 | 103 | /** 104 | * on_activate - Called on plugin activation. Does any initial setup 105 | * 106 | */ 107 | function on_activate(){ 108 | //Activation if needed. 109 | } 110 | 111 | /* 112 | function set_extend($blog_id) { 113 | //$trial_days = $this->get_setting('trial_days'); 114 | if ( $trial_days > 0 ) { 115 | $extend = $trial_days * 86400; 116 | $this->extend($blog_id, $extend, 'Trial', $this->get_setting('trial_level', 1)); 117 | } 118 | } 119 | */ 120 | 121 | /** 122 | * on-deactivate - called on deactivating the plugin. Performs any cleanup necessary 123 | * 124 | */ 125 | function on_deactivate(){ 126 | //Deactivation if needed. 127 | } 128 | /** 129 | * on_init - Calls init hook functions. 130 | * 131 | */ 132 | function on_init(){ 133 | load_plugin_textdomain( 'mrp', false, '/languages/' ); 134 | include_once ABSPATH . '/wp-admin/includes/ms.php'; 135 | } 136 | 137 | /** 138 | * on_admin_init - 139 | * 140 | */ 141 | function on_admin_init(){ 142 | register_setting('mrp','mrp_settings'); 143 | } 144 | 145 | /** 146 | * on_network_admin_menu - Add network menu for this plugin 147 | * 148 | */ 149 | function on_network_admin_menu() { 150 | if( function_exists( 'add_menu_page' ) ){ 151 | add_menu_page(__('WHMCS Provisioning','mrp'), __('WHMCS Provisioning','mrp'), 'manage_network_options', 'mrp-settings',array($this,'admin_settings_page')); 152 | } 153 | } 154 | 155 | /** 156 | * on_query_vars - Authorize query vars for this plugin 157 | * 158 | */ 159 | function on_query_vars($vars){ 160 | //Add any vars your going to be receiving from WHMCS 161 | $vars[] = 'whmcs'; //WHMCS data array 162 | return $vars; 163 | } 164 | 165 | /** 166 | * on_parse_request - See if the Query is for us 167 | * 168 | */ 169 | function on_parse_request($wp){ 170 | global $current_user; 171 | 172 | // If no whmcs data then not for us 173 | if(! array_key_exists('whmcs',$wp->query_vars)) return; 174 | 175 | 176 | $this->whmcs = $_REQUEST['whmcs']; 177 | 178 | $this->response = array(); //Values to be returned to whmcs' 179 | 180 | $this->settings = get_site_option('mrp_settings'); 181 | 182 | //Start the buffer so any extraneous error messages generated by debug errors won't get sent. 183 | if( ! $this->settings['debug']) ob_start(); 184 | 185 | 186 | // Get valid IPs we will accept requests from and trim and remove any blank lines. 187 | $this->ips = array_filter( array_map('trim',explode("\n",$this->settings['ips'])), create_function('$str','return !empty($str);'));; 188 | 189 | //See if we're ready and authorized to process requests 190 | $user = wp_signon($this->whmcs['credentials'], false); 191 | if( ! is_wp_error($user)) wp_set_current_user($user->ID); 192 | 193 | if(is_wp_error($user)){ 194 | $this->response['error'] = strip_tags($user->get_error_message()); 195 | } 196 | elseif((count($this->ips) > 0) && ! in_array($_SERVER['REMOTE_ADDR'], $this->ips)){ 197 | $this->response['error'] = __('This request was not from an authorized site:','mrp') . $_SERVER['REMOTE_ADDR']; 198 | } 199 | elseif(! current_user_can('manage_site')){ 200 | $this->response['error'] = __('You do not have permission to access this function.','mrp'); 201 | }elseif(! is_array($this->whmcs) ){ 202 | $this->response['error'] = __('You can\'t create an empty site.', 'mrp'); 203 | }else{ 204 | $this->process_request(); 205 | } 206 | 207 | //Throw away any extraneous error messages 208 | if( ! $this->settings['debug']) ob_end_clean(); 209 | 210 | exit(json_encode($this->response) ); 211 | } 212 | 213 | /** 214 | * process_request - validated request in $this->whmcs 215 | * 216 | */ 217 | function process_request(){ 218 | 219 | switch ($this->whmcs['action']){ 220 | case 'create' : $this->create_blog(); break; 221 | case 'suspend' : $this->suspend_blog(); break; 222 | case 'unsuspend' : $this->unsuspend_blog(); break; 223 | case 'terminate' : $this->terminate_blog(); break; 224 | case 'password' : $this->set_password(); break; 225 | case 'changepackage' : $this->changepackage(); break; 226 | 227 | default: $this->response['error'] = __('No valid action request.', 'mrp'); break; 228 | } 229 | } 230 | 231 | /** 232 | * create_blog - Creates a new Multisite blog using the parameters passed from whmcs 233 | * $this->whmcs contains (at least) 234 | * ['action'] 235 | * ['domain'] 236 | * ['title'] 237 | * ['email'] 238 | * ['user_name'] 239 | * ['password'] 240 | * ['last_name'] 241 | * ['first_name'] 242 | * ['nickname'] 243 | * ['default_role'] 244 | * ['upload_space'] 245 | * ['credentials'] 246 | * 247 | * @todo Honor the registration settings in WP Network settings. none user blog or all 248 | */ 249 | function create_blog(){ 250 | global $wpdb,$current_user,$current_site,$base; 251 | 252 | $domain = strtolower($this->whmcs['domain']); 253 | $mapped_domain = strtolower($this->whmcs['mapped_domain']); 254 | 255 | if( trim($domain) . trim($mapped_domain) == '') { 256 | $this->response['error'] = "$domain: " . __('Domain name is empty!', 'mrp'); 257 | return; 258 | } 259 | 260 | if (! preg_match('|^([a-zA-Z0-9-])+$|', $this->whmcs['domain'])){ 261 | $this->response['error'] = "$domain: " . __('Is not a valid domain name, alphanumeric and "-" only', 'mrp'); 262 | return; 263 | } 264 | 265 | // If not a subdomain install, make sure the domain isn't a reserved word 266 | if ( ! is_subdomain_install() ) { 267 | $subdirectory_reserved_names = apply_filters( 'subdirectory_reserved_names', array( 'page', 'comments', 'blog', 'files', 'feed' ) ); 268 | if ( in_array( $domain, $subdirectory_reserved_names ) ){ 269 | $this->response['error'] = sprintf( __('The following words are reserved for use by WordPress functions and cannot be used as blog names: %s' ), implode( ', ', $subdirectory_reserved_names ) ) ; 270 | return; 271 | } 272 | } 273 | 274 | 275 | //Is there already a domain with this mapping? 276 | // if($this->domain_mapping_active){ 277 | // if( 278 | // //Check blogs table 279 | // null != $this->db->get_row( $this->db->prepare("SELECT blog_id FROM {$this->db->blogs} WHERE domain = %s AND path = '/' /* domain mapping */", $mapped_domain) ) 280 | // 281 | // //Check mapped table 282 | // || null != $this->db->get_row( $this->db->prepare("SELECT blog_id FROM {$this->dmt} WHERE domain = %s /* domain mapping */", $mapped_domain ) ) ) { 283 | // $this->response['error'] = __("Mapped domain already exists for ",'mrp') . $mapped_domain; 284 | // return; 285 | // } 286 | // } 287 | 288 | $email = sanitize_email( $this->whmcs['email'] ); 289 | $user_id = email_exists($email); 290 | 291 | 292 | if($user_id){ 293 | $user_name = get_userdata($user_id)->user_login; // Can't change name so pass back to update WHMCS 294 | } else{ 295 | $user_name = (empty($this->whmcs['user_name'])) ? sanitize_user( strstr($email, '@', true) ) : sanitize_user($this->whmcs['user_name'], true); 296 | $ndx = 1; 297 | $un = $user_name; 298 | while($user = get_user_by('login', $user_name)){ 299 | $user_name = $un . $ndx++; //Avoid name collision 300 | } 301 | } 302 | 303 | $this->response['user_name'] = $user_name; //Send back to WHMCS 304 | $password = $this->whmcs['password']; 305 | 306 | $title = (empty($this->whmcs['title']) ) ? '' : $this->whmcs['title']; 307 | 308 | $credentials = $this->whmcs['credentials']; 309 | 310 | 311 | if ( empty( $domain ) ){ 312 | $this->response['error'] = __( 'Missing or invalid site address.','mrp'); 313 | return; 314 | } 315 | if ( empty( $email ) ){ 316 | $this->response['error'] = __( 'Missing email address.','mrp'); 317 | return; 318 | } 319 | if ( !is_email( $email ) ){ 320 | 321 | $this->response['error'] = __( 'Invalid email address.','mrp'); 322 | return; 323 | } 324 | 325 | if ( is_subdomain_install() ) { 326 | //Subdomain Install 327 | $this->response['install_type'] = 'subdomain'; 328 | $newdomain = $domain . '.' . preg_replace( '|^www\.|', '', $current_site->domain ); 329 | $path = $base; 330 | 331 | //Check for duplicate 332 | $ndx= 1; 333 | $nd = $newdomain; //Remember the originl $newdomain 334 | $blog_details = get_blog_details(array('domain' => $newdomain, 'path' => $path)); 335 | while(! empty($blog_details)){ 336 | // $whmcs_settings = get_blog_option($blog_details->blog_id,'whmcs_settings'); 337 | // if ( $whmcs_settings && $whmcs_settings['client_id'] == $credentials['whmcs_client_id']){ //Found owner of this blog 338 | // break; 339 | // } 340 | $newdomain = str_replace($domain, $domain . $ndx++, $nd); 341 | $blog_details = get_blog_details(array('domain' => $newdomain, 'path' => $path)); 342 | } 343 | } else { 344 | //Subdirectory Install 345 | $this->response['install_type'] = 'subdirectory'; 346 | $newdomain = $current_site->domain; 347 | $path = trailingslashit($base) . trailingslashit($domain); 348 | 349 | //Check for duplicate 350 | $ndx= 1; 351 | $p = $path; // remember original path 352 | $blog_details = get_blog_details(array('domain' => $newdomain, 'path' => $path)); 353 | while(! empty($blog_details)){ //Already there 354 | // $whmcs_settings = get_blog_option($blog_details->blog_id,'whmcs_settings'); 355 | // if ( $whmcs_settings && $whmcs_settings['client_id'] == $credentials['whmcs_client_id']){ //Found an owner of this blog 356 | // 357 | // break; 358 | // } 359 | $path = str_replace(trim($p,'/'), trim($p,'/') . $ndx++, $p); 360 | $blog_details = get_blog_details(array('domain' => $newdomain, 'path' => $path)); 361 | } 362 | } 363 | 364 | //Pass back the new values 365 | $this->response['domain'] = $newdomain; 366 | $this->response['path'] = $path; 367 | 368 | if ( !$user_id ) { // Create a new user with WHMCS password 369 | //$password = wp_generate_password( 12, false ); 370 | $user_id = wpmu_create_user( $user_name, $password, $email ); 371 | if ( false == $user_id ){ 372 | $this->response['error'] = __( 'There was an error creating the user.','mrp' ); 373 | return; 374 | } 375 | else { 376 | if($this->whmcs['last_name']) update_user_option($user_id, 'last_name', $this->whmcs['last_name'], true); 377 | if($this->whmcs['first_name']) update_user_option($user_id, 'first_name', $this->whmcs['first_name'], true); 378 | 379 | if($this->whmcs['nickname']) update_user_option($user_id, 'nickname', $this->whmcs['nickname'], true); 380 | else update_user_option($user_id, 'nickname', $this->whmcs['first_name'], true); 381 | 382 | wp_new_user_notification( $user_id, $password ); 383 | } 384 | } else { 385 | //Already in database 386 | $this->response['password'] =__('PREVIOUSLY SET', 'mrp'); //Send back to WHMCS 387 | } 388 | 389 | //return the login 390 | $userdata=get_userdata( $user_id ); 391 | $this->response['login']=$userdata->user_login; 392 | 393 | //remove from primary blog 394 | remove_user_from_blog( $user_id, $current_site->id ); //removes new user from main blog 395 | 396 | $wpdb->hide_errors(); 397 | $id = wpmu_create_blog( $newdomain, $path, $title, $user_id , array( 'public' => 1 ) ); 398 | 399 | $wpdb->show_errors(); 400 | 401 | //Blog wasn't created 402 | if ( is_wp_error( $id ) ) { 403 | $this->response['error'] = $id->get_error_message(); 404 | return; 405 | } 406 | 407 | $this->response['blog_id'] = $id; //Send back to WHMCS 408 | 409 | //add default role 410 | if ($this->whmcs['default_role']) { 411 | $role_name=$this->whmcs['default_role']; 412 | $role_slug=str_replace(' ','_',strtolower($role_name)); 413 | $role_slug=preg_replace("/[^a-zA-Z0-9\s]/", "", $role_slug); 414 | 415 | if(!get_role($role_slug)) { 416 | $roles=new WP_Roles(); 417 | $roles->add_role($role_slug,$role_name,array($role_slug)); 418 | } 419 | remove_user_from_blog($user_id, $id); 420 | add_user_to_blog($id, $user_id, $role_slug); 421 | $user = new WP_User($user_id); 422 | //$user->add_role($roleSlug); 423 | } 424 | 425 | if ( !is_super_admin( $user_id ) && !get_user_option( 'primary_blog', $user_id ) ) 426 | update_user_option( $user_id, 'primary_blog', $id, true ); 427 | 428 | //Save the WHMCS product data for this blog 429 | $whmcs_settings = get_blog_option($id,'whmcs_settings'); 430 | if (! $whmcs_settings) $whmcs_settings = array(); 431 | 432 | $whmcs_settings['client_id'] = $credentials['whmcs_client_id']; 433 | $whmcs_settings['service_id'] = $credentials['whmcs_service_id']; 434 | $whmcs_settings['product_id'] = $credentials['whmcs_product_id']; 435 | 436 | update_blog_option($id, 'whmcs_settings', $whmcs_settings); 437 | 438 | $content_mail = sprintf( __( "New site created by %1s\n\nAddress: %2s\nName: %3s"), $current_user->user_login , get_site_url( $id ), stripslashes( $title ) ); 439 | wp_mail( get_site_option('admin_email'), sprintf( __( '[%s] New Site Created' ), $current_site->site_name ), $content_mail, 'From: "Site Admin" <' . get_site_option( 'admin_email' ) . '>' ); 440 | wpmu_welcome_notification( $id, $user_id, $password, $title, array( 'public' => 1 ) ); 441 | //wp_redirect( add_query_arg( array( 'update' => 'added', 'id' => $id ), 'site-new.php' ) ); 442 | 443 | //If domain mapping, map domain 444 | if($this->domain_mapping_active){ 445 | if( null == $this->db->get_row( $this->db->prepare("SELECT blog_id FROM {$this->db->blogs} WHERE domain = %s AND path = '/' /* domain mapping */", $mapped_domain) ) 446 | && null == $this->db->get_row( $this->db->prepare("SELECT blog_id FROM {$this->dmt} WHERE domain = %s /* domain mapping */", $mapped_domain ) ) ) { 447 | $this->db->query( $this->db->prepare( "INSERT INTO {$this->dmt} ( `id` , `blog_id` , `domain` , `active` ) VALUES ( NULL, %d, %s, '1') /* domain mapping */", $id, $mapped_domain) ); 448 | $this->response['mapped_domain'] = $mapped_domain; // Handshake back that it worked 449 | } 450 | } 451 | 452 | //Blog specific stuff 453 | if (is_numeric($this->whmcs['upload_space'])){ 454 | update_blog_option($id, 'blog_upload_space',intval($this->whmcs['upload_space'])); 455 | } 456 | 457 | //Create the blog uploads directory 458 | if( ! is_dir( WP_CONTENT_DIR.'/blogs.dir/'.$id . '/files' ) ) { 459 | mkdir(WP_CONTENT_DIR.'/blogs.dir/'.$id . '/files', 0755, true); //0755 octal 460 | } 461 | 462 | 463 | /** 464 | * ProSites 465 | */ 466 | global $wpdb,$current_user,$current_site,$base, $psts; 467 | 468 | $level = trim( $this->whmcs['level'] ); 469 | 470 | if( !empty( $level ) ) { //Need to handle Pro-Sites? 471 | //Is pro-sites installed? 472 | if( empty($psts) ) { 473 | $this->response['error'] = __( 'Requesting Pro-sites which is not active on this site.','mrp'); 474 | return; 475 | } 476 | 477 | $levels = wp_list_pluck( get_site_option('psts_levels', array() ), 'name' ); 478 | $levels = array_map( 'trim', $levels ); 479 | if( empty($levels) ){ 480 | //No levels to search 481 | $this->response['error'] = __( 'No Levels defined in Pro-Sites.','mrp'); 482 | return; 483 | } 484 | 485 | $level_id = 0; 486 | foreach($levels as $key => $value) { 487 | if( strtolower($value) == strtolower($level) ) $level_id = intval($key); 488 | } 489 | 490 | if(empty($level_id) ) { 491 | if( ! strtolower( $psts->get_setting( 'free_name' ) ) == strtolower( $level ) ) { 492 | $this->response['error'] = __( "Invalid Pro Sites Level name in create_blog: $level",'mrp'); 493 | return; 494 | } 495 | } 496 | 497 | $this->response['level'] = $level_id; 498 | 499 | $ch_blog = $wpdb->get_row("SELECT blog_ID FROM " . $wpdb->base_prefix . "pro_sites WHERE blog_ID=$id LIMIT 1"); 500 | if(!empty($ch_blog->blog_ID)){ 501 | $wpdb->query($wpdb->prepare("UPDATE " . $wpdb->base_prefix . "pro_sites SET level=%d WHERE blog_ID=%s", $level_id, $id)); 502 | $psts->record_stat($id, 'upgrade'); 503 | } else { 504 | $wpdb->query($wpdb->prepare("INSERT INTO " . $wpdb->base_prefix . "pro_sites (blog_ID, expire, level, gateway, term) VALUES (%d, '9999999999', %s, 'WHMCS', 'Permanent')", $id, $level_id)); 505 | $psts->record_stat($id, 'signup'); 506 | } 507 | $psts->log_action($id, __("WHMCS created blog id {$id}. Expiration and payments will be handled by WHMCS", 'mrp') ); 508 | 509 | } 510 | //print_r($this->db); 511 | //exit(); 512 | //All done 513 | $this->response['success'] = true; 514 | } 515 | 516 | 517 | ///// Change LEVEL BLOG 518 | /** 519 | * changepackage - command to suspend a blog from WHMCS 520 | * $this->whmcs contains (at least) 521 | * ['action'] 522 | * ['domain'] 523 | * ['credentials'] 524 | * @since 1.1 525 | */ 526 | function changepackage(){ 527 | global $wpdb,$base, $psts; 528 | 529 | $id = intval($this->whmcs['blog_id']); 530 | $domain = $this->whmcs['domain']; 531 | $details = get_blog_details($id); 532 | $level = $this->whmcs['level']; 533 | 534 | if( !empty( $level ) ) { 535 | 536 | if( empty($psts) ) { 537 | $this->response['error'] = __( 'Requesting Pro-sites which is not active on this site.','mrp'); 538 | return; 539 | } 540 | 541 | $levels = get_site_option('psts_levels', array() ); 542 | if(empty($levels) ) { 543 | //No levels to search 544 | $this->response['error'] = __( 'No Levels defined in Pro-Sites.','mrp'); 545 | return; 546 | } 547 | 548 | $level_id = 0; 549 | foreach($levels as $key => $val) { 550 | if( strtolower($val['name']) == strtolower($level) ) $level_id = intval($key); 551 | } 552 | 553 | if(empty($level_id) ) { 554 | $this->response['error'] = __( 'Invalid Pro Sites Level name in changepackage.','mrp'); 555 | return; 556 | } 557 | $this->response['level'] = $level_id; 558 | 559 | $ch_blog = $wpdb->get_row("SELECT blog_ID FROM " . $wpdb->base_prefix . "pro_sites WHERE blog_ID=$id LIMIT 1"); 560 | if(!empty($ch_blog->blog_ID)){ 561 | $update_level = $wpdb->query($wpdb->prepare("UPDATE " . $wpdb->base_prefix . "pro_sites SET level=%d WHERE blog_ID=%d", $level_id, $id)); 562 | } else { 563 | $update_level = $wpdb->query($wpdb->prepare("INSERT INTO " . $wpdb->base_prefix . "pro_sites (blog_ID, expire, level, gateway, term) VALUES (%d, '9999999999', %d, 'WHMCS', 'Permanent')", $id, $level_id)); 564 | } 565 | 566 | if( $update_level === false ){ 567 | $this->response['error'] = "$domain: $update_level" . __( "Error SQL Update Level Status $wpdb->last_error $wpdb->last_query",'mrp'); 568 | return; 569 | } 570 | $this->response['message'] = "success"; 571 | $psts->record_stat($id, 'upgrade'); 572 | $psts->log_action($id, __("WHMCS changed Pro-Sites level to {$levels[$level_id]['name']} (Level ID {$level_id}).", 'mrp') ); 573 | } 574 | 575 | } 576 | 577 | /** 578 | * suspend_blog - command to suspend a blog from WHMCS 579 | * $this->whmcs contains (at least) 580 | * ['action'] 581 | * ['domain'] 582 | * ['credentials'] 583 | */ 584 | function suspend_blog(){ 585 | global $psts; 586 | 587 | 588 | $id = intval($this->whmcs['blog_id']); 589 | $domain = $this->whmcs['domain']; 590 | 591 | $details = get_blog_details($id); 592 | 593 | //Does blog exist? 594 | $this->response['message'] = "[$domain] [$id]"; 595 | 596 | if (empty($details)){ 597 | $this->response['error'] = "$domain: " . __( 'domain not found when trying to suspend','mrp'); 598 | return; 599 | } 600 | 601 | if( !empty($psts) ) $psts->log_action($id, __("WHMCS SUSPENDED blog id {$id}.", 'mrp') ); 602 | 603 | update_blog_status( $id, 'deleted', '1' ); 604 | } 605 | 606 | /** 607 | * unsuspend_blog - command to suspend a blog from WHMCS 608 | * $this->whmcs contains (at least) 609 | * ['action'] 610 | * ['domain'] 611 | * ['credentials'] 612 | * 613 | */ 614 | function unsuspend_blog(){ 615 | global $psts; 616 | 617 | $id = intval($this->whmcs['blog_id']); 618 | $domain = $this->whmcs['domain']; 619 | 620 | $details = get_blog_details($id); 621 | 622 | //Does blog exist? 623 | $this->response['message'] = "[$domain] [$id]"; 624 | if (empty($details)){ 625 | $this->response['error'] = "$domain: " . __( 'domain not found when trying to unsuspend','mrp'); 626 | return; 627 | } 628 | 629 | if( !empty($psts) ) $psts->log_action($id, __("WHMCS UNSUSPENDED blog id {$id}.", 'mrp') ); 630 | 631 | update_blog_status( $id, 'deleted', '0' ); 632 | } 633 | 634 | /** 635 | * terminate_blog - command to terminate (delete) a blog from WHMCS 636 | * Once called from WHMCS it can only be revoked by a superadmin on Wordpress. WHMCS cannot change further. 637 | * $this->whmcs contains (at least) 638 | * ['action'] 639 | * ['domain'] 640 | * ['credentials'] 641 | * 642 | */ 643 | function terminate_blog(){ 644 | global $psts; 645 | 646 | $id = intval($this->whmcs['blog_id']); 647 | $domain = $this->whmcs['domain']; 648 | 649 | $details = get_blog_details($id); 650 | 651 | //Does blog exist? 652 | $this->response['message'] = "[$domain] [$id]"; 653 | 654 | if (empty($details)){ 655 | $this->response['error'] = "$domain: " . __( 'domain not found when trying to terminate','mrp'); 656 | return; 657 | } 658 | 659 | if( !empty($psts) ) $psts->log_action($id, __("WHMCS TERMINATED blog id {$id}.", 'mrp') ); 660 | 661 | wpmu_delete_blog( $id, true ); 662 | } 663 | 664 | /** 665 | * set_password - command to set the password for the user on a blog 666 | * $this->whmcs contains (at least) 667 | * ['action'] 668 | * ['domain'] 669 | * ['email'] 670 | * ['user_name'] 671 | * ['password'] 672 | * ['credentials'] 673 | * 674 | */ 675 | function set_password(){ 676 | 677 | $domain = strtolower($this->whmcs['domain']); 678 | $password = $this->whmcs['password']; 679 | $email = sanitize_email($this->whmcs['email']); 680 | $user_name = sanitize_user($this->whmcs['user_name']); 681 | $user_id = email_exists($email); 682 | 683 | //Does User exist? 684 | $this->response['message'] = "[$email] [$user_id]"; 685 | if (! $user_id){ 686 | $this->response['error'] = __( "No user found for this $user_name/$email combination",'mrp'); 687 | return; 688 | } 689 | wp_set_password($password,$user_id); 690 | } 691 | 692 | /** 693 | * admin_settings_page - Displays the Admin settings page. 694 | * 695 | */ 696 | function admin_settings_page(){ 697 | 698 | if(! empty($_POST['mrp_wpnonce']) && wp_verify_nonce($_POST['mrp_wpnonce'],'mrp_admin')){ 699 | $settings = $_POST['mrp']; 700 | update_site_option('mrp_settings', $settings); 701 | echo '

Settings Updated

'; 702 | } 703 | $settings = get_site_option('mrp_settings'); 704 | ?> 705 |
706 |

707 |
708 |
709 |
710 |
711 | 712 |

713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 727 | 728 | 733 | 734 | 735 | 736 | 737 | 740 | 749 | 750 | 751 | 752 |
729 |
730 |
731 | 732 |
738 |
739 |
741 | 746 |

747 |

748 |
753 | 754 |
755 |
756 |
757 |
758 |
759 | "WHMCS Multisite Provisioning", 40 | "version" => "1.2", 41 | "author" => "wpmudev.org", 42 | "language" => "english", 43 | 44 | "fields" => array( 45 | 46 | /* 47 | "option1" => array ("FriendlyName" => "Multisite Administrator", "Type" => "text", "Size" => "25", "Description" => "Name of an administrator with rights to the Multisite Addon."), 48 | "option2" => array ("FriendlyName" => "Option2", "Type" => "password", "Size" => "25", "Description" => "Password"), 49 | "option3" => array ("FriendlyName" => "Option3", "Type" => "yesno", "Size" => "25", "Description" => "Sample Check Box"), 50 | "option4" => array ("FriendlyName" => "Option4", "Type" => "textarea", "Size" => "25", "Description" => "Textarea"), 51 | "option5" => array ("FriendlyName" => "Option5", "Type" => "dropdown", "Options" => "1,2,3,4,5", "Description" => "Sample Dropdown"), 52 | */ 53 | )); 54 | return $configarray; 55 | } 56 | 57 | function whmcs_multisite_activate() { 58 | 59 | # Create Custom DB Table 60 | $query = "CREATE TABLE IF NOT EXISTS `mod_whmcs_multisite` 61 | (`id` INT( 1 ) NOT NULL AUTO_INCREMENT PRIMARY KEY , 62 | `blog_id` INT NOT NULL, 63 | `service_id` INT NOT NULL, 64 | `domain` TEXT NOT NULL, 65 | `path` TEXT NOT NULL, 66 | `level` INT NOT NULL, 67 | KEY `service_id` (`service_id`, `blog_id`) 68 | )"; 69 | 70 | $result = mysql_query($query); 71 | //Add level field for Pro-Sites 72 | $col = mysql_query("SELECT `level` FROM `mod_whmcs_multisite`"); 73 | 74 | if( !$col ) { 75 | $query = "ALTER TABLE `mod_whmcs_multisite` ADD `level` INT NULL"; 76 | mysql_query( $query ); exit; 77 | } 78 | } 79 | 80 | function whmcs_multisite_deactivate() { 81 | 82 | # Remove Custom DB Table 83 | //$query = "DROP TABLE `mod_addonexample`"; 84 | //$result = mysql_query($query); 85 | 86 | } 87 | 88 | 89 | function whmcs_multisite_upgrade($vars) { 90 | $version = $vars['version']; 91 | 92 | /* 93 | # Run SQL Updates for V1.0 to V1.1 94 | if ($version < 1.1) { 95 | $query = "ALTER `mod_addonexample` ADD `demo2` TEXT NOT NULL "; 96 | $result = mysql_query($query); 97 | } 98 | 99 | # Run SQL Updates for V1.1 to V1.2 100 | if ($version < 1.2) { 101 | $query = "ALTER `mod_addonexample` ADD `demo3` TEXT NOT NULL "; 102 | $result = mysql_query($query); 103 | } 104 | */ 105 | } 106 | 107 | function whmcs_multisite_output($vars) { 108 | $modulelink = $vars['modulelink']; 109 | $version = $vars['version']; 110 | $option1 = $vars['option1']; 111 | $option2 = $vars['option2']; 112 | $option3 = $vars['option3']; 113 | $option4 = $vars['option4']; 114 | $option5 = $vars['option5']; 115 | $LANG = $vars['_lang']; 116 | /* 117 | echo 'output'; 118 | echo '

'.$LANG['intro'].'

119 |

'.$LANG['description'].'

120 |

'.$LANG['documentation'].'

'; 121 | */ 122 | echo "

Version: $version

"; 123 | echo "

With Pro-Sites support

"; 124 | 125 | } 126 | 127 | function whmcs_multisite_sidebar($vars) { 128 | /* 129 | $modulelink = $vars['modulelink']; 130 | $version = $vars['version']; 131 | $option1 = $vars['option1']; 132 | $option2 = $vars['option2']; 133 | $option3 = $vars['option3']; 134 | $option4 = $vars['option4']; 135 | $option5 = $vars['option5']; 136 | $LANG = $vars['_lang']; 137 | 138 | $sidebar = ' Example 139 | '; 143 | return $sidebar; 144 | */ 145 | return ''; 146 | } 147 | 148 | ?> -------------------------------------------------------------------------------- /whmcs/modules/servers/whmcs_multisite/whmcs_multisite.php: -------------------------------------------------------------------------------- 1 | array( "Type" => "text", "Size" => "25", "Description" => "
Title given to the blog if one is not entered in the Title custom field.", ), 42 | "Default Blog Domain" => array( "Type" => "text", "Size" => "25", "Description" => "
Domain name given to the blog if one is not entered in the Domain custom field.", ), 43 | "Use Title field" => array( "Type" => "yesno", "Description" => "Tick if you created a custom Title field." ), 44 | "Use Domain field" => array( "Type" => "yesno", "Description" => "Tick if you created a custom Domain field." ), 45 | "Default Role" => array( "Type" => "text", "Size" => "25", "Description" => "
This is the role that will be assigned to a user created by this product." ), 46 | "Web Space Quota" => array( "Type" => "text", "Size" => "5", "Description" => "MB
Allowed upload space or leave blank to use Wordpress defaults." ), 47 | "Product Administrator" => array( "Type" => "text", "Size" => "25", "Description" => "
The WHMCS Administrator authorizing this product.
REQUIRED for the API to function" ), 48 | "ProSites" => array( "Type" => "text", "Size" => "25", "Description" => "
ProSite Plan Name" ), 49 | //"Subdomains" => array( "Type" => "dropdown", "Options" => "1,2,5,10,25,50,Unlimited"), 50 | ); 51 | 52 | return $configarray; 53 | } 54 | 55 | /** 56 | * Get url content and response headers (given a url, follows all redirections on it 57 | * and returnes content and response headers of final url) 58 | * 59 | * @return array[0] content 60 | * array[1] array of response headers 61 | * array[2] curl Error message 62 | */ 63 | function get_url( $url, $post_fields, $javascript_loop = 0, $timeout = 30 ) 64 | { 65 | //$url = str_replace( "&", "&", urldecode(trim($url)) ); 66 | 67 | if($javascript_loop == 0) $curl_error = ''; 68 | 69 | $cookie = tempnam ("/tmp", "CURLCOOKIE"); 70 | $ch = curl_init(); 71 | curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3) Gecko/20041001 Firefox/0.10.1" ); 72 | curl_setopt( $ch, CURLOPT_URL, $url ); 73 | curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie ); 74 | curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, false ); //Follow redirect explicitly to avoid open_basedir and safemode problem 75 | curl_setopt( $ch, CURLOPT_ENCODING, "" ); 76 | curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields); 77 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); 78 | curl_setopt( $ch, CURLOPT_AUTOREFERER, true ); 79 | curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false ); // required for https urls 80 | curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // required for https urls 81 | curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout ); 82 | curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout ); 83 | curl_setopt( $ch, CURLOPT_MAXREDIRS, 10 ); 84 | $content = curl_exec( $ch ); 85 | $response = curl_getinfo( $ch ); 86 | 87 | //Save the error 88 | if (curl_error($ch)) 89 | $curl_error = "Connection Error: " . curl_errno($ch) . ' - ' . curl_error($ch); 90 | 91 | curl_close ( $ch ); 92 | 93 | // If any rediects get the new location 94 | if (in_array($response['http_code'], array(300, 301, 302, 303, 307) ) ){ 95 | ini_set("user_agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3) Gecko/20041001 Firefox/0.10.1"); 96 | 97 | if ( $headers = get_headers($response['url']) ){ 98 | foreach( $headers as $value ){ 99 | if ( preg_match('#Location: (.+)#i',$value,$match ) ) 100 | return get_url( trim($match[1]), $post_fields ); 101 | } 102 | } 103 | } 104 | 105 | //Check for javascript redirects 106 | if ( ( preg_match("/>[[:space:]]+window\.location\.replace\('(.*)'\)/i", $content, $value) 107 | || preg_match("/>[[:space:]]+window\.location\=\"(.*)\"/i", $content, $value) ) 108 | && $javascript_loop < 5 ){ 109 | return get_url( $value[1], $post_fields, $javascript_loop+1 ); 110 | } else { 111 | return array( $content, $response, $curl_error ); 112 | } 113 | } 114 | 115 | function whmcs_multisite_CreateAccount($params) { 116 | 117 | # ** The variables listed below are passed into all module functions ** 118 | 119 | $serviceid = $params["serviceid"]; # Unique ID of the product/service in the WHMCS Database 120 | $pid = $params["pid"]; # Product/Service ID 121 | $producttype = $params["producttype"]; # Product Type: hostingaccount, reselleraccount, server or other 122 | $domain = $params["domain"]; 123 | $username = $params["username"]; 124 | $password = $params["password"]; 125 | $clientsdetails = $params["clientsdetails"]; # Array of clients details - firstname, lastname, email, country, etc... 126 | $customfields = $params["customfields"]; # Array of custom field values for the product 127 | $configoptions = $params["configoptions"]; # Array of configurable option values for the product 128 | 129 | # Product module option settings from ConfigOptions array above 130 | $configoption1 = $params["configoption1"]; 131 | $configoption2 = $params["configoption2"]; 132 | $configoption3 = $params["configoption3"]; 133 | $configoption4 = $params["configoption4"]; 134 | $configoption5 = $params["configoption5"]; 135 | $configoption6 = $params["configoption6"]; 136 | 137 | # Additional variables if the product/service is linked to a server 138 | $server = $params["server"]; # True if linked to a server 139 | $serverid = $params["serverid"]; 140 | $serverhostname = $params["serverhostname"]; 141 | $serverip = $params["serverip"]; 142 | $serverusername = $params["serverusername"]; 143 | $serverpassword = $params["serverpassword"]; 144 | $serveraccesshash = $params["serveraccesshash"]; 145 | $serversecure = $params["serversecure"]; # If set, SSL Mode is enabled in the server config 146 | 147 | # Code to perform action goes here... 148 | 149 | $credentials = array( 150 | 'user_login' => $params['serverusername'], 151 | 'user_password' => $params['serverpassword'], 152 | 'remember' => 0, 153 | 'whmcs_client_id' => $clientsdetails['userid'], 154 | 'whmcs_service_id' => $params['serviceid'], 155 | 'whmcs_product_id' => $params['pid'], 156 | 'level' => $params['configoption8'], 157 | ); 158 | 159 | // Default Wordpress user name everything before the @ in their whmcs email 160 | $wp_user_name = explode('@',$clientsdetails['email']); 161 | $wp_user_name = $wp_user_name[0]; 162 | 163 | $api_admin = $params["configoption7"]; 164 | 165 | if (empty($api_admin)) return 'Product does not have an authorizing administrator defined. Please update the product module setting.'; 166 | 167 | $request = array(); 168 | 169 | $request['action'] = 'create'; 170 | 171 | // figure out the subdomain/subdirectory or domain mapping 172 | if (empty($params['domain'])){ 173 | //Has to be either subdomain or subdirectory 174 | $request['domain'] = ($params['configoption4'] == 'on') ? $customfields['Domain'] : $params['configoption2']; 175 | } else { 176 | //Could be domain mapping 177 | $request['mapped_domain'] = $params['domain']; 178 | //Or it could be sub 179 | $sub = explode('.',$params['domain']); 180 | if (count($sub > 2)) $request['domain'] = $sub[0]; 181 | else $request['domain'] = $params['configoption2']; 182 | } 183 | 184 | if( $request['domain'] . $request['mapped_domain'] == '') return 'Domain field is Empty!'; 185 | 186 | $request['title'] = ($params['configoption3'] == 'on' and !empty($customfields['Title'])) ? $customfields['Title'] : $params['configoption1']; 187 | 188 | $request['user_name'] = (empty($username)) ? $wp_user_name : $username; 189 | 190 | $request['password'] = $password; 191 | 192 | $request['email'] = $clientsdetails['email']; 193 | $request['last_name'] = $clientsdetails['lastname']; 194 | $request['first_name'] = $clientsdetails['firstname']; 195 | $request['default_role'] = $params['configoption5']; 196 | $request['upload_space'] = $params['configoption6']; 197 | $request['level'] = $params['configoption8']; 198 | $request['credentials'] = $credentials; 199 | 200 | $whmcs = array('whmcs' => $request); 201 | 202 | $post_fields = http_build_query($whmcs); 203 | 204 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 205 | $url .= $params['serverhostname']; 206 | 207 | $response = get_url($url, $post_fields); 208 | 209 | $ret = json_decode($response[0], true); 210 | 211 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION , 'CreateAccount', $post_fields, $response, $ret, array() ); 212 | 213 | //return print_r($response,true); 214 | 215 | if (empty($ret)) { 216 | if (! empty($response[2])) return $response[2]; 217 | return "Invalid data or no response: The receiving plugin may not be activated at: $url"; 218 | } 219 | 220 | if (is_array($ret) && isset($ret['error'])) { 221 | $result = $ret['error'] . ":" . $ret['message']; 222 | } else { 223 | //Good data so update whmcs 224 | 225 | 226 | //Save to database for updates 227 | insert_query('mod_whmcs_multisite', 228 | array( 229 | 'blog_id' => intval($ret['blog_id']), 230 | 'service_id' => intval($params["serviceid"]), 231 | 'domain' => $ret['domain'], 232 | 'path' => $ret['path'], 233 | 'level' => empty( $ret['level'] ) ? 0 : intval( $ret['level'] ), 234 | )); 235 | 236 | //Save to service record 237 | $update = array( 238 | 'serviceid' => $params["serviceid"], 239 | 'serviceusername' => $ret['user_name'], 240 | ); 241 | 242 | if( empty($ret['mapped_domain'])){ //Didn't domain map 243 | $update['domain'] = $ret['domain'].$ret['path']; 244 | }else{ 245 | $update['domain'] = $ret['mapped_domain']; 246 | } 247 | 248 | if( !empty( $ret['password'] ) ) $update['servicepassword'] = $ret['password']; 249 | 250 | $result = localAPI('updateclientproduct', $update, $api_admin); 251 | 252 | $result = ($result['result']=='success') ? $result['result'] : $result['message']; 253 | } 254 | return $result; 255 | } 256 | 257 | function get_blog_data($service_id){ 258 | $result = select_query('mod_whmcs_multisite','', array('service_id' => $service_id)); 259 | $data = mysql_fetch_array($result); 260 | return $data; 261 | } 262 | 263 | function whmcs_multisite_TerminateAccount($params) { 264 | 265 | # Code to perform action goes here... 266 | $customfields = $params["customfields"]; # Array of custom field values for the product 267 | $clientsdetails = $params["clientsdetails"]; # Array of clients details - firstname, lastname, email, country, etc... 268 | 269 | $credentials = array( 270 | 'user_login' => $params['serverusername'], 271 | 'user_password' => $params['serverpassword'], 272 | 'remember' => 0, 273 | 'whmcs_client_id' => $clientsdetails['userid'], 274 | 'whmcs_service_id' => $params['serviceid'], 275 | 'whmcs_product_id' => $params['pid'], 276 | ); 277 | 278 | $data = get_blog_data($params['serviceid']); 279 | 280 | $request = array(); 281 | 282 | $request['action'] = 'terminate'; 283 | $request['blog_id'] = $data['blog_id']; 284 | $request['domain'] = $data['domain']; 285 | $request['credentials'] = $credentials; 286 | 287 | $whmcs = array('whmcs' => $request); 288 | 289 | $post_fields = http_build_query($whmcs); 290 | 291 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 292 | $url .= $params['serverhostname']; 293 | 294 | $response = get_url($url, $post_fields); 295 | 296 | $ret = json_decode($response[0], true); 297 | 298 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION, 'TerminateAccount', $post_fields, $response, $ret, array() ); 299 | 300 | if (empty($ret)) { 301 | if(! empty($response[2])) return $response[2]; 302 | return "Invalid data: The receiving plugin may not be activated at: $url"; 303 | } 304 | 305 | if (is_array($ret) && isset($ret['error'])) { 306 | $result = $ret['error'] . ":" . $ret['message']; 307 | } else { 308 | $result = 'success'; 309 | } 310 | return $result; 311 | } 312 | 313 | function whmcs_multisite_SuspendAccount($params) { 314 | 315 | # Code to perform action goes here... 316 | $customfields = $params["customfields"]; # Array of custom field values for the product 317 | $clientsdetails = $params["clientsdetails"]; # Array of clients details - firstname, lastname, email, country, etc... 318 | 319 | $credentials = array( 320 | 'user_login' => $params['serverusername'], 321 | 'user_password' => $params['serverpassword'], 322 | 'remember' => 0, 323 | 'whmcs_client_id' => $clientsdetails['userid'], 324 | 'whmcs_service_id' => $params['serviceid'], 325 | 'whmcs_product_id' => $params['pid'], 326 | ); 327 | 328 | $data = get_blog_data($params['serviceid']); 329 | 330 | $request = array(); 331 | 332 | $request['action'] = 'suspend'; 333 | $request['blog_id'] = $data['blog_id']; 334 | $request['domain'] = $data['domain']; 335 | $request['credentials'] = $credentials; 336 | 337 | $whmcs = array('whmcs' => $request); 338 | 339 | $post_fields = http_build_query($whmcs); 340 | 341 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 342 | $url .= $params['serverhostname']; 343 | 344 | $response = get_url($url, $post_fields); 345 | 346 | $ret = json_decode($response[0], true); 347 | 348 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION, 'SuspendAccount', $post_fields, $response, $ret, array() ); 349 | 350 | if (empty($ret)) { 351 | if(! empty($response[2])) return $response[2]; 352 | return "Invalid data: The receiving plugin may not be activated at: $url"; 353 | } 354 | 355 | if (is_array($ret) && isset($ret['error'])) { 356 | $result = $ret['error'] . ":" . $ret['message']; 357 | } else { 358 | $result = 'success'; 359 | } 360 | return $result; 361 | } 362 | 363 | function whmcs_multisite_ChangePackage($params) { 364 | 365 | $credentials = array( 366 | 'user_login' => $params['serverusername'], 367 | 'user_password' => $params['serverpassword'], 368 | 'remember' => 0, 369 | 'whmcs_client_id' => $clientsdetails['userid'], 370 | 'whmcs_service_id' => $params['serviceid'], 371 | 'whmcs_product_id' => $params['pid'], 372 | ); 373 | 374 | $service_id = $params['serviceid']; 375 | $data = get_blog_data($params['serviceid']); 376 | $request = array(); 377 | $request['action'] = 'changepackage'; 378 | $request['blog_id'] = $data['blog_id']; 379 | $request['domain'] = $data['domain']; 380 | $request['credentials'] = $credentials; 381 | $request['level'] = $params['configoption8']; 382 | $whmcs = array('whmcs' => $request); 383 | $post_fields = http_build_query($whmcs); 384 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 385 | $url .= $params['serverhostname']; 386 | $response = get_url($url, $post_fields); 387 | $ret = json_decode($response[0], true); 388 | 389 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION, 'ChangePackage', $post_fields, $response, $ret, array() ); 390 | 391 | if (is_array($ret) && isset($ret['error'])) { 392 | $result = $ret['error'] . ":" . $ret['message']; 393 | } else { 394 | $level = empty( $ret['level'] ) ? 0 : intval( $ret['level'] ); 395 | update_query('mod_whmcs_multisite', array('level' => $level ), array( 'service_id' => $service_id ) ); 396 | $result = 'success'; 397 | } 398 | return $result; 399 | } 400 | 401 | function whmcs_multisite_UnsuspendAccount($params) { 402 | 403 | # Code to perform action goes here... 404 | $customfields = $params["customfields"]; # Array of custom field values for the product 405 | $clientsdetails = $params["clientsdetails"]; # Array of clients details - firstname, lastname, email, country, etc... 406 | 407 | $credentials = array( 408 | 'user_login' => $params['serverusername'], 409 | 'user_password' => $params['serverpassword'], 410 | 'remember' => 0, 411 | 'whmcs_client_id' => $clientsdetails['userid'], 412 | 'whmcs_service_id' => $params['serviceid'], 413 | 'whmcs_product_id' => $params['pid'], 414 | ); 415 | 416 | $data = get_blog_data($params['serviceid']); 417 | 418 | $request = array(); 419 | 420 | $request['action'] = 'unsuspend'; 421 | $request['blog_id'] = $data['blog_id']; 422 | $request['domain'] = $data['domain']; 423 | $request['credentials'] = $credentials; 424 | 425 | $whmcs = array('whmcs' => $request); 426 | 427 | $post_fields = http_build_query($whmcs); 428 | 429 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 430 | $url .= $params['serverhostname']; 431 | 432 | $response = get_url($url, $post_fields); 433 | 434 | $ret = json_decode($response[0], true); 435 | 436 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION, 'UnsuspendAccount', $post_fields, $response, $ret, array() ); 437 | 438 | if (empty($ret)) { 439 | if(! empty($response[2])) return $response[2]; 440 | return "Invalid data: The receiving plugin may not be activated at: $url"; 441 | } 442 | 443 | if (is_array($ret) && isset($ret['error'])) { 444 | $result = $ret['error'] . ":" . $ret['message']; 445 | } else { 446 | $result = 'success'; 447 | } 448 | return $result; 449 | } 450 | 451 | function whmcs_multisite_ChangePassword($params) { 452 | 453 | # Code to perform action goes here... 454 | $customfields = $params["customfields"]; # Array of custom field values for the product 455 | $clientsdetails = $params["clientsdetails"]; # Array of clients details - firstname, lastname, email, country, etc... 456 | $configoptions = $params["configoptions"]; # Array of configurable option values for the product 457 | $domain = $params["domain"]; 458 | $username = $params["username"]; 459 | $password = $params["password"]; 460 | 461 | $credentials = array( 462 | 'user_login' => $params['serverusername'], 463 | 'user_password' => $params['serverpassword'], 464 | 'remember' => 0, 465 | 'whmcs_client_id' => $clientsdetails['userid'], 466 | 'whmcs_service_id' => $params['serviceid'], 467 | 'whmcs_product_id' => $params['pid'], 468 | ); 469 | 470 | $data = get_blog_data($params['serviceid']); 471 | 472 | $request = array(); 473 | 474 | $request['action'] = 'password'; 475 | $request['blog_id'] = $data['blog_id']; 476 | $request['domain'] = $data['domain']; 477 | $request['user_name'] = $username; 478 | $request['password'] = $password; 479 | $request['email'] = $clientsdetails['email']; 480 | $request['credentials'] = $credentials; 481 | 482 | $whmcs = array('whmcs' => $request); 483 | 484 | $post_fields = http_build_query($whmcs); 485 | 486 | $url = (empty($params['serversecure'])) ? 'http://' : 'https://'; 487 | $url .= $params['serverhostname']; 488 | 489 | $response = get_url($url, $post_fields); 490 | 491 | $ret = json_decode($response[0], true); 492 | 493 | logModuleCall('WHMCS_Multisite Server '.WPMU_WHMCS_SERVER_VERSION, 'ChangePassword', $post_fields, $response, $ret, array() ); 494 | 495 | if (empty($ret)) { 496 | if(! empty($response[2])) return $response[2]; 497 | return "Invalid data: The receiving plugin may not be activated at: $url"; 498 | } 499 | 500 | if (is_array($ret) && isset($ret['error'])) { 501 | $result = $ret['error'] . ":" . $ret['message']; 502 | } else { 503 | $result = 'success'; 504 | } 505 | return $result; 506 | 507 | } 508 | 509 | function whmcs_multisite_AdminLink($params) { 510 | 511 | $code = '
512 | 513 | 514 | 515 | 516 |
'; 517 | return $code; 518 | 519 | } 520 | 521 | 522 | function whmcs_multisite_LoginLink($params) { 523 | 524 | // Lock the username and custom fields for Wordpress Sites since it shouldn't be changed 525 | ?> 526 | 536 | /wp-login.php?log=" target="wpadmin" style="color:#cc0000">Login to Wordpress 537 | 538 | "reboot", 572 | ); 573 | return $buttonarray; 574 | } 575 | 576 | function whmcs_multisite_AdminCustomButtonArray() { 577 | $buttonarray = array( 578 | "Reboot Server" => "reboot", 579 | "Shutdown Server" => "shutdown", 580 | ); 581 | return $buttonarray; 582 | } 583 | 584 | function whmcs_multisite_extrapage($params) { 585 | $pagearray = array( 586 | 'templatefile' => 'example', 587 | 'breadcrumb' => ' > Example Page', 588 | 'vars' => array( 589 | 'var1' => 'demo1', 590 | 'var2' => 'demo2', 591 | ), 592 | ); 593 | return $pagearray; 594 | } 595 | 596 | function whmcs_multisite_UsageUpdate($params) { 597 | 598 | $serverid = $params['serverid']; 599 | $serverhostname = $params['serverhostname']; 600 | $serverip = $params['serverip']; 601 | $serverusername = $params['serverusername']; 602 | $serverpassword = $params['serverpassword']; 603 | $serveraccesshash = $params['serveraccesshash']; 604 | $serversecure = $params['serversecure']; 605 | 606 | # Run connection to retrieve usage for all domains/accounts on $serverid 607 | 608 | # Now loop through results and update DB 609 | 610 | foreach ($results AS $domain=>$values) { 611 | update_query("tblhosting",array( 612 | "diskused"=>$values['diskusage'], 613 | "dislimit"=>$values['disklimit'], 614 | "bwused"=>$values['bwusage'], 615 | "bwlimit"=>$values['bwlimit'], 616 | "lastupdate"=>"now()", 617 | ),array("server"=>$serverid,"domain"=>$values['domain'])); 618 | } 619 | 620 | } 621 | */ 622 | 623 | function whmcs_multisite_AdminServicesTabFields($params) { 624 | 625 | $result = select_query("mod_whmcs_multisite","*",array("service_id" => $params['serviceid'])); 626 | $data = mysql_fetch_array($result); 627 | $domain = $data['domain']; 628 | $path = $data['path']; 629 | $level = $data['level']; 630 | 631 | $fieldsarray = array( 632 | 'Subdomain/Subdirectory' => $domain, 633 | 'Path' => $path, 634 | 'Pro-Sites Level' => $level, 635 | ); 636 | return $fieldsarray; 637 | 638 | } 639 | 640 | /* 641 | function whmcs_multisite_AdminServicesTabFieldsSave($params) { 642 | update_query("mod_customtable",array( 643 | "var1"=>$_POST['modulefields'][0], 644 | "var2"=>$_POST['modulefields'][1], 645 | "var3"=>$_POST['modulefields'][2], 646 | ),array("serviceid"=>$params['serviceid'])); 647 | } 648 | 649 | */ --------------------------------------------------------------------------------