Multihoster Integration

Integrating your application with Multihoster requires you to include the Software Bridge in your configuration file so that you can retrieve the database information of the tenant. You will also have to define a Software extension (PHP class) which Multihoster will use to communicate with your application and make changes to the database when neccesary (deletion, suspension, domain change, etc.).

Architecture

Each application resides in the software/ directory. The application files (including the static content) should be located in a directory dedicated to your application.

software/wordpress4/
software/myapplication/

Software Extension

Your application is not tied to Multihoster, you will have to define a Software extension in the root directory of your application. The name of the file must match the name of the application's directory (also referred to as base name).

software/myapplication/myapplication.php

The name of the software extension class must be {BaseName}Software.

Class MyApplicationSoftware {}

The class must define the name and version property.

Class MyApplicationSoftware {
	var $name = 'My Application';
	var $version = '1.0.0'
}

Constructor callback

The software extension must define the create constructor callback (not to be confused with class constructor). This method is called whenever the software (your application) must set up new tenant (website) and will receive the following arguments:

1. Input Data (array)
The input data from the setup form.
2. Website (Website)
// Website creation callback for WordPress 4.3
Class WordPress4Software {
	public function create(array $data, Website $website) {
		// Get the database connection
		$connection = $website->database()->connection();

		// Database dump (exported SQL file)
		$sql = file_get_contents(__DIR__."/src/data.sql");

		// Replace the default WP_ prefix with our prefix
		$sql = str_replace('wp_', $website->getPrefix(), $sql);

		// Import database
		$connection->unprepared($sql);

		// Create a dedicated storage directory
		mkdir(__DIR__.'/wp-content/storage/'.md5($website->getPrefix()).'/', 0755);

		// Update settings
		$connection->table($website->getPrefix().'options')->where('option_name', '=', 'siteurl')->update(['option_value' => $url = 'http://'.$website->getDomainName()]);
		$connection->table($website->getPrefix().'options')->where('option_name', '=', 'home')->update(['option_value' => $url]);
		$connection->table($website->getPrefix().'options')->where('option_name', '=', 'blogname')->update(['option_value' => $data['title']]);
		$connection->table($website->getPrefix().'options')->where('option_name', '=', 'blogdescription')->update(['option_value' => $data['wordpress4_description']]); // Custom setup field

		// Set up admin account (the default account is imported with the SQL dump)
		$connection->table($website->getPrefix().'users')->where('ID', '=', 1)->update([
			'user_login' => $data['wordpress4_username'],
			'user_pass' => wp_hash_password($data['wordpress4_password']),
			'user_nicename' => strtolower($data['wordpress4_username']),
			'user_email' => $website->user()->getEmail(),
			'display_name' => $data['wordpress4_username'],
			'user_registered' => Date::now()->toDateTimeString()
		]);
	}
}

Setup form

Your application can add additional input fields to the setup form using the form method which is called whenever the setup form is constructed and will receive the form as an argument.

// Custom setup fields for WordPress 4.3
Class WordPress4Software {
	public function form($form) {
		// Add a validation callback for the username
		$form->add('Admin username', 'wordpress4_username')->validate(function ($value) {
			if (strlen($value) < 6) {
				return 'Username must be at least 6 characters long!';
			}
		})->group('administrator');

		$form->password('Password', 'wordpress4_password')->group('administrator');
		$form->add('Blog description', 'wordpress4_description')->group('details');
	}
}

Domain update callback

Your application can define the setdomain method which is called whenever the domain of the website is updated from the customer panel.

// Domain update callback
// Useful if your application has cookie and URL settings (like WordPress and MyBB)
Class WordPress4Software {
    public function setDomain(Website $website, $domain) {
        // Retreive connection
        $connection = $website->database()->connection();

        // Update "siteurl" and "home"
        $connection->table($website->getPrefix().'options')->where('option_name', '=', 'siteurl')->update(['option_value' => 'http://'.$domain]);
        $connection->table($website->getPrefix().'options')->where('option_name', '=', 'home')->update(['option_value' => $domain]);
    }
}

Multitenancy

The first step to building an SaaS application is multitenancy support. Multitenancy is the the ability of the application to serve multiple instances from a single set of files. Multitenancy means that your application will connect to a different database and/or use a different table prefix based on the domain that the visitor is accessing. Although this might sound complicated it really isn't, Multihoster makes this process as easy as calling a method to retrieve the database information.

It is recommended that you include the Bridge in your configuration file since it is loaded whenever dynamic content is served. The Bridge.php file is located in the software/ directory.

// software/myapplication/config.php
require(__DIR__."/../../Bridge.php");

The next step is creating an instance of the Bridge. You will have to pass the http host ($_SERVER['HTTP_HOST']) and the application's base name as the second argument.

$tenant = new Multihoster\Bridge($_SERVER['HTTP_HOST'], 'myapp');

Your application most likely makes use of hard-coded database logins stored in the configuration file, you must swap out the values with method calls on the Bridge object.

// Hard-coded configuration
$config['database']['host'] = 'localhost';
$config['database']['username'] = 'username';
$config['database']['password'] = 'mysql-password';

// Swaped out...
$config['database']['host'] = $tenant->getDatabaseHost();
$config['database']['username'] = $tenant->getDatabaseUser();
$config['database']['password'] = $tenant->getDatabasePassword();

Multihoster doesn't suspend the website on it's own, you can take action if a suspended/unpaid website is visited. You can redirect the visitor to your website and display an error message or disable certain functionality in case the package expired.

if (!$tenant->exists()) {
	// This tenant doesn't exist
}

if (!$tenant->isActive() || !$tenant->hasActivePackage()) {
	// Handle inactive (suspended, pending activation) websites
	// Handle websites with an expired package
}

Software Options

Software options are settings which must be managed from the Multihoster admin panel and made available to the application whenever it is loaded.

Retreiving Software Options

You can retrieve a software option by calling the getSoftwareOption method on the Bridge object.

$tenant->getSoftwareOption('header_ad_code');

Defining Software Options

You can add a software option by defining the settings method on the Software Extension. The method will receive the settings form as it's only argument, you may add your custom settings fields using this form.

Class MyAppSoftware {
	public function settings($form) {
		$form->add('Header ad code', 'header_ad_code', $this->settings['header_ad_code']);
    }
}

Package Options

Package options define limitations (storage quota) and benefits (access to premium content) for a group of tenants. You can retrieve a package option by calling the getPackageOption method and passing the name of the option.

if ($tenant->getPackageOption('advertising')) {
	echo $tenant->getSoftwareOption('header_ad_code');
}