<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.open-xchange.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Richard.petersen</id>
	<title>Open-Xchange - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.open-xchange.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Richard.petersen"/>
	<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=Special:Contributions/Richard.petersen"/>
	<updated>2026-10-04T15:15:38Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.7</generator>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=21206</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=21206"/>
		<updated>2016-01-06T06:38:42Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo!). If you configure upsell in the &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; the properties are loaded when you trigger the &#039;&#039;&#039;live reload&#039;&#039;&#039; function. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links would require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change appearance===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-star&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
The color of custom upsell links can be changed with &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/color=&amp;quot;#f00&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger and the color string can be any css color string.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located at the bottom of the folderview of the mail app in the premium area. This trigger is styled as a button with the default text &#039;Try now&#039; and has no icon by default. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located at the bottom of the folderview of the contacts app in the premium area. This trigger is styled as a button with the default text &#039;Try now&#039; and has no icon by default. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located at the bottom of the folderview of the calendar app in the premium area. This trigger is styled as a button with the default text &#039;Try now&#039; and has no icon by default. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/infostore/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| boxcom or google or msliveconnect&lt;br /&gt;
| This trigger is located at the bottom of the folderview of the drive app in the premium area. This trigger is styled as a button with the default text &#039;Try now&#039; and has no icon by default. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit, icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. This upsell trigger has no icon by default. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Customers usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just enable &#039;&#039;plugins/upsell/simple-wizard&#039;&#039; by [[AppSuite:Capabilities#Set_a_capability|setting the capability]] &#039;&#039;&#039;simple-wizard&#039;&#039;&#039; server-side (or by adding it to the URL &#039;&#039;&#039;...&amp;amp;cap=simple-wizard&#039;&#039;&#039; for testing/development purposes).&lt;br /&gt;
&lt;br /&gt;
This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=21203</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=21203"/>
		<updated>2016-01-04T09:45:06Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo!). If you configure upsell in the &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; the properties are loaded when you trigger the &#039;&#039;&#039;live reload&#039;&#039;&#039; function. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links would require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change appearance===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-star&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
The color of custom upsell links can be changed with &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/color=&amp;quot;#f00&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger and the color string can be any css color string.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located at the bottom of the folder view in the mail app. This trigger contains the title and has a &#039;try now&#039; button below. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located at the bottom of the folder view in the contacts app. This trigger contains the title and has a &#039;try now&#039; button below. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located at the bottom of the folder view in the calendar app. This trigger contains the title and has a &#039;try now&#039; button below. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/infostore/bottom&lt;br /&gt;
| color&lt;br /&gt;
| title&lt;br /&gt;
| boxcom or google or msliveconnect&lt;br /&gt;
| This trigger is located at the bottom of the folder view in the drive app. This trigger contains the title and has a &#039;try now&#039; button below.&lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit, icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. This upsell trigger has no icon by default. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Customers usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just enable &#039;&#039;plugins/upsell/simple-wizard&#039;&#039; by [[AppSuite:Capabilities#Set_a_capability|setting the capability]] &#039;&#039;&#039;simple-wizard&#039;&#039;&#039; server-side (or by adding it to the URL &#039;&#039;&#039;...&amp;amp;cap=simple-wizard&#039;&#039;&#039; for testing/development purposes).&lt;br /&gt;
&lt;br /&gt;
This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20066</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20066"/>
		<updated>2015-07-29T09:47:51Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* Enable upsell */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo!). If you configure upsell in the &#039;&#039;&#039;upsell-appsuite.properties&#039;&#039;&#039; the properties are loaded when you trigger the &#039;&#039;&#039;live reload&#039;&#039;&#039; function. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links would require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change appearance===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-star&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
The color of custom upsell links can be changed with &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/color=&amp;quot;#f00&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger and the color string can be any css color string.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit, icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. This upsell trigger has no icon by default. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20065</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20065"/>
		<updated>2015-07-29T07:59:54Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* Custom upsell links */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links would require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change appearance===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-star&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
The color of custom upsell links can be changed with &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/color=&amp;quot;#f00&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger and the color string can be any css color string.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit, icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. This upsell trigger has no icon by default. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20048</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20048"/>
		<updated>2015-07-23T09:52:31Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit, icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. This upsell trigger has no icon by default. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20047</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20047"/>
		<updated>2015-07-23T09:49:06Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contacts&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20046</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=20046"/>
		<updated>2015-07-23T09:38:21Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It can contain an icon and text and can be colored.  It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon, color&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19991</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19991"/>
		<updated>2015-07-17T10:10:30Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| -&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|-&lt;br /&gt;
| mail-folderview-quota&lt;br /&gt;
| upsellLimit&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is appended below the mail quota in the folderview. You can set the &#039;&#039;&#039;upsellLimit&#039;&#039;&#039; (in Bytes). If the maximum mail quota is larger than &#039;&#039;&#039;upsellLimit&#039;&#039;&#039;, the upsell button will not be shown. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19947</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19947"/>
		<updated>2015-07-15T11:27:43Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| -&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean), icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting &#039;&#039;&#039;removable&#039;&#039;&#039; to true. If no image is used, the widget displays the title in the center with a customizable space separated list of font-awesome icons. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19946</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19946"/>
		<updated>2015-07-15T11:14:05Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| -&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|-&lt;br /&gt;
| portal-widget&lt;br /&gt;
| imageURL, removable (boolean)&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger adds a draggable portal widget to the appsuite portal. This widget is not removable by default and displays a default text. A customer can add a backgroundimage with &#039;&#039;&#039;imageURL&#039;&#039;&#039; and can make this widget removable by setting removable to true. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19931</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19931"/>
		<updated>2015-07-14T11:55:36Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Customize required capabilities===&lt;br /&gt;
If you want certain upsell triggers to appear on different capabilities, you can configure this inside a .properties file. Therefore, you have to configure the &#039;&#039;&#039;requires&#039;&#039;&#039; field of the appropriate trigger. This field expects a logical expression of capabilities. The following example requires eas and caldav or not carddav. If the actual capabilities does not satisfy the expression and the upsell capabilites satisfy this expression, the upsell trigger will be drawn. &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/requires=&amp;quot;active_sync &amp;amp;&amp;amp; (caldav || !carddav)&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| -&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19930</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19930"/>
		<updated>2015-07-14T09:37:28Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* List of custom triggers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
This sections lists the custom triggers and how they can be configured.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| -&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. This trigger is not shown on mobile devices due to space limitations. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/mail&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| active_sync&lt;br /&gt;
| This trigger is located below the folderview of mails. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/contact&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| carddav&lt;br /&gt;
| This trigger is located below the folderview of the address book. &lt;br /&gt;
|-&lt;br /&gt;
| folderview/calendar&lt;br /&gt;
| -&lt;br /&gt;
| title&lt;br /&gt;
| caldav&lt;br /&gt;
| This trigger is located below the folderview of the calendar. &lt;br /&gt;
|-&lt;br /&gt;
| topbar-dropdown&lt;br /&gt;
| icon&lt;br /&gt;
| title&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located on the first position of the dropdown in the secondary toolbar. It contains a text and an icon. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19918</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19918"/>
		<updated>2015-07-13T15:10:33Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===Customize strings===&lt;br /&gt;
&lt;br /&gt;
Some of the custom upsell triggers use have a title (or other strings) which a hoster could customize. It is important, that several translations are provided. You can provide your own texts via &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/i18n/$lang/title=&amp;quot;A custom title&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $lang is the current language identifier (e.g. &amp;quot;en_US&amp;quot;). You can see the current language identifier when you open the webconsole and type &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
ox.language&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Texts !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| -&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19917</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19917"/>
		<updated>2015-07-13T14:51:05Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=&amp;quot;fa-star&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19916</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19916"/>
		<updated>2015-07-13T14:48:02Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;active_sync&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;active_sync&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/active_sync=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=fa-star&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=fa-star&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| active_sync or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19915</id>
		<title>AppSuite:Upsell</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Upsell&amp;diff=19915"/>
		<updated>2015-07-13T12:51:25Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Upsell&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract.&#039;&#039;&#039; This article is mainly for UI developers and introduces the concept of upsell from a technical point of view. In short: End-user has a set of so-called &#039;&#039;capabilities&#039;&#039;. UI, however, offers functionality beyond that limited set for promotion purposes. Actions, e.g. inline links, that require missing capabilities trigger an &#039;&#039;&#039;in-app upsell&#039;&#039;&#039;. This process leads to a trial period or a new subscription. Technical challenge for the UI developer is to check what the end-user has, what can be shown beyond that, and how to handle upsell. It is also possible for hosting companies to easily integrate their own online shop into OX Upsell, since the internal mechanisms are &#039;&#039;loosely coupled&#039;&#039; via events.&lt;br /&gt;
__TOC__&lt;br /&gt;
==Enable upsell==&lt;br /&gt;
In order to configure upsell server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt; &lt;br /&gt;
io.ox/core//upsell/enabled/infostore=true&lt;br /&gt;
io.ox/core//upsell/enabled/portal=true&lt;br /&gt;
io.ox/core//upsell/enabled/tasks=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Each line enables a specific [[AppSuite:Upsell#Capabilities|capability]] for upsell. That means whenever a feature misses one these capabilities a special upsell-related event is triggered.&lt;br /&gt;
&lt;br /&gt;
Hint: For simple demo purposes, you can enable an internal upsell configuration by appending &#039;&#039;&#039;&amp;quot;&amp;amp;demo=upsell&amp;quot;&#039;&#039;&#039; to the URL. Needs to reload page, of course.&lt;br /&gt;
&lt;br /&gt;
==Custom upsell links==&lt;br /&gt;
Since other upsell triggers than the usual links require custom development of the UI, the appsuite provides several upsell triggers which can be configured via settings. Those triggers will appear, when the expression of required capabilities is not satisfied and the required set of upsell triggers is satisfied. If you configure the upsell settings, the custom upsell triggers will be enabled by default but you can disable them if you want to. &lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
&lt;br /&gt;
To clearify, when triggers are shown or not, we proceed with an example: A hoster can provide a custom upsell trigger in the secondary toolbar (next to the reload icon). This upsell trigger is inteded to sell a premium account to a user and has the default requirement of &#039;&#039;&#039;eas&#039;&#039;&#039; OR &#039;&#039;&#039;caldav&#039;&#039;&#039; OR &#039;&#039;&#039;carddav&#039;&#039;&#039;. That means, if one of those capabilities is not set for a user and the upsell is activated for &#039;&#039;&#039;eas&#039;&#039;&#039; AND &#039;&#039;&#039;caldav&#039;&#039;&#039; AND &#039;&#039;&#039;carddav&#039;&#039;&#039; the upsell trigger will be shown. You can enable upsell for those capabilities with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/enabled/eas=true&lt;br /&gt;
io.ox/core//upsell/enabled/caldav=true&lt;br /&gt;
io.ox/core//upsell/enabled/carddav=true&lt;br /&gt;
&amp;lt;/pre&amp;gt; &lt;br /&gt;
inside an existing or new &#039;&#039;&#039;.properties&#039;&#039;&#039; file. Note that you have to restart the server so that the changes take place.&lt;br /&gt;
&lt;br /&gt;
If a user clicks on the upsell trigger, a upsell event of type &#039;custom&#039; and with id &#039;secondary-toolbar&#039; is triggered so that the page or dialog which will be opened can react depending on the clicked link. &lt;br /&gt;
&lt;br /&gt;
===Change the icon===&lt;br /&gt;
&lt;br /&gt;
Any custom upsell triggers which have icons will use a &#039;&#039;&#039;fa-lock&#039;&#039;&#039; as default icon. You can change the default icon to any font-awesome icon (or a set of space separated icons) via&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//upsell/defaultIcon=fa-star&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also change the icon of individual custom triggers with&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/icon=fa-star&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
where $id is the id of the upsell trigger.&lt;br /&gt;
&lt;br /&gt;
If you want to disable a custom upsell trigger, then you can add &lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
io.ox/core//features/upsell/$id/enabled=false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to a &#039;&#039;&#039;.properties&#039;&#039;&#039; file. &lt;br /&gt;
&lt;br /&gt;
===List of custom triggers===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! ID !! Config !! Default capabilities !! Description&lt;br /&gt;
|-&lt;br /&gt;
| secondary-launcher&lt;br /&gt;
| icon&lt;br /&gt;
| eas or caldav or carddav&lt;br /&gt;
| This trigger is located in the secondary toolbar left of the notifications icon. It only contains icons without text. It is intended as &#039;&#039;&#039;upgrade to premium&#039;&#039;&#039; trigger. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Upsell Wizard==&lt;br /&gt;
&amp;lt;div style=&amp;quot;color: #3A87AD; text-align: right; margin: -1em 0 1em 0; padding: 0.5em; background-color: #D9EDF7;&amp;quot;&amp;gt;Shipped with 7.2.1&amp;lt;/div&amp;gt;&lt;br /&gt;
Hosters usually want to offer context-sensitive content in an IFRAME if the upsell is triggered. Therefore, App Suite comes with an integrated but optional plugin that takes care of this. Just [[AppSuite:UI_manifests_explained#Loading_custom_manifest_during_development|enable]] &#039;&#039;plugins/upsell/simple-wizard&#039;&#039;. This plugin registers for the event &#039;&#039;&amp;quot;upsell:requires-upsell&amp;quot;&#039;&#039;, opens a modal popup, and loads a custom URL in an embedded IFRAME.&lt;br /&gt;
&lt;br /&gt;
[[File:Simple_upsell_wizard.png|800 px|Custom content in an IFRAME]]&lt;br /&gt;
&lt;br /&gt;
===Wizard settings===&lt;br /&gt;
In order to configure this server-side, just create a new file &#039;&#039;&#039;upsell.properties&#039;&#039;&#039; or append to existing &#039;&#039;&#039;appsuite.properties&#039;&#039;&#039; (mind the &#039;&#039;&#039;double-slash&#039;&#039;&#039;; this in not a typo! plus: changing such settings requires a backend restart):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-settings&amp;quot;&amp;gt;&lt;br /&gt;
plugins/upsell/simple-wizard//url=blank.html?user=$user,user_id=$user_id,context_id=$context_id&lt;br /&gt;
plugins/upsell/simple-wizard//overlayOpacity=0.5&lt;br /&gt;
plugins/upsell/simple-wizard//overlayColor=black&lt;br /&gt;
plugins/upsell/simple-wizard//zeroPadding=true&lt;br /&gt;
plugins/upsell/simple-wizard//width=750&lt;br /&gt;
plugins/upsell/simple-wizard//height=390&lt;br /&gt;
plugins/upsell/simple-wizard//closeButton=true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Settings !! Description&lt;br /&gt;
|-&lt;br /&gt;
| url&lt;br /&gt;
| Custom URL that is loaded in IFRAME; can contain special [[AppSuite:Upsell#Custom_URL_variables|variables]].&lt;br /&gt;
|-&lt;br /&gt;
| overlayOpacity &lt;br /&gt;
| CSS opacity value for overlay; default is 0.5&lt;br /&gt;
|-&lt;br /&gt;
| overlayColor&lt;br /&gt;
| CSS background color for overlay; default is black&lt;br /&gt;
|-&lt;br /&gt;
| zeroPadding&lt;br /&gt;
| If true (default) there is no inner padding inside modal dialog, i.e. the IFRAME covers the popup&lt;br /&gt;
|-&lt;br /&gt;
| width&lt;br /&gt;
| Width of outer popup (not IFRAME) in pixel&lt;br /&gt;
|-&lt;br /&gt;
| height&lt;br /&gt;
| Height of IFRAME in pixel&lt;br /&gt;
|-&lt;br /&gt;
| closeButton&lt;br /&gt;
| If true (default) the wizard shows its own close button&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Custom URL variables===&lt;br /&gt;
The plugin offers a set of variables that help providing context-sensitive content. &#039;&#039;$missing&#039;&#039; is probably the most prominent one. Other variables help identifying the user. An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-setting&amp;quot;&amp;gt;&lt;br /&gt;
upsell.php?user_id=$user_id&amp;amp;context_id=$context_id&amp;amp;language=$language&amp;amp;missing=$missing&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Variable !! Description&lt;br /&gt;
|-&lt;br /&gt;
| $context_id&lt;br /&gt;
| context_id of current user&lt;br /&gt;
|-&lt;br /&gt;
| $hostname&lt;br /&gt;
| hostname of current session, e.g. www.one-of-countless-virtual-hosts.com&lt;br /&gt;
|-&lt;br /&gt;
| $id&lt;br /&gt;
| The trigger&#039;s identifier, e.g. &amp;quot;io.ox/files&amp;quot;. Can refer to an app, an inline action, or a portal plugin. See $type&lt;br /&gt;
|-&lt;br /&gt;
| $imap_login&lt;br /&gt;
| The current user&#039;s imap login&lt;br /&gt;
|-&lt;br /&gt;
| $language&lt;br /&gt;
| The current user&#039;s language, e.g. de_DE or en_US&lt;br /&gt;
|-&lt;br /&gt;
| $mail&lt;br /&gt;
| The current user&#039;s primary email address&lt;br /&gt;
|-&lt;br /&gt;
| $missing&lt;br /&gt;
| The set of missing capabilities, comma separated, e.g. &amp;quot;files&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| $session&lt;br /&gt;
| The current user&#039;s session id&lt;br /&gt;
|-&lt;br /&gt;
| $type&lt;br /&gt;
| Either app, inline-action, or portal-widget. Describes what triggered the upsell. See $id&lt;br /&gt;
|-&lt;br /&gt;
| $user&lt;br /&gt;
| The current user&#039;s login name (can include context name, i.e somebody@foo)&lt;br /&gt;
|-&lt;br /&gt;
| $user_id&lt;br /&gt;
| The current user&#039;s numeric id&lt;br /&gt;
|-&lt;br /&gt;
| $user_login&lt;br /&gt;
| The current user&#039;s login (usually without context name)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Develop and debug===&lt;br /&gt;
While experimenting or developing, you can use the following helpful functions:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// if you have no chance to enabled this plugin server-side, use the following approach &lt;br /&gt;
   // but don&#039;t use this in production plugins, it&#039;s just a hack for console: &lt;br /&gt;
   // if you don&#039;t know the difference please take a look at &lt;br /&gt;
   // http://requirejs.org/docs/errors.html#notloaded &lt;br /&gt;
var wizard; require([&#039;plugins/upsell/simple-wizard/register&#039;], function (w) { wizard = w; });&lt;br /&gt;
&lt;br /&gt;
// get variables (optional: options from upsell:require-upgrade event)&lt;br /&gt;
wizard.getVariables({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get URL (optional: options from upsell:require-upgrade event)&lt;br /&gt;
   // replaces placeholders ($foo) by variable values&lt;br /&gt;
wizard.getURL({ type: &#039;app&#039;, id: &#039;io.ox/files&#039;, missing: &#039;files&#039; });&lt;br /&gt;
&lt;br /&gt;
// get all settings (can be changed on the fly)&lt;br /&gt;
console.log(wizard.settings);&lt;br /&gt;
&lt;br /&gt;
// global upsell events; parameters: (e, popup)&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show&#039;, _.inspect);&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:close&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// special event to customize settings (e, variables, settings)&lt;br /&gt;
   // triggered before creating dialog instance;&lt;br /&gt;
   // 2nd parameter has variables like type, missing, user_id etc.&lt;br /&gt;
   // 3rd parameter refers to a local copy of wizard settings&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:init&#039;, _.inspect);&lt;br /&gt;
&lt;br /&gt;
// open wizard manually&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// close wizard manually&lt;br /&gt;
wizard.close();&lt;br /&gt;
&lt;br /&gt;
// disable wizard (unregisters upsell event)&lt;br /&gt;
wizard.disable();&lt;br /&gt;
&lt;br /&gt;
// and of course: enable wizard (registers for upsell event)&lt;br /&gt;
wizard.enable();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Some examples for customizations in UI plugins or in console:&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// get plugin (this muse be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;); &lt;br /&gt;
&lt;br /&gt;
// extend IFRAME constructor (see http://underscorejs.org/#compose)&lt;br /&gt;
var custom = function (iframe) {&lt;br /&gt;
  return iframe.css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
};&lt;br /&gt;
wizard.getIFrame = _.compose(custom, wizard.getIFrame);&lt;br /&gt;
&lt;br /&gt;
// use an event to customize the IFRAME&lt;br /&gt;
ox.on(&#039;upsell:simple-wizard:show:before&#039;, function (e, popup) {&lt;br /&gt;
  popup.getContentNode().find(&#039;iframe&#039;)&lt;br /&gt;
    .css({ border: &#039;5px solid #08c&#039;, boxSizing: &#039;border-box&#039; });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Close wizard===&lt;br /&gt;
The upsell wizard can easily be closed via javascript or by redirecting the IFRAME to a prepared HTML page. In order to see this in action, run the following code (step by step):&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get plugin (this must be properly loaded, otherwise you get a runtime error)&lt;br /&gt;
var wizard = require(&#039;plugins/upsell/simple-wizard/register&#039;);&lt;br /&gt;
&lt;br /&gt;
// open wizard&lt;br /&gt;
wizard.open();&lt;br /&gt;
&lt;br /&gt;
// redirect now&lt;br /&gt;
wizard.setSrc(&#039;apps/plugins/upsell/simple-wizard/close.html&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Custom backend systems that run on a different domain cannot use javascript to close the wizard [http://en.wikipedia.org/wiki/Cross-site_scripting]. However, such systems can redirect to &#039;&#039;close.html&#039;&#039;. Since this page is part of the UI and therefore located on the same domain, it is allowed to call the wizard&#039;s close function.&lt;br /&gt;
&lt;br /&gt;
==Custom development==&lt;br /&gt;
This section documents some of the inner workings of the upsell layer. It should provide some useful insights and hopefully helps at implementing custom upsell solutions.&lt;br /&gt;
===Events===&lt;br /&gt;
Whenever the user starts an app or clicks on an inline-action, a capability-check is performed. For example, all inline actions have native support for such checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
new Action(&#039;io.ox/calendar/detail/actions/sendmail&#039;, {&lt;br /&gt;
    // this action requires the capability &amp;quot;webmail&amp;quot;&lt;br /&gt;
    capabilities: &#039;webmail&#039;,&lt;br /&gt;
    action: function (baton) {&lt;br /&gt;
        // send mail&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the end-user does not have &amp;quot;webmail&amp;quot; (e.g. in a files-only setup) but calls this action, a proper event is fired:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// if any action misses a capability&lt;br /&gt;
ox.trigger(&#039;upsell:requires-upgrade&#039;);&lt;br /&gt;
// which provides the following data for apps:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;app&amp;quot;, // type of the upsell trigger&lt;br /&gt;
  id: &amp;quot;io.ox/mail/main&amp;quot;, // upsell trigger ID&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// and for inline-actions:&lt;br /&gt;
{&lt;br /&gt;
  type: &amp;quot;inline-action&amp;quot;,&lt;br /&gt;
  id: &amp;quot;io.ox/calendar/detail/actions/sendmail&amp;quot;,&lt;br /&gt;
  missing: &amp;quot;webmail&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Capabilities and Upsell triggers===&lt;br /&gt;
There are lots of different capabilities. They are defined on the server-side and basically they are just strings. Let&#039;s keep it simple and understand them as either services (e.g. mobility), specific functionalities (e.g. multiple_mail_accounts) or applications (e.g. calendar). Some obvious examples:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
! Capability !! Description !! Upsell trigger (if capability is missing)&lt;br /&gt;
|-&lt;br /&gt;
| calendar&lt;br /&gt;
| User has &amp;quot;Calendar&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/All recipients: Invite to appointment&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| contacts &lt;br /&gt;
| User has &amp;quot;Address Book&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail/App recipients: Save as distribution list&lt;br /&gt;
* Calendar: Save participants as distribution list&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| infostore&lt;br /&gt;
| User has &amp;quot;Files&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Save in infostore&lt;br /&gt;
* Add portal widget (My latest files, Recently changed files)&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| portal&lt;br /&gt;
| User has &amp;quot;Portal&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Add to portal&lt;br /&gt;
* Contacts: Add to portal&lt;br /&gt;
* Files: Add to portal&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| tasks&lt;br /&gt;
| User has &amp;quot;Tasks&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Mail: Remind me&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|-&lt;br /&gt;
| webmail&lt;br /&gt;
| User has &amp;quot;Mail&amp;quot; app&lt;br /&gt;
|&lt;br /&gt;
* Calendar: Send mail to all participants&lt;br /&gt;
* Contacts: Send mail&lt;br /&gt;
* Contacts: Send vCard&lt;br /&gt;
* Files: Send as link&lt;br /&gt;
* Files: Send by mail&lt;br /&gt;
* Add portal widget&lt;br /&gt;
* Top bar&lt;br /&gt;
* Launch pad&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// list all available capabilities&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example: Free-mail users might just have &#039;&#039;&#039;webmail&#039;&#039;&#039; and &#039;&#039;&#039;contacts&#039;&#039;&#039;. If &#039;&#039;&#039;infostore&#039;&#039;&#039; is enabled for upsell, end-users will see the link to store mail attachments. But since this capability is missing, the event &amp;quot;upsell:requires-upgrade&amp;quot; is triggered which starts the upsell process. Upon successful completion this process should unlock the capability &#039;&#039;&#039;infostore&#039;&#039;&#039; for the end-user.&lt;br /&gt;
&lt;br /&gt;
The advantage of using rather atomic capabilities as the foundation for upsell is that developers don&#039;t have to consider and implement sales programs or marketing matrices in UI code.&lt;br /&gt;
&lt;br /&gt;
===Example dialog===&lt;br /&gt;
Whenever the event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; is triggered there should be some response for the end-user. Usually an upsell dialog should open. This can be implemented as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
function showUpgradeDialog(e, options) {&lt;br /&gt;
    require([&#039;io.ox/core/tk/dialogs&#039;], function (dialogs) {&lt;br /&gt;
        new dialogs.ModalDialog({ easyOut: true })&lt;br /&gt;
            .build(function () {&lt;br /&gt;
                this.getHeader().append(&lt;br /&gt;
                    $(&#039;&amp;lt;h4&amp;gt;&#039;).text(&#039;Upgrade required&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.getContentNode().append(&lt;br /&gt;
                    $.txt(&#039;This feature is not available.&#039;),&lt;br /&gt;
                    $.txt(&#039;You need to upgrade your account now.&#039;),&lt;br /&gt;
                    $.txt(&#039; &#039;),&lt;br /&gt;
                    $.txt(&#039;The first 90 days are free.&#039;)&lt;br /&gt;
                );&lt;br /&gt;
                this.addPrimaryButton(&#039;upgrade&#039;, &#039;Get free upgrade&#039;);&lt;br /&gt;
                this.addButton(&#039;cancel&#039;, &#039;Cancel&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .setUnderlayStyle({&lt;br /&gt;
                opacity: 0.70,&lt;br /&gt;
                backgroundColor: &#039;#08C&#039;&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;upgrade&#039;, function () {&lt;br /&gt;
                ox.trigger(&#039;upsell:upgrade&#039;, options);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;show&#039;, function () {&lt;br /&gt;
                ox.off(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .on(&#039;close&#039;, function () {&lt;br /&gt;
                ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
            })&lt;br /&gt;
            .show();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function upgrade(e, options) {&lt;br /&gt;
    console.debug(&#039;upgrade&#039;, options);&lt;br /&gt;
    alert(&#039;User decided to upgrade! (global event: upsell:upgrade)&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ox.on(&#039;upsell:requires-upgrade&#039;, showUpgradeDialog);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * convention: &#039;upsell:upgrade&#039; is used to trigger final upsell&lt;br /&gt;
 * the current user and user_id can be found in global variables ox.user and ox.user_id&lt;br /&gt;
 */&lt;br /&gt;
ox.on(&#039;upsell:upgrade&#039;, upgrade);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The second event &#039;&#039;&#039;&amp;quot;upsell:upgrade&amp;quot;&#039;&#039;&#039; can be understood as the final imperative to request the upsell server-side.&lt;br /&gt;
&lt;br /&gt;
===Example portal widget===&lt;br /&gt;
Besides waiting for the user to click on such links, it&#039;s always a good idea to offer explicit controls to trigger an upsell. One option is creating a portal widget that advertises a premium subscription:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/**&lt;br /&gt;
 * This work is provided under the terms of the CREATIVE COMMONS PUBLIC&lt;br /&gt;
 * LICENSE. This work is protected by copyright and/or other applicable&lt;br /&gt;
 * law. Any use of the work other than as authorized under this license&lt;br /&gt;
 * or copyright law is prohibited.&lt;br /&gt;
 *&lt;br /&gt;
 * http://creativecommons.org/licenses/by-nc-sa/2.5/&lt;br /&gt;
 * © 2013 Open-Xchange Inc., Tarrytown, NY, USA. info@open-xchange.com&lt;br /&gt;
 *&lt;br /&gt;
 * @author Matthias Biggeleben &amp;lt;matthias.biggeleben@open-xchange.com&amp;gt;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
define(&#039;plugins/portal/upsell/register&#039;,&lt;br /&gt;
    [&#039;io.ox/core/extensions&#039;,&lt;br /&gt;
     &#039;io.ox/files/api&#039;,&lt;br /&gt;
     &#039;gettext!plugins/portal&#039;], function (ext, api, gt) {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var title = gt(&#039;Upgrade to premium&#039;);&lt;br /&gt;
&lt;br /&gt;
    ext.point(&#039;io.ox/portal/widget/upsell&#039;).extend({&lt;br /&gt;
&lt;br /&gt;
        title: title,&lt;br /&gt;
&lt;br /&gt;
        preview: function (baton) {&lt;br /&gt;
&lt;br /&gt;
            this.addClass(&#039;hide-title&#039;).append(&lt;br /&gt;
                $(&#039;&amp;amp;lt;div class=&amp;quot;content centered&amp;quot; style=&amp;quot;cursor: pointer; padding-top: 3em;&amp;quot;&amp;gt;&#039;).append(&lt;br /&gt;
                    $(&#039;&amp;amp;lt;h2&amp;gt;&#039;).append(&lt;br /&gt;
                        $.txt(title + &#039; &#039;),&lt;br /&gt;
                        $(&#039;&amp;amp;lt;i class=&amp;quot;icon-star&amp;quot;&amp;gt;&#039;)&lt;br /&gt;
                    ),&lt;br /&gt;
                    $(&#039;&amp;amp;lt;div&amp;gt;&#039;).text(gt(&#039;Click here for free trial.&#039;))&lt;br /&gt;
                )&lt;br /&gt;
                .on(&#039;click&#039;, function () {&lt;br /&gt;
                    ox.trigger(&#039;upsell:upgrade&#039;, {&lt;br /&gt;
                        type: &#039;widget&#039;,&lt;br /&gt;
                        id: &#039;io.ox/portal/widget/upsell&#039;,&lt;br /&gt;
                        missing: &#039;&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Accessing upsell settings===&lt;br /&gt;
The upsell configuration is located in the namespace &#039;&#039;&amp;quot;io.ox/core&amp;quot;&#039;&#039;, the path is &#039;&#039;&amp;quot;upsell/enabled&amp;quot;&#039;&#039;. Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
// get all capabilities that can trigger upsell&lt;br /&gt;
require(&#039;settings!io.ox/core&#039;).get(&#039;upsell/enabled&#039;);&lt;br /&gt;
&lt;br /&gt;
// contains data like this&lt;br /&gt;
{&lt;br /&gt;
  infostore: true,&lt;br /&gt;
  portal: true,&lt;br /&gt;
  tasks: true&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If upsell is &#039;&#039;&#039;not&#039;&#039;&#039; enabled and the end-user lacks specific capabilities, the app or the inline-action is not shown. If upsell is enabled by the upper configuration, inline-actions are shown and trigger the upsell event &#039;&#039;&amp;quot;upsell:requires-upgrade&amp;quot;&#039;&#039; if clicked (but do not execute the action itself).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
/* &lt;br /&gt;
 * if you want to create your own controls, you can use the following helpers &lt;br /&gt;
 */&lt;br /&gt;
var upsell = require(&#039;io.ox/core/upsell&#039;);&lt;br /&gt;
&lt;br /&gt;
// check capabilities (space-separated) &lt;br /&gt;
upsell.has(&#039;portal webmail&#039;);&lt;br /&gt;
&lt;br /&gt;
// get missing capabilities (would return &amp;quot;calendar&amp;quot; in demo mode) &lt;br /&gt;
upsell.missing([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* checks if upsell is enabled for a set of capabilities &lt;br /&gt;
 * true if at least one set matches &lt;br /&gt;
 */&lt;br /&gt;
upsell.enabled([&#039;portal webmail&#039;, &#039;webmail calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
/* convenience function: &amp;quot;visible&amp;quot; &lt;br /&gt;
 * checks if something should be visible depending on required capabilities &lt;br /&gt;
 * true if any item matches requires capabilities &lt;br /&gt;
 * true if any item does not match its requirements but is enabled for upsell &lt;br /&gt;
 * this function is used for any inline link, for example, to decide whether or not showing it &lt;br /&gt;
 */&lt;br /&gt;
upsell.visible([&#039;portal webmail&#039;, &#039;contacts&#039;, &#039;calendar&#039;]);&lt;br /&gt;
&lt;br /&gt;
// likewise if neither capability set nor enabled for upsell, we get a false &lt;br /&gt;
upsell.visible([&#039;foo&#039;]);&lt;br /&gt;
&lt;br /&gt;
// in case something weird happens (usually bad configuration) debug() helps&lt;br /&gt;
upsell.debug();&lt;br /&gt;
&lt;br /&gt;
// and this one&lt;br /&gt;
_(ox.serverConfig.capabilities).pluck(&#039;id&#039;).sort();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Upsell in OX6==&lt;br /&gt;
Please also consider [[Upsell|this article]] as it also covers backend aspects.&lt;br /&gt;
&lt;br /&gt;
[[Category:Upsell]]&lt;br /&gt;
[[Category:AppSuite]]&lt;br /&gt;
[[Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19684</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19684"/>
		<updated>2015-06-10T12:05:14Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (tested version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no support in 8.0.6&amp;lt;/span&amp;gt;&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with webkit-fake-url. There is no pure javascript solution to retrieve the image data for upload. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were tested as copy sources for images. Mainly, OS related applications does are not supported as copy source whereas image viewing/editing related applications are supported. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Photo Viewer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Paint&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| GimP&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19651</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19651"/>
		<updated>2015-06-03T13:45:07Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* Support */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (tested version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were tested as copy sources for images. Mainly, OS related applications does are not supported as copy source whereas image viewing/editing related applications are supported. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Photo Viewer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Paint&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| GimP&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19650</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19650"/>
		<updated>2015-06-03T13:44:36Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were tested as copy sources for images. Mainly, OS related applications does are not supported as copy source whereas image viewing/editing related applications are supported. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Photo Viewer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Paint&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| GimP&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19649</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19649"/>
		<updated>2015-06-03T13:42:22Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were used to copy an image and paste it into a browser afterwards. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Photo Viewer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Paint&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| GimP&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19637</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19637"/>
		<updated>2015-05-28T15:26:16Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts an img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were used to copy an image and paste it into a browser afterwards. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19636</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19636"/>
		<updated>2015-05-28T15:25:58Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts a img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
| Does not provide images in clipboardData. After pasting, the browser inserts a img-tag with base64 encoded src. &lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were used to copy an image and paste it into a browser afterwards. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19634</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19634"/>
		<updated>2015-05-28T13:04:02Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| IE 10 does not support image pasting.&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were used to copy an image and paste it into a browser afterwards. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19633</id>
		<title>AppSuite:Paste inline images</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Paste_inline_images&amp;diff=19633"/>
		<updated>2015-05-28T12:02:53Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: Created page with &amp;quot;{{Stability-experimental}}  &amp;#039;&amp;#039;&amp;#039;Abstract: &amp;#039;&amp;#039;&amp;#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract: &#039;&#039;&#039; Pasting images is basically supported by any major browser. But there are several difficulties to provide a cross-browser solution. This article covers, how to paste images into the different browsers. &lt;br /&gt;
&lt;br /&gt;
== Paste images ==&lt;br /&gt;
&lt;br /&gt;
If speaking about pasting images, it is important to consider the source where the image was copied. Basically, copying images from an application is possible (for details see Section Applications). It seems like these applications are simply storing the image data in the clipboard which can be accessed by the paste events of the browser. &lt;br /&gt;
&lt;br /&gt;
Pasting images from a folder of the operating system does not work on any tested environment. Whereas OS X does provide the filename and a dummy picture, the clipboard of all browsers on Windows Systems remains empty. &lt;br /&gt;
&lt;br /&gt;
== Support ==&lt;br /&gt;
&lt;br /&gt;
The following table lists all major browsers and their ability to insert pasted images from applications.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! browser&lt;br /&gt;
! paste support (version)&lt;br /&gt;
! specifics&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| 43.0.2357.81&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Safari&lt;br /&gt;
| 8.0.6&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Firefox&lt;br /&gt;
| 38.0.1&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| 11.0.9600.16428&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Applications ==&lt;br /&gt;
&lt;br /&gt;
This table lists some applications which were used to copy an image and paste it into a browser afterwards. &lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
! application&lt;br /&gt;
! paste support&lt;br /&gt;
! operating system&lt;br /&gt;
|-&lt;br /&gt;
| Finder&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color:red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Preview&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Fotos&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X&lt;br /&gt;
|-&lt;br /&gt;
| Chrome&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| OS X and Windows&lt;br /&gt;
|-&lt;br /&gt;
| Internet Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: green;&amp;quot;&amp;gt;yes&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
| Windows Explorer&lt;br /&gt;
| &amp;lt;span style=&amp;quot;color: red;&amp;quot;&amp;gt;no&amp;lt;/span&amp;gt;&lt;br /&gt;
| Windows&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=Portal:AppSuite_UI/Topics&amp;diff=19632</id>
		<title>Portal:AppSuite UI/Topics</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=Portal:AppSuite_UI/Topics&amp;diff=19632"/>
		<updated>2015-05-28T09:57:21Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Getting started ==&lt;br /&gt;
* [[AppSuite:GettingStarted | Getting started developing an external app]]&lt;br /&gt;
* [[AppSuite:Upgrade_app_using_yo | Upgrade your external app to latest App Suite version]]&lt;br /&gt;
* [[AppSuite:UI developer primer| Skills needed to develop the UI]]&lt;br /&gt;
* [[AppSuite:Getting started developing the UI | Getting started developing the UI]]&lt;br /&gt;
* [[AppSuite:UI_Development_Style_Guide | UI Development Style Guide]]&lt;br /&gt;
* [[AppSuite:Appserver | Appserver]]&lt;br /&gt;
* [[AppSuite:Apache Configuration | Apache Configuration]]&lt;br /&gt;
* [[AppSuite:Definition of done|Definition of done]]&lt;br /&gt;
* [[Appsuite:UI_FAQ | Frequently asked questions]]&lt;br /&gt;
&lt;br /&gt;
== Upgrading apps, plugins, and themes to 7.6 ==&lt;br /&gt;
* [[AppSuite:Upgrading_to_7_6|Upgrading to 7.6]]&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
* [[AppSuite:Test basics | Lessons learned while testing]]&lt;br /&gt;
* [[AppSuite:RunTests | Running ui tests]]&lt;br /&gt;
* [[AppSuite:Testing_3rd-party_code | In-depth article on testing]] aimed at external developers&lt;br /&gt;
&lt;br /&gt;
== How-to articles ==&lt;br /&gt;
* [[AppSuite:Action links|Understanding action links]]&lt;br /&gt;
** [[AppSuite:Files App Actions|Adding actions to the files app]]&lt;br /&gt;
* [[AppSuite:Date_and_time|Date and time]]&lt;br /&gt;
* [[AppSuite:i18n | Internationalization (i18n)]]&lt;br /&gt;
* [[AppSuite:a11y | Accessibility (a11y)]]&lt;br /&gt;
* [[AppSuite:Mediaplayer | Mediaplayer]]&lt;br /&gt;
* [[AppSuite:Theming | Theming]]&lt;br /&gt;
* [[AppSuite:UI manifests explained | UI manifests explained]]&lt;br /&gt;
* [[AppSuite:Upsell | Upsell]]&lt;br /&gt;
* [[AppSuite:VGrid | VGrid]]&lt;br /&gt;
* [[AppSuite:Mobile | Mobile development]]&lt;br /&gt;
* [[AppSuite:Upsell tools| Upsell tools]]&lt;br /&gt;
* [[AppSuite:Guided tours| Guided tours]]&lt;br /&gt;
* [[AppSuite:Browserdetection | Browser detection with form-login]]&lt;br /&gt;
* [[AppSuite:BackboneMiniViews | Backbone mini views]]&lt;br /&gt;
&lt;br /&gt;
=== Server communication ===&lt;br /&gt;
* [[AppSuite:APIs | APIs]]&lt;br /&gt;
* [[AppSuite:API_Factory | API Factory]]&lt;br /&gt;
* [[AppSuite:http.js | http.js]]&lt;br /&gt;
&lt;br /&gt;
=== Extension points ===&lt;br /&gt;
* [[AppSuite:Extending_the_UI_(Hands-on_introduction)| Hands-on introduction]]&lt;br /&gt;
* [[AppSuite:Extending_the_UI | General information on extension points]]&lt;br /&gt;
* [[AppSuite:Modifying forms by using extension points | Modifying forms]]&lt;br /&gt;
* [[AppSuite:Create custom folderview entries in settings app | Edit folderview entries in settings]]&lt;br /&gt;
* [[AppSuite:Extension points for calendar |extension points: Calendar]]&lt;br /&gt;
* [[AppSuite:Extension points for contact |extension points: Contact]]&lt;br /&gt;
* [[AppSuite:Extension points for core | extension points: Core]]&lt;br /&gt;
* [[AppSuite:Extension points for email | extension points: E-Mail]]&lt;br /&gt;
* [[AppSuite:Extension points for files |extension points: Files]]&lt;br /&gt;
* [[AppSuite:Extension points for tasks |extension points: Tasks]]&lt;br /&gt;
* [[AppSuite:Extension points for miscellaneous |extension points: Miscellaneous]]&lt;br /&gt;
&lt;br /&gt;
=== Writing components ===&lt;br /&gt;
* [[AppSuite:Writing a portal plugin | Writing a portal plugin]]&lt;br /&gt;
** [[AppSuite:Configuring portal plugins|Configuring a portal plugin]]&lt;br /&gt;
** [[AppSuite:Using the Upsell widget|Using the Upsell widget]]&lt;br /&gt;
* [[AppSuite:Writing a simple application | Writing a simple application]]&lt;br /&gt;
* [[AppSuite:Writing a simple application with embedded iframe  | Writing a simple application with embedded iframe and launcher link ]]&lt;br /&gt;
* [[AppSuite:Writing a notification area plugin | Writing a plugin for the notification area]]&lt;br /&gt;
* [[AppSuite:Writing a wizard | Writing a wizard]]&lt;br /&gt;
* [[AppSuite:Capabilities | Capabilities explained]] - if you want to enable or disable your plugin&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous articles ==&lt;br /&gt;
* [[AppSuite:External libraries for the UI | External libraries used by the UI]]&lt;br /&gt;
* [[AppSuite:Configuration| Configuration]]&lt;br /&gt;
* [[AppSuite:Custom configurations| Custom configurations]]&lt;br /&gt;
* [[AppSuite:UI build system| The UI build system]]&lt;br /&gt;
* [[AppSuite:Embedding your settings into AppSuite settings|How to embed your own settings into the Appsuite settings page]]&lt;br /&gt;
* [[AppSuite:Debugging the UI|Debugging the UI]]&lt;br /&gt;
* [[AppSuite:Memory leaks (UI)|Finding memory leaks in the UI]]&lt;br /&gt;
* [[AppSuite:Device reference|_.device() reference]]&lt;br /&gt;
* [[AppSuite:Paste inline images|Paste inline images]]&lt;br /&gt;
* All articles regarding the UI are filed in the  category [[:Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Create_custom_folderview_entries_in_settings_app&amp;diff=19612</id>
		<title>AppSuite:Create custom folderview entries in settings app</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Create_custom_folderview_entries_in_settings_app&amp;diff=19612"/>
		<updated>2015-05-20T12:53:37Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: Created page with &amp;quot;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Extension points:&amp;lt;br&amp;gt;Create custom folderview entries in settings&amp;lt;/div&amp;gt;  &amp;#039;&amp;#039;&amp;#039;Abstract&amp;#039;&amp;#039;&amp;#039;  &amp;#039;&amp;#039;This articles covers which extension points are provided by the s...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Extension points:&amp;lt;br&amp;gt;Create custom folderview entries in settings&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Abstract&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;This articles covers which extension points are provided by the settings app and how to extend them to add custom folderview entries. &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
==Add a new settings link==&lt;br /&gt;
&lt;br /&gt;
By default, the folderview in settings contains four sections. If you want to add a link to a section, you have to find out the ID of the section. The extension point you are looking for has the name &amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&#039;io.ox/settings/pane/&#039; + sectionID&amp;lt;/pre&amp;gt;For example, if you want to have a setting in the section external (this is where you should usually put your settings) you can use the following code: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
ext.point(&#039;io.ox/settings/pane/external&#039;).extend({&lt;br /&gt;
    title: gt(&#039;Title&#039;),&lt;br /&gt;
    index: 350,&lt;br /&gt;
    id: &#039;myUniqueID&#039;,&lt;br /&gt;
    ref: &#039;reference/to/settings/page&#039;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Create a subsetting link==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say you have a setting and you want to add a subsetting beyond that. To do this, you have to know the name and ID of the parent extension point. Then you simply have to extend the extension point with the name: &amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;parentName + &#039;/&#039; + parentID&amp;lt;/pre&amp;gt; The following code example creates a subsetting for the setting created above:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
ext.point(&#039;io.ox/settings/pane/external/myUniqueID&#039;).extend({&lt;br /&gt;
    title: gt(&#039;Title of Subsetting&#039;),&lt;br /&gt;
    index: 100,&lt;br /&gt;
    id: &#039;myOtherUniqueID&#039;,&lt;br /&gt;
    ref: &#039;reference/to/other/settings/page&#039;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Create a new settingsgroup==&lt;br /&gt;
&lt;br /&gt;
If you have several settings that should be provided in a seperate section, you can extend the following extension point: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt; &lt;br /&gt;
ext.point(&#039;io.ox/settings/pane&#039;).extend({&lt;br /&gt;
    id: &#039;mySectionID&#039;,&lt;br /&gt;
    index: 500,&lt;br /&gt;
    subgroup: &#039;io.ox/settings/pane/mySectionID&#039;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You have to provide a unique sectionID and a unique subgroupID to create a section. If you want to add links to this section, you just have to extend the extension point &amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&#039;io.ox/settings/pane/mySectionId&#039;&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=Portal:AppSuite_UI/Topics&amp;diff=19611</id>
		<title>Portal:AppSuite UI/Topics</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=Portal:AppSuite_UI/Topics&amp;diff=19611"/>
		<updated>2015-05-20T12:15:04Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Getting started ==&lt;br /&gt;
* [[AppSuite:GettingStarted | Getting started developing an external app]]&lt;br /&gt;
* [[AppSuite:Upgrade_app_using_yo | Upgrade your external app to latest App Suite version]]&lt;br /&gt;
* [[AppSuite:UI developer primer| Skills needed to develop the UI]]&lt;br /&gt;
* [[AppSuite:Getting started developing the UI | Getting started developing the UI]]&lt;br /&gt;
* [[AppSuite:UI_Development_Style_Guide | UI Development Style Guide]]&lt;br /&gt;
* [[AppSuite:Appserver | Appserver]]&lt;br /&gt;
* [[AppSuite:Apache Configuration | Apache Configuration]]&lt;br /&gt;
* [[AppSuite:Definition of done|Definition of done]]&lt;br /&gt;
* [[Appsuite:UI_FAQ | Frequently asked questions]]&lt;br /&gt;
&lt;br /&gt;
== Upgrading apps, plugins, and themes to 7.6 ==&lt;br /&gt;
* [[AppSuite:Upgrading_to_7_6|Upgrading to 7.6]]&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
* [[AppSuite:Test basics | Lessons learned while testing]]&lt;br /&gt;
* [[AppSuite:RunTests | Running ui tests]]&lt;br /&gt;
* [[AppSuite:Testing_3rd-party_code | In-depth article on testing]] aimed at external developers&lt;br /&gt;
&lt;br /&gt;
== How-to articles ==&lt;br /&gt;
* [[AppSuite:Action links|Understanding action links]]&lt;br /&gt;
** [[AppSuite:Files App Actions|Adding actions to the files app]]&lt;br /&gt;
* [[AppSuite:Date_and_time|Date and time]]&lt;br /&gt;
* [[AppSuite:i18n | Internationalization (i18n)]]&lt;br /&gt;
* [[AppSuite:a11y | Accessibility (a11y)]]&lt;br /&gt;
* [[AppSuite:Mediaplayer | Mediaplayer]]&lt;br /&gt;
* [[AppSuite:Theming | Theming]]&lt;br /&gt;
* [[AppSuite:UI manifests explained | UI manifests explained]]&lt;br /&gt;
* [[AppSuite:Upsell | Upsell]]&lt;br /&gt;
* [[AppSuite:VGrid | VGrid]]&lt;br /&gt;
* [[AppSuite:Mobile | Mobile development]]&lt;br /&gt;
* [[AppSuite:Upsell tools| Upsell tools]]&lt;br /&gt;
* [[AppSuite:Guided tours| Guided tours]]&lt;br /&gt;
* [[AppSuite:Browserdetection | Browser detection with form-login]]&lt;br /&gt;
* [[AppSuite:BackboneMiniViews | Backbone mini views]]&lt;br /&gt;
&lt;br /&gt;
=== Server communication ===&lt;br /&gt;
* [[AppSuite:APIs | APIs]]&lt;br /&gt;
* [[AppSuite:API_Factory | API Factory]]&lt;br /&gt;
* [[AppSuite:http.js | http.js]]&lt;br /&gt;
&lt;br /&gt;
=== Extension points ===&lt;br /&gt;
* [[AppSuite:Extending_the_UI_(Hands-on_introduction)| Hands-on introduction]]&lt;br /&gt;
* [[AppSuite:Extending_the_UI | General information on extension points]]&lt;br /&gt;
* [[AppSuite:Modifying forms by using extension points | Modifying forms]]&lt;br /&gt;
* [[AppSuite:Create custom folderview entries in settings app | Edit folderview entries in settings]]&lt;br /&gt;
* [[AppSuite:Extension points for calendar |extension points: Calendar]]&lt;br /&gt;
* [[AppSuite:Extension points for contact |extension points: Contact]]&lt;br /&gt;
* [[AppSuite:Extension points for core | extension points: Core]]&lt;br /&gt;
* [[AppSuite:Extension points for email | extension points: E-Mail]]&lt;br /&gt;
* [[AppSuite:Extension points for files |extension points: Files]]&lt;br /&gt;
* [[AppSuite:Extension points for tasks |extension points: Tasks]]&lt;br /&gt;
* [[AppSuite:Extension points for miscellaneous |extension points: Miscellaneous]]&lt;br /&gt;
&lt;br /&gt;
=== Writing components ===&lt;br /&gt;
* [[AppSuite:Writing a portal plugin | Writing a portal plugin]]&lt;br /&gt;
** [[AppSuite:Configuring portal plugins|Configuring a portal plugin]]&lt;br /&gt;
** [[AppSuite:Using the Upsell widget|Using the Upsell widget]]&lt;br /&gt;
* [[AppSuite:Writing a simple application | Writing a simple application]]&lt;br /&gt;
* [[AppSuite:Writing a simple application with embedded iframe  | Writing a simple application with embedded iframe and launcher link ]]&lt;br /&gt;
* [[AppSuite:Writing a notification area plugin | Writing a plugin for the notification area]]&lt;br /&gt;
* [[AppSuite:Writing a wizard | Writing a wizard]]&lt;br /&gt;
* [[AppSuite:Capabilities | Capabilities explained]] - if you want to enable or disable your plugin&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous articles ==&lt;br /&gt;
* [[AppSuite:External libraries for the UI | External libraries used by the UI]]&lt;br /&gt;
* [[AppSuite:Configuration| Configuration]]&lt;br /&gt;
* [[AppSuite:Custom configurations| Custom configurations]]&lt;br /&gt;
* [[AppSuite:UI build system| The UI build system]]&lt;br /&gt;
* [[AppSuite:Embedding your settings into AppSuite settings|How to embed your own settings into the Appsuite settings page]]&lt;br /&gt;
* [[AppSuite:Debugging the UI|Debugging the UI]]&lt;br /&gt;
* [[AppSuite:Memory leaks (UI)|Finding memory leaks in the UI]]&lt;br /&gt;
* [[AppSuite:Device reference|_.device() reference]]&lt;br /&gt;
* All articles regarding the UI are filed in the  category [[:Category:UI]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:UI_smartphone_device_classification&amp;diff=19389</id>
		<title>AppSuite:UI smartphone device classification</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:UI_smartphone_device_classification&amp;diff=19389"/>
		<updated>2015-04-29T09:25:22Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Smartphone device classification&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
For Appsuite UI development, it is important to determine whether the code is running on a mobile device or not. Furthermore, a solid distinction between smartphones and tablets is important. Since the development of 7 inch tablets, the device detection is more complicated. Therefore, we use a list of the 125 most used devices to verify our classification procedure. &lt;br /&gt;
&lt;br /&gt;
== Detection criteria ==&lt;br /&gt;
&lt;br /&gt;
A device is classified as a smartphone, if&lt;br /&gt;
* it has the correct size (smallest dimension &amp;lt;= 540),&lt;br /&gt;
* touch input and&lt;br /&gt;
* a mobile OS (iOS or Android)&lt;br /&gt;
&lt;br /&gt;
=== Device size === &lt;br /&gt;
&lt;br /&gt;
In a mobile browser, we have access to the css pixels of the screen in the variables &amp;lt;tt&amp;gt;screen.width&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;screen.height&amp;lt;/tt&amp;gt;. It turned out, that 99% of smartphones can be distinguished from tablets because one dimension of a smartphone has less than 540 pixels. &lt;br /&gt;
&lt;br /&gt;
We figured out that in the android stock browser these variables contain the actual physical pixels of the device. Therefore, the &amp;lt;tt&amp;gt;window.devicePixelRatio&amp;lt;/tt&amp;gt; is used as divider for the screen dimensions. &lt;br /&gt;
&lt;br /&gt;
=== Touch and Mobile OS ===&lt;br /&gt;
&lt;br /&gt;
To ensure, that a browser on a desktop pc which is scaled to a small size is not detected as a smartphone, we check if the device has touch input and is running a mobile operating system like android, ios, blackberry or windows phone. Furthermore, there are some small desktop screens with touch input which can only be detected using its operating system and/or if it uses touch input. &lt;br /&gt;
&lt;br /&gt;
=== Custom Detection ===&lt;br /&gt;
&lt;br /&gt;
There are two devices in our list which are not detected correctly. At first, the Kindle Fire HD 7 inch. But Kindle is not supported by the Appsuite. Thus, it can be neglected. The second device is the Motorola Droid Razr HD. This device has a resolution of 1280 * 720 and a css pixel ratio of 1. That is why the size test will fail and we detect this device as a special case.&lt;br /&gt;
&lt;br /&gt;
== Device list ==&lt;br /&gt;
&lt;br /&gt;
The following list shows all devices, we used to verify our classification. In this list the device width and height is in CSS pixels. &lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; style=&amp;quot;border-collapse:collapse&amp;quot;&lt;br /&gt;
| Brand      || Device                             || CSS Pixel Ratio || Device Width || Device Height || Smartphone or other || Detection  || Match || Comment              &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || Nexus 7 (2013 version)             || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || Nexus 7 (2012 version)             || 1.325           || 966          || 604           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || MeMO Pad FHD 10                    || 1.5             || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HD 8.9&amp;quot;                || 1.5             || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HDX 7&amp;quot;                 || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire                        || 1               || 1024         || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HD 7&amp;quot;                  || 1.5             || 853          || 533           || other               || smartphone || false || Not supported        &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HDX 8.9&amp;quot;               || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 6                           || 2               || 667          || 375           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPod Touch 4                       || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Air                           || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || MacBook Pro 15-inch Retina display || 2               || 1440         || 900           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 4                           || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 4S                          || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 2                             || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5C                          || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5S                          || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPod Touch 5                       || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 6 Plus                      || 3               || 736          || 414           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Mini 2                        || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || MacBook Pro 13-inch Retina display || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 3GS                         || 1               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 4                             || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 3                             || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5                           || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 1                             || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Mini                          || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Bold 9900               || 1               || 640          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry PlayBook                || 1               || 1024         || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Z10                     || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Z30                     || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 7                            || 1325            || 966          || 604           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 7 2                          || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 10                           || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 4                            || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 5                            || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Google Glass                       || 1.5             || 427          || 240           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Evo 3D                             || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Thunderbolt                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One X                              || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || HD2                                || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Sensation XE                       || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Nexus One                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Touch HD                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One SV                             || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Evo                                || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || EVO LTE                            || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Desire HD                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || J Butterfly                        || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Sensation                          || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One                                || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Kobo       || Arc 7HD                            || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus Black                   || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 2X                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 3D                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 4X HD                   || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG G3                              || 3               || 853          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Nexus 4                         || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Nexus 5                         || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus One                     || 1.5             || 320          || 213           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus G                       || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus G Pro                   || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus LTE                     || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Spectrum (VS920)                || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LU1400                             || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG G2                              || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface                            || 1               || 1366         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro 3                      || 1.5             || 1440         || 960           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro 2                      || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro                        || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface 2                          || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Xyboard                            || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Moto G                             || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid X                            || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid                              || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Moto E                             || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid 4                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Xoom                               || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid Razr HD                      || 1               || 1280         || 720           || smartphone          || other      || false || Gets extra detection &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Defy                               || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Driod Razor M                      || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Atrix 4G                           || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid 3                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Atrix 2                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid Razr                         || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Milestone                          || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N810                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N800                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 900                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 920                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 925                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C7                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 1020                         || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 7X0                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 8XX                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N900                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N97                                || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || X7                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C6                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C5                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 928                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N8                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Epic (D700)                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Ace                         || 1               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Mega 6.3                    || 1.8             || 711          || 400           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Nexus                       || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note                        || 2               || 640          || 400           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note 3                      || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note II                     || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note Pro 12.1               || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S4                          || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S II                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S III                       || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S Plus                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 10.1                    || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 7.7                     || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 8.9                     || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab Pro 10.1                || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab Pro 8.4                 || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy W                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus 10                           || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus S LCD                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus S SAMOLED                    || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Ion                         || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia P                           || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia S                           || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Sola                        || 1               || 854          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia U                           || 1               || 854          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Z                           || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Z1                          || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Tesco      || Hudl                               || 1.5             || 933          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Xiaomi     || Mi2                                || 3               || 427          || 240           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Xiaomi     || Mi3                                || 4               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:UI_smartphone_device_classification&amp;diff=18821</id>
		<title>AppSuite:UI smartphone device classification</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:UI_smartphone_device_classification&amp;diff=18821"/>
		<updated>2014-10-29T10:06:03Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: Created page with &amp;quot;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Smartphone device classification&amp;lt;/div&amp;gt;  == Introduction ==  For Appsuite UI development, it is important to determine whether the code is running on a mobil...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Smartphone device classification&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
For Appsuite UI development, it is important to determine whether the code is running on a mobile device or not. Furthermore, a solid distinction between smartphones and tablets is important. Since the development of 7 inch tablets, the device detection is more complicated. Therefore, we use a list of the 125 most used devices to verify our classification procedure. &lt;br /&gt;
&lt;br /&gt;
== Detection criteria ==&lt;br /&gt;
&lt;br /&gt;
A device is classified as a smartphone, if it has the correct size, touch input and a mobile OS on the one hand or is a special device on the other hand. In the following, the criteria are described in more detail. &lt;br /&gt;
&lt;br /&gt;
=== Device size === &lt;br /&gt;
&lt;br /&gt;
In a mobile browser, we have access to the css pixels of the screen in the variables &amp;lt;tt&amp;gt;screen.width&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;screen.height&amp;lt;/tt&amp;gt;. It turned out, that 99% of smartphones can be distinguished from tablets because one dimension of a smartphone has less than 540 pixels. &lt;br /&gt;
&lt;br /&gt;
=== Touch and Mobile OS ===&lt;br /&gt;
&lt;br /&gt;
To ensure, that a browser on a desktop pc which is scaled to a small size is not detected as a smartphone, we check if the device has touch input and is running a mobile operating system like android, ios, blackberry or windows phone. Furthermore, there are some small desktop screens with touch input which can only be detected using its operating system and/or if it uses touch input. &lt;br /&gt;
&lt;br /&gt;
=== Custom Detection ===&lt;br /&gt;
&lt;br /&gt;
There are two devices in our list which are not detected correctly. At first, the Kindle Fire HD 7 inch. But Kindle is not supported by the Appsuite. Thus, it can be neglected. The second device is the Motorola Droid Razr HD. This device has a resolution of 1280 * 720 and a css pixel ratio of 1. That is why the size test will fail and we detect this device as a special case. &lt;br /&gt;
&lt;br /&gt;
== Device list ==&lt;br /&gt;
&lt;br /&gt;
The following list shows all devices, we used to verify our classification. In this list the device width and height is in CSS pixels. &lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; style=&amp;quot;border-collapse:collapse&amp;quot;&lt;br /&gt;
| Brand      || Device                             || CSS Pixel Ratio || Device Width || Device Height || Smartphone or other || Detection  || Match || Comment              &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || Nexus 7 (2013 version)             || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || Nexus 7 (2012 version)             || 1.325           || 966          || 604           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| ASUS       || MeMO Pad FHD 10                    || 1.5             || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HD 8.9&amp;quot;                || 1.5             || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HDX 7&amp;quot;                 || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire                        || 1               || 1024         || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HD 7&amp;quot;                  || 1.5             || 853          || 533           || other               || smartphone || false || Not supported        &lt;br /&gt;
|-&lt;br /&gt;
| Amazon     || Kindle Fire HDX 8.9&amp;quot;               || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 6                           || 2               || 667          || 375           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPod Touch 4                       || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Air                           || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || MacBook Pro 15-inch Retina display || 2               || 1440         || 900           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 4                           || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 4S                          || 2               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 2                             || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5C                          || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5S                          || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPod Touch 5                       || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 6 Plus                      || 3               || 736          || 414           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Mini 2                        || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || MacBook Pro 13-inch Retina display || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 3GS                         || 1               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 4                             || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 3                             || 2               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPhone 5                           || 2               || 568          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad 1                             || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Apple      || iPad Mini                          || 1               || 1024         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Bold 9900               || 1               || 640          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry PlayBook                || 1               || 1024         || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Z10                     || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Blackberry || BlackBerry Z30                     || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 7                            || 1325            || 966          || 604           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 7 2                          || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 10                           || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 4                            || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Nexus 5                            || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Google     || Google Glass                       || 1.5             || 427          || 240           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Evo 3D                             || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Thunderbolt                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One X                              || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || HD2                                || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Sensation XE                       || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Nexus One                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Touch HD                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One SV                             || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Evo                                || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || EVO LTE                            || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Desire HD                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || J Butterfly                        || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || Sensation                          || 1.5             || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| HTC        || One                                || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Kobo       || Arc 7HD                            || 2               || 960          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus Black                   || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 2X                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 3D                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus 4X HD                   || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG G3                              || 3               || 853          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Nexus 4                         || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Nexus 5                         || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus One                     || 1.5             || 320          || 213           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus G                       || 2               || 640          || 384           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus G Pro                   || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Optimus LTE                     || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG Spectrum (VS920)                || 1.7             || 753          || 424           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LU1400                             || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| LG         || LG G2                              || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface                            || 1               || 1366         || 768           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro 3                      || 1.5             || 1440         || 960           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro 2                      || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface Pro                        || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Microsoft  || Surface 2                          || 1.5             || 1280         || 720           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Xyboard                            || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Moto G                             || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid X                            || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid                              || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Moto E                             || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid 4                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Xoom                               || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid Razr HD                      || 1               || 1280         || 720           || smartphone          || other      || false || Gets extra detection &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Defy                               || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Driod Razor M                      || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Atrix 4G                           || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid 3                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Atrix 2                            || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Droid Razr                         || 1               || 960          || 540           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Motorola   || Milestone                          || 1.5             || 569          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N810                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N800                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 900                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 920                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 925                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C7                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 1020                         || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 7X0                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 8XX                          || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N900                               || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N97                                || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || X7                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C6                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || C5                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || Lumia 928                          || 2.4             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Nokia      || N8                                 || 1               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Epic (D700)                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Ace                         || 1               || 480          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Mega 6.3                    || 1.8             || 711          || 400           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Nexus                       || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note                        || 2               || 640          || 400           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note 3                      || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note II                     || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Note Pro 12.1               || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S4                          || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S II                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S III                       || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy S Plus                      || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 10.1                    || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 7.7                     || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab 8.9                     || 1               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab Pro 10.1                || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy Tab Pro 8.4                 || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Galaxy W                           || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus 10                           || 2               || 1280         || 800           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus S LCD                        || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Samsung    || Nexus S SAMOLED                    || 1.5             || 533          || 320           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Ion                         || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia P                           || 2               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia S                           || 2               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Sola                        || 1               || 854          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia U                           || 1               || 854          || 480           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Z                           || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Sony       || Xperia Z1                          || 3               || 640          || 360           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Tesco      || Hudl                               || 1.5             || 933          || 600           || other               || other      || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Xiaomi     || Mi2                                || 3               || 427          || 240           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|-&lt;br /&gt;
| Xiaomi     || Mi3                                || 4               || 480          || 270           || smartphone          || smartphone || true  ||                      &lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Mobile&amp;diff=18819</id>
		<title>AppSuite:Mobile</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Mobile&amp;diff=18819"/>
		<updated>2014-10-29T08:46:49Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: /* The _.device function */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Developing for mobile devices&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
App Suite is designed to work on all device types and sizes. The UI uses responsive design principles to scale nicely on each device size. We do define three display sizes to macht the majority of devices. These are simply named &amp;quot;small&amp;quot;, &amp;quot;medium&amp;quot; and &amp;quot;large&amp;quot;. Theses classes are used to match smartphones, tablets and desktop PCs. If you are developing a app for App Suite make sure it runs nicely and looks great on all of these three device categories. (If you are not familiar with latest CSS techniques and the principles of responsive design you should have a look at this [[AppSuite:UI_developer_primer | article]]).&lt;br /&gt;
&lt;br /&gt;
Often the simple use of media queries is not enough to customize your app for small and medium screens, you may need to customize your application code as well. We have integrated a function to detect everything you might want to know during runtime in your javascript code.&lt;br /&gt;
&lt;br /&gt;
==Sizes==&lt;br /&gt;
&lt;br /&gt;
The minimum device target size is 320 x 480 pixels. Your App should work on devices with this resolution.&lt;br /&gt;
&lt;br /&gt;
{| border=1&lt;br /&gt;
|-&lt;br /&gt;
|small || up to 480px&lt;br /&gt;
|-&lt;br /&gt;
|medium || 481px up to 1024px&lt;br /&gt;
|-&lt;br /&gt;
|large || 1025px and higher&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
==The _.device function==&lt;br /&gt;
&lt;br /&gt;
We extended underscore with a new function called &amp;lt;tt&amp;gt;_.device&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;_.device()&amp;lt;/tt&amp;gt; function can be used to retrieve informations about the device. The function takes a string as argument which contains a boolean expression. This expression will be evaluated and the result is returned as a boolean.&lt;br /&gt;
&lt;br /&gt;
The device function uses &amp;lt;tt&amp;gt;_.browser&amp;lt;/tt&amp;gt; object for informations in combination with &amp;lt;tt&amp;gt;_.screenInfo&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The device class &amp;lt;tt&amp;gt;&#039;smartphone&#039;&amp;lt;/tt&amp;gt; is used to determine a mobile device and is detected by several criteria. For more details, see this [[AppSuite:UI_smartphone_device_classification | article about smartphone classification. ]]&lt;br /&gt;
&lt;br /&gt;
==Examples for _.device==&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// handle different mobile operating systems&lt;br /&gt;
if (_.device(&#039;ios&#039;)) {&lt;br /&gt;
   // true for all devices running iOS, no matter what version&lt;br /&gt;
   console.log(&#039;you are running iOS&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// combined statements&lt;br /&gt;
if (_.device(&#039;ios &amp;amp;&amp;amp; android&#039;)) {&lt;br /&gt;
   // true for all android and iOS devices&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// negation&lt;br /&gt;
if (_.device(&#039;!android&#039;)) {&lt;br /&gt;
    // true for all devices except android &lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// screen information&lt;br /&gt;
if (_.device(&#039;small &amp;amp;&amp;amp; iOS &#039;)) {&lt;br /&gt;
   // true for iPhone, not for iPad&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// shorthands&lt;br /&gt;
_.device(&#039;smartphone&#039;)&lt;br /&gt;
// true for small devices running a mobile OS (iOS, Android, BB or Windowsphone)&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;tablet&#039;)&lt;br /&gt;
// true for medium sized devices running a mobile OS&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;desktop&#039;)&lt;br /&gt;
// true for all devices not running a mobile OS&lt;br /&gt;
&lt;br /&gt;
// getting version informations&lt;br /&gt;
_.device(&#039;ios &amp;gt; 5 || android &amp;gt; 4&#039;) &lt;br /&gt;
// true for ios &amp;gt; 5, i.e. 5.1 and 6. Same for Android, 4.0 will fail 4.1 or 4.2 will be true. &lt;br /&gt;
&lt;br /&gt;
// enhanced screen information &lt;br /&gt;
_.device(&#039;iOS &amp;amp;&amp;amp; retina &amp;amp;&amp;amp; small&#039;)&lt;br /&gt;
// true for iPhone 4, 4s and 5 (retina display)&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;landscape &amp;amp;&amp;amp; android &amp;amp;&amp;amp; medium&#039;) &lt;br /&gt;
// true for android tablet held in landscape mode&lt;br /&gt;
&lt;br /&gt;
// other information, simple browser detection&lt;br /&gt;
_.device(&#039;safari || firefox&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Please note that information about device orientation may change during usage.&lt;br /&gt;
&lt;br /&gt;
==Mobile considerations==&lt;br /&gt;
&lt;br /&gt;
As of today mobile usage has become much more important than some years ago. Always consider the the fact a user may want to use your App on a smartphone. So, optimizing for mobile should not be last step in your development process, it should be one of the first. This will safe you a lot of painful debugging and layout fixes.&lt;br /&gt;
&lt;br /&gt;
You should ask you a simple question: Does function X in my App do have a mobile use case? Or more simple: Will anybody use this on a smartphone? &lt;br /&gt;
If not, disable or remove this function on a mobile device. Nobody will perform a complex 35-click action in your App on a smartphone.&lt;br /&gt;
&lt;br /&gt;
Developing for mobile should follow some simple rules:&lt;br /&gt;
* Mobile phones do have small screens. Safe space in your layout, reduce margins and paddings.&lt;br /&gt;
* Touch is not click, keep buttons and links big enough to be touchable. 40px should be a minium.&lt;br /&gt;
* Mobile networks are slow and have a high latency. Safe network requests and handle failing requests properly&lt;br /&gt;
* Mobile devices are not as fast as desktop PCs. Not everybody has a high end smartphone so keep your code clean and fast&lt;br /&gt;
* Always test your App on a real device&lt;br /&gt;
&lt;br /&gt;
==Remote Debugging==&lt;br /&gt;
&lt;br /&gt;
To setup remote debugging on windows, mac and linux you can follow the instructions from the chrome developer tools website. &lt;br /&gt;
&lt;br /&gt;
[https://developer.chrome.com/devtools/docs/remote-debugging Remote Debugging on Android with Chrome]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If this is not working or not applicable for you, use the following description:&lt;br /&gt;
&lt;br /&gt;
[[AppSuite:UI_remote_debugging_android_mac | How to setup remote debugging for Chrome on android devices with a mac.]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
	<entry>
		<id>https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Mobile&amp;diff=18786</id>
		<title>AppSuite:Mobile</title>
		<link rel="alternate" type="text/html" href="https://wiki.open-xchange.com/wiki/index.php?title=AppSuite:Mobile&amp;diff=18786"/>
		<updated>2014-10-22T09:00:12Z</updated>

		<summary type="html">&lt;p&gt;Richard.petersen: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stability-experimental}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;title&amp;quot;&amp;gt;Developing for mobile devices&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
&lt;br /&gt;
App Suite is designed to work on all device types and sizes. The UI uses responsive design principles to scale nicely on each device size. We do define three display sizes to macht the majority of devices. These are simply named &amp;quot;small&amp;quot;, &amp;quot;medium&amp;quot; and &amp;quot;large&amp;quot;. Theses classes are used to match smartphones, tablets and desktop PCs. If you are developing a app for App Suite make sure it runs nicely and looks great on all of these three device categories. (If you are not familiar with latest CSS techniques and the principles of responsive design you should have a look at this [[AppSuite:UI_developer_primer | article]]).&lt;br /&gt;
&lt;br /&gt;
Often the simple use of media queries is not enough to customize your app for small and medium screens, you may need to customize your application code as well. We have integrated a function to detect everything you might want to know during runtime in your javascript code.&lt;br /&gt;
&lt;br /&gt;
==Sizes==&lt;br /&gt;
&lt;br /&gt;
The minimum device target size is 320 x 480 pixels. Your App should work on devices with this resolution.&lt;br /&gt;
&lt;br /&gt;
{| border=1&lt;br /&gt;
|-&lt;br /&gt;
|small || up to 480px&lt;br /&gt;
|-&lt;br /&gt;
|medium || 481px up to 1024px&lt;br /&gt;
|-&lt;br /&gt;
|large || 1025px and higher&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
==The _.device function==&lt;br /&gt;
&lt;br /&gt;
We extended underscore with a new function called &amp;lt;tt&amp;gt;_.device&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;_.device()&amp;lt;/tt&amp;gt; function can be used to retrieve informations about the device. The function takes a string as argument which contains a boolean expression. This expression will be evaluated and the result is returned as a boolean.&lt;br /&gt;
&lt;br /&gt;
The device function uses &amp;lt;tt&amp;gt;_.browser&amp;lt;/tt&amp;gt; object for informations in combination with &amp;lt;tt&amp;gt;_.screenInfo&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Examples for _.device==&lt;br /&gt;
&amp;lt;pre class=&amp;quot;language-javascript&amp;quot;&amp;gt;&lt;br /&gt;
// handle different mobile operating systems&lt;br /&gt;
if (_.device(&#039;ios&#039;)) {&lt;br /&gt;
   // true for all devices running iOS, no matter what version&lt;br /&gt;
   console.log(&#039;you are running iOS&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// combined statements&lt;br /&gt;
if (_.device(&#039;ios &amp;amp;&amp;amp; android&#039;)) {&lt;br /&gt;
   // true for all android and iOS devices&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// negation&lt;br /&gt;
if (_.device(&#039;!android&#039;)) {&lt;br /&gt;
    // true for all devices except android &lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// screen information&lt;br /&gt;
if (_.device(&#039;small &amp;amp;&amp;amp; iOS &#039;)) {&lt;br /&gt;
   // true for iPhone, not for iPad&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// shorthands&lt;br /&gt;
_.device(&#039;smartphone&#039;)&lt;br /&gt;
// true for small devices running a mobile OS (iOS, Android, BB or Windowsphone)&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;tablet&#039;)&lt;br /&gt;
// true for medium sized devices running a mobile OS&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;desktop&#039;)&lt;br /&gt;
// true for all devices not running a mobile OS&lt;br /&gt;
&lt;br /&gt;
// getting version informations&lt;br /&gt;
_.device(&#039;ios &amp;gt; 5 || android &amp;gt; 4&#039;) &lt;br /&gt;
// true for ios &amp;gt; 5, i.e. 5.1 and 6. Same for Android, 4.0 will fail 4.1 or 4.2 will be true. &lt;br /&gt;
&lt;br /&gt;
// enhanced screen information &lt;br /&gt;
_.device(&#039;iOS &amp;amp;&amp;amp; retina &amp;amp;&amp;amp; small&#039;)&lt;br /&gt;
// true for iPhone 4, 4s and 5 (retina display)&lt;br /&gt;
&lt;br /&gt;
_.device(&#039;landscape &amp;amp;&amp;amp; android &amp;amp;&amp;amp; medium&#039;) &lt;br /&gt;
// true for android tablet held in landscape mode&lt;br /&gt;
&lt;br /&gt;
// other information, simple browser detection&lt;br /&gt;
_.device(&#039;safari || firefox&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Please note that information about device orientation may change during usage.&lt;br /&gt;
&lt;br /&gt;
==Mobile considerations==&lt;br /&gt;
&lt;br /&gt;
As of today mobile usage has become much more important than some years ago. Always consider the the fact a user may want to use your App on a smartphone. So, optimizing for mobile should not be last step in your development process, it should be one of the first. This will safe you a lot of painful debugging and layout fixes.&lt;br /&gt;
&lt;br /&gt;
You should ask you a simple question: Does function X in my App do have a mobile use case? Or more simple: Will anybody use this on a smartphone? &lt;br /&gt;
If not, disable or remove this function on a mobile device. Nobody will perform a complex 35-click action in your App on a smartphone.&lt;br /&gt;
&lt;br /&gt;
Developing for mobile should follow some simple rules:&lt;br /&gt;
* Mobile phones do have small screens. Safe space in your layout, reduce margins and paddings.&lt;br /&gt;
* Touch is not click, keep buttons and links big enough to be touchable. 40px should be a minium.&lt;br /&gt;
* Mobile networks are slow and have a high latency. Safe network requests and handle failing requests properly&lt;br /&gt;
* Mobile devices are not as fast as desktop PCs. Not everybody has a high end smartphone so keep your code clean and fast&lt;br /&gt;
* Always test your App on a real device&lt;br /&gt;
&lt;br /&gt;
==Remote Debugging==&lt;br /&gt;
&lt;br /&gt;
To setup remote debugging on windows, mac and linux you can follow the instructions from the chrome developer tools website. &lt;br /&gt;
&lt;br /&gt;
[https://developer.chrome.com/devtools/docs/remote-debugging Remote Debugging on Android with Chrome]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If this is not working or not applicable for you, use the following description:&lt;br /&gt;
&lt;br /&gt;
[[AppSuite:UI_remote_debugging_android_mac | How to setup remote debugging for Chrome on android devices with a mac.]]&lt;/div&gt;</summary>
		<author><name>Richard.petersen</name></author>
	</entry>
</feed>