http://wiki.x2crm.com/api.php?action=feedcontributions&user=Raymond+Colebaugh&feedformat=atomX2Engine - User contributions [en]2024-03-29T15:56:59ZUser contributionsMediaWiki 1.28.0http://wiki.x2crm.com/index.php?title=Permissions_System&diff=1968Permissions System2017-02-17T00:16:02Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Development]]<br />
= Introduction =<br />
Access control within X2CRM is facilitated through the combination of record assignment and visibility. Public visibility allows all users to see the record. A visibility of private will only allow a user to view the record if it is assigned explicitly to their user. Finally, a visibility of "User's groups" will allow visibility if the current user and the assigned user share a group (or the record is assigned to the group). These rules are then further limited by Role permissions. A Role can be assigned permission according to each module, limiting their access to particular operations, such as viewing or updating. Roles can also be granted granular permissions to view or edit individual fields in a module.<br />
<br />
= Assignment and Visibility =<br />
The assignment of a record is controlled by the assignedTo field. This may be a value such as "Anyone", a username, or a group ID. Record visibility is controlled by the visibility field, which at the time of writing can take the following integer values (see [[x2doc:X2PermissionsBehavior|X2PermissionsBehavior]] for more details):<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>Value</tt><br />
| <tt>Visibility</tt><br />
| <tt>Description</tt><br />
|-<br />
! scope="row" | 0<br />
| Private<br />
| This visibility value implies "private"; ordinarily visible only to assignee(s)/owner(s) of the record<br />
|-<br />
! scope="row" | 1<br />
| Public<br />
| This visibility setting implies the record is public/shared, and anyone can view.<br />
|-<br />
! scope="row" | 2<br />
| Groups<br />
| This visibility setting implies that the record is visible to the owners and other members of groups to which the owners belong ("groupmates").<br />
|-<br />
|}<br />
<br />
== Hidden Records ==<br />
When a record is assigned to 'Anyone' and given private visibility, the record becomes hidden from the UI, even for the admin user. This function is ordinarily used by the duplicate checker after the duplicates have been resolved. The data for the record is still present in the database, and can still be viewed and updated manually. In X2CRM version 6.5.3, a tool has been added to find records that may have been inadvertently hidden.<br />
<br />
= Roles and Permissions =<br />
Within X2CRM, each user may be assigned one or more Roles. Each of these Roles are associated with different groups of permissions. Each permission represents a different operation, such as viewing a record. This access control model is known as [https://en.wikipedia.org/wiki/Role-based_access_control Role-Based Access Control (RBAC)]. Within X2CRM, authorization is managed by the [[x2doc:X2AuthManager|X2AuthManager]] class, which extends Yii's [[yii:CDbAuthManager|CDbAuthManager]] class. Please see the Yii documentation page [[yiiguide:topics.auth|Authentication and Authorization]] for more information.<br />
<br />
== Configuring Module Access for a Role ==<br />
To configure module access for a particular role, first navigate to the admin panel, then view the "Edit User Permissions and Access Rules" page under the User Management section.<br />
<br />
Each of the roles can be granted permission for the following actions:<br />
* View<br />
* Create<br />
* Update<br />
* Delete<br />
* Admin<br />
<br />
The View, Update, and Delete actions can additionally be restricted to only those that are assigned to the user. Currently, only roles with Admin permission for a module can perform record import and export.<br />
<br />
[[File:EditRoleAccess.png]]<br />
<br />
== Configuring Field Level Permissions for a Role ==<br />
[[File:ManageRoles.png|200px|thumb|right|Configuring Field Level Permissions]]<br />
<br />
Access to data can be further limited by more granular control over the particular fields in each module that a role can access. For example, a role may have view permissions for most of the fields of a Contact, but can only edit a subset of those fields. Fields that cannot be viewed will be hidden from any grids or form layouts.<br />
<br />
To configure field level permissions for a role, first navigate to the admin panel, then view the "Manage Roles" page under the User Management section.<br />
<br />
It is on this screen that you can configure the view and edit permissions for each field, as well as which users belong to that role. You can also configure a custom session timeout for users who belong to this role. To add permission for a field, you can click the plus icon or drag the field into the left pane. You can also use the text field on the top of the multiselect to filter the available fields.<br />
<br />
= Custom Permissions =<br />
As a developer expands the functionality of their custom modules, they may find it advantageous to customize the restrictions placed upon users trying to access particular actions. By adding custom permissions entries for the actions you add, you can enable the administrator to manage user access to the action within the permissions configuration interface.<br />
<br />
== Adding a Custom Permission ==<br />
When adding custom actions to any of the controllers, it is likely that you will want to limit access using the permissions system. For standard controllers, e.g., those under protected/controllers, you can add custom access rules by modifying the accessRules() method of the controller. The 'users' definition describes which users have the ability to access that action. To limit access to a particular user, such as "admin", you would specify their username directly. To allow all users (including guest) to use the action, specify an asterisk ("*"). For example, to allow guest access for a new controller method actionTest(), one would add the following access rule:<br />
<br />
<syntaxhighlight lang="php"><br />
array('allow',<br />
'actions' => array('test'),<br />
'users' => array('*'),<br />
),<br />
</syntaxhighlight><br />
<br />
Please see the page on the [[Customization Framework]] for more information about safely persisting your customizations through updates.<br />
<br />
The access control in modules is handled a bit differently. While in standard Controllers you can use the accessRules() method to define the rules, the custom module system in X2CRM requires authorization rules to be loaded dynamically.<br />
<br />
To add a permission for a new custom module action, you'll want to instead add the necessary auth items and their relationships to the auth graph. The auth items themselves are stored in the <code>x2_auth_item</code> table. These are then associated with their appropriate parent permission with the <code>x2_auth_item_child</code> table. For example, in adding guest access to the actionTest() method of a Testmodule controller, one would insert the following database records:<br />
<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_auth_item (name, type, data) VALUES ('TestmoduleTest', 0, 'N;');<br />
INSERT INTO x2_auth_item_child (parent, child) VALUES ('GuestSiteFunctionsTask', 'TestmoduleTest');<br />
</syntaxhighlight><br />
<br />
<br />
== Verifying Permissions with the Auth Graph ==<br />
[[File:AuthGraph.png|200px|thumb|right|The Auth Graph]]<br />
<br />
After modifying or adding custom authorization rules, it may be helpful to visualize the current state of the authorization graph. This can help decide where a new permission belongs or catch inadvertent granting of permissions. As the administrator, navigate to /admin/authGraph. You will be presented with a listing of the various permissions, as well as their relation to their children.<br />
<br />
Of particular interest are a few specific permission groups, including GeneralAdminSettingsTask, AuthenticatedSiteFunctionsTask, and GuestSiteFunctionsTask. The GeneralAdminSettingsTask is required for most of the actions in the admin panel. The permissions in GuestSiteFunctionsTask are granted to users who are not logged in, while those in AuthenticatedSiteFunctions are granted to logged in users.<br />
<br />
Each of the modules have a few key permission groups, named {module}PrivateReadOnlyAccess, {module}PrivateUpdateAccess, {module}PrivateFullAccess, {module}PrivateBasicAccess, {module}ReadOnlyAccess, {module}BasicAccess, {module}UpdateAccess, {module}FullAccess, {module}AdminAccess. These groups correspond to the various levels of access granted on the Edit User Permissions and Access Rules page. Each auth item within the group represents the permissions for a controller action, as is named in the format {module}{action}. For example, the "create" action of the Accounts controller is associated with the permission "AccountsCreate."</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Permissions_System&diff=1967Permissions System2017-02-09T21:29:16Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Development]]<br />
= Introduction =<br />
<b>Warning:</b> This article is under construction.<br />
<br />
Access control within X2CRM is facilitated through the combination of record assignment and visibility. Public visibility allows all users to see the record. A visibility of private will only allow a user to view the record if it is assigned explicitly to their user. Finally, a visibility of "User's groups" will allow visibility if the current user and the assigned user share a group (or it is assigned to the group). These rules are then further limited by Role permissions. A Role can be assigned permission according to each module, limiting their access to particular operations, such as viewing or updating. Roles can also be granted granular permissions to view or edit individual fields in a module.<br />
<br />
= Assignment and Visibility =<br />
The assignment of a record is controlled by the assignedTo field. This may be a value such as "Anyone", a username, or a group ID. Record visibility is controlled by the visibility field, which at the time of writing can take the following integer values (see [[x2doc:X2PermissionsBehavior|X2PermissionsBehavior]] for more details):<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>Value</tt><br />
| <tt>Visibility</tt><br />
| <tt>Description</tt><br />
|-<br />
! scope="row" | 0<br />
| Private<br />
| This visibility value implies "private"; ordinarily visible only to assignee(s)/owner(s) of the record<br />
|-<br />
! scope="row" | 1<br />
| Public<br />
| This visibility setting implies the record is public/shared, and anyone can view.<br />
|-<br />
! scope="row" | 2<br />
| Groups<br />
| This visibility setting implies that the record is visible to the owners and other members of groups to which the owners belong ("groupmates").<br />
|-<br />
|}<br />
<br />
== Hidden Records ==<br />
When a record is assigned to 'Anyone' and given private visibility, the record becomes hidden from the UI, even for the admin user. This function is ordinarily used by the duplicate checker after the duplicates have been resolved. The data for the records is still present in the database, and can still be viewed and updated manually. In X2CRM version 6.5.3, a tool has been added to find records that may have been inadvertently hidden.<br />
<br />
= Roles and Permissions =<br />
Within X2CRM, each user may be assigned one or more Roles. Each of these Roles are associated with different groups of permissions. Each permission represents a different operation, such as viewing a record. This access control model is known as [https://en.wikipedia.org/wiki/Role-based_access_control Role-Based Access Control (RBAC)]. Within X2CRM, authorization is managed by the [[x2doc:X2AuthManager|X2AuthManager]] class, which extends Yii's [[yii:CDbAuthManager|CDbAuthManager]] class. Please see the Yii documentation page [[yiiguide:topics.auth|Authentication and Authorization]] for more information.<br />
<br />
== Configuring Module Access for a Role ==<br />
To configure module access for a particular role, first navigate to the admin panel, then view the "Edit Role Access" page under the User Management section.<br />
<br />
Each of the roles can be granted permission for the following actions:<br />
* View<br />
* Create<br />
* Update<br />
* Delete<br />
* Admin<br />
<br />
The View, Update, and Delete actions can additionally be restricted to only those that are assigned to the user. Currently, only roles with Admin permission for a module can perform record import and export.<br />
<br />
[[File:EditRoleAccess.png]]<br />
<br />
== Configuring Field Level Permissions for a Role ==<br />
[[File:ManageRoles.png|200px|thumb|right]]<br />
<br />
Access to data can be further limited by more granular control over the particular fields in each module that a role can access. For example, a role may have view permissions for most of the fields of a Contact, but can only edit a subset of those fields. Fields that cannot be viewed will be hidden from any grids or form layouts.<br />
<br />
To configure field level permissions for a role, first navigate to the admin panel, then view the "Manage Roles" page under the User Management section.<br />
<br />
= Custom Permissions =<br />
As a developer expands the functionality of their custom modules, they may find it advantageous to customize the restrictions placed upon users trying to access particular actions.<br />
<br />
== Adding a Custom Permission ==<br />
When adding custom actions to any of the controllers, it is likely that you will want to limit access using the permissions system. For standard controllers, e.g., those under protected/controllers, you can add custom access rules by modifying the accessRules() method of the controller. The 'users' definition describes which users have the ability to access that action. To limit access to a particular user, such as "admin", you would specify their username directly. To allow all users (including guest) to use the action, specify an asterisk ("*"). For example, to allow guest access for a new controller method actionTest(), one would add the following access rule:<br />
<br />
<syntaxhighlight lang="php"><br />
array('allow',<br />
'actions' => array('test'),<br />
'users' => array('*'),<br />
),<br />
</syntaxhighlight><br />
<br />
Please see the page on the [[Customization Framework]] for more information about safely persisting your customizations through updates.<br />
<br />
The access control in modules is handled a bit differently. While in standard Controllers you can use the accessRules() method to define the rules, the custom module system in X2CRM requires authorization rules to be loaded dynamically.<br />
<br />
To add a permission for a new custom module action, you'll want to instead add the necessary auth items and their relationships to the auth graph. The auth items themselves are stored in the <code>x2_auth_item</code> table. These are then associated with their appropriate parent permission with the <code>x2_auth_item_child</code> table. For example, in adding guest access to the actionTest() method of a Testmodule controller, one would insert the following database records:<br />
<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_auth_item (name, type, data) VALUES ('TestmoduleTest', 0, 'N;');<br />
INSERT INTO x2_auth_item_child (parent, child) VALUES ('GuestSiteFunctionsTask', 'TestmoduleTest');<br />
</syntaxhighlight><br />
<br />
<br />
== Verifying Permissions with the Auth Graph ==<br />
[[File:AuthGraph.png|200px|thumb|right]]<br />
<br />
After modifying or adding custom authorization rules, it may be helpful to visualize the current state of the authorization graph. This can help decide where a new permission belongs or catch inadvertent granting of permissions. As the administrator, navigate to /admin/authGraph. You will be presented with a listing of the various permissions, as well as their relation to their children.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Permissions_System&diff=1966Permissions System2017-02-09T19:45:01Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Development]]<br />
= Introduction =<br />
<b>Warning:</b> This article is under construction.<br />
<br />
= Assignment and Visibility =<br />
The assignment of a record is controlled by the assignedTo field. This may be a value such as "Anyone", a username, or a group ID. Record visibility is controlled by the visibility field, which at the time of writing can take the following integer values (see [[x2doc:X2PermissionsBehavior|X2PermissionsBehavior]] for more details):<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>Value</tt><br />
| <tt>Visibility</tt><br />
| <tt>Description</tt><br />
|-<br />
! scope="row" | 0<br />
| Private<br />
| This visibility value implies "private"; ordinarily visible only to assignee(s)/owner(s) of the record<br />
|-<br />
! scope="row" | 1<br />
| Public<br />
| This visibility setting implies the record is public/shared, and anyone can view.<br />
|-<br />
! scope="row" | 2<br />
| Groups<br />
| This visibility setting implies that the record is visible to the owners and other members of groups to which the owners belong ("groupmates").<br />
|-<br />
|}<br />
<br />
== Hidden Records ==<br />
When a record is assigned to 'Anyone' and given private visibility, the record becomes hidden from the UI, even for the admin user. This function is ordinarily used by the duplicate checker after the duplicates have been resolved. The data for the records is still present in the database, and can still be viewed and updated manually. In X2CRM version 6.5.3, a tool has been added to find records that may have been inadvertently hidden.<br />
<br />
= Roles and Permissions =<br />
Within X2CRM, each user may be assigned one or more Roles. Each of these Roles are associated with different groups of permissions. Each permission represents a different operation, such as viewing a record.<br />
<br />
== Configuring Module Access for a Role ==<br />
To configure module access for a particular role, first navigate to the admin panel, then view the "Edit Role Access" page under the User Management section.<br />
<br />
Each of the roles can be granted granular permission for the following actions:<br />
* View<br />
* Create<br />
* Update<br />
* Delete<br />
* Admin<br />
<br />
The View, Update, and Delete actions can additionally be restricted to only those that are assigned to the user. Currently, only roles with Admin permission for a module can perform record import and export.<br />
<br />
[[File:EditRoleAccess.png]]<br />
<br />
== Configuring Field Level Permissions for a Role ==<br />
[[File:ManageRoles.png|200px|thumb|right]]<br />
<br />
Access to data can be further limited by more granular control over the particular fields in each module that a role can access. For example, a role may have view permissions for most of the fields of a Contact, but can only edit a subset of those fields. Fields that cannot be viewed will be hidden from any grids or form layouts.<br />
<br />
To configure field level permissions for a role, first navigate to the admin panel, then view the "Manage Roles" page under the User Management section.<br />
<br />
= Custom Permissions =<br />
As a developer expands the functionality of their custom modules, they may find it advantageous to customize the restrictions placed upon users trying to access particular actions.<br />
<br />
== Adding a Custom Permission ==<br />
When adding custom actions to any of the controllers, it is likely that you will want to limit access using the permissions system. For standard controllers, e.g., those under protected/controllers, you can add custom access rules by modifying the accessRules() method of the controller. The 'users' definition describes which users have the ability to access that action. To limit access to a particular user, such as "admin", you would specify their username directly. To allow all users (including guest) to use the action, specify an asterisk ("*"). For example, to allow guest access for a new controller method actionTest(), one would add the following access rule:<br />
<br />
<syntaxhighlight lang="php"><br />
array('allow',<br />
'actions' => array('test'),<br />
'users' => array('*'),<br />
),<br />
</syntaxhighlight><br />
<br />
Please see the page on the [[Customization Framework]] for more information about safely persisting your customizations through updates.<br />
<br />
The access control in modules is handled a bit differently. While in standard Controllers you can use the accessRules() method to define the rules, the custom module system in X2CRM requires authorization rules to be loaded dynamically. To add a permission for a new custom module action, you'll want to instead add the necessary auth items and their relationships to the auth graph. The auth items themselves are stored in the <code>x2_auth_item</code> table. These are then associated with their appropriate parent permission with the <code>x2_auth_item_child</code> table. For example, in adding guest access to the actionTest() method of a Testmodule controller, one would insert the following database records:<br />
<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_auth_item (name, type, data) VALUES ('TestmoduleTest', 0, 'N;');<br />
INSERT INTO x2_auth_item_child (parent, child) VALUES ('GuestSiteFunctionsTask', 'TestmoduleTest');<br />
</syntaxhighlight><br />
<br />
<br />
== Verifying Permissions with the Auth Graph ==<br />
[[File:AuthGraph.png|200px|thumb|right]]<br />
<br />
After modifying or adding custom authorization rules, it may be helpful to visualize the current state of the authorization graph. This can help decide where a new permission belongs or catch inadvertent granting of permissions. As the administrator, navigate to /admin/authGraph. You will be presented with a listing of the various permissions, as well as their relation to their children.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:ManageRoles.png&diff=1965File:ManageRoles.png2017-02-09T19:32:50Z<p>Raymond Colebaugh: Manage Roles, accessible from the admin panel</p>
<hr />
<div>Manage Roles, accessible from the admin panel</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Permissions_System&diff=1964Permissions System2017-02-09T01:27:06Z<p>Raymond Colebaugh: Created page with "Category:Development = Introduction = <b>Warning:</b> This article is under construction. = Assignment and Visibility = The assignment of a record is controlled by the as..."</p>
<hr />
<div>[[Category:Development]]<br />
= Introduction =<br />
<b>Warning:</b> This article is under construction.<br />
<br />
= Assignment and Visibility =<br />
The assignment of a record is controlled by the assignedTo field. This may be a value such as "Anyone", a username, or a group ID. Record visibility is controlled by the visibility field, which at the time of writing can take the following integer values (see [[x2doc:X2PermissionsBehavior|X2PermissionsBehavior]] for more details):<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>Value</tt><br />
| <tt>Visibility</tt><br />
| <tt>Description</tt><br />
|-<br />
! scope="row" | 0<br />
| Private<br />
| This visibility value implies "private"; ordinarily visible only to assignee(s)/owner(s) of the record<br />
|-<br />
! scope="row" | 1<br />
| Public<br />
| This visibility setting implies the record is public/shared, and anyone can view.<br />
|-<br />
! scope="row" | 2<br />
| Groups<br />
| This visibility setting implies that the record is visible to the owners and other members of groups to which the owners belong ("groupmates").<br />
|-<br />
|}<br />
<br />
== Hidden Records ==<br />
When a record is assigned to 'Anyone' and given private visibility, the record becomes hidden from the UI, even for the admin user. This function is ordinarily used by the duplicate checker after the duplicates have been resolved. The data for the records is still present in the database, and can still be viewed and updated manually. In X2CRM version 6.5.3, a tool has been added to find records that may have been inadvertently hidden.<br />
<br />
= Roles and Permissions =<br />
Within X2CRM, each user may be assigned one or more Roles. Each of these Roles are associated with different groups of permissions. Each permission represents a different operation, such as viewing a record.<br />
<br />
To configure module access for a particular role, first navigate to the admin panel, then view the "Edit Role Access" page under the User Management section.<br />
<br />
[[File:EditRoleAccess.png]]<br />
<br />
Each of the roles can be granted granular permission for the following actions:<br />
* View<br />
* Create<br />
* Update<br />
* Delete<br />
* Admin<br />
<br />
The View, Update, and Delete actions can additionally be restricted to only those that are assigned to the user. Currently, only roles with Admin permission for a module can perform record import and export.<br />
<br />
== Adding a Custom Permission ==<br />
When adding custom actions to any of the controllers, it is likely that you will want to limit access using the permissions system. For standard controllers, e.g., those under protected/controllers, you can add custom access rules by modifying the accessRules() method of the controller. The 'users' definition describes which users have the ability to access that action. To limit access to a particular user, such as "admin", you would specify their username directly. To allow all users (including guest) to use the action, specify an asterisk ("*"). For example, to allow guest access for a new controller method actionTest(), one would add the following access rule:<br />
<br />
<syntaxhighlight lang="php"><br />
array('allow',<br />
'actions' => array('test'),<br />
'users' => array('*'),<br />
),<br />
</syntaxhighlight><br />
<br />
Please see the page on the [[Customization Framework]] for more information about safely persisting your customizations through updates.<br />
<br />
The access control in modules is handled a bit differently. While in standard Controllers you can use the accessRules() method to define the rules, the custom module system in X2CRM requires authorization rules to be loaded dynamically. To add a permission for a new custom module action, you'll want to instead add the necessary auth items and their relationships to the auth graph. The auth items themselves are stored in the <code>x2_auth_item</code> table. These are then associated with their appropriate parent permission with the <code>x2_auth_item_child</code> table. For example, in adding guest access to the actionTest() method of a Testmodule controller, one would insert the following database records:<br />
<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_auth_item (name, type, data) VALUES ('TestmoduleTest', 0, 'N;');<br />
INSERT INTO x2_auth_item_child (parent, child) VALUES ('GuestSiteFunctionsTask', 'TestmoduleTest');<br />
</syntaxhighlight><br />
<br />
<br />
== Verifying Permissions with the Auth Graph ==<br />
[[File:AuthGraph.png|200px|thumb|right]]<br />
<br />
After modifying or adding custom authorization rules, it may be helpful to visualize the current state of the authorization graph. This can help decide where a new permission belongs or catch inadvertent granting of permissions. As the administrator, navigate to /admin/authGraph. You will be presented with a listing of the various permissions, as well as their relation to their children.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:EditRoleAccess.png&diff=1963File:EditRoleAccess.png2017-02-09T00:29:27Z<p>Raymond Colebaugh: Edit Role Access page, accessible from the admin panel</p>
<hr />
<div>Edit Role Access page, accessible from the admin panel</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:AuthGraph.png&diff=1962File:AuthGraph.png2017-02-09T00:10:49Z<p>Raymond Colebaugh: The permissions auth graph, accessible to administrators at /admin/authGraph.</p>
<hr />
<div>The permissions auth graph, accessible to administrators at /admin/authGraph.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=User:X2josef&diff=1960User:X2josef2017-02-07T23:53:37Z<p>Raymond Colebaugh: Creating user page for new user.</p>
<hr />
<div>I currently am a Jr. Software Engineer for X2CRM and a student at UCSC. I learn new things everyday and try to implement new ideas that would change people's perception of how things work and feel. Innovation is the key to the future and is something I continually strive for.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=User_talk:X2josef&diff=1961User talk:X2josef2017-02-07T23:53:37Z<p>Raymond Colebaugh: Welcome!</p>
<hr />
<div>'''Welcome to ''X2Engine''!'''<br />
We hope you will contribute much and well.<br />
You will probably want to read the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents help pages].<br />
Again, welcome and have fun! [[User:Raymond Colebaugh|Raymond Colebaugh]] ([[User talk:Raymond Colebaugh|talk]]) 23:53, 7 February 2017 (UTC)</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=X2_Hub_Integration&diff=1959X2 Hub Integration2017-02-07T18:51:05Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
X2 Hub Services is an external connectivity provider to various third-party services, including Google Maps, Google Calendar sync, and two factor auth through Twilio. By configuring X2 Hub Services for your X2CRM instance, you will greatly reduce the amount of time and effort required in configuring and managing credentials for each of the supported providers. This allows you to quickly and easily extend the functionality of your CRM with a wide variety of integrations. Please visit the [https://www.x2crm.com/products/ Products page] for purchasing information.<br />
<br />
== Initial Activation ==<br />
[[File:HubActivation.png|200px|thumb|left]]<br />
<br />
Once you have purchased a subscription to X2 Hub Services, please allow up to 24 hours for review and activation. Once your account has been activated, you will receive an email containing your X2 Hub product key. As admin, navigate to the admin panel and visit the "Configure X2 Hub Integration" page under the X2 Hub Services section.<br />
<br />
Paste the product key you received in your activation email into the text field and ensure that "Enabled" is selected. Once you've entered a valid product key and enabled Hub, the status indicator on the Hub integration page should read "Enabled."<br />
<br />
On this screen, you will also be presented with checkboxes for the various external services. If for any reason you wish to disable Hub for a particular service and use your existing integration configuration, please deselect the appropriate service before saving. For example, if you wish to enable X2 Hub for Google Maps integration, but would like to restrict Google Calendar usage to occur through your existing Google configuration, you can deselect the Google Calendar checkbox on the Hub configuration page.<br />
<br />
== Additional Setup ==<br />
Various components of X2CRM require additional setup after enabling Hub. Please see the following sections for additional steps in configuring your X2 Hub Services integration.<br />
<br />
=== Google Calendar Sync ===<br />
After the administrator has configured X2 Hub, users will be able to link their Google Calendars as they would ordinarily. Each user will first want to ensure that their Google ID is configured in their profile settings. Once the user's Google ID is registered, they will be able to link a Google Calendar to any of their calendars in X2CRM.<br />
<br />
First, either create your new calendar, or update an existing calendar. Then, select "Link to Google Calendar." You will be presented with the Google authorization screen and the required permissions. After accepting, you'll be redirected back to the calendar. Next, revisit the calendar configuration, select your desired Google Calendar from the dropdown of available remote calendars, and hit "Save."<br />
<br />
=== Two Factor Authentication ===<br />
[[File:ActivateTwoFactor.png|200px|thumb|right]]<br />
<br />
Once the administrator has activated X2 Hub Services, there is an additional step which must be performed before users can enable two factor authentication. From the admin panel, navigate to the 'Advanced Security Settings' page under the Security Settings section. Under "Two Factor Authentication," select "X2 Hub Services" as the active two factor auth credentials, before hitting "Save." Once the two factor auth credentials have been selected, each user will be able to enable two factor authentication for their user login.<br />
<br />
To activate two factor authentication for a particular user, first navigate to the Preferences page. Next, select the checkbox to enable two factor auth. A verification code will be sent to the user's cell phone, which must be entered into the verification text field. If the verification succeeds, next time you login you will be presented with an additional text field to enter your confirmation code. Note: verification codes will be sent to the cell phone number set in the user's profile settings.<br />
<br />
In the event that a user is unable to login to the system after enabling two factor auth, such as if their phone is lost or stolen, please notify the administrator. The administrator has privilege to remove the two factor auth requirement for that user by visiting the user's page in the Users module, allowing the user to login again and reconfigure two factor auth.<br />
<br />
== Troubleshooting Connectivity ==<br />
If there are any issues when connecting or the status indicator still reads "Disabled," please verify that your server already meets the requirements for running X2CRM. In particular, you'll want to ensure that you have either installed the PHP curl extension, or that your have enabled allow_url_fopen in your php.ini configuration. Furthermore, connectivity to X2 Hub Services requires that outbound network traffic on port 443 is allowed from the server. To verify network connectivity by command line, on your server execute <tt>curl https://hub.x2crm.com/index.php/site/ping\?unique_id=KEY</tt>, substituting your product key as appropriate.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=X2_Hub_Integration&diff=1958X2 Hub Integration2017-02-07T18:44:54Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
X2 Hub Services is an external connectivity provider to various third-party services, including Google Maps, Google Calendar sync, and two factor auth through Twilio. By configuring X2 Hub Services for your X2CRM instance, you will greatly reduces the amount of time and effort required in configuring and managing credentials for each of the supported providers. This allows you to quickly and easily extend the functionality of your CRM with a wide variety of integrations. Please visit the [https://www.x2crm.com/products/ Products page] for purchasing information.<br />
<br />
== Initial Activation ==<br />
[[File:HubActivation.png|200px|thumb|left]]<br />
<br />
Once you have purchased a subscription to X2 Hub Services, please allow up to 24 hours for review and activation. Once your account has been activated, you will receive an email containing your X2 Hub product key. As admin, navigate to the admin panel and visit the "Configure X2 Hub Integration" page under the X2 Hub Services section.<br />
<br />
Paste the product key you received in your activation email into the text field and ensure that "Enabled" is selected. Once you've entered a valid product key and enabled Hub, the status indicator on the Hub integration page should read "Enabled."<br />
<br />
On this screen, you will also be presented with checkboxes for the various external services. If for any reason you wish to disable Hub for a particular service and use your existing integration configuration, please deselect the appropriate service before saving. For example, if you wish to enable X2 Hub for Google Maps integration, but would like to restrict Google Calendar usage to occur through your existing Google configuration, you can deselect the Google Calendar checkbox on the Hub configuration page.<br />
<br />
== Additional Setup ==<br />
Various components of X2CRM require additional setup after enabling Hub. Please see the following sections for addition steps in configuring your X2 Hub Services integration.<br />
<br />
=== Google Calendar Sync ===<br />
After the administrator has configured X2 Hub, users will be able to link their Google Calendars as they would ordinarily. Each user will first want to ensure that their Google ID is configured in their profile settings. Once the user's Google ID is registered, they will be able to link a Google Calendar to any of their calendars in X2CRM.<br />
<br />
First, either create your new calendar, or update an existing calendar. Then, select "Link to Google Calendar." You will be presented with the Google authorization screen and the required permissions. After accepting, you'll be redirected back to the calendar. Next, revisit the calendar configuration, select your desired Google Calendar from the dropdown of available remote calendars, and hit "Save."<br />
<br />
=== Two Factor Authentication ===<br />
[[File:ActivateTwoFactor.png|200px|thumb|right]]<br />
<br />
Once the administrator has activated X2 Hub Services, there is an additional step which must be performed before user's can enable two factor authentication. From the admin panel, navigate to the 'Advanced Security Settings' page under the Security Settings section. Under "Two Factor Authentication," select "X2 Hub Services" as the active two factor auth credentials, before hitting "Save." Once the two factor auth credentials have been selected, each user will be able to enabled two factor authentication for their user login.<br />
<br />
To activate two factor authentication for a particular user, first navigate to the Preferences page. Next, select the checkbox to enable two factor auth. A verification code will be sent to the user's cell phone, which must be entered into the verification text field. If the verification succeeds, next time you login you will be presented with an additional text field to enter your confirmation code. Note: verification codes will be sent to the cell phone number in the user's profile settings.<br />
<br />
In the event that a user is unable to login to the system after enabling two factor auth, such as if their phone is lost or stolen, please notify the administrator. The administrator has privilege to remove the two factor auth requirement for that user by visiting the user's page in the Users module, allowing the user to login again and reconfigure two factor auth.<br />
<br />
== Troubleshooting Connectivity ==<br />
If there are any issues when connecting or the status indicator still reads "Disabled," please verify that your server already meets the requirements for running X2CRM. In particular, you'll want to ensure that you have either installed the PHP curl extension, or that your have enabled allow_url_fopen in your php.ini configuration. Furthermore, connectivity to X2 Hub Services requires that outbound network traffic on port 443 is allowed from the server. To verify network connectivity by command line, on your server execute <tt>curl https://hub.x2crm.com/index.php/site/ping\?unique_id=KEY</tt>, substituting your product key as appropriate.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:HubAdminSection.png&diff=1957File:HubAdminSection.png2017-02-07T18:16:53Z<p>Raymond Colebaugh: X2 Hub Services section in the admin panel</p>
<hr />
<div>X2 Hub Services section in the admin panel</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=X2_Hub_Integration&diff=1956X2 Hub Integration2017-02-07T00:44:03Z<p>Raymond Colebaugh: Created page with "Category:Support X2 Hub Services is an external connectivity provider to various third-party services, including Google Maps, Google Calendar sync, and two factor auth thr..."</p>
<hr />
<div>[[Category:Support]]<br />
X2 Hub Services is an external connectivity provider to various third-party services, including Google Maps, Google Calendar sync, and two factor auth through Twilio. By configuring X2 Hub Services for your X2CRM instance, you will greatly reduces the amount of time and effort required in configuring and managing credentials for each of the supported providers. This allows you to quickly and easily extend the functionality of your CRM with a wide variety of integrations. Please visit the [https://www.x2crm.com/products/ Products page] for purchasing information.<br />
<br />
== Initial Activation ==<br />
[[File:HubActivation.png|200px|thumb|left]]<br />
<br />
Once you have purchased a subscription to X2 Hub Services, please allow up to 24 hours for review and activation. Once your account has been activated, you will receive an email containing your X2 Hub product key. As admin, navigate to the admin panel and visit the "Configure X2 Hub Integration" page under the X2 Hub Services section. Paste the product key you received in your activation email into the text field and ensure that "Enabled" is selected. Once you've entered a valid product key and enabled Hub, the status indicator on the Hub integration page should read "Enabled."<br />
<br />
On this screen, you will also be presented with checkboxes for the various external services. If for any reason you wish to disable Hub for a particular service and use your existing integration configuration, please deselect the appropriate service before saving. For example, if you wish to enable X2 Hub for Google Maps integration, but would like to restrict Google Calendar usage to occur through your existing Google configuration, you can deselect the Google Calendar checkbox on the Hub configuration page.<br />
<br />
== Additional Setup ==<br />
Various components of X2CRM require additional setup after enabling Hub. Please see the following sections for addition steps in configuring your X2 Hub Services integration.<br />
<br />
=== Google Calendar Sync ===<br />
After the administrator has configured X2 Hub, users will be able to link their Google Calendars as they would ordinarily. Each user will first want to ensure that their Google ID is configured in their profile settings. Once the user's Google ID is registered, they will be able to link a Google Calendar to any of their calendars in X2CRM.<br />
<br />
First, either create your new calendar, or update an existing calendar. Then, select "Link to Google Calendar." You will be presented with the Google authorization screen and the required permissions. After accepting, you'll be redirected back to the calendar. Next, revisit the calendar configuration, select your desired Google Calendar from the dropdown of available remote calendars, and hit "Save."<br />
<br />
=== Two Factor Authentication ===<br />
[[File:ActivateTwoFactor.png|200px|thumb|right]]<br />
<br />
Once the administrator has activated X2 Hub Services, there is an additional step which must be performed before user's can enable two factor authentication. From the admin panel, navigate to the 'Advanced Security Settings' page under the Security Settings section. Under "Two Factor Authentication," select "X2 Hub Services" as the active two factor auth credentials, before hitting "Save." Once the two factor auth credentials have been selected, each user will be able to enabled two factor authentication for their user login.<br />
<br />
To activate two factor authentication for a particular user, first navigate to the Preferences page. Next, select the checkbox to enable two factor auth. A verification code will be sent to the user's cell phone, which must be entered into the verification text field. If the verification succeeds, next time you login you will be presented with an additional text field to enter your confirmation code. Note: verification codes will be sent to the cell phone number in the user's profile settings.<br />
<br />
In the event that a user is unable to login to the system after enabling two factor auth, such as if their phone is lost or stolen, please notify the administrator. The administrator has privilege to remove the two factor auth requirement for that user by visiting the user's page in the Users module, allowing the user to login again and reconfigure two factor auth.<br />
<br />
== Troubleshooting Connectivity ==<br />
If there are any issues when connecting or the status indicator still reads "Disabled," please verify that your server already meets the requirements for running X2CRM. In particular, you'll want to ensure that you have either installed the PHP curl extension, or that your have enabled allow_url_fopen in your php.ini configuration. Furthermore, connectivity to X2 Hub Services requires that outbound network traffic on port 443 is allowed from the server. To verify network connectivity by command line, on your server execute <tt>curl https://hub.x2crm.com/index.php/site/ping\?unique_id=KEY</tt>, substituting your product key as appropriate.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:ActivateTwoFactor.png&diff=1955File:ActivateTwoFactor.png2017-02-07T00:37:19Z<p>Raymond Colebaugh: Two Factor auth configuration</p>
<hr />
<div>Two Factor auth configuration</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:HubActivation.png&diff=1954File:HubActivation.png2017-02-07T00:21:10Z<p>Raymond Colebaugh: X2 Hub Services configuration and activation</p>
<hr />
<div>X2 Hub Services configuration and activation</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Configuring_External_Reports&diff=1953Configuring External Reports2016-09-02T18:31:39Z<p>Raymond Colebaugh: Created page with "Category:Support Beginning with version 6.1, the reporting module was expanded to support the use of external reports provided by a Jasper Reports server. While the repor..."</p>
<hr />
<div>[[Category:Support]]<br />
<br />
Beginning with version 6.1, the reporting module was expanded to support the use of external reports provided by a Jasper Reports server. While the reporting module supports a wide array of functionality, sometimes you might want to report on data that may not yet be conveniently aggregated with the controls. In this case, you may choose to create external reports using Jasper Reports, a widely popular reporting tool. Reports are designed within Jasper Studio, a fork of the Eclipse IDE, before being published to the Jasper server for rendering and embedding. You can download the Jasper Studio installer for your operating system on the [http://community.jaspersoft.com/project/jaspersoft-studio/releases Jasper Studio releases page].<br />
<br />
=== Adding a Data Source ===<br />
[[File:jasperDataSources.png|200px|thumb|right|Adding Jasper Data Sources]]<br />
The Jasper Reports server and Jasper Studio tools utilize what are known as "data sources." Each data source represents a connection to an external database, such as your X2CRM MySQL database. To add a data source in the Jasper Server, right-click in your directory tree, and select "Add Resource" > "Data Source." Make sure you are using a JDBC data source with the MariaDB open source MySQL driver, then populate the connection details with the user, password, and hostname for your database, before testing the connection and saving the data source.<br />
<br />
To add a data source in the Jasper Studio, right-click on the data adapters entry in the Repository Explorer, then select "Create Data Adapter." Choose a database JDBC connection, select the MySQL driver, and populate the form with your connection details. Data sources can also be imported from the Jasper Server. However, one detail to keep in mind is that the password for the data source will not be imported, so you'll be required to enter it manually.<br />
<br />
=== Creating and Publishing a Report ===<br />
[[File:jasperReportConfig.png|200px|thumb|right|Configuring a Jasper Report]]<br />
[[File:jasperReportPreview.png|200px|thumb|right|Jasper Report Preview]]<br />
<br />
To design your first report, first download and install the Jasper studio tool. Before creating the report, you will need to add your database as a data source. Then, select "File" > "New" > "Jasper Report." Choose a report template and hit next, enter the path you would like the report to be saved, and then select your data source. Next, you'll want to provide at least a basic SQL query so that Jasper Studio can automatically discover the available fields on your table, such as:<br />
<pre><br />
SELECT * FROM x2_contacts;<br />
</pre><br />
If you already know which fields you want to report on, provide them in the <tt>SELECT</tt> clause, or enter a more advanced query, for example joining with another table. On the next window, you'll be provided with a multiselect where you can choose which fields to include in the report. If you've already restricted your fields with the query, you can simply use the ">>" button to add all fields. After proceeding, you're given the option to group by certain fields if necessary, before finishing the wizard. Finally, you can customize the appearence of your layout on the template, such as setting the headers or moving layout elements like the page number.<br />
<br />
After you've customized your report according to your needs, you can publish the report to the Jasper Server for convenient viewing and embedding. With your Jasper report tab selected within Jasper Studio, select "Project" > "Publish the file on JasperServer." Then, expand the server you would like to publish to, and select the path to store the report before proceeding. You'll be prompted for which data source to use when executing the report. If you've already connected your data source to the Jasper Server, then you can select "Data Source from Repository," then press the ellipses button to select from available data sources.<br />
<br />
=== Embedding a Report in X2CRM ===<br />
[[File:jasperReportEmbed.png|200px|thumb|right|Embedding a Jasper Report]]<br />
Before any actual reports can be embedded within your CRM, you must create your Jasper Server credentials. First, add the Jasper Server to your repository by right-clicking on the "Servers" option under the "Repository Explorer" pane and selecting "Create JasperReports Server Connection." Populate the form with your Jasper Server details, test the connection, and save.<br />
<br />
Next, navigate to the X2CRM admin panel, then select "Jasper Server Integration." Supply the URL of your Jasper Server instance, username, and password, before saving the credentials. It is recommended to create a new ordinary user to connect to the Jasper server, instead of connecting as the Jasper admin. Then, navigate to the reports module and select "External Report." Enter the full path to your report on the Jasper Server, which can be found by right clicking on your report in the Jasper Server UI and selecting "Properties." Finally, you can save the report to have it appear in your saved reports grid.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:JasperReportPreview.png&diff=1952File:JasperReportPreview.png2016-09-02T18:04:26Z<p>Raymond Colebaugh: Jasper Report Preview</p>
<hr />
<div>Jasper Report Preview</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:JasperReportEmbed.png&diff=1951File:JasperReportEmbed.png2016-09-02T18:03:52Z<p>Raymond Colebaugh: Sample Embedded Jasper Report</p>
<hr />
<div>Sample Embedded Jasper Report</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:JasperReportConfig.png&diff=1950File:JasperReportConfig.png2016-09-02T18:03:25Z<p>Raymond Colebaugh: Configuring a Jasper Report</p>
<hr />
<div>Configuring a Jasper Report</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:JasperDataSources.png&diff=1949File:JasperDataSources.png2016-09-02T18:03:06Z<p>Raymond Colebaugh: Configuring Data Sources in Jasper Server and Jasper Studio</p>
<hr />
<div>Configuring Data Sources in Jasper Server and Jasper Studio</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=REST_API_Reference&diff=1947REST API Reference2015-12-21T21:01:33Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Development]]<br />
X2Engine's second-generation HTTP-based API, '''available as of version 4.1''', is (for the most part) REST-ful, and includes many improvements over the original API. <br />
<br />
The source code of the main components used in this API are:<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/controllers/Api2Controller.php protected/controllers/Api2Controller.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/ResponseBehavior.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/util/ResponseUtil.php]<br />
Functional tests for the API can be found alongside the tests for the legacy API, in [https://github.com/X2Engine/X2Engine/tree/master/x2engine/protected/tests/api protected/tests/api].<br />
<br />
= Introduction =<br />
<br />
This API within X2Engine, can be accessed via the URI<br />
index.php/api2<br />
It also:<br />
* Exclusively uses JSON for data input and output<br />
* Tends to use similar URIs for both input and output (distinguishing operations via the request method) <br />
* Uses a variety of server response codes to distinguish error scenarios in the case of an unsuccessful transaction<br />
* Uses the "HTTP Basic Auth" method for authentication<br />
<br />
For example, to create a contact, one would send a <tt>POST</tt> request with its body a JSON-encoded attributes list to the URI <br />
index.php/api2/Contacts<br />
with the <tt>Content-Type</tt> header set to <tt>application/json</tt>, and the request body as (for example):<br />
<syntaxhighlight lang="javascript"><br />
{"firstName":"John","lastName":"Smith","visibility":1,"email":"johnsmith@example.com"}<br />
</syntaxhighlight><br />
<br />
If creation of the contact is successful, the server should respond with status code <tt>201</tt> ("Created"), and the response should contain a <tt>Location</tt> header with the full URL (including protocol) of the newly created contact (in addition to all the attributes of the new contact). If for example the new contact's ID is 123, that URI would be <br />
index.php/api2/Contacts/123.json<br />
and a GET request to that URI would elicit a response from the server whose body contains a JSON-encoded list of attributes.<br />
<br />
== <span class="noglossary">URI</span> Formats, Terminology and Conventions ==<br />
Note, the above example, the URI to which the <tt>POST</tt> request is sent (to create the contact) is referred to in this documentation as a '''base <span class="noglossary">URI</span>'''. Base URIs, when requested using the <tt>GET</tt> method, return a JSON-encoded array, each entry in the array corresponding to a record and being a dictionary of column:value pairs. So for example, if sending <tt>GET</tt> to the contacts model base URI<br />
index.php/api2/Contacts<br />
the response would look like this:<br />
<syntaxhighlight lang="javascript">[...,{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...},...]</syntaxhighlight><br />
Whenever a URI points to an object or resource uniquely identified with a specific database record, that URI is referred to as a '''direct <span class="noglossary">URI</span>'''. Direct URIs end in ".json" and respond to <tt>GET</tt> requests with JSON-encoded dictionary objects of column values for the record as it is in the database. So, the URI in the above example (<tt>index.php/api2/Contacts/123.json</tt>) would respond with:<br />
<syntaxhighlight lang="javascript">{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...}</syntaxhighlight><br />
<br />
Direct URIs will almost always end in ".json", and base URIs will not. '''In general, the following convention applies''' almost universally within the API: ''If the <span class="noglossary">URI</span> ends in ".json", the resource will be a JSON-encoded dictionary object. Otherwise, it will be a JSON-encoded array. In the latter case, if each element of the array is a dictionary object, the dictionary objects should have uniform structure.''<br />
<br />
== Access Credentials ==<br />
To use the API, you will need to obtain X2Engine API credentials, which include a username and an API key. An X2Engine user with administrative privileges can get or set API keys in the Users module, by going to the edit page for that user. To summarize:<br />
# As the administrator, go to the user management module. You'll find it under "Users"<br />
# Click on the desired user<br />
# Click "Update User"<br />
# See the "API Key" field.<br />
<br />
While there is a user in this section called "API User" you should not use it for this version of the API. That user exists to maintain backwards compatibility for a very old version of the API so that users who have set up API connectors with those endpoints will not have their code break. This user will not function with the current API and attempting to use it will generate a 403 error.<br />
<br />
== Explore the API Using Your Web Browser ==<br />
Once you have API credentials, you can examine the web API using your web browser by making <tt>GET</tt> requests to locations within <tt>index.php/api2</tt> (simply by typing them into your browser's location bar). You can get a nicer view of the data returned by the server by installing a JSON viewing plugin in your web browser. A recommended plugin for this purpose is [http://jsonview.com/ JSONView], which is available [https://addons.mozilla.org/en-us/firefox/addon/jsonview/ for Firefox] and [https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc for Google Chrome] (it's an unofficial port, but the developer of the API and author of this article uses it).<br />
<br />
=== Example 1: hello, world ===<br />
Try visiting the following URI within X2Engine:<br />
index.php/api2/appInfo.json<br />
Initially you will be prompted to enter the username and password to complete authentication; enter the API key corresponding to the user in the password field. The above URL should respond with a JSON string containing some basic info about the X2Engine application.<br />
<br />
=== Example 2: direct access ===<br />
You can get the ID of any given account record by going to it inside X2Engine as you normally would and examining the URI. It should generally look something like this:<br />
index.php/accounts/32<br />
or:<br />
index.php/accounts/id/32<br />
In both of the above examples, the primary key value (id) of the record in question is 32. '''To view it in the API:'''<br />
index.php/api2/Accounts/32.json<br />
<br />
Note how in the API, the first letter of "Accounts" in the direct URI is capitalized. This is because active record data in the API is accessed not via specifying the module containing the active record class, or to which the class corresponds, but by specifying the actual ''class name'' to use when accessing (or querying) data.<br />
<br />
=== Example 3: querying ===<br />
index.php/api2/Actions?_order=-id&_limit=3<br />
This will show you the last 3 action records (i.e. emails, call logs, to-do's) created in the system (which the current acting API user has permission to view), in descending order of their primary key values (column "id"). If you have no action records in your system, you should receive an empty array.<br />
<br />
== Prerequisites for API Applications ==<br />
When writing an application to interface with X2Engine via the API, it is required or strongly recommended that your language/coding environment of choice:<br />
<br />
* Have a JSON parsing and encoding library available<br />
* Have a library for making HTTP requests which can:<br />
** Set request headers<br />
** Parse response headers and read the response status code<br />
** Natively support requests using HTTP Basic Auth for access<br />
** Read responses even when the response code is not in the "success" category (2xx)<br />
** Make <tt>POST</tt>, <tt>PATCH</tt>/<tt>PUT</tt> and <tt>DELETE</tt> requests<br />
* Can access the network<br />
<br />
Many high-level languages, such as Perl, PHP, Python and Ruby, meet these requirements. The specific usage of these languages is beyond the scope of this article; you will need to refer to the documentation of the library/libraries in use.<br />
<br />
It is expected in the near future that a growing number of official API access classes (each in a different programming language) will be available for quick and easy development of API applications.<br />
<br />
== Authentication ==<br />
As stated before, the API uses "HTTP Basic" authorization. Many HTTP client libraries will have native methods of setting headers for HTTP basic auth. It is recommended that you use such a method for authentication and read the relevant documentation, rather than setting headers manually, as that will save time and more likely lead to quicker success.<br />
<br />
In all other cases, to authenticate and access the API using this method, each request must include the <tt>Authorization</tt> header. To compose the header, first combine the username and API key into a single string with a colon, as:<br />
{username}:{userKey}<br />
Next, obtain the string in Base-64 encoding. For example, '''<tt>username:password</tt>''' in base 64 is <tt>dXNlcjpwYXNzd29yZA==</tt>. Thus, the resulting header would look like this:<br />
Authorization: Basic dXNlcjpwYXNzd29yZA==<br />
<br />
See also the [[wikipedia:Basic access authentication#Client side|the Wikipedia article on this topic]].<br />
<br />
<br />
=== Caveats ===<br />
'''Note, firstly:''' because headers are sent without any built-in encryption, it is highly recommended that you use the API over HTTPS (HTTP encrypted using TLS), if available, or make API requests only within a network where packets are not easily intercepted.<br />
<br />
Furthermore, if you have password-protected any web directories that contain X2Engine, you will need to make a "Satisfy any" exception for URIs within the API, or clients may not be able to authenticate.<br />
<br />
= Model-based Input and Output =<br />
Most of the modules in X2Engine (i.e. Contacts) will each have a corresponding active record model. This model is what is customized whenever adding a custom field. It is essentially a PHP class that is a child of [[x2doc:X2Model|X2Model]] (see: [[X2Model and Dynamic Fields]] for more information). Almost all API-based functions involving such data objects will contain the name of that class in the URL, i.e.<br />
index.php/api2/Accounts<br />
index.php/api2/Contacts/135/Actions<br />
index.php/api2/Contacts/112.json<br />
In general, the base URI for functions pertaining to models is<br />
index.php/api2/{_class} <br />
where <tt>{_class}</tt> is the class. The direct URI is generally:<br />
index.php/api2/{_class}/{_id}.json<br />
where <tt>{_id}</tt> is the ID of the record to access.<br />
<br />
== Getting Model Classes ==<br />
From the following URI one can obtain a list of models:<br />
index.php/api2/models<br />
Each model in the list is a dictionary containing:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>modelName</tt><br />
|The class of the model<br />
|-<br />
! scope="row" |<tt>title</tt><br />
|The human-readable name of the model<br />
|-<br />
! scope="row" |<tt>attributes</tt><br />
|An array of attribute names<br />
|-<br />
|}<br />
<br />
To include only fully-supported classes versus partially-supported model classes, include the "partialSupport" parameter and have it equal zero:<br />
index.php/api2/models?partialSupport=0<br />
<br />
== Fully-Supported Model Classes ==<br />
As of this writing, X2Engine by default has the following model classes that are fully supported in the API &mdash; meaning, all or nearly all of the most essential functionality that is possible in X2Engine via a web browser, in terms of manipulation of persistent data storage, is also possible via the API.<br />
* <tt>Accounts</tt><br />
* <tt>BugReports</tt><br />
* <tt>Contacts</tt><br />
* <tt>Campaign</tt><br />
* <tt>Opportunity</tt><br />
* <tt>Product</tt><br />
* <tt>Services</tt><br />
<br />
Additionally, any custom modules will also have corresponding active record models fully supported by the API. This should usually be the same name as the module, but without spaces and the first letter always capitalized. If in doubt, to find the model class corresponding to a given module (i.e. a custom module), look in the "models" sub-directory of that module. The name of the file excluding the extension (<tt>.php</tt>) should be the name of the class. For instance, in the file <tt>protected/modules/contacts/models/Contacts.php</tt> there should be the following line:<br />
<syntaxhighlight lang="php"><br />
class Contacts extends X2Model {<br />
</syntaxhighlight><br />
<br />
== Partially-Supported Models ==<br />
Manipulation of data using the following models (or certain aspects of the following models) is not fully supported in the API as of this writing &mdash; meaning, while most operations are possible, some important functionality is not yet possible:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>Actions</tt> <br />
|Actions can be created, updated, viewed, queried and deleted as all other model types. However:<br />
* Action completion and backdating (setting or overwriting the completion date) is not yet supported unless one enables "raw input" (Platinum Edition only), because <tt>completeDate</tt> is a "read-only" field<br />
* Associated "action timer" records (applicable only to Professional/Platinum edition) cannot be accessed or manipulated<br />
* Comparisons based on action description in queries:<br />
** They are unaffected by options that control comparisons; the comparison is un-escaped "LIKE" and the criteria combination operator is effectively "AND" (<tt>_partial=1&_escape=0</tt>). <br />
** They will also be very slow; effectively, the comparison in the query is being performed on a <tt>TEXT</tt> column in a joined table<br />
<br />
The limitations of filtering by action description are endemic to how the "field" is actually stored in a different database table than the contacts, and the type of the column is <tt>TEXT</tt>.<br />
|-<br />
! scope="row" | <tt>Docs</tt> <br />
|Can be accessed/manipulated as with other models. However, "edit permissions" do not work the same way as they do in the application.<br />
|-<br />
! scope="row" | <tt>Groups</tt><br />
|Groups can be queried, viewed and created, albeit only by an administrative user, and in a very limited capacity. Users cannot be added to or removed from groups; manipulating the associated "group-to-user" data is not yet possible.<br />
|-<br />
! scope="row" | <tt>Media</tt><br />
|Media records can be accessed and manipulated as with any other model, but the API does not yet support directly uploading files to go with them.<br />
|-<br />
! scope="row" | <tt>Quote</tt><br />
|Quote records can be accessed and manipulated, but associated "line items" data cannot be viewed or manipulated.<br />
|-<br />
! scope="row" | <tt>X2Leads</tt><br />
|This data type can be fully accessed and manipulated, but there is not yet any built-in action available for directly converting a lead to an opportunity.<br />
|-<br />
! scope="row" | <tt>X2List</tt><br />
|This data type (contact lists in X2Engine) can be accessed and manipulated, but the actual contents of the list (whether dynamic or static) cannot.<br />
|}<br />
<br />
== Creating, Viewing Updating and Deleting Records ==<br />
To create a record, perform a <tt>POST</tt> request to the base URI for the model, i.e.<br />
index.php/api2/Contacts<br />
to create a contact. As mentioned in the example in the [[#Introduction]], the body of the request must be a JSON-encoded library of attributes to set in the model, and the <tt>Content-Type</tt> header must be set to <tt>application/json</tt>.<br />
<br />
To view, update or delete a record, first determine its direct URI within the API, as set in the <tt>Location</tt> header if creation was successful, or as determined via its class and id, i.e.<br />
index.php/api2/Contacts/33.json<br />
to specify a contact record with its id equal to 33.<br />
<br />
To update a record, send a <tt>PUT</tt> or <tt>PATCH</tt> request to a direct URI, and set the body and <tt>Content-Type</tt> header as one would in a <tt>POST</tt> request to create such a record. Finally, to delete the record, send a DELETE request to that same direct URI. It is not necessary in the case of deletion to include a body or set the <tt>Content-Type</tt> header.<br />
<br />
If you need to retrieve the record count of any of the supported classes, then you can append the "count" operation to the URI of a GET request. For example, to retrieve the number of Contacts in the system, one would call the URL:<br />
index.php/api2/Contacts/count<br />
This can also be combined with the _findBy directive to allow count of models matching specific criteria, i.e., to count the number of Contacts named "Thaddeus" the respective API call would be:<br />
index.php/api2/Contacts/count/by:firstName=Thaddeus.json<br />
<br />
== Direct Manipulation by Attributes ==<br />
Given a set of uniquely-identifying attribute names and values, it is possible to directly access and manipulate an existing X2Model-based record by without first querying it. The direct URI for this use case is:<br />
<br />
index.php/api2/{_class}/by:{_findBy}.json<br />
<br />
where the query parameter <tt>{_findBy}</tt> is a list of key and value pairs formatted as <tt>key1=value1;key2=value2</tt>. <br />
<br />
For example: provided the email address "james@example.com" and first name "James" uniquely identify the contact with ID of 457, the following two direct access URIs are equivalent:<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json<br />
index.php/api2/Contacts/457.json<br />
<br />
Furthermore, note what happens in the following scenarios:<br />
<br />
'''If the set of attributes (i.e. email address) is not unique to a single record:''' the find-by-attributes direct URI will return with a 300 status and an [[#Error Objects|error object]] containing the two special properties: ''queryUri'', the URI to query for records by the attributes given, and: ''directUris'', a list containing the direct URI of each matching record.<br />
<br />
''' If no contact matches the attributes given:''' the URI will respond with a 404 status.<br />
<br />
=== Using the first matching record ===<br />
If one wants to forcefully select and use the first record matching the attributes, regardless of how many match, append the <tt>_useFirst</tt> parameter and set equal to 1, i.e.<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json?_useFirst=1<br />
<br />
== Working With Associated Actions ==<br />
The Actions model is unique in that it can be "associated" with almost any other type of model record. Actions records comprise all "history" items on any given record, i.e. emails, calls logged, notes, calendar events and also plain actions. Actions that have an association with another model can be used via clean URIs that point to "within" the associated model.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
<br />
To view/query all actions associated with an active record model of class <tt>{_class}</tt> and id <tt>{_id}</tt>, use the following base URI:<br />
index.php/api2/{_class}/{_id}/Actions<br />
<br />
For instance, one could find all actions, including emails, on contact id=1233, via:<br />
index.php/api2/Contacts/1233/Actions<br />
<br />
To view an individual action of id <tt>{_actionId}</tt>, one can use this direct URI:<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<br />
=== Creating, Updating and Deleting Actions ===<br />
Similar to the the basic model access API function, <tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt> and <tt>DELETE</tt> requests can be sent to associated action URIs to create/modify/delete records just as those URIs can also be used to view data. <tt>POST</tt> to the base URI,<br />
index.php/api2/{_class}/{_id}/Actions<br />
to create a new action associated with model record of class <tt>{_class}</tt> and id <tt>{_id}</tt>. Then, using the direct URI,<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<tt>PATCH</tt>/<tt>PUT</tt>/<tt>DELETE</tt> requests can be used to modify/delete an existing associated action record.<br />
<br />
= Metadata Functions =<br />
There is "structural" metadata that one can retrieve through the API to more effectively determine how to proceed with future API transactions. There are also some functions in the API that pertain to functionality associated with model records, but do not modify data within the records themselves, and create associated metadata.<br />
<br />
== Fields ==<br />
One can access field metadata for a given model class by using the following base URI (which supports querying):<br />
index.php/api2/{_class}/fields<br />
One can directly access the metadata of a field by its name via the following direct URI format:<br />
index.php/api2/{_class}/fields/{_fieldName}.json<br />
For instance, this URI<br />
index.php/api2/Contacts/fields/leadSource.json<br />
would respond with:<br />
<br />
<tt><br />
{"id":"88", "modelName":"Contacts", "fieldName":"leadSource", "attributeLabel":"Lead Source", "modified":"0", "custom":"0", "type":"dropdown", "required":"0", "uniqueConstraint":"0", "safe":"1", "readOnly":"0", "linkType":"103", "searchable":"0", "relevance":"", "isVirtual":"0","defaultValue":null,"keyType":null}<br />
</tt><br />
<br />
== Field-Level Permissions ==<br />
Any given user's access level to a field can be controlled by assigning them to a role and then setting field permissions for that role via ''Manage Roles'' under ''Admin''.<br />
<br />
A <tt>GET</tt> to the following will respond with a dictionary of active record model attributes, each value corresponding to field-level access permissions for that field granted the current acting API user.<br />
index.php/api2/{_class}/fieldPermissions.json<br />
For example, as the default administrator, for model Contacts, getting the following:<br />
index.php/api2/Accounts/fieldPermissions.json<br />
<br />
will respond with:<br />
<br />
<tt>{"leadtype":2, "leadSource":2, "leadstatus":2, "leadDate":2, "leadscore":2, "interest":2, "dealvalue":2, "closedate":2, "rating":2, "dealstatus":2, "name":2, "nameId":1, "id":1, "website":2, "type":2, "visibility":2, "annualRevenue":2, "phone":2, "tickerSymbol":2, "address":2, "city":2, "state":2, "country":2, "zipcode":2, "parentAccount":2, "primaryContact":2, "employees":2, "assignedTo":2, "createDate":1, "description":2, "lastUpdated":1, "lastActivity":1, "updatedBy":1}</tt><br />
<br />
For each of these entries, the number associated with the field indicates the following:<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |0<br />
|No access; when directly accessing a model record, the response data will not include the content of that field<br />
|-<br />
! scope="row" |1<br />
|Read-only access; responses will include the content of that field, but any input to that field will be discarded<br />
|-<br />
! scope="row" |2<br />
|Read/write access. The current API user can both view and edit data in the field.<br />
|-<br />
|}<br />
<br />
== Zapier-Friendly Fields ==<br />
There is similarly a dynamic fields API action that returns an array of fields intended for use by Zapier. The object format returned from this action is described in [https://zapier.com/developer/documentation/reference/#action-fields-custom Action Fields (Custom)] in the Zapier developer documentation.<br />
<br />
The base URI is:<br />
index.php/api2/{_class}/zapierFields<br />
<br />
This action is generally useful for API usage insofar as it will also return ranges of acceptable values for each field, if applicable, in the '''<tt>choices</tt>''' property. For example, if the type of a field is <tt>dropdown</tt>, the dropdown options will be returned in the <tt>choices</tt> property of each element in the returned array. Similarly, if the type of the field is <tt>assignment</tt>, the element's <tt>choices</tt> property will include a list of users and groups. Furthermore, it has the ability to easily select only fields of a given permission level or greater. For this, use the <tt>_permissionLevel</tt> parameter. For example, to get all writable fields in Contacts:<br />
index.php/api2/Contacts/zapierFields?_permissionLevel=2<br />
(see [[#Field-Level Permissions]] for more information)<br />
<br />
Unfortunately, the action does not support querying, although it does not need to for its intended purpose.<br />
<br />
== Dropdowns ==<br />
Note, to get a list of acceptable values for a given model in cases when the field type is not <tt>dropdown</tt>, use the <tt>choices</tt> property of data returned by [[#Zapier-Friendly Fields]].<br />
<br />
Some fields, i.e. lead source in contacts, are of type "dropdown"; their content is intended to be either blank, or an option in a static list. Dropdown menus can be queried at base URI<br />
index.php/api2/dropdowns<br />
Dropdown fields can also be directly accessed via<br />
index.php/api2/dropdowns/{_id}.json<br />
To find out if a field is of type dropdown, and which dropdown menu it uses: the value for <tt>type</tt> in the field's metadata record should be "dropdown", and the <tt>linkType</tt> field should contain the dropdown's ID. So, using the example in [[#Fields]], the corresponding dropdown record is at<br />
index.php/api2/dropdowns/103.json<br />
which contains:<br />
<br />
<tt>{"id":"103", "name":"Lead Source", "options":{"None":"None", "Google":"Google", "Facebook":"Facebook", "Walk In":"Walk In"}, "multi":"0", "parent":null, "parentVal":null}</tt><br />
<br />
Note, for convenience's sake the "options" field won't be returned verbatim as the raw JSON (that's how the options are stored). Rather, that field will be decoded into a sub-dictionary of the overall object.<br />
<br />
== Relationships ==<br />
It is possible to create, view, and delete relationships between supported API models in X2Engine via the API.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Relations functionality is in general accessed within the following base URI:<br />
index.php/api2/{_class}/{_id}/relationships<br />
<br />
So, to view all relationships going to or from account 131:<br />
index.php/api2/Accounts/131/relationships<br />
<br />
To view the contents (related model class and ID) of a specific relationship on the account (let's say the relation record has its id=304281 for instance):<br />
index.php/api2/Accounts/131/relationships/304281.json<br />
<br />
The <tt>Relationships</tt> active record model has the following attributes that can be used in queries:<br />
;id<br />
: Unique numeric identifier for the relationship<br />
;<tt>firstType</tt>, <tt>firstId</tt><br />
: The model class and record ID at one end of the relationship, respectively<br />
;<tt>secondType</tt>, <tt>secondId</tt><br />
: The model class and record ID at the other end of the relationship, respectively<br />
<br />
For instance, to find all outgoing relationships with Accounts to contact id=126:<br />
index.php/api2/Contacts/126/relationships?secondType=Accounts<br />
<br />
=== Adding/Removing Relationships ===<br />
To create a new relationship, sent <tt>POST</tt> to the base URI.<br />
<br />
To remove a relationship, send <tt>DELETE</tt> to the direct URI of the record, i.e. <tt>index.php/api2/Accounts/131/relationships/304281.json</tt> in the earlier example.<br />
<br />
== Tags ==<br />
All basic, fully-supported models in X2Engine should support tagging. Tags cannot be individually modified, but can only be created, viewed, queried and removed, in order to enforce preservation of the important metadata such as who added the tag and at what date.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Tags on a given model record can be retrieved via <tt>GET</tt> at the following base URI:<br />
index.php/api2/{_class}/{_id}/tags<br />
The response should be a flat array of tag names. For example, if account 51 has tags "#customer" and "#local", sending <tt>GET</tt> to the following URI<br />
index.php/api2/Accounts/51/tags<br />
will yield:<br />
<syntaxhighlight lang="javascript">["#customer","#local"]</syntaxhighlight><br />
<br />
To view more extensive metadata of the tag, i.e. who added the tag and at what date:<br />
index.php/api2/{_class}/{_id}/tags/{_tagName}.json<br />
i.e.<br />
index.php/api2/Accounts/51/tags/local.json<br />
The above will return a dictionary of <tt>x2_tags</tt> column names and values. Note, the tag name in the URI must either exclude the preceding hash mark or include it via its corresponding URL encoding sequence, <tt>'''%23'''</tt>. This is because the hash mark is a special character in the HTTP protocol and will interfere with proper resolution of the URI. Using the above example:<br />
index.php/api2/Accounts/51/tags/%23local.json<br />
That URI will return the exact same data as the previous URI; they are considered equivalent.<br />
<br />
=== Adding Tags to a Record ===<br />
Adding tags also utilizes that same URI scheme as with viewing tags. To add one or more tags, send them as string elements in a flat JSON-encoded array via <tt>POST</tt> to that location. For example, to use the previous example, one can apply the tags "#customer" and "#local" from "record 51" to record 52 by <tt>POST</tt>-ing the same JSON returned from a <tt>GET</tt> at: <br />
index.php/api2/Accounts/51/tags<br />
to:<br />
index.php/api2/Accounts/52/tags<br />
<br />
=== Removing Tags ===<br />
To delete a tag, send a <tt>DELETE</tt> request to the direct viewing location of the tag. So, for instance, to delete "#local" from account 51, send <tt>DELETE</tt> to <tt>index.php/api2/Accounts/51/tags/local.json</tt><br />
<br />
= Querying Data =<br />
Almost any "base" URI, which can be used for accessing all records of a type or for creating new records of a type, can also be used for querying records of that type. Responses to queries (and <tt>GET</tt> requests to these URIs in general) will always be JSON-encoded arrays of records, each record represented as an attribute dictionary.<br />
<br />
Options for searching, as well as attributes to match column values against (for filtering), are all specified as query parameters (a.k.a. "get parameters"). The search options, to protect against name collisions with column names, each have names that begin with an underscore, i.e. <tt>_order</tt>.<br />
<br />
== General Search Option Parameters ==<br />
In queries, one can use a variety of advanced options that include sorting, pagination, partial matching, and in some cases tags.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Parameter<br />
! scope="col" | Meaning<br />
! scope="col" | Default<br />
! scope="col" | Usage<br />
|-<br />
! scope="row" | <tt>_escape</tt><br />
|Wildcard usage<br />
|<tt>1</tt><br />
|Set 0 in parameters to allow characters like "%" and "_" to be used as SQL wildcards in search filter attributes. Controls the resultant value of the <tt>$escape</tt> argument sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] in configuring the search. Note, to perform wildcard searches properly, the parameter <tt>_partial</tt> must be set to <tt>1</tt> so that <tt>CDbCriteria</tt> uses <tt>LIKE</tt> for the value comparison. For info on SQL wildcards and comparisons, see: [http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html MySQL: String Comparison Functions]<br />
|-<br />
! scope="row" | <tt>_limit</tt><br />
|Page size<br />
|may vary<br />
|Set to a number to control the maximum number of records to include in the results of the search. The default and maximum page size is <tt>1000</tt>, and in ''Platinum Edition'' this default amount is user-configurable.<br />
|-<br />
! scope="row" | <tt>_or</tt><br />
|Use "OR" operator <br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to make the operator used for combining search criteria <tt>OR</tt> instead of the default, <tt>AND</tt>.<br />
|-<br />
! scope="row" | <tt>_order</tt><br />
|Sorting<br />
|none<br />
|Set equal to the name of a column to sort by, optionally prefixed with a plus or minus sign to specify ascending or descending order (respectively). For example, <tt>_order=-leadScore</tt> in a Contacts query sorts contacts by lead score with the highest-scored contacts first. Note, sorting applies not only to the current page but to the data set spanning all pages. Thus, if the total number of possible results is larger than the page size, the set of results shown in the current page will be affected.<br />
|-<br />
! scope="row" | <tt>_page</tt><br />
|Page number<br />
|<tt>0</tt><br />
|The zero-starting-point page number of the data. Useful for when the query would return more results than the page size specified.<br />
|-<br />
! scope="row" | <tt>_partial</tt><br />
|Partial match<br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to enable partial matching in search filters. This parameter controls the value sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] as the <tt>$partialMatch</tt> argument. If true, the <tt>LIKE</tt> comparison will be used; otherwise, full equality will be used as the comparison.<br />
|-<br />
! scope="row" | <tt>_tagOr</tt><br />
|Inclusive tag search<br />
|<tt>0</tt><br />
|When performing tag-based searches, set to <tt>1</tt> to indicate to include records with ''any'' of the specified tags rather than all of them.<br />
|-<br />
! scope="row" | <tt>_tags</tt><br />
|Has tag(s)<br />
|none<br />
|When querying tag-supporting [[x2doc:X2Model|X2Model]] sub-classes (meaning, those which can be tagged), this can be included and set to a comma-delineated list of tags. This will restrict results to records having all of said tags, or if <tt>_tagOr</tt> is enabled, any of the tags. Note, tag names should not contain their preceding "#" (or, at least should not contain it without URL-encoding it) because "#" is a special character in the HTTP protocol that will interfere with how your request to the server is interpreted.<br />
|-<br />
|}<br />
<br />
== Adding Query Parameters ==<br />
Appending option parameters proceeds as it would for any script that can receive URL-encoded variables via the request: follow the base URI with a question mark, and delineate <tt>[name]=[value]</tt> parameter declarations with ampersands ''(note: this might be different, depending on your web server's configuration, but the nearly-ubiquitous default is ampersand-delineation)''. For example:<br />
index.php/api2/Contacts?_limit=10&firstName=Harry&lastName=P%25&_partial=1&_escape=0&_order=+lastName<br />
This will return the first ten contacts out the list of contacts having first name "Harry" and last name beginning with "P", sorted alphabetically by last name.<br />
<br />
== Searching For Models By Tag ==<br />
In addition to the <tt>_tags</tt> search option, there is a "pretty" dedicated base URI format for tag searching:<br />
index.php/api2/tags/{_tags}/{_class}<br />
So, for instance, to find all contacts with the tags "#customer" and "#important":<br />
index.php/api2/tags/customer,important/Contacts<br />
The above is equivalent to<br />
index.php/api2/Contacts?_tags=customer,important<br />
Note, additional search parameters can also be included. For instance, to return the first ten most recently updated contacts with the above tags:<br />
index.php/api2/tags/customer,important/Contacts?_order=-lastUpdated&_limit=10<br />
<br />
The reason for this is to express tags as categories, and thus in a loose sense "folders" in which one would find records.<br />
<br />
= Web Hooks =<br />
To develop real-time integration, that is to say, to have data sent from X2Engine to a third-party service immediately when a triggering event occurs, the best method is web hooks. Note, there is also the means of sending web requests to external URLs via the "Remote API Call" X2Flow action. For more information about this feature, see [http://www.x2engine.com/x2flow_user_manual/#remoteAPI the X2Flow documentation] for this action.<br />
<br />
While said X2Flow action may suffice in many basic use cases, there are limitations to it that are addressed by web hooks:<br />
* Configuring X2Flow cannot be performed via the API<br />
* The action (making a web request) cannot be performed on a per-user basis (i.e. making a different request for each user)<br />
* The X2Flow user interface is not available in the open source edition of X2Engine<br />
<br />
In cases where the remote end, which will receive data from X2Engine, can be modified with custom code, web hooks are a method of "subscribing" to events in X2Engine via the API. Whenever an event would happen in X2Engine, X2Engine will submit payload data to a return URL specified in the original webhook request, as JSON-encoded data in the request body.<br />
<br />
== Creating Web Hooks ==<br />
To create a hook with an event that depends on a model type (i.e. contact updated vs. account updated), send a JSON-encoded list of hook attributes via <tt>POST</tt> to the following URI:<br />
index.php/api2/{_class}/hooks<br />
Or, to create a web hook associated with a generic event (that does not depend on model type), or a model of determined/unambiguous type (i.e. "Action Complete"):<br />
index.php/api2/hooks<br />
<br />
The POST-ed JSON-encoded dictionary object should contain the following properties: <br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>event</tt><br />
| An event name; see [[#Events Reference]]<br />
|-<br />
! scope="row" | <tt>target_url</tt><br />
| The remote URL to receive the payload<br />
|-<br />
! scope="row" | <tt>directPayload</tt><br />
| (optional): a 1 or 0 (or true/false). See [[#Interpreting Payload Data]]<br />
|-<br />
|}<br />
<br />
Upon successful creation of a web hook, note that the server will respond with a '''201''' status. The body will be the JSON-encoded attributes of the new hook record, and the <tt>Location</tt> header of the response will be set to a URL that can be used to remove the web hook when it is no longer needed (see [[#Deleting Web Hooks]]).<br />
<br />
== Supported Event Names and X2Flow ==<br />
Events for which web hooks can be created are all named after X2Flow trigger class names. Trigger classes (whose files are named after them, like all other class files) are stored in the directory<br />
protected/components/x2flow/triggers<br />
<br />
In fact, any time that <tt>[[x2propdoc:X2Flow.html#_trigger|X2Flow::trigger]]</tt> is called, a corresponding call to <tt>[[x2propdoc:ApiHook.html#_runAll|ApiHook::runAll]]</tt> is also made, to execute all web hooks associated with that trigger event. The payload that is sent for all web hooks corresponding to that event is based on the value of the <tt>$params</tt> argument that is sent to <tt>X2Flow::trigger</tt>. In all cases, the payload is first converted to a pure array, i.e. not containing any objects or resource handles, so that it can be JSON-encoded and sent to the web hook target URL. The exact payload data will differ depending on the action; see "[[#Interpreting Payload Data]]" (coming soon) for further information.<br />
<br />
== Interpreting Payload Data ==<br />
When you create a hook, you will have the option of setting its <tt>directPayload</tt> property to 1. A value of 0/false (the default value) for this field implies that if one of the trigger parameters is named "model" and is a subclass of X2Model (i.e. contact, account, opportunity), the (JSON-encoded) payload data will contain a property <tt>'''resource_url'''</tt> pointing to the URL on X2Engine of that model record. Thus, upon receiving the webhook request from X2Engine, the client end should then make a <tt>GET</tt> request from that URL to retrieve the model part of the payload data. If, on the other hand, <tt>directPayload</tt> is set to 1/true, what will instead happen is that the model will be included in the payload's <tt>'''model'''</tt> property as a dictionary of attribute-value pairs.<br />
<br />
Note, the two most common possible properties <tt>resource_url</tt> and <tt>model</tt> are mutually exclusive; which one if any will get included depends on the <tt>direcPayload</tt>, other properties of the payload include but may not be limited to the following:<br />
<br />
The class of model (and thus the structure of data) that should be expected to come from any given web hook generally depends on the model class to which the event corresponds. For example, the model is of class <tt>Actions</tt> in an <tt>ActionCompleteTrigger</tt> event, but they could be any general record type (contacts, actions, accounts, etc.) in a <tt>RecordCreateTrigger</tt> event.<br />
<br />
== Events Reference ==<br />
'''As of version 4.1.1''', the following trigger events are available in X2Flow:<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Event<br />
! scope="col" | Description<br />
! scope="col" | Payload<br />
|-<br />
|ActionCompleteTrigger<br />
|An action has been marked as complete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who completed the action<br />
|-<br />
|ActionOverdueTrigger<br />
|An action is overdue (cron required)<br />
|'''model'''/'''resource_url''': an action; '''duration''': the amount of time that has passed since the due date of the action<br />
|-<br />
|ActionUncompleteTrigger<br />
|An action has been marked as uncomplete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who marked the action as uncomplete<br />
|-<br />
|CampaignEmailClickTrigger<br />
|A tracking link in a campaign has been clicked<br />
|'''model''': the contact who clicked the link in their campaign email; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignUnsubscribeTrigger<br />
|A contact has unsubscribed from email campaigns<br />
|'''model''': the contact; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignWebActivityTrigger<br />
|''(Professional Edition only)'' A contact who was part of a campaign is visiting your website<br />
|'''model'''/'''resource_url''': the contact; '''campaign''': Name of the email campaign; '''url''': URL of the page that the contact is visiting<br />
|-<br />
|NewsletterEmailClickTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has clicked a tracking link<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign; '''url''': URL visited by the subscriber<br />
|-<br />
|NewsletterEmailOpenTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has opened an email<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|NewsletterUnsubscribeTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has unsubscribed<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|RecordCreateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been created<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordDeleteTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been deleted<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordTagAddTrigger<br />
|A record has been tagged<br />
|'''model'''/'''resource_url''': The record that was tagged; '''tags''': an array of strings (tags) that were added.<br />
|-<br />
|RecordTagRemoveTrigger<br />
|Tags have been deleted from a record<br />
|'''model'''/'''resource_url''': The record whose tags were changed; '''tags''': an array of strings (tags) that were removed<br />
|-<br />
|RecordUpdateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been updated<br />
|'''model'''/'''resource_url''': The data that was updated<br />
|-<br />
|RecordViewTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been viewed<br />
|'''model'''/'''resource_url''': The data that was viewed<br />
|-<br />
|TargetedContentRequestTrigger<br />
|''(Professional Edition Only)'' Targeted content is being accessed on your website<br />
|'''model'''/'''resource_url''': The contact on your website; '''url''': the URL being viewed; '''flowId''': internal parameter (not of much use outside X2Engine)<br />
|-<br />
|UserLoginTrigger<br />
|A user has logged in<br />
|'''user''': the username of the user who has logged in<br />
|-<br />
|UserLogoutTrigger<br />
|A user has logged out<br />
|'''user''': the username of the user who has logged out<br />
|-<br />
|WebActivityTrigger<br />
|''(Professional Edition Only)'' A contact is viewing your website<br />
|'''model'''/'''resource_url''': the contact; '''url''': the URL being viewed; '''probability''' ''(Platinum Edition only)'' percentage confidence of browser-fingerprinting-based match between a web client and a contact<br />
|-<br />
|WebleadTrigger<br />
|A new web lead has come in<br />
|'''model'''/'''resource_url''': the contact associated with the form submission; '''tags''': a list of tags added to the contact through the submission, if applicable <br />
|-<br />
|WorkflowCompleteStageTrigger<br />
|A process stage has been completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine); '''stageNumber''': the number of the stage in the process that was completed.<br />
|-<br />
|WorkflowCompleteTrigger<br />
|A process has been fully completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine)<br />
|-<br />
|WorkflowRevertStageTrigger<br />
|A process stage has been reverted.<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartStageTrigger<br />
|A process stage has been started<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartTrigger<br />
|A process has been started<br />
|Same as WorkflowCompleteTrigger<br />
|-<br />
|}<br />
<br />
== Deleting Web Hooks ==<br />
Given the numeric identifier of a web hook, it can be deleted by sending a <tt>DELETE</tt> to a URI formatted as follows:<br />
index.php/api2/hooks/:{_id}<br />
So, for a web hook with id=74:<br />
index.php/api2/hooks/:74<br />
<br />
The full URL should have been given to the API client in the <tt>Location</tt> header in the response to the original web hook creation request. Upon successful deletion, the server will respond with status code '''204'''.<br />
<br />
= Interpreting Server Responses =<br />
<br />
== HTTP Response Codes ==<br />
It is important to be able to read and interpret status codes (and for that matter, response headers) because in all success scenarios, the API does not respond with data envelopes. By this it is meant the act of wrapping a data model's attributes inside another array containing metadata about the status of the request. This is in effort to streamline and make more elegant code that handles response data. It is also for consistency's sake. A contacts model accessed as a resource object with name ending with <tt>.json</tt> is expected to be ''that contact'', and not rather an array with server response info or other metadata.<br />
<br />
In general, the meanings of response codes closely or exactly follow the formal definitions as defined in [http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html RFC2616], as well as the informal definitions of unconventional status codes (See [[wikipedia:List of HTTP status codes]]). The following table lists each of the possible status codes that the API will respond with, the contexts in which they would appear, and what they indicate.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Code<br />
! scope="col" | Request type or usage scenario<br />
! scope="col" | Meaning<br />
|-<br />
! colspan="3" | '''Success''' (<tt>2XX</tt>)<br />
|-<br />
! scope="row" | <tt>200</tt><br />
|<tt>GET</tt>, <tt>PATCH</tt>, <tt>PUT</tt> <br />
Also, when adding tags via <tt>POST</tt><br />
|''OK.'' General success status<br />
|-<br />
! scope="row" | <tt>201</tt><br />
|<tt>POST</tt><br />
|''Created.'' A record was created; see the value of the <tt>Location</tt> response header for its URL in the API<br />
|-<br />
! scope="row" | <tt>204</tt><br />
|<tt>DELETE</tt><br />
|''No content.'' An action was completed but the server does not need to return any content. The body of the response will be empty.<br />
|-<br />
! colspan="3" | '''Redirection''' (<tt>3XX</tt>)<br />
|-<br />
! scope="row" | <tt>300</tt><br />
|When using the "[[#Direct Manipulation by Attributes|find by attributes]]" direct access URI<br />
|''Multiple choices.'' There criteria specified for direct access (as opposed to querying) match more than one record.<br />
|-<br />
! scope="row" | <tt>303</tt><br />
|<tt>GET</tt><br />
When requesting an object that exists but is not actually associated with the record specified in the request <br />
|''See other.'' The requested resource is somewhere else. The <tt>Location</tt> header should contain the correct, full URL to the resource.<br />
|-<br />
! colspan="3" | '''Client Error''' (<tt>4XX</tt>)<br />
|-<br />
! scope="row" | <tt>400</tt><br />
|All request methods/actions<br />
|''Invalid request.'' General status code for when something is missing, malformed or incorrect in the request headers/body.<br />
|-<br />
! scope="row" | <tt>401</tt><br />
|All request methods/actions.<br />
|Authentication failure or missing authentication credentials.<br />
|-<br />
! scope="row" | <tt>403</tt><br />
|All request methods/actions.<br />
|''Forbidden.'' This may be because the user in API authentication does not actually have permission in X2Engine to perform the specified action. In Platinum Edition, this code can also indicate that the IP address of the API client has been blocked.<br />
|-<br />
! scope="row" | <tt>404</tt><br />
|All request methods/actions.<br />
|''Not found,'' or invalid URI. It is used whenever a specified record to retrieve directly does not exist, but also as a catch-all for a location that the API was not configured to interpret.<br />
|-<br />
! scope="row" | <tt>405</tt><br />
|All request methods/actions<br />
|''Method not allowed.'' The URI is not malformed, but the method of the request being sent to the server is not permitted. An example would be sending a POST request, which is intended for creating new records, to the URI of an existing record, or sending a <tt>DELETE</tt>/<tt>POST</tt>/<tt>PUT</tt> request to a "read-only" resource.<br />
|-<br />
! scope="row" | <tt>408</tt><br />
|All methods; server-generated<br />
|''Request timeout.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>413</tt><br />
|All methods; server-generated<br />
|''Request entity too large.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>415</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt><br />
|''Unsupported media type.'' As of this writing, this happens only whenever the request is sent with a body and the <tt>Content-Type</tt> header is not set to "application/json"<br />
|-<br />
! scope="row" | <tt>422</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt> and <tt>PUT</tt><br />
When creating or updating a record<br />
|''Unprocessable entity.'' This always indicates a data validation error when attempting to set fields of and save an active record model. The client is expected to resolve these issues and resubmit the data with all of the fields formatted in such a way that it is acceptable.<br />
|-<br />
! scope="row" | <tt>429</tt><br />
|''Platinum Edition only''<br />
All request methods/actions<br />
|''Too many requests.'' When rate limiting is enabled in the API settings, the server will use this code to indicate that the API client has been making requests to the API too frequently. When this status is sent, the response should also contain a <tt>Retry-After</tt> header specifying the number of seconds to wait before trying again.<br />
<br />
There is one exception to this, however: when attempting to create a web hook for a given user, event and model name, if the hook limit has already been reached for a user, event and model name, a 429 without <tt>Retry-After</tt> would be sent, and this implies that no more hooks for that combination should be created.<br />
|-<br />
! colspan="3" | '''Server Error''' (<tt>5XX</tt>)<br />
|-<br />
! scope="row" | <tt>500</tt><br />
|All request methods/actions<br />
|''Internal server error.'' General status for when something isn't right on the server. PHP and database errors will produce this status code.<br />
|-<br />
! scope="row" | <tt>501</tt><br />
|All request methods/actions<br />
|''Not implemented.'' The API should use this code to denote that a given method, action, etc. is not yet available, but may be in the future.<br />
|-<br />
! scope="row" | <tt>503</tt><br />
|''Professional and Platinum Editions''<br />
All request methods/actions<br />
|''Unavailable.'' X2Engine and/or its API service has been disabled/locked by an administrator.<br />
|-<br />
|}<br />
<br />
== Error Objects ==<br />
In all error responses produced by the API and not by the server itself, the body of the response will be a JSON containing metadata about the error and the response. This is for compatibility with less-than-satisfactory client libraries which cannot read the actual response code or headers, but might be able to read the response body when the code is not in the success (<tt>2XX</tt>) range. In such cases, the response will be a JSON-encoded object with at least the following properties:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row | httpHeaders<br />
|A dictionary of HTTP headers that were set deliberately by the API (as opposed to automatically, by the web server software) in the response.<br />
|-<br />
! scope="row | message<br />
|A general message about what happened and why. This will in most cases also be saved in the API log.<br />
|-<br />
! scope="row | error<br />
|Boolean true/false; will generally be true.<br />
|-<br />
! scope="row | status<br />
|The HTTP status code that was sent in the response header<br />
|-<br />
|}<br />
In some cases, the response JSON may include the following additional properties:<br />
{|class="wikitable"<br />
|-<br />
! scope="row | errors<br />
| A dictionary of validation errors encountered when attempting to save an active record model; included with responses of status code '''422'''.<br />
The structure will be indexed by attribute name, each entry containing a list of applicable validation errors. It is essentially the resulting value of the [[yii:CModel#errors-detail|CModel.errors]] property.<br />
|-<br />
|}</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=REST_API_Reference&diff=1946REST API Reference2015-12-21T20:59:46Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Development]]<br />
X2Engine's second-generation HTTP-based API, '''available as of version 4.1''', is (for the most part) REST-ful, and includes many improvements over the original API. <br />
<br />
The source code of the main components used in this API are:<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/controllers/Api2Controller.php protected/controllers/Api2Controller.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/ResponseBehavior.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/util/ResponseUtil.php]<br />
Functional tests for the API can be found alongside the tests for the legacy API, in [https://github.com/X2Engine/X2Engine/tree/master/x2engine/protected/tests/api protected/tests/api].<br />
<br />
= Introduction =<br />
<br />
This API within X2Engine, can be accessed via the URI<br />
index.php/api2<br />
It also:<br />
* Exclusively uses JSON for data input and output<br />
* Tends to use similar URIs for both input and output (distinguishing operations via the request method) <br />
* Uses a variety of server response codes to distinguish error scenarios in the case of an unsuccessful transaction<br />
* Uses the "HTTP Basic Auth" method for authentication<br />
<br />
For example, to create a contact, one would send a <tt>POST</tt> request with its body a JSON-encoded attributes list to the URI <br />
index.php/api2/Contacts<br />
with the <tt>Content-Type</tt> header set to <tt>application/json</tt>, and the request body as (for example):<br />
<syntaxhighlight lang="javascript"><br />
{"firstName":"John","lastName":"Smith","visibility":1,"email":"johnsmith@example.com"}<br />
</syntaxhighlight><br />
<br />
If creation of the contact is successful, the server should respond with status code <tt>201</tt> ("Created"), and the response should contain a <tt>Location</tt> header with the full URL (including protocol) of the newly created contact (in addition to all the attributes of the new contact). If for example the new contact's ID is 123, that URI would be <br />
index.php/api2/Contacts/123.json<br />
and a GET request to that URI would elicit a response from the server whose body contains a JSON-encoded list of attributes.<br />
<br />
== <span class="noglossary">URI</span> Formats, Terminology and Conventions ==<br />
Note, the above example, the URI to which the <tt>POST</tt> request is sent (to create the contact) is referred to in this documentation as a '''base <span class="noglossary">URI</span>'''. Base URIs, when requested using the <tt>GET</tt> method, return a JSON-encoded array, each entry in the array corresponding to a record and being a dictionary of column:value pairs. So for example, if sending <tt>GET</tt> to the contacts model base URI<br />
index.php/api2/Contacts<br />
the response would look like this:<br />
<syntaxhighlight lang="javascript">[...,{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...},...]</syntaxhighlight><br />
Whenever a URI points to an object or resource uniquely identified with a specific database record, that URI is referred to as a '''direct <span class="noglossary">URI</span>'''. Direct URIs end in ".json" and respond to <tt>GET</tt> requests with JSON-encoded dictionary objects of column values for the record as it is in the database. So, the URI in the above example (<tt>index.php/api2/Contacts/123.json</tt>) would respond with:<br />
<syntaxhighlight lang="javascript">{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...}</syntaxhighlight><br />
<br />
Direct URIs will almost always end in ".json", and base URIs will not. '''In general, the following convention applies''' almost universally within the API: ''If the <span class="noglossary">URI</span> ends in ".json", the resource will be a JSON-encoded dictionary object. Otherwise, it will be a JSON-encoded array. In the latter case, if each element of the array is a dictionary object, the dictionary objects should have uniform structure.''<br />
<br />
== Access Credentials ==<br />
To use the API, you will need to obtain X2Engine API credentials, which include a username and an API key. An X2Engine user with administrative privileges can get or set API keys in the Users module, by going to the edit page for that user. To summarize:<br />
# As the administrator, go to the user management module. You'll find it under "Users"<br />
# Click on the desired user<br />
# Click "Update User"<br />
# See the "API Key" field.<br />
<br />
While there is a user in this section called "API User" you should not use it for this version of the API. That user exists to maintain backwards compatibility for a very old version of the API so that users who have set up API connectors with those endpoints will not have their code break. This user will not function with the current API and attempting to use it will generate a 403 error.<br />
<br />
== Explore the API Using Your Web Browser ==<br />
Once you have API credentials, you can examine the web API using your web browser by making <tt>GET</tt> requests to locations within <tt>index.php/api2</tt> (simply by typing them into your browser's location bar). You can get a nicer view of the data returned by the server by installing a JSON viewing plugin in your web browser. A recommended plugin for this purpose is [http://jsonview.com/ JSONView], which is available [https://addons.mozilla.org/en-us/firefox/addon/jsonview/ for Firefox] and [https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc for Google Chrome] (it's an unofficial port, but the developer of the API and author of this article uses it).<br />
<br />
=== Example 1: hello, world ===<br />
Try visiting the following URI within X2Engine:<br />
index.php/api2/appInfo.json<br />
Initially you will be prompted to enter the username and password to complete authentication; enter the API key corresponding to the user in the password field. The above URL should respond with a JSON string containing some basic info about the X2Engine application.<br />
<br />
=== Example 2: direct access ===<br />
You can get the ID of any given account record by going to it inside X2Engine as you normally would and examining the URI. It should generally look something like this:<br />
index.php/accounts/32<br />
or:<br />
index.php/accounts/id/32<br />
In both of the above examples, the primary key value (id) of the record in question is 32. '''To view it in the API:'''<br />
index.php/api2/Accounts/32.json<br />
<br />
Note how in the API, the first letter of "Accounts" in the direct URI is capitalized. This is because active record data in the API is accessed not via specifying the module containing the active record class, or to which the class corresponds, but by specifying the actual ''class name'' to use when accessing (or querying) data.<br />
<br />
=== Example 3: querying ===<br />
index.php/api2/Actions?_order=-id&_limit=3<br />
This will show you the last 3 action records (i.e. emails, call logs, to-do's) created in the system (which the current acting API user has permission to view), in descending order of their primary key values (column "id"). If you have no action records in your system, you should receive an empty array.<br />
<br />
== Prerequisites for API Applications ==<br />
When writing an application to interface with X2Engine via the API, it is required or strongly recommended that your language/coding environment of choice:<br />
<br />
* Have a JSON parsing and encoding library available<br />
* Have a library for making HTTP requests which can:<br />
** Set request headers<br />
** Parse response headers and read the response status code<br />
** Natively support requests using HTTP Basic Auth for access<br />
** Read responses even when the response code is not in the "success" category (2xx)<br />
** Make <tt>POST</tt>, <tt>PATCH</tt>/<tt>PUT</tt> and <tt>DELETE</tt> requests<br />
* Can access the network<br />
<br />
Many high-level languages, such as Perl, PHP, Python and Ruby, meet these requirements. The specific usage of these languages is beyond the scope of this article; you will need to refer to the documentation of the library/libraries in use.<br />
<br />
It is expected in the near future that a growing number of official API access classes (each in a different programming language) will be available for quick and easy development of API applications.<br />
<br />
== Authentication ==<br />
As stated before, the API uses "HTTP Basic" authorization. Many HTTP client libraries will have native methods of setting headers for HTTP basic auth. It is recommended that you use such a method for authentication and read the relevant documentation, rather than setting headers manually, as that will save time and more likely lead to quicker success.<br />
<br />
In all other cases, to authenticate and access the API using this method, each request must include the <tt>Authorization</tt> header. To compose the header, first combine the username and API key into a single string with a colon, as:<br />
{username}:{userKey}<br />
Next, obtain the string in Base-64 encoding. For example, '''<tt>username:password</tt>''' in base 64 is <tt>dXNlcjpwYXNzd29yZA==</tt>. Thus, the resulting header would look like this:<br />
Authorization: Basic dXNlcjpwYXNzd29yZA==<br />
<br />
See also the [[wikipedia:Basic access authentication#Client side|the Wikipedia article on this topic]].<br />
<br />
<br />
=== Caveats ===<br />
'''Note, firstly:''' because headers are sent without any built-in encryption, it is highly recommended that you use the API over HTTPS (HTTP encrypted using TLS), if available, or make API requests only within a network where packets are not easily intercepted.<br />
<br />
Furthermore, if you have password-protected any web directories that contain X2Engine, you will need to make a "Satisfy any" exception for URIs within the API, or clients may not be able to authenticate.<br />
<br />
= Model-based Input and Output =<br />
Most of the modules in X2Engine (i.e. Contacts) will each have a corresponding active record model. This model is what is customized whenever adding a custom field. It is essentially a PHP class that is a child of [[x2doc:X2Model|X2Model]] (see: [[X2Model and Dynamic Fields]] for more information). Almost all API-based functions involving such data objects will contain the name of that class in the URL, i.e.<br />
index.php/api2/Accounts<br />
index.php/api2/Contacts/135/Actions<br />
index.php/api2/Contacts/112.json<br />
In general, the base URI for functions pertaining to models is<br />
index.php/api2/{_class} <br />
where <tt>{_class}</tt> is the class. The direct URI is generally:<br />
index.php/api2/{_class}/{_id}.json<br />
where <tt>{_id}</tt> is the ID of the record to access.<br />
<br />
== Getting Model Classes ==<br />
From the following URI one can obtain a list of models:<br />
index.php/api2/models<br />
Each model in the list is a dictionary containing:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>modelName</tt><br />
|The class of the model<br />
|-<br />
! scope="row" |<tt>title</tt><br />
|The human-readable name of the model<br />
|-<br />
! scope="row" |<tt>attributes</tt><br />
|An array of attribute names<br />
|-<br />
|}<br />
<br />
To include only fully-supported classes versus partially-supported model classes, include the "partialSupport" parameter and have it equal zero:<br />
index.php/api2/models?partialSupport=0<br />
<br />
== Fully-Supported Model Classes ==<br />
As of this writing, X2Engine by default has the following model classes that are fully supported in the API &mdash; meaning, all or nearly all of the most essential functionality that is possible in X2Engine via a web browser, in terms of manipulation of persistent data storage, is also possible via the API.<br />
* <tt>Accounts</tt><br />
* <tt>BugReports</tt><br />
* <tt>Contacts</tt><br />
* <tt>Campaign</tt><br />
* <tt>Opportunity</tt><br />
* <tt>Product</tt><br />
* <tt>Services</tt><br />
<br />
Additionally, any custom modules will also have corresponding active record models fully supported by the API. This should usually be the same name as the module, but without spaces and the first letter always capitalized. If in doubt, to find the model class corresponding to a given module (i.e. a custom module), look in the "models" sub-directory of that module. The name of the file excluding the extension (<tt>.php</tt>) should be the name of the class. For instance, in the file <tt>protected/modules/contacts/models/Contacts.php</tt> there should be the following line:<br />
<syntaxhighlight lang="php"><br />
class Contacts extends X2Model {<br />
</syntaxhighlight><br />
<br />
== Partially-Supported Models ==<br />
Manipulation of data using the following models (or certain aspects of the following models) is not fully supported in the API as of this writing &mdash; meaning, while most operations are possible, some important functionality is not yet possible:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>Actions</tt> <br />
|Actions can be created, updated, viewed, queried and deleted as all other model types. However:<br />
* Action completion and backdating (setting or overwriting the completion date) is not yet supported unless one enables "raw input" (Platinum Edition only), because <tt>completeDate</tt> is a "read-only" field<br />
* Associated "action timer" records (applicable only to Professional/Platinum edition) cannot be accessed or manipulated<br />
* Comparisons based on action description in queries:<br />
** They are unaffected by options that control comparisons; the comparison is un-escaped "LIKE" and the criteria combination operator is effectively "AND" (<tt>_partial=1&_escape=0</tt>). <br />
** They will also be very slow; effectively, the comparison in the query is being performed on a <tt>TEXT</tt> column in a joined table<br />
<br />
The limitations of filtering by action description are endemic to how the "field" is actually stored in a different database table than the contacts, and the type of the column is <tt>TEXT</tt>.<br />
|-<br />
! scope="row" | <tt>Docs</tt> <br />
|Can be accessed/manipulated as with other models. However, "edit permissions" do not work the same way as they do in the application.<br />
|-<br />
! scope="row" | <tt>Groups</tt><br />
|Groups can be queried, viewed and created, albeit only by an administrative user, and in a very limited capacity. Users cannot be added to or removed from groups; manipulating the associated "group-to-user" data is not yet possible.<br />
|-<br />
! scope="row" | <tt>Media</tt><br />
|Media records can be accessed and manipulated as with any other model, but the API does not yet support directly uploading files to go with them.<br />
|-<br />
! scope="row" | <tt>Quote</tt><br />
|Quote records can be accessed and manipulated, but associated "line items" data cannot be viewed or manipulated.<br />
|-<br />
! scope="row" | <tt>X2Leads</tt><br />
|This data type can be fully accessed and manipulated, but there is not yet any built-in action available for directly converting a lead to an opportunity.<br />
|-<br />
! scope="row" | <tt>X2List</tt><br />
|This data type (contact lists in X2Engine) can be accessed and manipulated, but the actual contents of the list (whether dynamic or static) cannot.<br />
|}<br />
<br />
== Creating, Viewing Updating and Deleting Records ==<br />
To create a record, perform a <tt>POST</tt> request to the base URI for the model, i.e.<br />
index.php/api2/Contacts<br />
to create a contact. As mentioned in the example in the [[#Introduction]], the body of the request must be a JSON-encoded library of attributes to set in the model, and the <tt>Content-Type</tt> header must be set to <tt>application/json</tt>.<br />
<br />
To view, update or delete a record, first determine its direct URI within the API, as set in the <tt>Location</tt> header if creation was successful, or as determined via its class and id, i.e.<br />
index.php/api2/Contacts/33.json<br />
to specify a contact record with its id equal to 33.<br />
<br />
To update a record, send a <tt>PUT</tt> or <tt>PATCH</tt> request to a direct URI, and set the body and <tt>Content-Type</tt> header as one would in a <tt>POST</tt> request to create such a record. Finally, to delete the record, send a DELETE request to that same direct URI. It is not necessary in the case of deletion to include a body or set the <tt>Content-Type</tt> header.<br />
<br />
== Direct Manipulation by Attributes ==<br />
Given a set of uniquely-identifying attribute names and values, it is possible to directly access and manipulate an existing X2Model-based record by without first querying it. The direct URI for this use case is:<br />
<br />
index.php/api2/{_class}/by:{_findBy}.json<br />
<br />
where the query parameter <tt>{_findBy}</tt> is a list of key and value pairs formatted as <tt>key1=value1;key2=value2</tt>. <br />
<br />
For example: provided the email address "james@example.com" and first name "James" uniquely identify the contact with ID of 457, the following two direct access URIs are equivalent:<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json<br />
index.php/api2/Contacts/457.json<br />
<br />
Furthermore, note what happens in the following scenarios:<br />
<br />
'''If the set of attributes (i.e. email address) is not unique to a single record:''' the find-by-attributes direct URI will return with a 300 status and an [[#Error Objects|error object]] containing the two special properties: ''queryUri'', the URI to query for records by the attributes given, and: ''directUris'', a list containing the direct URI of each matching record.<br />
<br />
''' If no contact matches the attributes given:''' the URI will respond with a 404 status.<br />
<br />
=== Using the first matching record ===<br />
If one wants to forcefully select and use the first record matching the attributes, regardless of how many match, append the <tt>_useFirst</tt> parameter and set equal to 1, i.e.<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json?_useFirst=1<br />
<br />
== Working With Associated Actions ==<br />
The Actions model is unique in that it can be "associated" with almost any other type of model record. Actions records comprise all "history" items on any given record, i.e. emails, calls logged, notes, calendar events and also plain actions. Actions that have an association with another model can be used via clean URIs that point to "within" the associated model.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
<br />
To view/query all actions associated with an active record model of class <tt>{_class}</tt> and id <tt>{_id}</tt>, use the following base URI:<br />
index.php/api2/{_class}/{_id}/Actions<br />
<br />
For instance, one could find all actions, including emails, on contact id=1233, via:<br />
index.php/api2/Contacts/1233/Actions<br />
<br />
To view an individual action of id <tt>{_actionId}</tt>, one can use this direct URI:<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<br />
If you need to retrieve the record count of any of the supported classes, then you can append the "count" operation to the URI of a GET request. For example, to retrieve the number of Contacts in the system, one would call the URL:<br />
index.php/api2/Contacts/count<br />
This can also be combined with the _findBy directive to allow count of models matching specific criteria, i.e., to count the number of Contacts named "Thaddeus" the respective API call would be:<br />
index.php/api2/Contacts/count/by:firstName=Thaddeus.json<br />
<br />
=== Creating, Updating and Deleting Actions ===<br />
Similar to the the basic model access API function, <tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt> and <tt>DELETE</tt> requests can be sent to associated action URIs to create/modify/delete records just as those URIs can also be used to view data. <tt>POST</tt> to the base URI,<br />
index.php/api2/{_class}/{_id}/Actions<br />
to create a new action associated with model record of class <tt>{_class}</tt> and id <tt>{_id}</tt>. Then, using the direct URI,<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<tt>PATCH</tt>/<tt>PUT</tt>/<tt>DELETE</tt> requests can be used to modify/delete an existing associated action record.<br />
<br />
= Metadata Functions =<br />
There is "structural" metadata that one can retrieve through the API to more effectively determine how to proceed with future API transactions. There are also some functions in the API that pertain to functionality associated with model records, but do not modify data within the records themselves, and create associated metadata.<br />
<br />
== Fields ==<br />
One can access field metadata for a given model class by using the following base URI (which supports querying):<br />
index.php/api2/{_class}/fields<br />
One can directly access the metadata of a field by its name via the following direct URI format:<br />
index.php/api2/{_class}/fields/{_fieldName}.json<br />
For instance, this URI<br />
index.php/api2/Contacts/fields/leadSource.json<br />
would respond with:<br />
<br />
<tt><br />
{"id":"88", "modelName":"Contacts", "fieldName":"leadSource", "attributeLabel":"Lead Source", "modified":"0", "custom":"0", "type":"dropdown", "required":"0", "uniqueConstraint":"0", "safe":"1", "readOnly":"0", "linkType":"103", "searchable":"0", "relevance":"", "isVirtual":"0","defaultValue":null,"keyType":null}<br />
</tt><br />
<br />
== Field-Level Permissions ==<br />
Any given user's access level to a field can be controlled by assigning them to a role and then setting field permissions for that role via ''Manage Roles'' under ''Admin''.<br />
<br />
A <tt>GET</tt> to the following will respond with a dictionary of active record model attributes, each value corresponding to field-level access permissions for that field granted the current acting API user.<br />
index.php/api2/{_class}/fieldPermissions.json<br />
For example, as the default administrator, for model Contacts, getting the following:<br />
index.php/api2/Accounts/fieldPermissions.json<br />
<br />
will respond with:<br />
<br />
<tt>{"leadtype":2, "leadSource":2, "leadstatus":2, "leadDate":2, "leadscore":2, "interest":2, "dealvalue":2, "closedate":2, "rating":2, "dealstatus":2, "name":2, "nameId":1, "id":1, "website":2, "type":2, "visibility":2, "annualRevenue":2, "phone":2, "tickerSymbol":2, "address":2, "city":2, "state":2, "country":2, "zipcode":2, "parentAccount":2, "primaryContact":2, "employees":2, "assignedTo":2, "createDate":1, "description":2, "lastUpdated":1, "lastActivity":1, "updatedBy":1}</tt><br />
<br />
For each of these entries, the number associated with the field indicates the following:<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |0<br />
|No access; when directly accessing a model record, the response data will not include the content of that field<br />
|-<br />
! scope="row" |1<br />
|Read-only access; responses will include the content of that field, but any input to that field will be discarded<br />
|-<br />
! scope="row" |2<br />
|Read/write access. The current API user can both view and edit data in the field.<br />
|-<br />
|}<br />
<br />
== Zapier-Friendly Fields ==<br />
There is similarly a dynamic fields API action that returns an array of fields intended for use by Zapier. The object format returned from this action is described in [https://zapier.com/developer/documentation/reference/#action-fields-custom Action Fields (Custom)] in the Zapier developer documentation.<br />
<br />
The base URI is:<br />
index.php/api2/{_class}/zapierFields<br />
<br />
This action is generally useful for API usage insofar as it will also return ranges of acceptable values for each field, if applicable, in the '''<tt>choices</tt>''' property. For example, if the type of a field is <tt>dropdown</tt>, the dropdown options will be returned in the <tt>choices</tt> property of each element in the returned array. Similarly, if the type of the field is <tt>assignment</tt>, the element's <tt>choices</tt> property will include a list of users and groups. Furthermore, it has the ability to easily select only fields of a given permission level or greater. For this, use the <tt>_permissionLevel</tt> parameter. For example, to get all writable fields in Contacts:<br />
index.php/api2/Contacts/zapierFields?_permissionLevel=2<br />
(see [[#Field-Level Permissions]] for more information)<br />
<br />
Unfortunately, the action does not support querying, although it does not need to for its intended purpose.<br />
<br />
== Dropdowns ==<br />
Note, to get a list of acceptable values for a given model in cases when the field type is not <tt>dropdown</tt>, use the <tt>choices</tt> property of data returned by [[#Zapier-Friendly Fields]].<br />
<br />
Some fields, i.e. lead source in contacts, are of type "dropdown"; their content is intended to be either blank, or an option in a static list. Dropdown menus can be queried at base URI<br />
index.php/api2/dropdowns<br />
Dropdown fields can also be directly accessed via<br />
index.php/api2/dropdowns/{_id}.json<br />
To find out if a field is of type dropdown, and which dropdown menu it uses: the value for <tt>type</tt> in the field's metadata record should be "dropdown", and the <tt>linkType</tt> field should contain the dropdown's ID. So, using the example in [[#Fields]], the corresponding dropdown record is at<br />
index.php/api2/dropdowns/103.json<br />
which contains:<br />
<br />
<tt>{"id":"103", "name":"Lead Source", "options":{"None":"None", "Google":"Google", "Facebook":"Facebook", "Walk In":"Walk In"}, "multi":"0", "parent":null, "parentVal":null}</tt><br />
<br />
Note, for convenience's sake the "options" field won't be returned verbatim as the raw JSON (that's how the options are stored). Rather, that field will be decoded into a sub-dictionary of the overall object.<br />
<br />
== Relationships ==<br />
It is possible to create, view, and delete relationships between supported API models in X2Engine via the API.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Relations functionality is in general accessed within the following base URI:<br />
index.php/api2/{_class}/{_id}/relationships<br />
<br />
So, to view all relationships going to or from account 131:<br />
index.php/api2/Accounts/131/relationships<br />
<br />
To view the contents (related model class and ID) of a specific relationship on the account (let's say the relation record has its id=304281 for instance):<br />
index.php/api2/Accounts/131/relationships/304281.json<br />
<br />
The <tt>Relationships</tt> active record model has the following attributes that can be used in queries:<br />
;id<br />
: Unique numeric identifier for the relationship<br />
;<tt>firstType</tt>, <tt>firstId</tt><br />
: The model class and record ID at one end of the relationship, respectively<br />
;<tt>secondType</tt>, <tt>secondId</tt><br />
: The model class and record ID at the other end of the relationship, respectively<br />
<br />
For instance, to find all outgoing relationships with Accounts to contact id=126:<br />
index.php/api2/Contacts/126/relationships?secondType=Accounts<br />
<br />
=== Adding/Removing Relationships ===<br />
To create a new relationship, sent <tt>POST</tt> to the base URI.<br />
<br />
To remove a relationship, send <tt>DELETE</tt> to the direct URI of the record, i.e. <tt>index.php/api2/Accounts/131/relationships/304281.json</tt> in the earlier example.<br />
<br />
== Tags ==<br />
All basic, fully-supported models in X2Engine should support tagging. Tags cannot be individually modified, but can only be created, viewed, queried and removed, in order to enforce preservation of the important metadata such as who added the tag and at what date.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Tags on a given model record can be retrieved via <tt>GET</tt> at the following base URI:<br />
index.php/api2/{_class}/{_id}/tags<br />
The response should be a flat array of tag names. For example, if account 51 has tags "#customer" and "#local", sending <tt>GET</tt> to the following URI<br />
index.php/api2/Accounts/51/tags<br />
will yield:<br />
<syntaxhighlight lang="javascript">["#customer","#local"]</syntaxhighlight><br />
<br />
To view more extensive metadata of the tag, i.e. who added the tag and at what date:<br />
index.php/api2/{_class}/{_id}/tags/{_tagName}.json<br />
i.e.<br />
index.php/api2/Accounts/51/tags/local.json<br />
The above will return a dictionary of <tt>x2_tags</tt> column names and values. Note, the tag name in the URI must either exclude the preceding hash mark or include it via its corresponding URL encoding sequence, <tt>'''%23'''</tt>. This is because the hash mark is a special character in the HTTP protocol and will interfere with proper resolution of the URI. Using the above example:<br />
index.php/api2/Accounts/51/tags/%23local.json<br />
That URI will return the exact same data as the previous URI; they are considered equivalent.<br />
<br />
=== Adding Tags to a Record ===<br />
Adding tags also utilizes that same URI scheme as with viewing tags. To add one or more tags, send them as string elements in a flat JSON-encoded array via <tt>POST</tt> to that location. For example, to use the previous example, one can apply the tags "#customer" and "#local" from "record 51" to record 52 by <tt>POST</tt>-ing the same JSON returned from a <tt>GET</tt> at: <br />
index.php/api2/Accounts/51/tags<br />
to:<br />
index.php/api2/Accounts/52/tags<br />
<br />
=== Removing Tags ===<br />
To delete a tag, send a <tt>DELETE</tt> request to the direct viewing location of the tag. So, for instance, to delete "#local" from account 51, send <tt>DELETE</tt> to <tt>index.php/api2/Accounts/51/tags/local.json</tt><br />
<br />
= Querying Data =<br />
Almost any "base" URI, which can be used for accessing all records of a type or for creating new records of a type, can also be used for querying records of that type. Responses to queries (and <tt>GET</tt> requests to these URIs in general) will always be JSON-encoded arrays of records, each record represented as an attribute dictionary.<br />
<br />
Options for searching, as well as attributes to match column values against (for filtering), are all specified as query parameters (a.k.a. "get parameters"). The search options, to protect against name collisions with column names, each have names that begin with an underscore, i.e. <tt>_order</tt>.<br />
<br />
== General Search Option Parameters ==<br />
In queries, one can use a variety of advanced options that include sorting, pagination, partial matching, and in some cases tags.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Parameter<br />
! scope="col" | Meaning<br />
! scope="col" | Default<br />
! scope="col" | Usage<br />
|-<br />
! scope="row" | <tt>_escape</tt><br />
|Wildcard usage<br />
|<tt>1</tt><br />
|Set 0 in parameters to allow characters like "%" and "_" to be used as SQL wildcards in search filter attributes. Controls the resultant value of the <tt>$escape</tt> argument sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] in configuring the search. Note, to perform wildcard searches properly, the parameter <tt>_partial</tt> must be set to <tt>1</tt> so that <tt>CDbCriteria</tt> uses <tt>LIKE</tt> for the value comparison. For info on SQL wildcards and comparisons, see: [http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html MySQL: String Comparison Functions]<br />
|-<br />
! scope="row" | <tt>_limit</tt><br />
|Page size<br />
|may vary<br />
|Set to a number to control the maximum number of records to include in the results of the search. The default and maximum page size is <tt>1000</tt>, and in ''Platinum Edition'' this default amount is user-configurable.<br />
|-<br />
! scope="row" | <tt>_or</tt><br />
|Use "OR" operator <br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to make the operator used for combining search criteria <tt>OR</tt> instead of the default, <tt>AND</tt>.<br />
|-<br />
! scope="row" | <tt>_order</tt><br />
|Sorting<br />
|none<br />
|Set equal to the name of a column to sort by, optionally prefixed with a plus or minus sign to specify ascending or descending order (respectively). For example, <tt>_order=-leadScore</tt> in a Contacts query sorts contacts by lead score with the highest-scored contacts first. Note, sorting applies not only to the current page but to the data set spanning all pages. Thus, if the total number of possible results is larger than the page size, the set of results shown in the current page will be affected.<br />
|-<br />
! scope="row" | <tt>_page</tt><br />
|Page number<br />
|<tt>0</tt><br />
|The zero-starting-point page number of the data. Useful for when the query would return more results than the page size specified.<br />
|-<br />
! scope="row" | <tt>_partial</tt><br />
|Partial match<br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to enable partial matching in search filters. This parameter controls the value sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] as the <tt>$partialMatch</tt> argument. If true, the <tt>LIKE</tt> comparison will be used; otherwise, full equality will be used as the comparison.<br />
|-<br />
! scope="row" | <tt>_tagOr</tt><br />
|Inclusive tag search<br />
|<tt>0</tt><br />
|When performing tag-based searches, set to <tt>1</tt> to indicate to include records with ''any'' of the specified tags rather than all of them.<br />
|-<br />
! scope="row" | <tt>_tags</tt><br />
|Has tag(s)<br />
|none<br />
|When querying tag-supporting [[x2doc:X2Model|X2Model]] sub-classes (meaning, those which can be tagged), this can be included and set to a comma-delineated list of tags. This will restrict results to records having all of said tags, or if <tt>_tagOr</tt> is enabled, any of the tags. Note, tag names should not contain their preceding "#" (or, at least should not contain it without URL-encoding it) because "#" is a special character in the HTTP protocol that will interfere with how your request to the server is interpreted.<br />
|-<br />
|}<br />
<br />
== Adding Query Parameters ==<br />
Appending option parameters proceeds as it would for any script that can receive URL-encoded variables via the request: follow the base URI with a question mark, and delineate <tt>[name]=[value]</tt> parameter declarations with ampersands ''(note: this might be different, depending on your web server's configuration, but the nearly-ubiquitous default is ampersand-delineation)''. For example:<br />
index.php/api2/Contacts?_limit=10&firstName=Harry&lastName=P%25&_partial=1&_escape=0&_order=+lastName<br />
This will return the first ten contacts out the list of contacts having first name "Harry" and last name beginning with "P", sorted alphabetically by last name.<br />
<br />
== Searching For Models By Tag ==<br />
In addition to the <tt>_tags</tt> search option, there is a "pretty" dedicated base URI format for tag searching:<br />
index.php/api2/tags/{_tags}/{_class}<br />
So, for instance, to find all contacts with the tags "#customer" and "#important":<br />
index.php/api2/tags/customer,important/Contacts<br />
The above is equivalent to<br />
index.php/api2/Contacts?_tags=customer,important<br />
Note, additional search parameters can also be included. For instance, to return the first ten most recently updated contacts with the above tags:<br />
index.php/api2/tags/customer,important/Contacts?_order=-lastUpdated&_limit=10<br />
<br />
The reason for this is to express tags as categories, and thus in a loose sense "folders" in which one would find records.<br />
<br />
= Web Hooks =<br />
To develop real-time integration, that is to say, to have data sent from X2Engine to a third-party service immediately when a triggering event occurs, the best method is web hooks. Note, there is also the means of sending web requests to external URLs via the "Remote API Call" X2Flow action. For more information about this feature, see [http://www.x2engine.com/x2flow_user_manual/#remoteAPI the X2Flow documentation] for this action.<br />
<br />
While said X2Flow action may suffice in many basic use cases, there are limitations to it that are addressed by web hooks:<br />
* Configuring X2Flow cannot be performed via the API<br />
* The action (making a web request) cannot be performed on a per-user basis (i.e. making a different request for each user)<br />
* The X2Flow user interface is not available in the open source edition of X2Engine<br />
<br />
In cases where the remote end, which will receive data from X2Engine, can be modified with custom code, web hooks are a method of "subscribing" to events in X2Engine via the API. Whenever an event would happen in X2Engine, X2Engine will submit payload data to a return URL specified in the original webhook request, as JSON-encoded data in the request body.<br />
<br />
== Creating Web Hooks ==<br />
To create a hook with an event that depends on a model type (i.e. contact updated vs. account updated), send a JSON-encoded list of hook attributes via <tt>POST</tt> to the following URI:<br />
index.php/api2/{_class}/hooks<br />
Or, to create a web hook associated with a generic event (that does not depend on model type), or a model of determined/unambiguous type (i.e. "Action Complete"):<br />
index.php/api2/hooks<br />
<br />
The POST-ed JSON-encoded dictionary object should contain the following properties: <br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>event</tt><br />
| An event name; see [[#Events Reference]]<br />
|-<br />
! scope="row" | <tt>target_url</tt><br />
| The remote URL to receive the payload<br />
|-<br />
! scope="row" | <tt>directPayload</tt><br />
| (optional): a 1 or 0 (or true/false). See [[#Interpreting Payload Data]]<br />
|-<br />
|}<br />
<br />
Upon successful creation of a web hook, note that the server will respond with a '''201''' status. The body will be the JSON-encoded attributes of the new hook record, and the <tt>Location</tt> header of the response will be set to a URL that can be used to remove the web hook when it is no longer needed (see [[#Deleting Web Hooks]]).<br />
<br />
== Supported Event Names and X2Flow ==<br />
Events for which web hooks can be created are all named after X2Flow trigger class names. Trigger classes (whose files are named after them, like all other class files) are stored in the directory<br />
protected/components/x2flow/triggers<br />
<br />
In fact, any time that <tt>[[x2propdoc:X2Flow.html#_trigger|X2Flow::trigger]]</tt> is called, a corresponding call to <tt>[[x2propdoc:ApiHook.html#_runAll|ApiHook::runAll]]</tt> is also made, to execute all web hooks associated with that trigger event. The payload that is sent for all web hooks corresponding to that event is based on the value of the <tt>$params</tt> argument that is sent to <tt>X2Flow::trigger</tt>. In all cases, the payload is first converted to a pure array, i.e. not containing any objects or resource handles, so that it can be JSON-encoded and sent to the web hook target URL. The exact payload data will differ depending on the action; see "[[#Interpreting Payload Data]]" (coming soon) for further information.<br />
<br />
== Interpreting Payload Data ==<br />
When you create a hook, you will have the option of setting its <tt>directPayload</tt> property to 1. A value of 0/false (the default value) for this field implies that if one of the trigger parameters is named "model" and is a subclass of X2Model (i.e. contact, account, opportunity), the (JSON-encoded) payload data will contain a property <tt>'''resource_url'''</tt> pointing to the URL on X2Engine of that model record. Thus, upon receiving the webhook request from X2Engine, the client end should then make a <tt>GET</tt> request from that URL to retrieve the model part of the payload data. If, on the other hand, <tt>directPayload</tt> is set to 1/true, what will instead happen is that the model will be included in the payload's <tt>'''model'''</tt> property as a dictionary of attribute-value pairs.<br />
<br />
Note, the two most common possible properties <tt>resource_url</tt> and <tt>model</tt> are mutually exclusive; which one if any will get included depends on the <tt>direcPayload</tt>, other properties of the payload include but may not be limited to the following:<br />
<br />
The class of model (and thus the structure of data) that should be expected to come from any given web hook generally depends on the model class to which the event corresponds. For example, the model is of class <tt>Actions</tt> in an <tt>ActionCompleteTrigger</tt> event, but they could be any general record type (contacts, actions, accounts, etc.) in a <tt>RecordCreateTrigger</tt> event.<br />
<br />
== Events Reference ==<br />
'''As of version 4.1.1''', the following trigger events are available in X2Flow:<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Event<br />
! scope="col" | Description<br />
! scope="col" | Payload<br />
|-<br />
|ActionCompleteTrigger<br />
|An action has been marked as complete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who completed the action<br />
|-<br />
|ActionOverdueTrigger<br />
|An action is overdue (cron required)<br />
|'''model'''/'''resource_url''': an action; '''duration''': the amount of time that has passed since the due date of the action<br />
|-<br />
|ActionUncompleteTrigger<br />
|An action has been marked as uncomplete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who marked the action as uncomplete<br />
|-<br />
|CampaignEmailClickTrigger<br />
|A tracking link in a campaign has been clicked<br />
|'''model''': the contact who clicked the link in their campaign email; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignUnsubscribeTrigger<br />
|A contact has unsubscribed from email campaigns<br />
|'''model''': the contact; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignWebActivityTrigger<br />
|''(Professional Edition only)'' A contact who was part of a campaign is visiting your website<br />
|'''model'''/'''resource_url''': the contact; '''campaign''': Name of the email campaign; '''url''': URL of the page that the contact is visiting<br />
|-<br />
|NewsletterEmailClickTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has clicked a tracking link<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign; '''url''': URL visited by the subscriber<br />
|-<br />
|NewsletterEmailOpenTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has opened an email<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|NewsletterUnsubscribeTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has unsubscribed<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|RecordCreateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been created<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordDeleteTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been deleted<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordTagAddTrigger<br />
|A record has been tagged<br />
|'''model'''/'''resource_url''': The record that was tagged; '''tags''': an array of strings (tags) that were added.<br />
|-<br />
|RecordTagRemoveTrigger<br />
|Tags have been deleted from a record<br />
|'''model'''/'''resource_url''': The record whose tags were changed; '''tags''': an array of strings (tags) that were removed<br />
|-<br />
|RecordUpdateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been updated<br />
|'''model'''/'''resource_url''': The data that was updated<br />
|-<br />
|RecordViewTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been viewed<br />
|'''model'''/'''resource_url''': The data that was viewed<br />
|-<br />
|TargetedContentRequestTrigger<br />
|''(Professional Edition Only)'' Targeted content is being accessed on your website<br />
|'''model'''/'''resource_url''': The contact on your website; '''url''': the URL being viewed; '''flowId''': internal parameter (not of much use outside X2Engine)<br />
|-<br />
|UserLoginTrigger<br />
|A user has logged in<br />
|'''user''': the username of the user who has logged in<br />
|-<br />
|UserLogoutTrigger<br />
|A user has logged out<br />
|'''user''': the username of the user who has logged out<br />
|-<br />
|WebActivityTrigger<br />
|''(Professional Edition Only)'' A contact is viewing your website<br />
|'''model'''/'''resource_url''': the contact; '''url''': the URL being viewed; '''probability''' ''(Platinum Edition only)'' percentage confidence of browser-fingerprinting-based match between a web client and a contact<br />
|-<br />
|WebleadTrigger<br />
|A new web lead has come in<br />
|'''model'''/'''resource_url''': the contact associated with the form submission; '''tags''': a list of tags added to the contact through the submission, if applicable <br />
|-<br />
|WorkflowCompleteStageTrigger<br />
|A process stage has been completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine); '''stageNumber''': the number of the stage in the process that was completed.<br />
|-<br />
|WorkflowCompleteTrigger<br />
|A process has been fully completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine)<br />
|-<br />
|WorkflowRevertStageTrigger<br />
|A process stage has been reverted.<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartStageTrigger<br />
|A process stage has been started<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartTrigger<br />
|A process has been started<br />
|Same as WorkflowCompleteTrigger<br />
|-<br />
|}<br />
<br />
== Deleting Web Hooks ==<br />
Given the numeric identifier of a web hook, it can be deleted by sending a <tt>DELETE</tt> to a URI formatted as follows:<br />
index.php/api2/hooks/:{_id}<br />
So, for a web hook with id=74:<br />
index.php/api2/hooks/:74<br />
<br />
The full URL should have been given to the API client in the <tt>Location</tt> header in the response to the original web hook creation request. Upon successful deletion, the server will respond with status code '''204'''.<br />
<br />
= Interpreting Server Responses =<br />
<br />
== HTTP Response Codes ==<br />
It is important to be able to read and interpret status codes (and for that matter, response headers) because in all success scenarios, the API does not respond with data envelopes. By this it is meant the act of wrapping a data model's attributes inside another array containing metadata about the status of the request. This is in effort to streamline and make more elegant code that handles response data. It is also for consistency's sake. A contacts model accessed as a resource object with name ending with <tt>.json</tt> is expected to be ''that contact'', and not rather an array with server response info or other metadata.<br />
<br />
In general, the meanings of response codes closely or exactly follow the formal definitions as defined in [http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html RFC2616], as well as the informal definitions of unconventional status codes (See [[wikipedia:List of HTTP status codes]]). The following table lists each of the possible status codes that the API will respond with, the contexts in which they would appear, and what they indicate.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Code<br />
! scope="col" | Request type or usage scenario<br />
! scope="col" | Meaning<br />
|-<br />
! colspan="3" | '''Success''' (<tt>2XX</tt>)<br />
|-<br />
! scope="row" | <tt>200</tt><br />
|<tt>GET</tt>, <tt>PATCH</tt>, <tt>PUT</tt> <br />
Also, when adding tags via <tt>POST</tt><br />
|''OK.'' General success status<br />
|-<br />
! scope="row" | <tt>201</tt><br />
|<tt>POST</tt><br />
|''Created.'' A record was created; see the value of the <tt>Location</tt> response header for its URL in the API<br />
|-<br />
! scope="row" | <tt>204</tt><br />
|<tt>DELETE</tt><br />
|''No content.'' An action was completed but the server does not need to return any content. The body of the response will be empty.<br />
|-<br />
! colspan="3" | '''Redirection''' (<tt>3XX</tt>)<br />
|-<br />
! scope="row" | <tt>300</tt><br />
|When using the "[[#Direct Manipulation by Attributes|find by attributes]]" direct access URI<br />
|''Multiple choices.'' There criteria specified for direct access (as opposed to querying) match more than one record.<br />
|-<br />
! scope="row" | <tt>303</tt><br />
|<tt>GET</tt><br />
When requesting an object that exists but is not actually associated with the record specified in the request <br />
|''See other.'' The requested resource is somewhere else. The <tt>Location</tt> header should contain the correct, full URL to the resource.<br />
|-<br />
! colspan="3" | '''Client Error''' (<tt>4XX</tt>)<br />
|-<br />
! scope="row" | <tt>400</tt><br />
|All request methods/actions<br />
|''Invalid request.'' General status code for when something is missing, malformed or incorrect in the request headers/body.<br />
|-<br />
! scope="row" | <tt>401</tt><br />
|All request methods/actions.<br />
|Authentication failure or missing authentication credentials.<br />
|-<br />
! scope="row" | <tt>403</tt><br />
|All request methods/actions.<br />
|''Forbidden.'' This may be because the user in API authentication does not actually have permission in X2Engine to perform the specified action. In Platinum Edition, this code can also indicate that the IP address of the API client has been blocked.<br />
|-<br />
! scope="row" | <tt>404</tt><br />
|All request methods/actions.<br />
|''Not found,'' or invalid URI. It is used whenever a specified record to retrieve directly does not exist, but also as a catch-all for a location that the API was not configured to interpret.<br />
|-<br />
! scope="row" | <tt>405</tt><br />
|All request methods/actions<br />
|''Method not allowed.'' The URI is not malformed, but the method of the request being sent to the server is not permitted. An example would be sending a POST request, which is intended for creating new records, to the URI of an existing record, or sending a <tt>DELETE</tt>/<tt>POST</tt>/<tt>PUT</tt> request to a "read-only" resource.<br />
|-<br />
! scope="row" | <tt>408</tt><br />
|All methods; server-generated<br />
|''Request timeout.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>413</tt><br />
|All methods; server-generated<br />
|''Request entity too large.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>415</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt><br />
|''Unsupported media type.'' As of this writing, this happens only whenever the request is sent with a body and the <tt>Content-Type</tt> header is not set to "application/json"<br />
|-<br />
! scope="row" | <tt>422</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt> and <tt>PUT</tt><br />
When creating or updating a record<br />
|''Unprocessable entity.'' This always indicates a data validation error when attempting to set fields of and save an active record model. The client is expected to resolve these issues and resubmit the data with all of the fields formatted in such a way that it is acceptable.<br />
|-<br />
! scope="row" | <tt>429</tt><br />
|''Platinum Edition only''<br />
All request methods/actions<br />
|''Too many requests.'' When rate limiting is enabled in the API settings, the server will use this code to indicate that the API client has been making requests to the API too frequently. When this status is sent, the response should also contain a <tt>Retry-After</tt> header specifying the number of seconds to wait before trying again.<br />
<br />
There is one exception to this, however: when attempting to create a web hook for a given user, event and model name, if the hook limit has already been reached for a user, event and model name, a 429 without <tt>Retry-After</tt> would be sent, and this implies that no more hooks for that combination should be created.<br />
|-<br />
! colspan="3" | '''Server Error''' (<tt>5XX</tt>)<br />
|-<br />
! scope="row" | <tt>500</tt><br />
|All request methods/actions<br />
|''Internal server error.'' General status for when something isn't right on the server. PHP and database errors will produce this status code.<br />
|-<br />
! scope="row" | <tt>501</tt><br />
|All request methods/actions<br />
|''Not implemented.'' The API should use this code to denote that a given method, action, etc. is not yet available, but may be in the future.<br />
|-<br />
! scope="row" | <tt>503</tt><br />
|''Professional and Platinum Editions''<br />
All request methods/actions<br />
|''Unavailable.'' X2Engine and/or its API service has been disabled/locked by an administrator.<br />
|-<br />
|}<br />
<br />
== Error Objects ==<br />
In all error responses produced by the API and not by the server itself, the body of the response will be a JSON containing metadata about the error and the response. This is for compatibility with less-than-satisfactory client libraries which cannot read the actual response code or headers, but might be able to read the response body when the code is not in the success (<tt>2XX</tt>) range. In such cases, the response will be a JSON-encoded object with at least the following properties:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row | httpHeaders<br />
|A dictionary of HTTP headers that were set deliberately by the API (as opposed to automatically, by the web server software) in the response.<br />
|-<br />
! scope="row | message<br />
|A general message about what happened and why. This will in most cases also be saved in the API log.<br />
|-<br />
! scope="row | error<br />
|Boolean true/false; will generally be true.<br />
|-<br />
! scope="row | status<br />
|The HTTP status code that was sent in the response header<br />
|-<br />
|}<br />
In some cases, the response JSON may include the following additional properties:<br />
{|class="wikitable"<br />
|-<br />
! scope="row | errors<br />
| A dictionary of validation errors encountered when attempting to save an active record model; included with responses of status code '''422'''.<br />
The structure will be indexed by attribute name, each entry containing a list of applicable validation errors. It is essentially the resulting value of the [[yii:CModel#errors-detail|CModel.errors]] property.<br />
|-<br />
|}</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=REST_API_Reference&diff=1945REST API Reference2015-12-21T20:57:02Z<p>Raymond Colebaugh: /* Creating, Viewing Updating and Deleting Records */</p>
<hr />
<div>[[Category:Development]]<br />
X2Engine's second-generation HTTP-based API, '''available as of version 4.1''', is (for the most part) REST-ful, and includes many improvements over the original API. <br />
<br />
The source code of the main components used in this API are:<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/controllers/Api2Controller.php protected/controllers/Api2Controller.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/ResponseBehavior.php]<br />
* [https://github.com/X2Engine/X2Engine/blob/master/x2engine/protected/components/util/ResponseUtil.php protected/components/util/ResponseUtil.php]<br />
Functional tests for the API can be found alongside the tests for the legacy API, in [https://github.com/X2Engine/X2Engine/tree/master/x2engine/protected/tests/api protected/tests/api].<br />
<br />
= Introduction =<br />
<br />
This API within X2Engine, can be accessed via the URI<br />
index.php/api2<br />
It also:<br />
* Exclusively uses JSON for data input and output<br />
* Tends to use similar URIs for both input and output (distinguishing operations via the request method) <br />
* Uses a variety of server response codes to distinguish error scenarios in the case of an unsuccessful transaction<br />
* Uses the "HTTP Basic Auth" method for authentication<br />
<br />
For example, to create a contact, one would send a <tt>POST</tt> request with its body a JSON-encoded attributes list to the URI <br />
index.php/api2/Contacts<br />
with the <tt>Content-Type</tt> header set to <tt>application/json</tt>, and the request body as (for example):<br />
<syntaxhighlight lang="javascript"><br />
{"firstName":"John","lastName":"Smith","visibility":1,"email":"johnsmith@example.com"}<br />
</syntaxhighlight><br />
<br />
If creation of the contact is successful, the server should respond with status code <tt>201</tt> ("Created"), and the response should contain a <tt>Location</tt> header with the full URL (including protocol) of the newly created contact (in addition to all the attributes of the new contact). If for example the new contact's ID is 123, that URI would be <br />
index.php/api2/Contacts/123.json<br />
and a GET request to that URI would elicit a response from the server whose body contains a JSON-encoded list of attributes.<br />
<br />
== <span class="noglossary">URI</span> Formats, Terminology and Conventions ==<br />
Note, the above example, the URI to which the <tt>POST</tt> request is sent (to create the contact) is referred to in this documentation as a '''base <span class="noglossary">URI</span>'''. Base URIs, when requested using the <tt>GET</tt> method, return a JSON-encoded array, each entry in the array corresponding to a record and being a dictionary of column:value pairs. So for example, if sending <tt>GET</tt> to the contacts model base URI<br />
index.php/api2/Contacts<br />
the response would look like this:<br />
<syntaxhighlight lang="javascript">[...,{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...},...]</syntaxhighlight><br />
Whenever a URI points to an object or resource uniquely identified with a specific database record, that URI is referred to as a '''direct <span class="noglossary">URI</span>'''. Direct URIs end in ".json" and respond to <tt>GET</tt> requests with JSON-encoded dictionary objects of column values for the record as it is in the database. So, the URI in the above example (<tt>index.php/api2/Contacts/123.json</tt>) would respond with:<br />
<syntaxhighlight lang="javascript">{"email": "johnsmith@example.com","firstName": "John","lastName": "Smith",...}</syntaxhighlight><br />
<br />
Direct URIs will almost always end in ".json", and base URIs will not. '''In general, the following convention applies''' almost universally within the API: ''If the <span class="noglossary">URI</span> ends in ".json", the resource will be a JSON-encoded dictionary object. Otherwise, it will be a JSON-encoded array. In the latter case, if each element of the array is a dictionary object, the dictionary objects should have uniform structure.''<br />
<br />
== Access Credentials ==<br />
To use the API, you will need to obtain X2Engine API credentials, which include a username and an API key. An X2Engine user with administrative privileges can get or set API keys in the Users module, by going to the edit page for that user. To summarize:<br />
# As the administrator, go to the user management module. You'll find it under "Users"<br />
# Click on the desired user<br />
# Click "Update User"<br />
# See the "API Key" field.<br />
<br />
While there is a user in this section called "API User" you should not use it for this version of the API. That user exists to maintain backwards compatibility for a very old version of the API so that users who have set up API connectors with those endpoints will not have their code break. This user will not function with the current API and attempting to use it will generate a 403 error.<br />
<br />
== Explore the API Using Your Web Browser ==<br />
Once you have API credentials, you can examine the web API using your web browser by making <tt>GET</tt> requests to locations within <tt>index.php/api2</tt> (simply by typing them into your browser's location bar). You can get a nicer view of the data returned by the server by installing a JSON viewing plugin in your web browser. A recommended plugin for this purpose is [http://jsonview.com/ JSONView], which is available [https://addons.mozilla.org/en-us/firefox/addon/jsonview/ for Firefox] and [https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc for Google Chrome] (it's an unofficial port, but the developer of the API and author of this article uses it).<br />
<br />
=== Example 1: hello, world ===<br />
Try visiting the following URI within X2Engine:<br />
index.php/api2/appInfo.json<br />
Initially you will be prompted to enter the username and password to complete authentication; enter the API key corresponding to the user in the password field. The above URL should respond with a JSON string containing some basic info about the X2Engine application.<br />
<br />
=== Example 2: direct access ===<br />
You can get the ID of any given account record by going to it inside X2Engine as you normally would and examining the URI. It should generally look something like this:<br />
index.php/accounts/32<br />
or:<br />
index.php/accounts/id/32<br />
In both of the above examples, the primary key value (id) of the record in question is 32. '''To view it in the API:'''<br />
index.php/api2/Accounts/32.json<br />
<br />
Note how in the API, the first letter of "Accounts" in the direct URI is capitalized. This is because active record data in the API is accessed not via specifying the module containing the active record class, or to which the class corresponds, but by specifying the actual ''class name'' to use when accessing (or querying) data.<br />
<br />
=== Example 3: querying ===<br />
index.php/api2/Actions?_order=-id&_limit=3<br />
This will show you the last 3 action records (i.e. emails, call logs, to-do's) created in the system (which the current acting API user has permission to view), in descending order of their primary key values (column "id"). If you have no action records in your system, you should receive an empty array.<br />
<br />
== Prerequisites for API Applications ==<br />
When writing an application to interface with X2Engine via the API, it is required or strongly recommended that your language/coding environment of choice:<br />
<br />
* Have a JSON parsing and encoding library available<br />
* Have a library for making HTTP requests which can:<br />
** Set request headers<br />
** Parse response headers and read the response status code<br />
** Natively support requests using HTTP Basic Auth for access<br />
** Read responses even when the response code is not in the "success" category (2xx)<br />
** Make <tt>POST</tt>, <tt>PATCH</tt>/<tt>PUT</tt> and <tt>DELETE</tt> requests<br />
* Can access the network<br />
<br />
Many high-level languages, such as Perl, PHP, Python and Ruby, meet these requirements. The specific usage of these languages is beyond the scope of this article; you will need to refer to the documentation of the library/libraries in use.<br />
<br />
It is expected in the near future that a growing number of official API access classes (each in a different programming language) will be available for quick and easy development of API applications.<br />
<br />
== Authentication ==<br />
As stated before, the API uses "HTTP Basic" authorization. Many HTTP client libraries will have native methods of setting headers for HTTP basic auth. It is recommended that you use such a method for authentication and read the relevant documentation, rather than setting headers manually, as that will save time and more likely lead to quicker success.<br />
<br />
In all other cases, to authenticate and access the API using this method, each request must include the <tt>Authorization</tt> header. To compose the header, first combine the username and API key into a single string with a colon, as:<br />
{username}:{userKey}<br />
Next, obtain the string in Base-64 encoding. For example, '''<tt>username:password</tt>''' in base 64 is <tt>dXNlcjpwYXNzd29yZA==</tt>. Thus, the resulting header would look like this:<br />
Authorization: Basic dXNlcjpwYXNzd29yZA==<br />
<br />
See also the [[wikipedia:Basic access authentication#Client side|the Wikipedia article on this topic]].<br />
<br />
<br />
=== Caveats ===<br />
'''Note, firstly:''' because headers are sent without any built-in encryption, it is highly recommended that you use the API over HTTPS (HTTP encrypted using TLS), if available, or make API requests only within a network where packets are not easily intercepted.<br />
<br />
Furthermore, if you have password-protected any web directories that contain X2Engine, you will need to make a "Satisfy any" exception for URIs within the API, or clients may not be able to authenticate.<br />
<br />
= Model-based Input and Output =<br />
Most of the modules in X2Engine (i.e. Contacts) will each have a corresponding active record model. This model is what is customized whenever adding a custom field. It is essentially a PHP class that is a child of [[x2doc:X2Model|X2Model]] (see: [[X2Model and Dynamic Fields]] for more information). Almost all API-based functions involving such data objects will contain the name of that class in the URL, i.e.<br />
index.php/api2/Accounts<br />
index.php/api2/Contacts/135/Actions<br />
index.php/api2/Contacts/112.json<br />
In general, the base URI for functions pertaining to models is<br />
index.php/api2/{_class} <br />
where <tt>{_class}</tt> is the class. The direct URI is generally:<br />
index.php/api2/{_class}/{_id}.json<br />
where <tt>{_id}</tt> is the ID of the record to access.<br />
<br />
== Getting Model Classes ==<br />
From the following URI one can obtain a list of models:<br />
index.php/api2/models<br />
Each model in the list is a dictionary containing:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |<tt>modelName</tt><br />
|The class of the model<br />
|-<br />
! scope="row" |<tt>title</tt><br />
|The human-readable name of the model<br />
|-<br />
! scope="row" |<tt>attributes</tt><br />
|An array of attribute names<br />
|-<br />
|}<br />
<br />
To include only fully-supported classes versus partially-supported model classes, include the "partialSupport" parameter and have it equal zero:<br />
index.php/api2/models?partialSupport=0<br />
<br />
== Fully-Supported Model Classes ==<br />
As of this writing, X2Engine by default has the following model classes that are fully supported in the API &mdash; meaning, all or nearly all of the most essential functionality that is possible in X2Engine via a web browser, in terms of manipulation of persistent data storage, is also possible via the API.<br />
* <tt>Accounts</tt><br />
* <tt>BugReports</tt><br />
* <tt>Contacts</tt><br />
* <tt>Campaign</tt><br />
* <tt>Opportunity</tt><br />
* <tt>Product</tt><br />
* <tt>Services</tt><br />
<br />
Additionally, any custom modules will also have corresponding active record models fully supported by the API. This should usually be the same name as the module, but without spaces and the first letter always capitalized. If in doubt, to find the model class corresponding to a given module (i.e. a custom module), look in the "models" sub-directory of that module. The name of the file excluding the extension (<tt>.php</tt>) should be the name of the class. For instance, in the file <tt>protected/modules/contacts/models/Contacts.php</tt> there should be the following line:<br />
<syntaxhighlight lang="php"><br />
class Contacts extends X2Model {<br />
</syntaxhighlight><br />
<br />
== Partially-Supported Models ==<br />
Manipulation of data using the following models (or certain aspects of the following models) is not fully supported in the API as of this writing &mdash; meaning, while most operations are possible, some important functionality is not yet possible:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>Actions</tt> <br />
|Actions can be created, updated, viewed, queried and deleted as all other model types. However:<br />
* Action completion and backdating (setting or overwriting the completion date) is not yet supported unless one enables "raw input" (Platinum Edition only), because <tt>completeDate</tt> is a "read-only" field<br />
* Associated "action timer" records (applicable only to Professional/Platinum edition) cannot be accessed or manipulated<br />
* Comparisons based on action description in queries:<br />
** They are unaffected by options that control comparisons; the comparison is un-escaped "LIKE" and the criteria combination operator is effectively "AND" (<tt>_partial=1&_escape=0</tt>). <br />
** They will also be very slow; effectively, the comparison in the query is being performed on a <tt>TEXT</tt> column in a joined table<br />
<br />
The limitations of filtering by action description are endemic to how the "field" is actually stored in a different database table than the contacts, and the type of the column is <tt>TEXT</tt>.<br />
|-<br />
! scope="row" | <tt>Docs</tt> <br />
|Can be accessed/manipulated as with other models. However, "edit permissions" do not work the same way as they do in the application.<br />
|-<br />
! scope="row" | <tt>Groups</tt><br />
|Groups can be queried, viewed and created, albeit only by an administrative user, and in a very limited capacity. Users cannot be added to or removed from groups; manipulating the associated "group-to-user" data is not yet possible.<br />
|-<br />
! scope="row" | <tt>Media</tt><br />
|Media records can be accessed and manipulated as with any other model, but the API does not yet support directly uploading files to go with them.<br />
|-<br />
! scope="row" | <tt>Quote</tt><br />
|Quote records can be accessed and manipulated, but associated "line items" data cannot be viewed or manipulated.<br />
|-<br />
! scope="row" | <tt>X2Leads</tt><br />
|This data type can be fully accessed and manipulated, but there is not yet any built-in action available for directly converting a lead to an opportunity.<br />
|-<br />
! scope="row" | <tt>X2List</tt><br />
|This data type (contact lists in X2Engine) can be accessed and manipulated, but the actual contents of the list (whether dynamic or static) cannot.<br />
|}<br />
<br />
== Creating, Viewing Updating and Deleting Records ==<br />
To create a record, perform a <tt>POST</tt> request to the base URI for the model, i.e.<br />
index.php/api2/Contacts<br />
to create a contact. As mentioned in the example in the [[#Introduction]], the body of the request must be a JSON-encoded library of attributes to set in the model, and the <tt>Content-Type</tt> header must be set to <tt>application/json</tt>.<br />
<br />
To view, update or delete a record, first determine its direct URI within the API, as set in the <tt>Location</tt> header if creation was successful, or as determined via its class and id, i.e.<br />
index.php/api2/Contacts/33.json<br />
to specify a contact record with its id equal to 33.<br />
<br />
To update a record, send a <tt>PUT</tt> or <tt>PATCH</tt> request to a direct URI, and set the body and <tt>Content-Type</tt> header as one would in a <tt>POST</tt> request to create such a record. Finally, to delete the record, send a DELETE request to that same direct URI. It is not necessary in the case of deletion to include a body or set the <tt>Content-Type</tt> header.<br />
<br />
If you need to retrieve the record count of any of the supported classes, then you can append the "count" operation to the URI of a GET request. For example, to retrieve the number of Contacts in the system, one would call the URL <br />
index.php/api2/Contacts/count<br />
This can also be combined with the _findBy directive to allow count of models matching specific criteria, i.e., to count the number of Contacts named "Thaddeus" the respective API call would be<br />
index.php/api2/Contacts/count/by:firstName=Thaddeus.json<br />
<br />
== Direct Manipulation by Attributes ==<br />
Given a set of uniquely-identifying attribute names and values, it is possible to directly access and manipulate an existing X2Model-based record by without first querying it. The direct URI for this use case is:<br />
<br />
index.php/api2/{_class}/by:{_findBy}.json<br />
<br />
where the query parameter <tt>{_findBy}</tt> is a list of key and value pairs formatted as <tt>key1=value1;key2=value2</tt>. <br />
<br />
For example: provided the email address "james@example.com" and first name "James" uniquely identify the contact with ID of 457, the following two direct access URIs are equivalent:<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json<br />
index.php/api2/Contacts/457.json<br />
<br />
Furthermore, note what happens in the following scenarios:<br />
<br />
'''If the set of attributes (i.e. email address) is not unique to a single record:''' the find-by-attributes direct URI will return with a 300 status and an [[#Error Objects|error object]] containing the two special properties: ''queryUri'', the URI to query for records by the attributes given, and: ''directUris'', a list containing the direct URI of each matching record.<br />
<br />
''' If no contact matches the attributes given:''' the URI will respond with a 404 status.<br />
<br />
=== Using the first matching record ===<br />
If one wants to forcefully select and use the first record matching the attributes, regardless of how many match, append the <tt>_useFirst</tt> parameter and set equal to 1, i.e.<br />
<br />
index.php/api2/Contacts/by:email=james@example.com;firstName=James.json?_useFirst=1<br />
<br />
== Working With Associated Actions ==<br />
The Actions model is unique in that it can be "associated" with almost any other type of model record. Actions records comprise all "history" items on any given record, i.e. emails, calls logged, notes, calendar events and also plain actions. Actions that have an association with another model can be used via clean URIs that point to "within" the associated model.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
<br />
To view/query all actions associated with an active record model of class <tt>{_class}</tt> and id <tt>{_id}</tt>, use the following base URI:<br />
index.php/api2/{_class}/{_id}/Actions<br />
<br />
For instance, one could find all actions, including emails, on contact id=1233, via:<br />
index.php/api2/Contacts/1233/Actions<br />
<br />
To view an individual action of id <tt>{_actionId}</tt>, one can use this direct URI:<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<br />
=== Creating, Updating and Deleting Actions ===<br />
Similar to the the basic model access API function, <tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt> and <tt>DELETE</tt> requests can be sent to associated action URIs to create/modify/delete records just as those URIs can also be used to view data. <tt>POST</tt> to the base URI,<br />
index.php/api2/{_class}/{_id}/Actions<br />
to create a new action associated with model record of class <tt>{_class}</tt> and id <tt>{_id}</tt>. Then, using the direct URI,<br />
index.php/api2/{_class}/{_id}/Actions/{_actionId}.json<br />
<tt>PATCH</tt>/<tt>PUT</tt>/<tt>DELETE</tt> requests can be used to modify/delete an existing associated action record.<br />
<br />
= Metadata Functions =<br />
There is "structural" metadata that one can retrieve through the API to more effectively determine how to proceed with future API transactions. There are also some functions in the API that pertain to functionality associated with model records, but do not modify data within the records themselves, and create associated metadata.<br />
<br />
== Fields ==<br />
One can access field metadata for a given model class by using the following base URI (which supports querying):<br />
index.php/api2/{_class}/fields<br />
One can directly access the metadata of a field by its name via the following direct URI format:<br />
index.php/api2/{_class}/fields/{_fieldName}.json<br />
For instance, this URI<br />
index.php/api2/Contacts/fields/leadSource.json<br />
would respond with:<br />
<br />
<tt><br />
{"id":"88", "modelName":"Contacts", "fieldName":"leadSource", "attributeLabel":"Lead Source", "modified":"0", "custom":"0", "type":"dropdown", "required":"0", "uniqueConstraint":"0", "safe":"1", "readOnly":"0", "linkType":"103", "searchable":"0", "relevance":"", "isVirtual":"0","defaultValue":null,"keyType":null}<br />
</tt><br />
<br />
== Field-Level Permissions ==<br />
Any given user's access level to a field can be controlled by assigning them to a role and then setting field permissions for that role via ''Manage Roles'' under ''Admin''.<br />
<br />
A <tt>GET</tt> to the following will respond with a dictionary of active record model attributes, each value corresponding to field-level access permissions for that field granted the current acting API user.<br />
index.php/api2/{_class}/fieldPermissions.json<br />
For example, as the default administrator, for model Contacts, getting the following:<br />
index.php/api2/Accounts/fieldPermissions.json<br />
<br />
will respond with:<br />
<br />
<tt>{"leadtype":2, "leadSource":2, "leadstatus":2, "leadDate":2, "leadscore":2, "interest":2, "dealvalue":2, "closedate":2, "rating":2, "dealstatus":2, "name":2, "nameId":1, "id":1, "website":2, "type":2, "visibility":2, "annualRevenue":2, "phone":2, "tickerSymbol":2, "address":2, "city":2, "state":2, "country":2, "zipcode":2, "parentAccount":2, "primaryContact":2, "employees":2, "assignedTo":2, "createDate":1, "description":2, "lastUpdated":1, "lastActivity":1, "updatedBy":1}</tt><br />
<br />
For each of these entries, the number associated with the field indicates the following:<br />
{|class="wikitable"<br />
|-<br />
! scope="row" |0<br />
|No access; when directly accessing a model record, the response data will not include the content of that field<br />
|-<br />
! scope="row" |1<br />
|Read-only access; responses will include the content of that field, but any input to that field will be discarded<br />
|-<br />
! scope="row" |2<br />
|Read/write access. The current API user can both view and edit data in the field.<br />
|-<br />
|}<br />
<br />
== Zapier-Friendly Fields ==<br />
There is similarly a dynamic fields API action that returns an array of fields intended for use by Zapier. The object format returned from this action is described in [https://zapier.com/developer/documentation/reference/#action-fields-custom Action Fields (Custom)] in the Zapier developer documentation.<br />
<br />
The base URI is:<br />
index.php/api2/{_class}/zapierFields<br />
<br />
This action is generally useful for API usage insofar as it will also return ranges of acceptable values for each field, if applicable, in the '''<tt>choices</tt>''' property. For example, if the type of a field is <tt>dropdown</tt>, the dropdown options will be returned in the <tt>choices</tt> property of each element in the returned array. Similarly, if the type of the field is <tt>assignment</tt>, the element's <tt>choices</tt> property will include a list of users and groups. Furthermore, it has the ability to easily select only fields of a given permission level or greater. For this, use the <tt>_permissionLevel</tt> parameter. For example, to get all writable fields in Contacts:<br />
index.php/api2/Contacts/zapierFields?_permissionLevel=2<br />
(see [[#Field-Level Permissions]] for more information)<br />
<br />
Unfortunately, the action does not support querying, although it does not need to for its intended purpose.<br />
<br />
== Dropdowns ==<br />
Note, to get a list of acceptable values for a given model in cases when the field type is not <tt>dropdown</tt>, use the <tt>choices</tt> property of data returned by [[#Zapier-Friendly Fields]].<br />
<br />
Some fields, i.e. lead source in contacts, are of type "dropdown"; their content is intended to be either blank, or an option in a static list. Dropdown menus can be queried at base URI<br />
index.php/api2/dropdowns<br />
Dropdown fields can also be directly accessed via<br />
index.php/api2/dropdowns/{_id}.json<br />
To find out if a field is of type dropdown, and which dropdown menu it uses: the value for <tt>type</tt> in the field's metadata record should be "dropdown", and the <tt>linkType</tt> field should contain the dropdown's ID. So, using the example in [[#Fields]], the corresponding dropdown record is at<br />
index.php/api2/dropdowns/103.json<br />
which contains:<br />
<br />
<tt>{"id":"103", "name":"Lead Source", "options":{"None":"None", "Google":"Google", "Facebook":"Facebook", "Walk In":"Walk In"}, "multi":"0", "parent":null, "parentVal":null}</tt><br />
<br />
Note, for convenience's sake the "options" field won't be returned verbatim as the raw JSON (that's how the options are stored). Rather, that field will be decoded into a sub-dictionary of the overall object.<br />
<br />
== Relationships ==<br />
It is possible to create, view, and delete relationships between supported API models in X2Engine via the API.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Relations functionality is in general accessed within the following base URI:<br />
index.php/api2/{_class}/{_id}/relationships<br />
<br />
So, to view all relationships going to or from account 131:<br />
index.php/api2/Accounts/131/relationships<br />
<br />
To view the contents (related model class and ID) of a specific relationship on the account (let's say the relation record has its id=304281 for instance):<br />
index.php/api2/Accounts/131/relationships/304281.json<br />
<br />
The <tt>Relationships</tt> active record model has the following attributes that can be used in queries:<br />
;id<br />
: Unique numeric identifier for the relationship<br />
;<tt>firstType</tt>, <tt>firstId</tt><br />
: The model class and record ID at one end of the relationship, respectively<br />
;<tt>secondType</tt>, <tt>secondId</tt><br />
: The model class and record ID at the other end of the relationship, respectively<br />
<br />
For instance, to find all outgoing relationships with Accounts to contact id=126:<br />
index.php/api2/Contacts/126/relationships?secondType=Accounts<br />
<br />
=== Adding/Removing Relationships ===<br />
To create a new relationship, sent <tt>POST</tt> to the base URI.<br />
<br />
To remove a relationship, send <tt>DELETE</tt> to the direct URI of the record, i.e. <tt>index.php/api2/Accounts/131/relationships/304281.json</tt> in the earlier example.<br />
<br />
== Tags ==<br />
All basic, fully-supported models in X2Engine should support tagging. Tags cannot be individually modified, but can only be created, viewed, queried and removed, in order to enforce preservation of the important metadata such as who added the tag and at what date.<br />
<br />
=== <span class="noglossary">URI</span> Formats ===<br />
Tags on a given model record can be retrieved via <tt>GET</tt> at the following base URI:<br />
index.php/api2/{_class}/{_id}/tags<br />
The response should be a flat array of tag names. For example, if account 51 has tags "#customer" and "#local", sending <tt>GET</tt> to the following URI<br />
index.php/api2/Accounts/51/tags<br />
will yield:<br />
<syntaxhighlight lang="javascript">["#customer","#local"]</syntaxhighlight><br />
<br />
To view more extensive metadata of the tag, i.e. who added the tag and at what date:<br />
index.php/api2/{_class}/{_id}/tags/{_tagName}.json<br />
i.e.<br />
index.php/api2/Accounts/51/tags/local.json<br />
The above will return a dictionary of <tt>x2_tags</tt> column names and values. Note, the tag name in the URI must either exclude the preceding hash mark or include it via its corresponding URL encoding sequence, <tt>'''%23'''</tt>. This is because the hash mark is a special character in the HTTP protocol and will interfere with proper resolution of the URI. Using the above example:<br />
index.php/api2/Accounts/51/tags/%23local.json<br />
That URI will return the exact same data as the previous URI; they are considered equivalent.<br />
<br />
=== Adding Tags to a Record ===<br />
Adding tags also utilizes that same URI scheme as with viewing tags. To add one or more tags, send them as string elements in a flat JSON-encoded array via <tt>POST</tt> to that location. For example, to use the previous example, one can apply the tags "#customer" and "#local" from "record 51" to record 52 by <tt>POST</tt>-ing the same JSON returned from a <tt>GET</tt> at: <br />
index.php/api2/Accounts/51/tags<br />
to:<br />
index.php/api2/Accounts/52/tags<br />
<br />
=== Removing Tags ===<br />
To delete a tag, send a <tt>DELETE</tt> request to the direct viewing location of the tag. So, for instance, to delete "#local" from account 51, send <tt>DELETE</tt> to <tt>index.php/api2/Accounts/51/tags/local.json</tt><br />
<br />
= Querying Data =<br />
Almost any "base" URI, which can be used for accessing all records of a type or for creating new records of a type, can also be used for querying records of that type. Responses to queries (and <tt>GET</tt> requests to these URIs in general) will always be JSON-encoded arrays of records, each record represented as an attribute dictionary.<br />
<br />
Options for searching, as well as attributes to match column values against (for filtering), are all specified as query parameters (a.k.a. "get parameters"). The search options, to protect against name collisions with column names, each have names that begin with an underscore, i.e. <tt>_order</tt>.<br />
<br />
== General Search Option Parameters ==<br />
In queries, one can use a variety of advanced options that include sorting, pagination, partial matching, and in some cases tags.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Parameter<br />
! scope="col" | Meaning<br />
! scope="col" | Default<br />
! scope="col" | Usage<br />
|-<br />
! scope="row" | <tt>_escape</tt><br />
|Wildcard usage<br />
|<tt>1</tt><br />
|Set 0 in parameters to allow characters like "%" and "_" to be used as SQL wildcards in search filter attributes. Controls the resultant value of the <tt>$escape</tt> argument sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] in configuring the search. Note, to perform wildcard searches properly, the parameter <tt>_partial</tt> must be set to <tt>1</tt> so that <tt>CDbCriteria</tt> uses <tt>LIKE</tt> for the value comparison. For info on SQL wildcards and comparisons, see: [http://dev.mysql.com/doc/refman/5.0/en/string-comparison-functions.html MySQL: String Comparison Functions]<br />
|-<br />
! scope="row" | <tt>_limit</tt><br />
|Page size<br />
|may vary<br />
|Set to a number to control the maximum number of records to include in the results of the search. The default and maximum page size is <tt>1000</tt>, and in ''Platinum Edition'' this default amount is user-configurable.<br />
|-<br />
! scope="row" | <tt>_or</tt><br />
|Use "OR" operator <br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to make the operator used for combining search criteria <tt>OR</tt> instead of the default, <tt>AND</tt>.<br />
|-<br />
! scope="row" | <tt>_order</tt><br />
|Sorting<br />
|none<br />
|Set equal to the name of a column to sort by, optionally prefixed with a plus or minus sign to specify ascending or descending order (respectively). For example, <tt>_order=-leadScore</tt> in a Contacts query sorts contacts by lead score with the highest-scored contacts first. Note, sorting applies not only to the current page but to the data set spanning all pages. Thus, if the total number of possible results is larger than the page size, the set of results shown in the current page will be affected.<br />
|-<br />
! scope="row" | <tt>_page</tt><br />
|Page number<br />
|<tt>0</tt><br />
|The zero-starting-point page number of the data. Useful for when the query would return more results than the page size specified.<br />
|-<br />
! scope="row" | <tt>_partial</tt><br />
|Partial match<br />
|<tt>0</tt><br />
|Set to <tt>1</tt> to enable partial matching in search filters. This parameter controls the value sent to [[yii:CDbCriteria#compare-detail|CDbCriteria.compare()]] as the <tt>$partialMatch</tt> argument. If true, the <tt>LIKE</tt> comparison will be used; otherwise, full equality will be used as the comparison.<br />
|-<br />
! scope="row" | <tt>_tagOr</tt><br />
|Inclusive tag search<br />
|<tt>0</tt><br />
|When performing tag-based searches, set to <tt>1</tt> to indicate to include records with ''any'' of the specified tags rather than all of them.<br />
|-<br />
! scope="row" | <tt>_tags</tt><br />
|Has tag(s)<br />
|none<br />
|When querying tag-supporting [[x2doc:X2Model|X2Model]] sub-classes (meaning, those which can be tagged), this can be included and set to a comma-delineated list of tags. This will restrict results to records having all of said tags, or if <tt>_tagOr</tt> is enabled, any of the tags. Note, tag names should not contain their preceding "#" (or, at least should not contain it without URL-encoding it) because "#" is a special character in the HTTP protocol that will interfere with how your request to the server is interpreted.<br />
|-<br />
|}<br />
<br />
== Adding Query Parameters ==<br />
Appending option parameters proceeds as it would for any script that can receive URL-encoded variables via the request: follow the base URI with a question mark, and delineate <tt>[name]=[value]</tt> parameter declarations with ampersands ''(note: this might be different, depending on your web server's configuration, but the nearly-ubiquitous default is ampersand-delineation)''. For example:<br />
index.php/api2/Contacts?_limit=10&firstName=Harry&lastName=P%25&_partial=1&_escape=0&_order=+lastName<br />
This will return the first ten contacts out the list of contacts having first name "Harry" and last name beginning with "P", sorted alphabetically by last name.<br />
<br />
== Searching For Models By Tag ==<br />
In addition to the <tt>_tags</tt> search option, there is a "pretty" dedicated base URI format for tag searching:<br />
index.php/api2/tags/{_tags}/{_class}<br />
So, for instance, to find all contacts with the tags "#customer" and "#important":<br />
index.php/api2/tags/customer,important/Contacts<br />
The above is equivalent to<br />
index.php/api2/Contacts?_tags=customer,important<br />
Note, additional search parameters can also be included. For instance, to return the first ten most recently updated contacts with the above tags:<br />
index.php/api2/tags/customer,important/Contacts?_order=-lastUpdated&_limit=10<br />
<br />
The reason for this is to express tags as categories, and thus in a loose sense "folders" in which one would find records.<br />
<br />
= Web Hooks =<br />
To develop real-time integration, that is to say, to have data sent from X2Engine to a third-party service immediately when a triggering event occurs, the best method is web hooks. Note, there is also the means of sending web requests to external URLs via the "Remote API Call" X2Flow action. For more information about this feature, see [http://www.x2engine.com/x2flow_user_manual/#remoteAPI the X2Flow documentation] for this action.<br />
<br />
While said X2Flow action may suffice in many basic use cases, there are limitations to it that are addressed by web hooks:<br />
* Configuring X2Flow cannot be performed via the API<br />
* The action (making a web request) cannot be performed on a per-user basis (i.e. making a different request for each user)<br />
* The X2Flow user interface is not available in the open source edition of X2Engine<br />
<br />
In cases where the remote end, which will receive data from X2Engine, can be modified with custom code, web hooks are a method of "subscribing" to events in X2Engine via the API. Whenever an event would happen in X2Engine, X2Engine will submit payload data to a return URL specified in the original webhook request, as JSON-encoded data in the request body.<br />
<br />
== Creating Web Hooks ==<br />
To create a hook with an event that depends on a model type (i.e. contact updated vs. account updated), send a JSON-encoded list of hook attributes via <tt>POST</tt> to the following URI:<br />
index.php/api2/{_class}/hooks<br />
Or, to create a web hook associated with a generic event (that does not depend on model type), or a model of determined/unambiguous type (i.e. "Action Complete"):<br />
index.php/api2/hooks<br />
<br />
The POST-ed JSON-encoded dictionary object should contain the following properties: <br />
{|class="wikitable"<br />
|-<br />
! scope="row" | <tt>event</tt><br />
| An event name; see [[#Events Reference]]<br />
|-<br />
! scope="row" | <tt>target_url</tt><br />
| The remote URL to receive the payload<br />
|-<br />
! scope="row" | <tt>directPayload</tt><br />
| (optional): a 1 or 0 (or true/false). See [[#Interpreting Payload Data]]<br />
|-<br />
|}<br />
<br />
Upon successful creation of a web hook, note that the server will respond with a '''201''' status. The body will be the JSON-encoded attributes of the new hook record, and the <tt>Location</tt> header of the response will be set to a URL that can be used to remove the web hook when it is no longer needed (see [[#Deleting Web Hooks]]).<br />
<br />
== Supported Event Names and X2Flow ==<br />
Events for which web hooks can be created are all named after X2Flow trigger class names. Trigger classes (whose files are named after them, like all other class files) are stored in the directory<br />
protected/components/x2flow/triggers<br />
<br />
In fact, any time that <tt>[[x2propdoc:X2Flow.html#_trigger|X2Flow::trigger]]</tt> is called, a corresponding call to <tt>[[x2propdoc:ApiHook.html#_runAll|ApiHook::runAll]]</tt> is also made, to execute all web hooks associated with that trigger event. The payload that is sent for all web hooks corresponding to that event is based on the value of the <tt>$params</tt> argument that is sent to <tt>X2Flow::trigger</tt>. In all cases, the payload is first converted to a pure array, i.e. not containing any objects or resource handles, so that it can be JSON-encoded and sent to the web hook target URL. The exact payload data will differ depending on the action; see "[[#Interpreting Payload Data]]" (coming soon) for further information.<br />
<br />
== Interpreting Payload Data ==<br />
When you create a hook, you will have the option of setting its <tt>directPayload</tt> property to 1. A value of 0/false (the default value) for this field implies that if one of the trigger parameters is named "model" and is a subclass of X2Model (i.e. contact, account, opportunity), the (JSON-encoded) payload data will contain a property <tt>'''resource_url'''</tt> pointing to the URL on X2Engine of that model record. Thus, upon receiving the webhook request from X2Engine, the client end should then make a <tt>GET</tt> request from that URL to retrieve the model part of the payload data. If, on the other hand, <tt>directPayload</tt> is set to 1/true, what will instead happen is that the model will be included in the payload's <tt>'''model'''</tt> property as a dictionary of attribute-value pairs.<br />
<br />
Note, the two most common possible properties <tt>resource_url</tt> and <tt>model</tt> are mutually exclusive; which one if any will get included depends on the <tt>direcPayload</tt>, other properties of the payload include but may not be limited to the following:<br />
<br />
The class of model (and thus the structure of data) that should be expected to come from any given web hook generally depends on the model class to which the event corresponds. For example, the model is of class <tt>Actions</tt> in an <tt>ActionCompleteTrigger</tt> event, but they could be any general record type (contacts, actions, accounts, etc.) in a <tt>RecordCreateTrigger</tt> event.<br />
<br />
== Events Reference ==<br />
'''As of version 4.1.1''', the following trigger events are available in X2Flow:<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Event<br />
! scope="col" | Description<br />
! scope="col" | Payload<br />
|-<br />
|ActionCompleteTrigger<br />
|An action has been marked as complete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who completed the action<br />
|-<br />
|ActionOverdueTrigger<br />
|An action is overdue (cron required)<br />
|'''model'''/'''resource_url''': an action; '''duration''': the amount of time that has passed since the due date of the action<br />
|-<br />
|ActionUncompleteTrigger<br />
|An action has been marked as uncomplete<br />
|'''model'''/'''resource_url''': An action; '''user''': username of the user who marked the action as uncomplete<br />
|-<br />
|CampaignEmailClickTrigger<br />
|A tracking link in a campaign has been clicked<br />
|'''model''': the contact who clicked the link in their campaign email; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignUnsubscribeTrigger<br />
|A contact has unsubscribed from email campaigns<br />
|'''model''': the contact; '''campaign''': Name of the email campaign<br />
|-<br />
|CampaignWebActivityTrigger<br />
|''(Professional Edition only)'' A contact who was part of a campaign is visiting your website<br />
|'''model'''/'''resource_url''': the contact; '''campaign''': Name of the email campaign; '''url''': URL of the page that the contact is visiting<br />
|-<br />
|NewsletterEmailClickTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has clicked a tracking link<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign; '''url''': URL visited by the subscriber<br />
|-<br />
|NewsletterEmailOpenTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has opened an email<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|NewsletterUnsubscribeTrigger<br />
|''(Professional Edition only)'' A newsletter subscriber has unsubscribed<br />
|'''item''': The list item record (<tt>{"emailAddress":<email address>,"opened":<timestamp opened>,"clicked":<timestamp clicked>,"unsubscribed":<has unsubscribed>}</tt>); '''email''': email address of subscriber; '''campaign''': name of the newsletter campaign<br />
|-<br />
|RecordCreateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been created<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordDeleteTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been deleted<br />
|'''model'''/'''resource_url''': The data that was inserted<br />
|-<br />
|RecordTagAddTrigger<br />
|A record has been tagged<br />
|'''model'''/'''resource_url''': The record that was tagged; '''tags''': an array of strings (tags) that were added.<br />
|-<br />
|RecordTagRemoveTrigger<br />
|Tags have been deleted from a record<br />
|'''model'''/'''resource_url''': The record whose tags were changed; '''tags''': an array of strings (tags) that were removed<br />
|-<br />
|RecordUpdateTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been updated<br />
|'''model'''/'''resource_url''': The data that was updated<br />
|-<br />
|RecordViewTrigger<br />
|A record of one of the main types (contact, action, account, opportunity, etc.) has been viewed<br />
|'''model'''/'''resource_url''': The data that was viewed<br />
|-<br />
|TargetedContentRequestTrigger<br />
|''(Professional Edition Only)'' Targeted content is being accessed on your website<br />
|'''model'''/'''resource_url''': The contact on your website; '''url''': the URL being viewed; '''flowId''': internal parameter (not of much use outside X2Engine)<br />
|-<br />
|UserLoginTrigger<br />
|A user has logged in<br />
|'''user''': the username of the user who has logged in<br />
|-<br />
|UserLogoutTrigger<br />
|A user has logged out<br />
|'''user''': the username of the user who has logged out<br />
|-<br />
|WebActivityTrigger<br />
|''(Professional Edition Only)'' A contact is viewing your website<br />
|'''model'''/'''resource_url''': the contact; '''url''': the URL being viewed; '''probability''' ''(Platinum Edition only)'' percentage confidence of browser-fingerprinting-based match between a web client and a contact<br />
|-<br />
|WebleadTrigger<br />
|A new web lead has come in<br />
|'''model'''/'''resource_url''': the contact associated with the form submission; '''tags''': a list of tags added to the contact through the submission, if applicable <br />
|-<br />
|WorkflowCompleteStageTrigger<br />
|A process stage has been completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine); '''stageNumber''': the number of the stage in the process that was completed.<br />
|-<br />
|WorkflowCompleteTrigger<br />
|A process has been fully completed<br />
|'''workflow''': The workflow template model (has properties name, isDefault, lastUpdated and colors); '''model'''/'''resource_url''': the record (i.e. Opportunity/Contact) for which the stage was completed; '''workflowId''': internal use (not very useful outside X2Engine)<br />
|-<br />
|WorkflowRevertStageTrigger<br />
|A process stage has been reverted.<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartStageTrigger<br />
|A process stage has been started<br />
|Same as WorkflowCompleteStageTrigger<br />
|-<br />
|WorkflowStartTrigger<br />
|A process has been started<br />
|Same as WorkflowCompleteTrigger<br />
|-<br />
|}<br />
<br />
== Deleting Web Hooks ==<br />
Given the numeric identifier of a web hook, it can be deleted by sending a <tt>DELETE</tt> to a URI formatted as follows:<br />
index.php/api2/hooks/:{_id}<br />
So, for a web hook with id=74:<br />
index.php/api2/hooks/:74<br />
<br />
The full URL should have been given to the API client in the <tt>Location</tt> header in the response to the original web hook creation request. Upon successful deletion, the server will respond with status code '''204'''.<br />
<br />
= Interpreting Server Responses =<br />
<br />
== HTTP Response Codes ==<br />
It is important to be able to read and interpret status codes (and for that matter, response headers) because in all success scenarios, the API does not respond with data envelopes. By this it is meant the act of wrapping a data model's attributes inside another array containing metadata about the status of the request. This is in effort to streamline and make more elegant code that handles response data. It is also for consistency's sake. A contacts model accessed as a resource object with name ending with <tt>.json</tt> is expected to be ''that contact'', and not rather an array with server response info or other metadata.<br />
<br />
In general, the meanings of response codes closely or exactly follow the formal definitions as defined in [http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html RFC2616], as well as the informal definitions of unconventional status codes (See [[wikipedia:List of HTTP status codes]]). The following table lists each of the possible status codes that the API will respond with, the contexts in which they would appear, and what they indicate.<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="col" | Code<br />
! scope="col" | Request type or usage scenario<br />
! scope="col" | Meaning<br />
|-<br />
! colspan="3" | '''Success''' (<tt>2XX</tt>)<br />
|-<br />
! scope="row" | <tt>200</tt><br />
|<tt>GET</tt>, <tt>PATCH</tt>, <tt>PUT</tt> <br />
Also, when adding tags via <tt>POST</tt><br />
|''OK.'' General success status<br />
|-<br />
! scope="row" | <tt>201</tt><br />
|<tt>POST</tt><br />
|''Created.'' A record was created; see the value of the <tt>Location</tt> response header for its URL in the API<br />
|-<br />
! scope="row" | <tt>204</tt><br />
|<tt>DELETE</tt><br />
|''No content.'' An action was completed but the server does not need to return any content. The body of the response will be empty.<br />
|-<br />
! colspan="3" | '''Redirection''' (<tt>3XX</tt>)<br />
|-<br />
! scope="row" | <tt>300</tt><br />
|When using the "[[#Direct Manipulation by Attributes|find by attributes]]" direct access URI<br />
|''Multiple choices.'' There criteria specified for direct access (as opposed to querying) match more than one record.<br />
|-<br />
! scope="row" | <tt>303</tt><br />
|<tt>GET</tt><br />
When requesting an object that exists but is not actually associated with the record specified in the request <br />
|''See other.'' The requested resource is somewhere else. The <tt>Location</tt> header should contain the correct, full URL to the resource.<br />
|-<br />
! colspan="3" | '''Client Error''' (<tt>4XX</tt>)<br />
|-<br />
! scope="row" | <tt>400</tt><br />
|All request methods/actions<br />
|''Invalid request.'' General status code for when something is missing, malformed or incorrect in the request headers/body.<br />
|-<br />
! scope="row" | <tt>401</tt><br />
|All request methods/actions.<br />
|Authentication failure or missing authentication credentials.<br />
|-<br />
! scope="row" | <tt>403</tt><br />
|All request methods/actions.<br />
|''Forbidden.'' This may be because the user in API authentication does not actually have permission in X2Engine to perform the specified action. In Platinum Edition, this code can also indicate that the IP address of the API client has been blocked.<br />
|-<br />
! scope="row" | <tt>404</tt><br />
|All request methods/actions.<br />
|''Not found,'' or invalid URI. It is used whenever a specified record to retrieve directly does not exist, but also as a catch-all for a location that the API was not configured to interpret.<br />
|-<br />
! scope="row" | <tt>405</tt><br />
|All request methods/actions<br />
|''Method not allowed.'' The URI is not malformed, but the method of the request being sent to the server is not permitted. An example would be sending a POST request, which is intended for creating new records, to the URI of an existing record, or sending a <tt>DELETE</tt>/<tt>POST</tt>/<tt>PUT</tt> request to a "read-only" resource.<br />
|-<br />
! scope="row" | <tt>408</tt><br />
|All methods; server-generated<br />
|''Request timeout.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>413</tt><br />
|All methods; server-generated<br />
|''Request entity too large.'' This code is not as of this writing not produced by the API; it might be returned by the web server itself.<br />
|-<br />
! scope="row" | <tt>415</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt>, <tt>PUT</tt><br />
|''Unsupported media type.'' As of this writing, this happens only whenever the request is sent with a body and the <tt>Content-Type</tt> header is not set to "application/json"<br />
|-<br />
! scope="row" | <tt>422</tt><br />
|<tt>PATCH</tt>, <tt>POST</tt> and <tt>PUT</tt><br />
When creating or updating a record<br />
|''Unprocessable entity.'' This always indicates a data validation error when attempting to set fields of and save an active record model. The client is expected to resolve these issues and resubmit the data with all of the fields formatted in such a way that it is acceptable.<br />
|-<br />
! scope="row" | <tt>429</tt><br />
|''Platinum Edition only''<br />
All request methods/actions<br />
|''Too many requests.'' When rate limiting is enabled in the API settings, the server will use this code to indicate that the API client has been making requests to the API too frequently. When this status is sent, the response should also contain a <tt>Retry-After</tt> header specifying the number of seconds to wait before trying again.<br />
<br />
There is one exception to this, however: when attempting to create a web hook for a given user, event and model name, if the hook limit has already been reached for a user, event and model name, a 429 without <tt>Retry-After</tt> would be sent, and this implies that no more hooks for that combination should be created.<br />
|-<br />
! colspan="3" | '''Server Error''' (<tt>5XX</tt>)<br />
|-<br />
! scope="row" | <tt>500</tt><br />
|All request methods/actions<br />
|''Internal server error.'' General status for when something isn't right on the server. PHP and database errors will produce this status code.<br />
|-<br />
! scope="row" | <tt>501</tt><br />
|All request methods/actions<br />
|''Not implemented.'' The API should use this code to denote that a given method, action, etc. is not yet available, but may be in the future.<br />
|-<br />
! scope="row" | <tt>503</tt><br />
|''Professional and Platinum Editions''<br />
All request methods/actions<br />
|''Unavailable.'' X2Engine and/or its API service has been disabled/locked by an administrator.<br />
|-<br />
|}<br />
<br />
== Error Objects ==<br />
In all error responses produced by the API and not by the server itself, the body of the response will be a JSON containing metadata about the error and the response. This is for compatibility with less-than-satisfactory client libraries which cannot read the actual response code or headers, but might be able to read the response body when the code is not in the success (<tt>2XX</tt>) range. In such cases, the response will be a JSON-encoded object with at least the following properties:<br />
<br />
{|class="wikitable"<br />
|-<br />
! scope="row | httpHeaders<br />
|A dictionary of HTTP headers that were set deliberately by the API (as opposed to automatically, by the web server software) in the response.<br />
|-<br />
! scope="row | message<br />
|A general message about what happened and why. This will in most cases also be saved in the API log.<br />
|-<br />
! scope="row | error<br />
|Boolean true/false; will generally be true.<br />
|-<br />
! scope="row | status<br />
|The HTTP status code that was sent in the response header<br />
|-<br />
|}<br />
In some cases, the response JSON may include the following additional properties:<br />
{|class="wikitable"<br />
|-<br />
! scope="row | errors<br />
| A dictionary of validation errors encountered when attempting to save an active record model; included with responses of status code '''422'''.<br />
The structure will be indexed by attribute name, each entry containing a list of applicable validation errors. It is essentially the resulting value of the [[yii:CModel#errors-detail|CModel.errors]] property.<br />
|-<br />
|}</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Email_Configuration&diff=1944Email Configuration2015-12-21T20:05:53Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
This article covers configuring X2Engine for sending (and, in special cases, receiving) email.<br />
<br />
= Introduction =<br />
X2Engine provides the means to send email, natively, within the web application. Furthermore, with Professional Edition, the local MTA on the web server (if any) can be configured to allow X2Engine to receive emails and automatically associate them with the addressee's contact record.<br />
<br />
= <span class="noglossary">Configuring SMTP Accounts</span> =<br />
This section describes the current, recommended method of sending email in X2Engine. It makes use of a credentials storage system X2Engine features, which encrypts passwords and other authentication data at the database storage level. It is currently only used for storing email account passwords, but may in the foreseeable future be extended to store other types of passwords. To access the credentials manager, click "Manage Apps" under the user menu.<br />
<br />
== Creating Credentials Records ==<br />
[[File:New-Email-Account.png|200px|thumb|right|The create page for a generic email account]]<br />
To create a new email account, first select a type in the dropdown menu next to the '''Add New''' button. Then, click '''Add New'''. The easiest type of email account to add is Google Mail (i.e. GMail or email accounts offered via Google Apps for businesses). For such email accounts, SMTP server data such as the host name, port, and security type are already set, and all you have to enter is your username, password, and sender name. For all other types of email, first contact your email hosting provider or peruse their documentation on configuring email client programs (i.e. Outlook / Thunderbird) for use with their email service. The essential information that you need:<br />
* '''Server''': host name, i.e. smtp.email.com<br />
* '''Port''': a 2-3 digit number, i.e. 25 for unsecured SMTP, or 465 (legacy) or 587 for secure SMTP.<br />
* '''Security Type''': TLS/SSL. In most cases these are interchangeable.<br />
* '''User name'''/'''Password''': If the SMTP server does not use authentication, leave these blank. Otherwise, set them according to the username/password used for logging in to the email<br />
<br />
== Creating System-Owned Credentials ==<br />
One may wish to have all users use a specific email account/address for email blasting, or set a sender account for system notifications, or responses to service case submitters (etc). One can do so by creating publicly-usable email accounts. Such accounts will be usable by all users, but may only be viewed or edited by administrators, and thus are ideal for such purposes as they aren't necessarily attached to / identified with any given user.<br />
<br />
To designate an account as system-owned, set the '''Owner''' to be "System" when creating it.<br />
<br />
== Creating Non-Private Credentials ==<br />
Non-administrators also have the ability to create credentials that can be used by administrators, in the rare case where it may be desirable for the owner to send mail on their behalf. To do so (allowing administrators to masquerade as the other user when sending email), that user must consent to allow their email account to be used by other people, by un-checking the '''Private''' checkbox. Note, however, that this will not allow administrators to view or edit the credentials, only to use them as a delivery method option when sending email.<br />
<br />
== Setting Personal Default Credentials ==<br />
[[File:1-my-default.png|300px|thumb|right|Setting one's own personal default credentials]]<br />
It is usually desirable to have a preferred email account for use in X2Engine, while at the same time having the option to send email using the system-wide (legacy) method, or via some other email account set up through the credentials manager.<br />
<br />
Next to each credentials record, there will be a "pill" with a checkbox in it that says "Set as my default", or "Set as default" (when system-owned and being viewed by an administrative user). When the box is checked, an "apply" button will appear to enact the change. After clicking "apply" a green pill label appears on the record to denote that it has been selected as default. What this means is that whenever one opens the inline email widget (i.e. on a contact view, by clicking the email button), the "send as" field will be set to the selected account by default. This way it still allow switching email accounts on-the-fly, but removes an extra step that would otherwise be necessary to remember whenever sending an email.<br />
<br />
== Setting System Default Credentials ==<br />
[[File:System-Default.png|450px|thumb|right|Setting default credentials for the system and/or oneself]]<br />
All users have the ability to set system-owned credentials as their personal defaults. However, administrative users can also designate the credentials for system use purposes, i.e. bulk email and system notifications. For each credentials record owned by the system, when being viewed as the administrator, the pill will say "set as default" instead. When the box is checked, it will display a dropdown menu of system roles for which the account isn't already selected to use by default. After selecting the appropriate role to apply it as default for, and clicking "apply" as one would for setting personal defaults, it will be set as the default email account to use for that purpose.<br />
<br />
Note, also, setting defaults for system roles in the credentials manager is just another way of changing settings in "Email Server Configuration" (accessed from within the Admin panel), and vice versa. These settings are actually synchronized. Some definitions of system uses:<br />
<br />
;Bulk Email Account<br />
: Will be selected by default when users create a new campaign (overrides the users' personal default)<br />
;Service Case Email Account<br />
: Used when sending responses to service case submitters.<br />
;System Response Emailer<br />
: Used when sending responses to web leads, and is selected by default in X2Flow's send-an-email actions (but can be changed).<br />
;System Notification Emailer<br />
: Used for miscellaneous notification tasks, most notably, sending "broadcast event" emails to targeted users when users broadcast an event, select users to broadcast it to, and enable the option to notify users by email.<br />
<br />
== Configuring Automated Email Logging ==<br />
Beginning in version 5.4, the Email module has the capability to automatically poll for new incoming and outgoing messages associated with an email inbox. Similarly to logging a message manually, auto logging emails for an inbox will attach a message to all of the contacts that are related to the message. Additionally, the arrival of inbound email messages will be indicated with an event posted to the user's activity feed. With this functionality, two new X2Workflow triggers have been added, allowing X2Workflow actions to be taken on both inbound and outbound email messages.<br />
<br />
To configure automated email logging, the administrator will first need to configure the "Auto-Log Emails" entry on the Cron Table settings page from the administration section, just as they would for configuring automatic updates and X2Workflow cron automation. It is recommended not to specify too short of a cron interval in order to avoid any of the email accounts having their connections throttled. Then, each user that would like to use automated logging will need to update their inbox by selecting "Configure My Inbox" in the left hand actions menu of the Email module to enable both inbound and outbound logging, both of which can be enabled independently.<br />
<br />
= Using The Legacy (system-wide) Email Delivery Methods =<br />
Apart from using separate, third-party SMTP accounts for each user (and system purpose), there is a way to use a singular email delivery method for all emails sent via X2Engine. However, this method is not recommended, unless your web server also serves as your mail server. It is very much considered a legacy feature.<br />
<br />
<br />
There are four options for methods of sending email, which can be found in the '''Outbound Email Server''' section in '''Email Settings''' under the '''Email Configuration''' section in the '''Admin''' page. These methods are:<br />
<br />
# PHP Mail<br />
# Sendmail<br />
# QMail<br />
# SMTP<br />
<br />
If any of these aren't available, they won't be listed in the '''Method''' menu.<br />
<br />
PHP Mail, Sendmail and QMail each, while independent of any offsite, third-party service, require that an MTA be available on the web server. The main difference is that PHP Mail uses whichever MTA that PHP itself is configured to use, whereas the Sendmail and QMail methods are specific to the Exim4/Postfix and QMail MTA's (respectively).<br />
<br />
For all instances of X2Engine Professional Edition cloud/ondemand, the methods available for sending mail include all of these except QMail.<br />
<br />
== Circumventing Spam Filters ==<br />
While the non-SMTP methods for sending email within X2Engine may work immediately, a common problem encountered with them is that email goes into spam folders on the receiving end, or is not being received at all. ''This is especially likely to happen when the email addresses specified by users in their profiles'' (which get used as the sender address in the headers of mail sent by X2Engine) ''do not belong to the same domain name as the server hosting X2Engine.'' So, for example, it would be problematic if a user uses an email address in their X2Engine profile that ends in gmail.com, and the X2Engine system is hosted on example123.com<br />
<br />
This is '''not''' a fault in X2Engine (or [http://phpmailer.worxware.com/ PHPMailer], which X2Engine uses for mail delivery). Rather, it is endemic to all web applications (i.e. Wordpress, Drupal, etc.) due to how sender address spoofing (telling the recipient that the email comes from somewhere else) is a common behavior among spammers. Furthermore, the general proliferation of email spam through various means (but especially PHP scripts running on web servers) has resulted in greatly elevated standards in the spam-detection mechanisms and heuristics of most email servers. This generally poses an entry barrier to getting around spam filters. It is thus recommended that, before sending emails using these legacy methods, measures are taken to ensure that mail sent will have the best chance of avoiding spam filters.<br />
<br />
=== Work-around 1: Aliasing ===<br />
This works by first creating mail forwarding aliases on the server, i.e. through a Virtualmin or CPanel control interface, or manually, by editing the MTA's virtual user aliases table (see their respective documentation for more information). Next, instruct all users to use these email addresses as their contact address in their X2Engine profile, so that when contacts reply, the response emails will be forwarded to the desired addresses (or even a list of addresses) specified by each alias.<br />
<br />
=== Work-around 2: Migration ===<br />
This, of course, is the most straightforward solution: to use the hosting provider's email service. However, it requires switching from one's existing email service/addresses to service and addresses provided by the hosting provider, and it is also extra effort.<br />
<br />
=== Work-around 3: Third-party Service ===<br />
''Note, using this method is '''not''' recommended; see <span class="noglossary">[[#Configuring SMTP Accounts|Configuring SMTP Accounts]]</span> for the preferred method of using SMTP for email delivery.''<br />
<br />
Using this method, the entire organization that uses X2Engine sends email using a single email account managed by a third-party service that supports SMTP. Thus, sending the email and digital signing are handled by the third-party service, and getting caught in spam filters generally tends to be a smaller problem.<br />
<br />
In most cases, this method will work. How it works is that the reply-to address in the email will be set to the email address of user who is sending the mail through X2Engine, while the actual mail system user is used to authenticate with the mail server. In such cases, the address of the mail user might still show up in one of the headers of the resulting email. However, '''this is known to not work with many email providers, most notably GMail''', which (for security and anti-spam purposes) does not permit sending emails if the sender address differs from the address associated with the Google account that was used to authenticate. If this is the case, it is recommended to use separate SMTP accounts via the credentials manager.<br />
<br />
=== Using <span class="noglossary">DKIM</span> ===<br />
(see [[wikipedia:DomainKeys_Identified_Mail|main article]] on Wikipedia)<br />
<br />
This method, which requires server-end configuration, is a very secure and reliable method of distinguishing email as being of legitimate and trustworthy origin. '''Almost every''' reputable email service is configured to use a mail signing agent to add a special signature sent from such addresses, in addition to checking signatures of incoming emails. Emails that are digitally signed are far more likely to be recognized by mail servers worldwide as being from a reputable source, and thus less likely to go be caught in spam filters.<br />
<br />
However, it still requires that the sender's email address be of the same domain as the email server. That is because the sender address cannot be "spoofed", because DKIM was designed to prevent this sort of activity (which is common among spammers). Furthermore, if outgoing email is signed for domains not corresponding to the point of origin, it may actually make matters worse; many reputable mail services (including GMail) themselves use DKIM to avoid their domain name being abused by address spoofing. Thus, if a DKIM signature header is added to the email, and the signature does not match the DKIM record of the sender's domain, it will be considered a forgery attempt and blocked/filtered by most email servers.<ref>DKIM uses the RSA public/private key cryptographic technique to ensure that signatures are secure and cannot be forged. Thus, there is no legitimate way of circumventing this issue.</ref><br />
<br />
= Troubleshooting <span class="noglossary">SMTP</span> and <span class="noglossary">IMAP</span> Connectivity =<br />
{|class="wikitable" style="float: right;"<br />
|-<br />
! scope="row" |<tt>Protocol</tt><br />
| <tt>Port</tt><br />
| <tt>Security Type</tt><br />
|-<br />
! scope="row" | SMTP<br />
| 25<br />
| None<br />
|-<br />
! scope="row" | &nbsp;<br />
| 465<br />
| SSL<br />
|-<br />
! scope="row" | &nbsp;<br />
| 587<br />
| TLS<br />
|-<br />
! scope="row" | IMAP<br />
| 143<br />
| None<br />
|-<br />
! scope="row" | &nbsp;<br />
| 993<br />
| TLS<br />
|-<br />
|}<br />
<br />
If, when attempting to send an email, you see an error message "SMTP Error: Could not connect to SMTP host" (from an exception thrown in PHPMailer), this indicates exactly what it says; it was unable to connect. There are a wide variety of reasons why an SMTP connection should fail, but they almost always fall into one of the following two categories:<br />
# Configuration of the connection and authentication<br />
# Local network environment<br />
It is recommended to check them in that order. First, to test configuration, check:<br />
# The host name is correct<br />
# The SMTP port number is correct<br />
# The option to use TLS is enabled, if required by the email service provider<br />
# The username and password are correct and exactly as specified by the service provider (i.e. if the SMTP username is whole email address, it should be entered as the whole email address).<br />
You can then attempt to verify your credentials upon viewing them in the "Manage Apps" section. If the credentials do not authenticate, and you have ensured that your details are correct, then the issue is likely a server or network configuration issue.<br />
<br />
Testing the network configuration is more involved and will require logging into the server that hosts X2Engine via SSH or otherwise. One should first acquire the IP address of the SMTP host, via the command <tt>ping [smtp host]</tt> in a DOS or Unix shell, from any machine that is known to be able to access the mail server. You must then verify, locally on the X2Engine hosting server, that:<br />
# The SMTP host can be reached, i.e. there exists no valid network route and/or NAT rule allowing access to it, from the server on which X2Engine is hosted. <br />
# The SMTP hostname can be resolved (DNS is available). Try <tt>ping</tt> again, on the server hosting X2Engine itself.<br />
# The port number is not being blocked by the ISP for outbound connections on the port. This is the case with most consumer providers, who block port 25 in an effort to block spam from infected hosts. If the first two requirements are satisfied and SMTP connections still fail, this is most likely the cause of the problem. One can also test this via port scanning or [http://www.port25.com/how-to-check-an-smtp-connection-with-a-manual-telnet-session-2/ with telnet].<br />
# On a Linux host, check that the outbound firewall rules are not interfering. Ensure that the outbound firewall policy is to either to accept traffic, or in a more restricted environment in which the default target is to drop or reject packets, ensure that the required ports are allowed outbound. This can be verified using iptables from the command line with:<br />
<nowiki>#</nowiki> If accepting packets by default:<br />
[user@localhost ~]$ sudo iptables -L OUTPUT<br />
Chain OUTPUT (policy ACCEPT)<br />
target prot opt source destination<br />
<br />
<nowiki>#</nowiki> Or if packets are dropped or denied by default:<br />
[user@localhost ~]$ sudo iptables -L OUTPUT<br />
Chain OUTPUT (policy DROP)<br />
target prot opt source destination<br />
DROP all -- anywhere anywhere state INVALID<br />
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:domain<br />
ACCEPT udp -- anywhere anywhere udp dpt:domain<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:submission<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:imaps<br />
<br />
If you receive errors trying to load the email module, and you have already verified your credentials and can successfully send mail, then double-check that the PHP IMAP extension is properly installed. This package is called '''php-imap''' on Red Hat based distributions, and '''php5-imap''' on Debian based distributions.<br />
<br />
= Configuring the "Email Dropbox" For Receiving Emails =<br />
This feature, available in X2Engine Professional Edition, provides the means to automatically create contacts and email-type action records by capturing emails from external email software, i.e. Outlook and GMail.<br />
<br />
'''Note, if you are a customer of X2Engine Professional Cloud/On Demand:''' you do not need to set up the necessary prerequisites for this feature; that has been done for you. The feature should already be ready to use, and the alias will be <tt>dropbox@[yoursubdomain].x2vps.com</tt>. You will only need to configure the Email Dropbox if you are using Download/On Premise.<br />
<br />
Setting it up requires a Linux/Unix server environment, a MTA, and knowledge of how to set up email aliases. There are two methods of integration: direct, and via API.<br />
<br />
== Using the Email Dropbox ==<br />
As of the current version, there are four ways of using the email dropbox:<br />
;CC<br />
: When sending an email to a contact, include the email alias in the CC field, and the email will be attached to the contact as an outgoing email.<br />
;Forward<br />
: After receiving an email from a contact, forward it to the email alias. It will be attached to the contact as an incoming email.<br />
;Direct<br />
: Send an email directly to the alias, '''without "Fwd:" in the subject'''. A post in the social feed will be created using the contents of the email's body.<br />
;Case Attachment<br />
: Same as forward or CC, but include a user-defined code in the email body to specify that the email should be converted to/attached to a case in the Services module.<br />
<br />
== Direct Integration ==<br />
This set-up method works by locally sending the email contents directly to X2Engine through the system console. It is more straightforward to configure, but requires that the MTA and web server hosting X2Engine be on the same machine, virtual or physical. Furthermore, it can run into file permissions/ownership issues.<br />
<ol><br />
<li>Using CPanel, Webmin/Virtualmin, or any other web host administrative tool, create a mail alias on the server that begins with "dropbox@", i.e. "dropbox@yourdomain.com". (Note: you can also do this manually by editing the mail server's configuration files, although doing that is beyond the scope of this guide.)</li><br />
<li>Find the absolute path on the web server's filesystem to X2Engine, if you're not sure where it is already:<br />
<ol style="list-style-type:lower-alpha"><br />
<li>Paste the following into a PHP file called "path.php", in the same directory as X2Engine: <syntaxhighlight lang="php"><?php echo realpath(dirname(__FILE__)); ?></syntaxhighlight></li><br />
<li>Navigate to the file using a web browser</li><br />
<li>Record the path, append "/email.php", and use the resulting string as the path to the capture script in the mail alias.</li><br />
<li>Delete the file "path.php" from the server.</li><br />
</ol></li><br />
<li>Create an email alias. Set its destination a pipe to a program: '''<tt>/usr/bin/php /path/to/X2Engine/email.php</tt>''' (versions up to 2.5.2) or '''<tt>/path/to/X2Engine/protected/yiic emaildropbox</tt>''' (versions 2.7 and later)</li><br />
<li>(X2Engine versions 2.7 and later) Change the permissions on the <tt>protected/runtime</tt> folder to '''777''' (all users can read/write).<ref>If you cannot do this for security purposes, but are a system administrator, you can instead change them to 770. If you do so, however, you must change the group ownership of the directory to the group under which the MTA spawns processes (typically "nobody" or "nogroup").</ref></li><br />
<li>Test the alias by sending a message to a fictitious name/email address (i.e. <tt>"Sue Doenimm" <test@example.com></tt>) and CC-ing the mail alias. </li><br />
<li>Check in the Contacts module after sending the email, and if the fictitious contact was created, check to see that the email was attached properly to the contact record. If the contact was not created or the email was not imported, check the logs of the MTA for any errors.<br />
<li>Test to see if your email software's forwarded message format is supported by forwarding an email from a contact to the mail alias.</li><br />
</ol><br />
<br />
== API Integration ==<br />
This method works by sending the email contents to X2Engine through the remote API, and thus permits the MTA and X2Engine to live on different servers. Similar to the direct method, an alias will need to be constructed to receive the email and send it to a program, but in this case the program acts as a proxy by sending the contents to X2Engine via a web request. This method is more flexible, and it is far less likely to result in file permission issues (i.e. when the MTA attempts to run the Yii console application), but takes slightly more effort to set up.<br />
<br />
For information on API authentication, see [[Remote_API#Authenticating|Remote API: Authenticating]]. <br />
<br />
The setup process is as follows: <br />
<br />
<ol><br />
<li>'''Obtain the API script.''' Using your file/FTP manager of choice, navigate to the <tt>protected</tt> folder inside X2Engine, find the <tt>integration</tt> folder, and inside of that, navigate to the "MTA" folder.</li><br />
<li>Download/copy <tt>emailImport.php</tt></li><br />
<li>Open the file in your editor of choice, and look for the following lines of code:<br />
<syntaxhighlight lang="php"><br />
///////////////////////////<br />
// Configuration details //<br />
///////////////////////////<br />
// Set this to the IP address or domain name of the server<br />
$host = '';<br />
// Set this to the protocol (use "https://" for an SSL-enabled web server)<br />
$proto = 'http://';<br />
// Set this to the URI on the web server of X2Engine, without the trailing slash.<br />
// So, if the login URL is "http://example.com/X2Engine/index.php/site/login",<br />
// this variable should be "/X2Engine"<br />
$baseUri = '';<br />
// Leave this null if the host specified by $host will resolve correctly.<br />
// Otherwise, if in an environment where (for instance) the domain does not resolve<br />
// properly, and the IP address must be used, but the CRM is on a specifically-named<br />
// virtual host on a shared IP, set this to the domain name of that host, and set<br />
// $host to the IP address of the web server.<br />
$hostName = '';<br />
$data = array(<br />
'user' => '',<br />
'userKey' => '',<br />
);</syntaxhighlight><br />
</li><br />
<li>Change the variables according to their comments by putting the appropriate values in the (currently empty) pairs of single quotes. For instance, if your CRM is hosted at "https://crm.domain.com/crm", you will put "https://" in for <tt>$proto</tt>, "crm.domain.com" for <tt>$host</tt>, and "/crm" for <tt>$baseUri</tt>.</li><br />
<li>In the <tt>$data</tt> array, set the <tt>user</tt> and <tt>userKey</tt> to the API user and API key to be used (see: [[Remote_API#Authenticating|Remote API: Authenticating]]). So, for "admin" and key "aabbccddee": <br />
<syntaxhighlight lang="php"><br />
$data = array(<br />
'user' => 'admin',<br />
'userKey' => 'aabbccddee',<br />
);</syntaxhighlight><br />
</li><br />
<li>Upload the script to the server to be handling email.</li><br />
<li>Ensure the script has proper ownership and permissions to be run by the MTA user. If you're not sure what user the MTA will run under when executing mail handling scripts, set the permissions on emailImport.php to '''755''' (all can read, owner/group can write, all can execute)</li><br />
<li>Create an email alias on the server to be handling email, which pipes to the program: '''/usr/bin/php /path/to/emailImport.php''' (replacing "/path/to" with the actual filesystem path where you uploaded the script).</li><br />
</ol><br />
<br />
== Troubleshooting ==<br />
Due to how this feature requires interaction between multiple software systems, a non-functioning email dropbox can have a very wide variety of possible causes.<br />
=== Email is not captured ===<br />
It may be that mail is not actually getting transmitted to X2Engine in the first place. You should see something in the MTA logs that looks a bit like this (i.e. if you're using Postfix and the "direct" integration method):<br />
<br />
<tt>Feb 10 10:28:21 HOSTNAME postfix/local[2546]: D8C0B13613CB: to=<dropbox-USER@DOMAIN>, orig_to=<USER@DOMAIN>, relay=local, delay=1.2, delays=0.86/0/0/0.39, dsn=2.0.0, status=sent (delivered to command: /home/USER/public_html/protected/yiic emaildropbox)</tt><br />
<br />
In the case of API integration, substitute the "yiic" command with "/usr/bin/php /path/to/emailImport.php"; one way or another, what's happening on the most basic level is that email is getting handled by a program rather than going into a mailbox.<br />
<br />
If you can verify that this event happened (delivery to a local mail handling program), and no error email response is received, but the dropbox "silently" fails (by not capturing the email), verify the following:<br />
<ul><br />
<li>Your user profile email address matches the address from which you are sending email.</li><br />
<li>If using the API method of integration, check:<br />
<ul><br />
<li>The domain name of the X2Engine-hosting server is resolvable on the mail host</li><br />
<li>The host with X2Engine on it can be accessed from the mail host via a HTTP (web) request</li><br />
<li>If X2Engine is on a server that is behind a firewall, port 80 (or 443, if using HTTPS) on the external IP address is forwarded to the X2Engine web host</li><br />
<li>The API user and user key are correct in the API configuration</li><br />
</ul></li><br />
<li>If you have "Create Contacts from Emails" disabled, the recipient address of the email (or original sender, if you are forwarding the email) matches that of an existing contact in X2Engine</li><br />
</ul><br />
<br />
=== Email rejected with error message ===<br />
Verify:<br />
* Correct email server settings, DNS, and email alias<br />
* Correct permissions/ownership on all scripts run by the MTA<br />
<br />
=== (API Method) No request to X2Engine showed up in the web server logs ===<br />
This is due to one of the following problems:<br />
# The mail handling server never received the message<br />
# Any of the same conditions that would cause the MTA to bounce the email back to the sender with an error message<br />
# The mail handling server couldn't connect to X2Engine's server<br />
Ultimately, it boils down to there being no network connection between the mail handling server and X2Engine to begin with.<br />
<br />
To investigate if #1 and #2, first look at the MTA's logs. They are typically stored in /var/log, and named <tt>maillog</tt> (RHEL/CentOS) or <tt>mail.log</tt> and <tt>mail.err</tt> (Debian/Ubuntu). If there is no record of mail received:<br />
* Does the mail handling server have a fully-qualified domain name?<br />
* Check the MTA's configuration. Is it configured to allow receiving mail for the system's FQDN?<br />
* Is there a route to the mail host from the internet? And, are the necessary ports open for receiving mail?<br />
<br />
Otherwise, if there are lines in the mail log(s) matching the alias, they will most often immediately indicate what went wrong. In such cases, the issue will typically be one of an improperly typed pipe alias (i.e. feeding directly to the script, rather than the PHP interpreter which runs the script), or permissions (meaning, Postfix child processes cannot read the import script file). Note that each node (parent directory) in the path to the script must be executable by the Postfix subprocess; else it cannot access anything within.<br />
<br />
If, on the other hand (#3) there were no errors reported in the mail log for delivery to the alias, the problem is at the level of connecting between servers. Thus, you must verify:<br />
* Is the domain name of the web server correct?<br />
* Can the web server of the CRM be resolved to the proper IP address on the mail server?<br />
* Can HTTP connections can be made to the server (i.e. verify the port isn't blocked, a valid route exists to the host, etc.)?<br />
* Is the php_curl extension available in the command line environment on the mail handling server?<br />
<br />
=== Response email regarding an unsupported forwarded message format ===<br />
Forward the the email to [mailto:customersupport@x2engine.com X2Engine Customer Support], using the same email software that was used to test the feature. Due to the great diversity of email software and the inconsistency of forwarded/attached message formatting across platforms, the forwarded message capture may not immediately work with your email software of choice. However, per request, support for new forwarded message formats will be added in the next release.<br />
<br />
= Notes =<br />
<references /></div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Frequently_Asked_Questions&diff=1942Frequently Asked Questions2015-10-14T18:05:27Z<p>Raymond Colebaugh: /* How do I update existing records through import? */</p>
<hr />
<div>[[Category:Support]]<br />
= API =<br />
== Contacts ==<br />
=== I made a contact through the API. Why doesn't the assignedTo field get set according to lead routing rules? ===<br />
When creating records through the API, all fields must be explicitly defined. One can, however, obtain an assignee for a new contact that is distributed according to lead routing type via the public [[x2propdoc:AdminController.html#_actionGetRoutingType|"getLeadRoutingType" action of AdminController]]. An example of this can be found in [[x2doc:APIModel|APIModel]] (which is a class that was written for the express purpose of creating contacts and other records via the API).<br />
<br />
=== Why doesn't the "Record Created" X2Flow trigger fire when creating a contact via API? ===<br />
Note, the data submitted to the server should not contain the "createDate" field. If it does not, the field will be automatically set to the current timestamp at record insertion, and the "Record Created" trigger will be activated. Otherwise, it is assumed that a data import of some sort is happening, because a non-empty creation date implies that it was created on some other system first. In the latter case, the record created trigger will not be activated, and the field will not be automatically set.<br />
<br />
=== Why doesn't the "Web Lead" X2Flow trigger fire when creating a web lead via API? ===<br />
The "create" API method, intended as a generic method for creating records, cannot assume that it is being used for creating a web lead in particular. One might choose instead to create a flow triggered by record creation and add the criteria that the lead type must be "Web", and set the lead type to "Web" in the submission to the API, which be practically the same as having the web lead trigger activate in the API.<br />
<br />
== Actions &amp; Action History ==<br />
<br />
=== How do I create a note on a record? ===<br />
Creating a note (or call log, or email, or any action history record) amounts to creating an action record with its association type set to the module name and the association ID to the ID of the record to which you'll be attaching the action history item. Note, the <tt>actionDescription</tt> attribute controls the content of the action record. So, for example (if using APIModel and attaching to contact 123):<br />
<br />
<syntaxhighlight lang="php"><br />
$note->type = 'note';<br />
$note->associationId = '123';<br />
$note->associationType = 'contacts';<br />
$note->actionDescription = 'text in the note';<br />
$note->modelCreateUpdate('Actions', 'create');<br />
</syntaxhighlight><br />
<br />
= Customization =<br />
<br />
== Custom fields ==<br />
=== I made a custom field and it doesn't show up. Now what? ===<br />
Once you create a field, you must add it to the view and form as you see fit, as follows:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model by selecting the model from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
=== Why isn't any data going into my custom field getting saved? ===<br />
If this occurs for a user that has been assigned any role, you will need to add permissions to that field for the role:<br />
# Go to the "Role Manager" in Admin.<br />
# Select the role to be edited from the dropdown menu.<br />
# Under "View" and "Edit" permissions, click the "+" sign next to the field in the right panels to add them.<br />
# Click "Save".<br />
<br />
== Custom modules ==<br />
=== Why don't I see anything when I go to create/view a record in a new custom module? ===<br />
The first step after creating a custom module is designing a form and view for it. To do so:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model corresponding to the new module by selecting it from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
<br />
= Email =<br />
== Attachments and embedded media ==<br />
=== Why are all my images broken? ===<br />
The first thing you should check and double-check is whether, in "Public Info Settings", the public web root / base URI are set.<br />
<br />
If they are not, and you access your CRM at a non-public URL when sending email, the image links will be generated such that they point to the CRM through that inaccessible URL. Thus, the images will naturally be broken in any browser accessing the web from a network that cannot access your CRM. This is what the public info settings options were meant to address. In order for campaigns to work properly, there must be some way of publicly accessing the CRM from the web. This is so that, among many things, the "unsubscribe" link works (the user is essentially visiting a page in the CRM that unsubscribes them).<br />
<br />
This happens in GMail even if the CRM is accessible from where you're using GMail because Google tries to access and cache images in emails. Google's servers won't be able to access them on account of Google not dwelling within your subnet (or network where the CRM is accessed), so naturally, all images with non-public URLs will show up broken regardless of how you're accessing the network.<br />
<br />
=== Why is there a broken image in the email, even though I didn't attach anything? ===<br />
This is the tracking image, which allows X2Engine to register that the user has opened the email. It should ordinarily be invisible, but is broken because your CRM is not publicly accessible. See the answer to "[[#Why are all my images broken?|Why are all my images broken?]]" <br />
<br />
== Campaigns ==<br />
=== Why don't the "Unsubscribe" link and email opened tracking work? ===<br />
See the answer to the above question, "[[#Why are all my images broken?|Why are all my images broken?]]"<br />
<br />
= Importing &amp; Exporting =<br />
<br />
== Record Importer ==<br />
<br />
=== What format does my CSV have to be in order to use the record importer? ===<br />
The following prerequisites must apply to the CSV file:<br />
# The first line of the file must '''not''' be a record of data, but the names of the fields in the data.<br />
# The delimiter must be a comma, the enclosure must be a double quote ('''"'''), and the escape character must be a backslash ('''\''').<br />
# All commas in the file that are not field delimeters must be escaped (preceded by a backslash).<br />
<br />
Take for example the following first two lines of a typical contacts CSV export:<br />
<pre><br />
id,name,firstName,lastName,title,company,phone,phone2,email<br />
1,"Steve Mcqueen",Steve,McQueen,"Operations Manager","Pierre Office Supplies",605-636-5634,,steve@example.com<br />
</pre><br />
<br />
If your CSV does not meet the second or third criterion, X2Engine recommends the following procedure:<br />
# Import your CSV into a spreadsheet program.<br />
# Export your contacts data back into a CSV with commas properly escaped and the delimiter/enclosure chosen properly.<br />
# Attempt to import your contacts again using the new CSV file.<br />
<br />
It is worth noting that the contacts importer gets data by calling PHP's [http://us1.php.net/fgetcsv fgetcsv] function, with only the first argument specified. Thus, the prerequisites of the input CSV's formatting and internal conventions are derived from the default behavior of that function.<br />
<br />
=== Why does the importer process only one record before stopping? ===<br />
This may be indicative of a common problem that occurs when using Microsoft Excel in Windows, and the Web server with X2Engine is Linux, Unix or pretty much anything that isn't Windows. What's happening is that Excel is exporting the CSV file with Windows-style line breaks only. Thus, the server interprets the file as though it were just one line long.<br />
<br />
=== Why are my records running into validation failures? ===<br />
This is caused by legitimately "bad" data (or formatting) in the input CSV. For example, the email field might not actually be formatted like an email address, or the first and last names of certain contacts might be blank whereas they are required when creating or updating contacts and cannot be made blank. To get around this, if you want to import the full set and then fix the data once inside X2Engine, you should perform the following steps:<br />
# Look for the file "failedContacts.csv" on the server, after the import with contacts that failed validation.<br />
# If applicable, undo the existing contact imports where there were contacts that failed import due to validation errors.<br />
# Go to "Admin"<br />
# Under "X2Studio", go "Manage Fields"<br />
# Edit the fields whose analogues in the CSV contain invalid data. To determine which fields contain invalid data, examine the contents of the "failedContacts.csv" file.<br />
<br />
=== How do I update existing records through import? ===<br />
[[File:importupdate1.jpg|thumb|Selecting a Match Attribute]]<br />
<br />
When updating a large portion of your records, it may be more convenient to use a familiar spreadsheet program to manipulate the data. After exporting the data to CSV, you can update your data before importing the CSV, selecting which attribute is used to lookup existing records.<br />
# Export a CSV file of the records to be updated, then open it in your spreadsheet program and remove the columns which will not be updated or used to match on existing records.<br />
# Perform all necessary updates to the data in the CSV.<br />
# Navigate to the import page for the module the records were exported from and upload the CSV.<br />
# In the importer options, select "Update Existing Records," then select the attribute to match on existing records.<br />
# Modify your import map as appropriate and set other import options, then proceed with the import. If you have selected a field to match on which is not marked "unique" in the Field Manager, you will need to confirm a warning message. Should multiple records be matched by the value in the match attribute, unexpected data manipulation may occur.<br />
<br />
= Updating =<br />
<br />
== Compatibility Issues Detected ==<br />
<br />
=== How can I get around this? ===<br />
You can still update. If compatibility issues are detected, this will not prevent you from updating; it was merely intended to draw attention to potential issues with the update. This is especially applicable in the case of customized files. Simply click the "Apply Changes" button.<br />
<br />
However, if you notice a message regarding conflicting fields among the compatibility issues messages, you will probably run into a database error in the update process. There is one exception to this, the "actionDescription" field in the Actions model, which was removed in 3.0 and re-added in 3.5.5 (so if you are updating from before 3.0, you can disregard this message with respect to that particular field).<br />
<br />
=== Files in protected/config are going to be overwritten? ===<br />
You can generally always ignore this message. These files were originally included as part of the default fileset, to provide examples of how to write them. However, due to how the average end user does not customize X2Engine at the source code level, inclusion of these files in the default codebase has been discontinued so as to avoid this unnecessary message.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Frequently_Asked_Questions&diff=1941Frequently Asked Questions2015-10-14T18:04:26Z<p>Raymond Colebaugh: /* How do I update existing records through import? */</p>
<hr />
<div>[[Category:Support]]<br />
= API =<br />
== Contacts ==<br />
=== I made a contact through the API. Why doesn't the assignedTo field get set according to lead routing rules? ===<br />
When creating records through the API, all fields must be explicitly defined. One can, however, obtain an assignee for a new contact that is distributed according to lead routing type via the public [[x2propdoc:AdminController.html#_actionGetRoutingType|"getLeadRoutingType" action of AdminController]]. An example of this can be found in [[x2doc:APIModel|APIModel]] (which is a class that was written for the express purpose of creating contacts and other records via the API).<br />
<br />
=== Why doesn't the "Record Created" X2Flow trigger fire when creating a contact via API? ===<br />
Note, the data submitted to the server should not contain the "createDate" field. If it does not, the field will be automatically set to the current timestamp at record insertion, and the "Record Created" trigger will be activated. Otherwise, it is assumed that a data import of some sort is happening, because a non-empty creation date implies that it was created on some other system first. In the latter case, the record created trigger will not be activated, and the field will not be automatically set.<br />
<br />
=== Why doesn't the "Web Lead" X2Flow trigger fire when creating a web lead via API? ===<br />
The "create" API method, intended as a generic method for creating records, cannot assume that it is being used for creating a web lead in particular. One might choose instead to create a flow triggered by record creation and add the criteria that the lead type must be "Web", and set the lead type to "Web" in the submission to the API, which be practically the same as having the web lead trigger activate in the API.<br />
<br />
== Actions &amp; Action History ==<br />
<br />
=== How do I create a note on a record? ===<br />
Creating a note (or call log, or email, or any action history record) amounts to creating an action record with its association type set to the module name and the association ID to the ID of the record to which you'll be attaching the action history item. Note, the <tt>actionDescription</tt> attribute controls the content of the action record. So, for example (if using APIModel and attaching to contact 123):<br />
<br />
<syntaxhighlight lang="php"><br />
$note->type = 'note';<br />
$note->associationId = '123';<br />
$note->associationType = 'contacts';<br />
$note->actionDescription = 'text in the note';<br />
$note->modelCreateUpdate('Actions', 'create');<br />
</syntaxhighlight><br />
<br />
= Customization =<br />
<br />
== Custom fields ==<br />
=== I made a custom field and it doesn't show up. Now what? ===<br />
Once you create a field, you must add it to the view and form as you see fit, as follows:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model by selecting the model from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
=== Why isn't any data going into my custom field getting saved? ===<br />
If this occurs for a user that has been assigned any role, you will need to add permissions to that field for the role:<br />
# Go to the "Role Manager" in Admin.<br />
# Select the role to be edited from the dropdown menu.<br />
# Under "View" and "Edit" permissions, click the "+" sign next to the field in the right panels to add them.<br />
# Click "Save".<br />
<br />
== Custom modules ==<br />
=== Why don't I see anything when I go to create/view a record in a new custom module? ===<br />
The first step after creating a custom module is designing a form and view for it. To do so:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model corresponding to the new module by selecting it from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
<br />
= Email =<br />
== Attachments and embedded media ==<br />
=== Why are all my images broken? ===<br />
The first thing you should check and double-check is whether, in "Public Info Settings", the public web root / base URI are set.<br />
<br />
If they are not, and you access your CRM at a non-public URL when sending email, the image links will be generated such that they point to the CRM through that inaccessible URL. Thus, the images will naturally be broken in any browser accessing the web from a network that cannot access your CRM. This is what the public info settings options were meant to address. In order for campaigns to work properly, there must be some way of publicly accessing the CRM from the web. This is so that, among many things, the "unsubscribe" link works (the user is essentially visiting a page in the CRM that unsubscribes them).<br />
<br />
This happens in GMail even if the CRM is accessible from where you're using GMail because Google tries to access and cache images in emails. Google's servers won't be able to access them on account of Google not dwelling within your subnet (or network where the CRM is accessed), so naturally, all images with non-public URLs will show up broken regardless of how you're accessing the network.<br />
<br />
=== Why is there a broken image in the email, even though I didn't attach anything? ===<br />
This is the tracking image, which allows X2Engine to register that the user has opened the email. It should ordinarily be invisible, but is broken because your CRM is not publicly accessible. See the answer to "[[#Why are all my images broken?|Why are all my images broken?]]" <br />
<br />
== Campaigns ==<br />
=== Why don't the "Unsubscribe" link and email opened tracking work? ===<br />
See the answer to the above question, "[[#Why are all my images broken?|Why are all my images broken?]]"<br />
<br />
= Importing &amp; Exporting =<br />
<br />
== Record Importer ==<br />
<br />
=== What format does my CSV have to be in order to use the record importer? ===<br />
The following prerequisites must apply to the CSV file:<br />
# The first line of the file must '''not''' be a record of data, but the names of the fields in the data.<br />
# The delimiter must be a comma, the enclosure must be a double quote ('''"'''), and the escape character must be a backslash ('''\''').<br />
# All commas in the file that are not field delimeters must be escaped (preceded by a backslash).<br />
<br />
Take for example the following first two lines of a typical contacts CSV export:<br />
<pre><br />
id,name,firstName,lastName,title,company,phone,phone2,email<br />
1,"Steve Mcqueen",Steve,McQueen,"Operations Manager","Pierre Office Supplies",605-636-5634,,steve@example.com<br />
</pre><br />
<br />
If your CSV does not meet the second or third criterion, X2Engine recommends the following procedure:<br />
# Import your CSV into a spreadsheet program.<br />
# Export your contacts data back into a CSV with commas properly escaped and the delimiter/enclosure chosen properly.<br />
# Attempt to import your contacts again using the new CSV file.<br />
<br />
It is worth noting that the contacts importer gets data by calling PHP's [http://us1.php.net/fgetcsv fgetcsv] function, with only the first argument specified. Thus, the prerequisites of the input CSV's formatting and internal conventions are derived from the default behavior of that function.<br />
<br />
=== Why does the importer process only one record before stopping? ===<br />
This may be indicative of a common problem that occurs when using Microsoft Excel in Windows, and the Web server with X2Engine is Linux, Unix or pretty much anything that isn't Windows. What's happening is that Excel is exporting the CSV file with Windows-style line breaks only. Thus, the server interprets the file as though it were just one line long.<br />
<br />
=== Why are my records running into validation failures? ===<br />
This is caused by legitimately "bad" data (or formatting) in the input CSV. For example, the email field might not actually be formatted like an email address, or the first and last names of certain contacts might be blank whereas they are required when creating or updating contacts and cannot be made blank. To get around this, if you want to import the full set and then fix the data once inside X2Engine, you should perform the following steps:<br />
# Look for the file "failedContacts.csv" on the server, after the import with contacts that failed validation.<br />
# If applicable, undo the existing contact imports where there were contacts that failed import due to validation errors.<br />
# Go to "Admin"<br />
# Under "X2Studio", go "Manage Fields"<br />
# Edit the fields whose analogues in the CSV contain invalid data. To determine which fields contain invalid data, examine the contents of the "failedContacts.csv" file.<br />
<br />
=== How do I update existing records through import? ===<br />
[[File:importupdate1.jpg|thumb]]<br />
<br />
When updating a large portion of your records, it may be more convenient to use a familiar spreadsheet program to manipulate the data. After exporting the data to CSV, you can update your data before importing the CSV, selecting which attribute is used to lookup existing records.<br />
# Export a CSV file of the records to be updated, then open it in your spreadsheet program and remove the columns which will not be updated or used to match on existing records.<br />
# Perform all necessary updates to the data in the CSV.<br />
# Navigate to the import page for the module the records were exported from and upload the CSV.<br />
# In the importer options, select "Update Existing Records," then select the attribute to match on existing records.<br />
# Modify your import map as appropriate and set other import options, then proceed with the import. If you have selected a field to match on which is not marked "unique" in the Field Manager, you will need to confirm a warning message. Should multiple records be matched by the value in the match attribute, unexpected data manipulation may occur.<br />
<br />
= Updating =<br />
<br />
== Compatibility Issues Detected ==<br />
<br />
=== How can I get around this? ===<br />
You can still update. If compatibility issues are detected, this will not prevent you from updating; it was merely intended to draw attention to potential issues with the update. This is especially applicable in the case of customized files. Simply click the "Apply Changes" button.<br />
<br />
However, if you notice a message regarding conflicting fields among the compatibility issues messages, you will probably run into a database error in the update process. There is one exception to this, the "actionDescription" field in the Actions model, which was removed in 3.0 and re-added in 3.5.5 (so if you are updating from before 3.0, you can disregard this message with respect to that particular field).<br />
<br />
=== Files in protected/config are going to be overwritten? ===<br />
You can generally always ignore this message. These files were originally included as part of the default fileset, to provide examples of how to write them. However, due to how the average end user does not customize X2Engine at the source code level, inclusion of these files in the default codebase has been discontinued so as to avoid this unnecessary message.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Frequently_Asked_Questions&diff=1940Frequently Asked Questions2015-10-14T18:04:08Z<p>Raymond Colebaugh: /* How do I update existing records through import? */</p>
<hr />
<div>[[Category:Support]]<br />
= API =<br />
== Contacts ==<br />
=== I made a contact through the API. Why doesn't the assignedTo field get set according to lead routing rules? ===<br />
When creating records through the API, all fields must be explicitly defined. One can, however, obtain an assignee for a new contact that is distributed according to lead routing type via the public [[x2propdoc:AdminController.html#_actionGetRoutingType|"getLeadRoutingType" action of AdminController]]. An example of this can be found in [[x2doc:APIModel|APIModel]] (which is a class that was written for the express purpose of creating contacts and other records via the API).<br />
<br />
=== Why doesn't the "Record Created" X2Flow trigger fire when creating a contact via API? ===<br />
Note, the data submitted to the server should not contain the "createDate" field. If it does not, the field will be automatically set to the current timestamp at record insertion, and the "Record Created" trigger will be activated. Otherwise, it is assumed that a data import of some sort is happening, because a non-empty creation date implies that it was created on some other system first. In the latter case, the record created trigger will not be activated, and the field will not be automatically set.<br />
<br />
=== Why doesn't the "Web Lead" X2Flow trigger fire when creating a web lead via API? ===<br />
The "create" API method, intended as a generic method for creating records, cannot assume that it is being used for creating a web lead in particular. One might choose instead to create a flow triggered by record creation and add the criteria that the lead type must be "Web", and set the lead type to "Web" in the submission to the API, which be practically the same as having the web lead trigger activate in the API.<br />
<br />
== Actions &amp; Action History ==<br />
<br />
=== How do I create a note on a record? ===<br />
Creating a note (or call log, or email, or any action history record) amounts to creating an action record with its association type set to the module name and the association ID to the ID of the record to which you'll be attaching the action history item. Note, the <tt>actionDescription</tt> attribute controls the content of the action record. So, for example (if using APIModel and attaching to contact 123):<br />
<br />
<syntaxhighlight lang="php"><br />
$note->type = 'note';<br />
$note->associationId = '123';<br />
$note->associationType = 'contacts';<br />
$note->actionDescription = 'text in the note';<br />
$note->modelCreateUpdate('Actions', 'create');<br />
</syntaxhighlight><br />
<br />
= Customization =<br />
<br />
== Custom fields ==<br />
=== I made a custom field and it doesn't show up. Now what? ===<br />
Once you create a field, you must add it to the view and form as you see fit, as follows:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model by selecting the model from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
=== Why isn't any data going into my custom field getting saved? ===<br />
If this occurs for a user that has been assigned any role, you will need to add permissions to that field for the role:<br />
# Go to the "Role Manager" in Admin.<br />
# Select the role to be edited from the dropdown menu.<br />
# Under "View" and "Edit" permissions, click the "+" sign next to the field in the right panels to add them.<br />
# Click "Save".<br />
<br />
== Custom modules ==<br />
=== Why don't I see anything when I go to create/view a record in a new custom module? ===<br />
The first step after creating a custom module is designing a form and view for it. To do so:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model corresponding to the new module by selecting it from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
<br />
= Email =<br />
== Attachments and embedded media ==<br />
=== Why are all my images broken? ===<br />
The first thing you should check and double-check is whether, in "Public Info Settings", the public web root / base URI are set.<br />
<br />
If they are not, and you access your CRM at a non-public URL when sending email, the image links will be generated such that they point to the CRM through that inaccessible URL. Thus, the images will naturally be broken in any browser accessing the web from a network that cannot access your CRM. This is what the public info settings options were meant to address. In order for campaigns to work properly, there must be some way of publicly accessing the CRM from the web. This is so that, among many things, the "unsubscribe" link works (the user is essentially visiting a page in the CRM that unsubscribes them).<br />
<br />
This happens in GMail even if the CRM is accessible from where you're using GMail because Google tries to access and cache images in emails. Google's servers won't be able to access them on account of Google not dwelling within your subnet (or network where the CRM is accessed), so naturally, all images with non-public URLs will show up broken regardless of how you're accessing the network.<br />
<br />
=== Why is there a broken image in the email, even though I didn't attach anything? ===<br />
This is the tracking image, which allows X2Engine to register that the user has opened the email. It should ordinarily be invisible, but is broken because your CRM is not publicly accessible. See the answer to "[[#Why are all my images broken?|Why are all my images broken?]]" <br />
<br />
== Campaigns ==<br />
=== Why don't the "Unsubscribe" link and email opened tracking work? ===<br />
See the answer to the above question, "[[#Why are all my images broken?|Why are all my images broken?]]"<br />
<br />
= Importing &amp; Exporting =<br />
<br />
== Record Importer ==<br />
<br />
=== What format does my CSV have to be in order to use the record importer? ===<br />
The following prerequisites must apply to the CSV file:<br />
# The first line of the file must '''not''' be a record of data, but the names of the fields in the data.<br />
# The delimiter must be a comma, the enclosure must be a double quote ('''"'''), and the escape character must be a backslash ('''\''').<br />
# All commas in the file that are not field delimeters must be escaped (preceded by a backslash).<br />
<br />
Take for example the following first two lines of a typical contacts CSV export:<br />
<pre><br />
id,name,firstName,lastName,title,company,phone,phone2,email<br />
1,"Steve Mcqueen",Steve,McQueen,"Operations Manager","Pierre Office Supplies",605-636-5634,,steve@example.com<br />
</pre><br />
<br />
If your CSV does not meet the second or third criterion, X2Engine recommends the following procedure:<br />
# Import your CSV into a spreadsheet program.<br />
# Export your contacts data back into a CSV with commas properly escaped and the delimiter/enclosure chosen properly.<br />
# Attempt to import your contacts again using the new CSV file.<br />
<br />
It is worth noting that the contacts importer gets data by calling PHP's [http://us1.php.net/fgetcsv fgetcsv] function, with only the first argument specified. Thus, the prerequisites of the input CSV's formatting and internal conventions are derived from the default behavior of that function.<br />
<br />
=== Why does the importer process only one record before stopping? ===<br />
This may be indicative of a common problem that occurs when using Microsoft Excel in Windows, and the Web server with X2Engine is Linux, Unix or pretty much anything that isn't Windows. What's happening is that Excel is exporting the CSV file with Windows-style line breaks only. Thus, the server interprets the file as though it were just one line long.<br />
<br />
=== Why are my records running into validation failures? ===<br />
This is caused by legitimately "bad" data (or formatting) in the input CSV. For example, the email field might not actually be formatted like an email address, or the first and last names of certain contacts might be blank whereas they are required when creating or updating contacts and cannot be made blank. To get around this, if you want to import the full set and then fix the data once inside X2Engine, you should perform the following steps:<br />
# Look for the file "failedContacts.csv" on the server, after the import with contacts that failed validation.<br />
# If applicable, undo the existing contact imports where there were contacts that failed import due to validation errors.<br />
# Go to "Admin"<br />
# Under "X2Studio", go "Manage Fields"<br />
# Edit the fields whose analogues in the CSV contain invalid data. To determine which fields contain invalid data, examine the contents of the "failedContacts.csv" file.<br />
<br />
=== How do I update existing records through import? ===<br />
[[File:importupdate0.jpg|thumb]]<br />
[[File:importupdate1.jpg|thumb]]<br />
<br />
When updating a large portion of your records, it may be more convenient to use a familiar spreadsheet program to manipulate the data. After exporting the data to CSV, you can update your data before importing the CSV, selecting which attribute is used to lookup existing records.<br />
# Export a CSV file of the records to be updated, then open it in your spreadsheet program and remove the columns which will not be updated or used to match on existing records.<br />
# Perform all necessary updates to the data in the CSV.<br />
# Navigate to the import page for the module the records were exported from and upload the CSV.<br />
# In the importer options, select "Update Existing Records," then select the attribute to match on existing records.<br />
# Modify your import map as appropriate and set other import options, then proceed with the import. If you have selected a field to match on which is not marked "unique" in the Field Manager, you will need to confirm a warning message. Should multiple records be matched by the value in the match attribute, unexpected data manipulation may occur.<br />
<br />
= Updating =<br />
<br />
== Compatibility Issues Detected ==<br />
<br />
=== How can I get around this? ===<br />
You can still update. If compatibility issues are detected, this will not prevent you from updating; it was merely intended to draw attention to potential issues with the update. This is especially applicable in the case of customized files. Simply click the "Apply Changes" button.<br />
<br />
However, if you notice a message regarding conflicting fields among the compatibility issues messages, you will probably run into a database error in the update process. There is one exception to this, the "actionDescription" field in the Actions model, which was removed in 3.0 and re-added in 3.5.5 (so if you are updating from before 3.0, you can disregard this message with respect to that particular field).<br />
<br />
=== Files in protected/config are going to be overwritten? ===<br />
You can generally always ignore this message. These files were originally included as part of the default fileset, to provide examples of how to write them. However, due to how the average end user does not customize X2Engine at the source code level, inclusion of these files in the default codebase has been discontinued so as to avoid this unnecessary message.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:Importupdate1.jpg&diff=1939File:Importupdate1.jpg2015-10-14T17:48:49Z<p>Raymond Colebaugh: </p>
<hr />
<div></div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=File:Importupdate0.jpg&diff=1938File:Importupdate0.jpg2015-10-14T17:46:59Z<p>Raymond Colebaugh: </p>
<hr />
<div></div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Frequently_Asked_Questions&diff=1936Frequently Asked Questions2015-09-10T23:38:59Z<p>Raymond Colebaugh: /* Importing &amp; Exporting */</p>
<hr />
<div>[[Category:Support]]<br />
= API =<br />
== Contacts ==<br />
=== I made a contact through the API. Why doesn't the assignedTo field get set according to lead routing rules? ===<br />
When creating records through the API, all fields must be explicitly defined. One can, however, obtain an assignee for a new contact that is distributed according to lead routing type via the public [[x2propdoc:AdminController.html#_actionGetRoutingType|"getLeadRoutingType" action of AdminController]]. An example of this can be found in [[x2doc:APIModel|APIModel]] (which is a class that was written for the express purpose of creating contacts and other records via the API).<br />
<br />
=== Why doesn't the "Record Created" X2Flow trigger fire when creating a contact via API? ===<br />
Note, the data submitted to the server should not contain the "createDate" field. If it does not, the field will be automatically set to the current timestamp at record insertion, and the "Record Created" trigger will be activated. Otherwise, it is assumed that a data import of some sort is happening, because a non-empty creation date implies that it was created on some other system first. In the latter case, the record created trigger will not be activated, and the field will not be automatically set.<br />
<br />
=== Why doesn't the "Web Lead" X2Flow trigger fire when creating a web lead via API? ===<br />
The "create" API method, intended as a generic method for creating records, cannot assume that it is being used for creating a web lead in particular. One might choose instead to create a flow triggered by record creation and add the criteria that the lead type must be "Web", and set the lead type to "Web" in the submission to the API, which be practically the same as having the web lead trigger activate in the API.<br />
<br />
== Actions &amp; Action History ==<br />
<br />
=== How do I create a note on a record? ===<br />
Creating a note (or call log, or email, or any action history record) amounts to creating an action record with its association type set to the module name and the association ID to the ID of the record to which you'll be attaching the action history item. Note, the <tt>actionDescription</tt> attribute controls the content of the action record. So, for example (if using APIModel and attaching to contact 123):<br />
<br />
<syntaxhighlight lang="php"><br />
$note->type = 'note';<br />
$note->associationId = '123';<br />
$note->associationType = 'contacts';<br />
$note->actionDescription = 'text in the note';<br />
$note->modelCreateUpdate('Actions', 'create');<br />
</syntaxhighlight><br />
<br />
= Customization =<br />
<br />
== Custom fields ==<br />
=== I made a custom field and it doesn't show up. Now what? ===<br />
Once you create a field, you must add it to the view and form as you see fit, as follows:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model by selecting the model from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
=== Why isn't any data going into my custom field getting saved? ===<br />
If this occurs for a user that has been assigned any role, you will need to add permissions to that field for the role:<br />
# Go to the "Role Manager" in Admin.<br />
# Select the role to be edited from the dropdown menu.<br />
# Under "View" and "Edit" permissions, click the "+" sign next to the field in the right panels to add them.<br />
# Click "Save".<br />
<br />
== Custom modules ==<br />
=== Why don't I see anything when I go to create/view a record in a new custom module? ===<br />
The first step after creating a custom module is designing a form and view for it. To do so:<br />
# Go to "Admin"<br />
# Scroll down to the section "Utilities"<br />
# Go to "Form Editor"<br />
# Open the views/forms for the model corresponding to the new module by selecting it from the "Model" dropdown<br />
# Select "Form (Default)" from the "Version" dropdown<br />
# Drag and drop the custom field into the form where it is desired.<br />
# Hit "Save"<br />
# Repeat the process with the "View" to change the record view page for the model type.<br />
<br />
= Email =<br />
== Attachments and embedded media ==<br />
=== Why are all my images broken? ===<br />
The first thing you should check and double-check is whether, in "Public Info Settings", the public web root / base URI are set.<br />
<br />
If they are not, and you access your CRM at a non-public URL when sending email, the image links will be generated such that they point to the CRM through that inaccessible URL. Thus, the images will naturally be broken in any browser accessing the web from a network that cannot access your CRM. This is what the public info settings options were meant to address. In order for campaigns to work properly, there must be some way of publicly accessing the CRM from the web. This is so that, among many things, the "unsubscribe" link works (the user is essentially visiting a page in the CRM that unsubscribes them).<br />
<br />
This happens in GMail even if the CRM is accessible from where you're using GMail because Google tries to access and cache images in emails. Google's servers won't be able to access them on account of Google not dwelling within your subnet (or network where the CRM is accessed), so naturally, all images with non-public URLs will show up broken regardless of how you're accessing the network.<br />
<br />
=== Why is there a broken image in the email, even though I didn't attach anything? ===<br />
This is the tracking image, which allows X2Engine to register that the user has opened the email. It should ordinarily be invisible, but is broken because your CRM is not publicly accessible. See the answer to "[[#Why are all my images broken?|Why are all my images broken?]]" <br />
<br />
== Campaigns ==<br />
=== Why don't the "Unsubscribe" link and email opened tracking work? ===<br />
See the answer to the above question, "[[#Why are all my images broken?|Why are all my images broken?]]"<br />
<br />
= Importing &amp; Exporting =<br />
<br />
== Record Importer ==<br />
<br />
=== What format does my CSV have to be in order to use the record importer? ===<br />
The following prerequisites must apply to the CSV file:<br />
# The first line of the file must '''not''' be a record of data, but the names of the fields in the data.<br />
# The delimiter must be a comma, the enclosure must be a double quote ('''"'''), and the escape character must be a backslash ('''\''').<br />
# All commas in the file that are not field delimeters must be escaped (preceded by a backslash).<br />
<br />
Take for example the following first two lines of a typical contacts CSV export:<br />
<pre><br />
id,name,firstName,lastName,title,company,phone,phone2,email<br />
1,"Steve Mcqueen",Steve,McQueen,"Operations Manager","Pierre Office Supplies",605-636-5634,,steve@example.com<br />
</pre><br />
<br />
If your CSV does not meet the second or third criterion, X2Engine recommends the following procedure:<br />
# Import your CSV into a spreadsheet program.<br />
# Export your contacts data back into a CSV with commas properly escaped and the delimiter/enclosure chosen properly.<br />
# Attempt to import your contacts again using the new CSV file.<br />
<br />
It is worth noting that the contacts importer gets data by calling PHP's [http://us1.php.net/fgetcsv fgetcsv] function, with only the first argument specified. Thus, the prerequisites of the input CSV's formatting and internal conventions are derived from the default behavior of that function.<br />
<br />
=== Why does the importer process only one record before stopping? ===<br />
This may be indicative of a common problem that occurs when using Microsoft Excel in Windows, and the Web server with X2Engine is Linux, Unix or pretty much anything that isn't Windows. What's happening is that Excel is exporting the CSV file with Windows-style line breaks only. Thus, the server interprets the file as though it were just one line long.<br />
<br />
=== Why are my records running into validation failures? ===<br />
This is caused by legitimately "bad" data (or formatting) in the input CSV. For example, the email field might not actually be formatted like an email address, or the first and last names of certain contacts might be blank whereas they are required when creating or updating contacts and cannot be made blank. To get around this, if you want to import the full set and then fix the data once inside X2Engine, you should perform the following steps:<br />
# Look for the file "failedContacts.csv" on the server, after the import with contacts that failed validation.<br />
# If applicable, undo the existing contact imports where there were contacts that failed import due to validation errors.<br />
# Go to "Admin"<br />
# Under "X2Studio", go "Manage Fields"<br />
# Edit the fields whose analogues in the CSV contain invalid data. To determine which fields contain invalid data, examine the contents of the "failedContacts.csv" file.<br />
<br />
=== How do I update existing records through import? ===<br />
When updating a large portion of your records, it may be more convenient to use a familiar spreadsheet program to manipulate the data. After exporting the data to CSV, you can update your data before importing the CSV, selecting which attribute is used to lookup existing records.<br />
# Export a CSV file of the records to be updated, then open it in your spreadsheet program and remove the columns which will not be updated or used to match on existing records.<br />
# Perform all necessary updates to the data in the CSV.<br />
# Navigate to the import page for the module the records were exported from and upload the CSV.<br />
# In the importer options, select "Update Existing Records," then select the attribute to match on existing records.<br />
# Modify your import map as appropriate and set other import options, then proceed with the import. If you have selected a field to match on which is not marked "unique" in the Field Manager, you will need to confirm a warning message. Should multiple records be matched by the value in the match attribute, unexpected data manipulation may occur.<br />
<br />
= Updating =<br />
<br />
== Compatibility Issues Detected ==<br />
<br />
=== How can I get around this? ===<br />
You can still update. If compatibility issues are detected, this will not prevent you from updating; it was merely intended to draw attention to potential issues with the update. This is especially applicable in the case of customized files. Simply click the "Apply Changes" button.<br />
<br />
However, if you notice a message regarding conflicting fields among the compatibility issues messages, you will probably run into a database error in the update process. There is one exception to this, the "actionDescription" field in the Actions model, which was removed in 3.0 and re-added in 3.5.5 (so if you are updating from before 3.0, you can disregard this message with respect to that particular field).<br />
<br />
=== Files in protected/config are going to be overwritten? ===<br />
You can generally always ignore this message. These files were originally included as part of the default fileset, to provide examples of how to write them. However, due to how the average end user does not customize X2Engine at the source code level, inclusion of these files in the default codebase has been discontinued so as to avoid this unnecessary message.</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Email_Configuration&diff=1934Email Configuration2015-04-03T20:54:57Z<p>Raymond Colebaugh: /* Troubleshooting SMTP */</p>
<hr />
<div>[[Category:Support]]<br />
This article covers configuring X2Engine for sending (and, in special cases, receiving) email.<br />
<br />
= Introduction =<br />
X2Engine provides the means to send email, natively, within the web application. Furthermore, with Professional Edition, the local MTA on the web server (if any) can be configured to allow X2Engine to receive emails and automatically associate them with the addressee's contact record.<br />
<br />
= <span class="noglossary">Configuring SMTP Accounts</span> =<br />
This section describes the current, recommended method of sending email in X2Engine. It makes use of a credentials storage system X2Engine features, which encrypts passwords and other authentication data at the database storage level. It is currently only used for storing email account passwords, but may in the foreseeable future be extended to store other types of passwords. To access the credentials manager, click "Manage Apps" under the user menu.<br />
<br />
== Creating Credentials Records ==<br />
[[File:New-Email-Account.png|200px|thumb|right|The create page for a generic email account]]<br />
To create a new email account, first select a type in the dropdown menu next to the '''Add New''' button. Then, click '''Add New'''. The easiest type of email account to add is Google Mail (i.e. GMail or email accounts offered via Google Apps for businesses). For such email accounts, SMTP server data such as the host name, port, and security type are already set, and all you have to enter is your username, password, and sender name. For all other types of email, first contact your email hosting provider or peruse their documentation on configuring email client programs (i.e. Outlook / Thunderbird) for use with their email service. The essential information that you need:<br />
* '''Server''': host name, i.e. smtp.email.com<br />
* '''Port''': a 2-3 digit number, i.e. 25 for unsecured SMTP, or 465 (legacy) or 587 for secure SMTP.<br />
* '''Security Type''': TLS/SSL. In most cases these are interchangeable.<br />
* '''User name'''/'''Password''': If the SMTP server does not use authentication, leave these blank. Otherwise, set them according to the username/password used for logging in to the email<br />
<br />
== Creating System-Owned Credentials ==<br />
One may wish to have all users use a specific email account/address for email blasting, or set a sender account for system notifications, or responses to service case submitters (etc). One can do so by creating publicly-usable email accounts. Such accounts will be usable by all users, but may only be viewed or edited by administrators, and thus are ideal for such purposes as they aren't necessarily attached to / identified with any given user.<br />
<br />
To designate an account as system-owned, set the '''Owner''' to be "System" when creating it.<br />
<br />
== Creating Non-Private Credentials ==<br />
Non-administrators also have the ability to create credentials that can be used by administrators, in the rare case where it may be desirable for the owner to send mail on their behalf. To do so (allowing administrators to masquerade as the other user when sending email), that user must consent to allow their email account to be used by other people, by un-checking the '''Private''' checkbox. Note, however, that this will not allow administrators to view or edit the credentials, only to use them as a delivery method option when sending email.<br />
<br />
== Setting Personal Default Credentials ==<br />
[[File:1-my-default.png|300px|thumb|right|Setting one's own personal default credentials]]<br />
It is usually desirable to have a preferred email account for use in X2Engine, while at the same time having the option to send email using the system-wide (legacy) method, or via some other email account set up through the credentials manager.<br />
<br />
Next to each credentials record, there will be a "pill" with a checkbox in it that says "Set as my default", or "Set as default" (when system-owned and being viewed by an administrative user). When the box is checked, an "apply" button will appear to enact the change. After clicking "apply" a green pill label appears on the record to denote that it has been selected as default. What this means is that whenever one opens the inline email widget (i.e. on a contact view, by clicking the email button), the "send as" field will be set to the selected account by default. This way it still allow switching email accounts on-the-fly, but removes an extra step that would otherwise be necessary to remember whenever sending an email.<br />
<br />
== Setting System Default Credentials ==<br />
[[File:System-Default.png|450px|thumb|right|Setting default credentials for the system and/or oneself]]<br />
All users have the ability to set system-owned credentials as their personal defaults. However, administrative users can also designate the credentials for system use purposes, i.e. bulk email and system notifications. For each credentials record owned by the system, when being viewed as the administrator, the pill will say "set as default" instead. When the box is checked, it will display a dropdown menu of system roles for which the account isn't already selected to use by default. After selecting the appropriate role to apply it as default for, and clicking "apply" as one would for setting personal defaults, it will be set as the default email account to use for that purpose.<br />
<br />
Note, also, setting defaults for system roles in the credentials manager is just another way of changing settings in "Email Server Configuration" (accessed from within the Admin panel), and vice versa. These settings are actually synchronized. Some definitions of system uses:<br />
<br />
;Bulk Email Account<br />
: Will be selected by default when users create a new campaign (overrides the users' personal default)<br />
;Service Case Email Account<br />
: Used when sending responses to service case submitters.<br />
;System Response Emailer<br />
: Used when sending responses to web leads, and is selected by default in X2Flow's send-an-email actions (but can be changed).<br />
;System Notification Emailer<br />
: Used for miscellaneous notification tasks, most notably, sending "broadcast event" emails to targeted users when users broadcast an event, select users to broadcast it to, and enable the option to notify users by email.<br />
<br />
= Using The Legacy (system-wide) Email Delivery Methods =<br />
Apart from using separate, third-party SMTP accounts for each user (and system purpose), there is a way to use a singular email delivery method for all emails sent via X2Engine. However, this method is not recommended, unless your web server also serves as your mail server. It is very much considered a legacy feature.<br />
<br />
<br />
There are four options for methods of sending email, which can be found in the '''Outbound Email Server''' section in '''Email Settings''' under the '''Email Configuration''' section in the '''Admin''' page. These methods are:<br />
<br />
# PHP Mail<br />
# Sendmail<br />
# QMail<br />
# SMTP<br />
<br />
If any of these aren't available, they won't be listed in the '''Method''' menu.<br />
<br />
PHP Mail, Sendmail and QMail each, while independent of any offsite, third-party service, require that an MTA be available on the web server. The main difference is that PHP Mail uses whichever MTA that PHP itself is configured to use, whereas the Sendmail and QMail methods are specific to the Exim4/Postfix and QMail MTA's (respectively).<br />
<br />
For all instances of X2Engine Professional Edition cloud/ondemand, the methods available for sending mail include all of these except QMail.<br />
<br />
== Circumventing Spam Filters ==<br />
While the non-SMTP methods for sending email within X2Engine may work immediately, a common problem encountered with them is that email goes into spam folders on the receiving end, or is not being received at all. ''This is especially likely to happen when the email addresses specified by users in their profiles'' (which get used as the sender address in the headers of mail sent by X2Engine) ''do not belong to the same domain name as the server hosting X2Engine.'' So, for example, it would be problematic if a user uses an email address in their X2Engine profile that ends in gmail.com, and the X2Engine system is hosted on example123.com<br />
<br />
This is '''not''' a fault in X2Engine (or [http://phpmailer.worxware.com/ PHPMailer], which X2Engine uses for mail delivery). Rather, it is endemic to all web applications (i.e. Wordpress, Drupal, etc.) due to how sender address spoofing (telling the recipient that the email comes from somewhere else) is a common behavior among spammers. Furthermore, the general proliferation of email spam through various means (but especially PHP scripts running on web servers) has resulted in greatly elevated standards in the spam-detection mechanisms and heuristics of most email servers. This generally poses an entry barrier to getting around spam filters. It is thus recommended that, before sending emails using these legacy methods, measures are taken to ensure that mail sent will have the best chance of avoiding spam filters.<br />
<br />
=== Work-around 1: Aliasing ===<br />
This works by first creating mail forwarding aliases on the server, i.e. through a Virtualmin or CPanel control interface, or manually, by editing the MTA's virtual user aliases table (see their respective documentation for more information). Next, instruct all users to use these email addresses as their contact address in their X2Engine profile, so that when contacts reply, the response emails will be forwarded to the desired addresses (or even a list of addresses) specified by each alias.<br />
<br />
=== Work-around 2: Migration ===<br />
This, of course, is the most straightforward solution: to use the hosting provider's email service. However, it requires switching from one's existing email service/addresses to service and addresses provided by the hosting provider, and it is also extra effort.<br />
<br />
=== Work-around 3: Third-party Service ===<br />
''Note, using this method is '''not''' recommended; see <span class="noglossary">[[#Configuring SMTP Accounts|Configuring SMTP Accounts]]</span> for the preferred method of using SMTP for email delivery.''<br />
<br />
Using this method, the entire organization that uses X2Engine sends email using a single email account managed by a third-party service that supports SMTP. Thus, sending the email and digital signing are handled by the third-party service, and getting caught in spam filters generally tends to be a smaller problem.<br />
<br />
In most cases, this method will work. How it works is that the reply-to address in the email will be set to the email address of user who is sending the mail through X2Engine, while the actual mail system user is used to authenticate with the mail server. In such cases, the address of the mail user might still show up in one of the headers of the resulting email. However, '''this is known to not work with many email providers, most notably GMail''', which (for security and anti-spam purposes) does not permit sending emails if the sender address differs from the address associated with the Google account that was used to authenticate. If this is the case, it is recommended to use separate SMTP accounts via the credentials manager.<br />
<br />
=== Using <span class="noglossary">DKIM</span> ===<br />
(see [[wikipedia:DomainKeys_Identified_Mail|main article]] on Wikipedia)<br />
<br />
This method, which requires server-end configuration, is a very secure and reliable method of distinguishing email as being of legitimate and trustworthy origin. '''Almost every''' reputable email service is configured to use a mail signing agent to add a special signature sent from such addresses, in addition to checking signatures of incoming emails. Emails that are digitally signed are far more likely to be recognized by mail servers worldwide as being from a reputable source, and thus less likely to go be caught in spam filters.<br />
<br />
However, it still requires that the sender's email address be of the same domain as the email server. That is because the sender address cannot be "spoofed", because DKIM was designed to prevent this sort of activity (which is common among spammers). Furthermore, if outgoing email is signed for domains not corresponding to the point of origin, it may actually make matters worse; many reputable mail services (including GMail) themselves use DKIM to avoid their domain name being abused by address spoofing. Thus, if a DKIM signature header is added to the email, and the signature does not match the DKIM record of the sender's domain, it will be considered a forgery attempt and blocked/filtered by most email servers.<ref>DKIM uses the RSA public/private key cryptographic technique to ensure that signatures are secure and cannot be forged. Thus, there is no legitimate way of circumventing this issue.</ref><br />
<br />
= Troubleshooting <span class="noglossary">SMTP</span> and <span class="noglossary">IMAP</span> Connectivity =<br />
{|class="wikitable" style="float: right;"<br />
|-<br />
! scope="row" |<tt>Protocol</tt><br />
| <tt>Port</tt><br />
| <tt>Security Type</tt><br />
|-<br />
! scope="row" | SMTP<br />
| 25<br />
| None<br />
|-<br />
! scope="row" | &nbsp;<br />
| 465<br />
| SSL<br />
|-<br />
! scope="row" | &nbsp;<br />
| 587<br />
| TLS<br />
|-<br />
! scope="row" | IMAP<br />
| 143<br />
| None<br />
|-<br />
! scope="row" | &nbsp;<br />
| 993<br />
| TLS<br />
|-<br />
|}<br />
<br />
If, when attempting to send an email, you see an error message "SMTP Error: Could not connect to SMTP host" (from an exception thrown in PHPMailer), this indicates exactly what it says; it was unable to connect. There are a wide variety of reasons why an SMTP connection should fail, but they almost always fall into one of the following two categories:<br />
# Configuration of the connection and authentication<br />
# Local network environment<br />
It is recommended to check them in that order. First, to test configuration, check:<br />
# The host name is correct<br />
# The SMTP port number is correct<br />
# The option to use TLS is enabled, if required by the email service provider<br />
# The username and password are correct and exactly as specified by the service provider (i.e. if the SMTP username is whole email address, it should be entered as the whole email address).<br />
You can then attempt to verify your credentials upon viewing them in the "Manage Apps" section. If the credentials do not authenticate, and you have ensured that your details are correct, then the issue is likely a server or network configuration issue.<br />
<br />
Testing the network configuration is more involved and will require logging into the server that hosts X2Engine via SSH or otherwise. One should first acquire the IP address of the SMTP host, via the command <tt>ping [smtp host]</tt> in a DOS or Unix shell, from any machine that is known to be able to access the mail server. You must then verify, locally on the X2Engine hosting server, that:<br />
# The SMTP host can be reached, i.e. there exists no valid network route and/or NAT rule allowing access to it, from the server on which X2Engine is hosted. <br />
# The SMTP hostname can be resolved (DNS is available). Try <tt>ping</tt> again, on the server hosting X2Engine itself.<br />
# The port number is not being blocked by the ISP for outbound connections on the port. This is the case with most consumer providers, who block port 25 in an effort to block spam from infected hosts. If the first two requirements are satisfied and SMTP connections still fail, this is most likely the cause of the problem. One can also test this via port scanning or [http://www.port25.com/how-to-check-an-smtp-connection-with-a-manual-telnet-session-2/ with telnet].<br />
# On a Linux host, check that the outbound firewall rules are not interfering. Ensure that the outbound firewall policy is to either to accept traffic, or in a more restricted environment in which the default target is to drop or reject packets, ensure that the required ports are allowed outbound. This can be verified using iptables from the command line with:<br />
<nowiki>#</nowiki> If accepting packets by default:<br />
[user@localhost ~]$ sudo iptables -L OUTPUT<br />
Chain OUTPUT (policy ACCEPT)<br />
target prot opt source destination<br />
<br />
<nowiki>#</nowiki> Or if packets are dropped or denied by default:<br />
[user@localhost ~]$ sudo iptables -L OUTPUT<br />
Chain OUTPUT (policy DROP)<br />
target prot opt source destination<br />
DROP all -- anywhere anywhere state INVALID<br />
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:domain<br />
ACCEPT udp -- anywhere anywhere udp dpt:domain<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:submission<br />
ACCEPT tcp -- anywhere anywhere tcp dpt:imaps<br />
<br />
If you receive errors trying to load the email module, and you have already verified your credentials and can successfully send mail, then double-check that the PHP IMAP extension is properly installed. This package is called '''php-imap''' on Red Hat based distributions, and '''php5-imap''' on Debian based distributions.<br />
<br />
= Configuring the "Email Dropbox" For Receiving Emails =<br />
This feature, available in X2Engine Professional Edition, provides the means to automatically create contacts and email-type action records by capturing emails from external email software, i.e. Outlook and GMail.<br />
<br />
'''Note, if you are a customer of X2Engine Professional Cloud/On Demand:''' you do not need to set up the necessary prerequisites for this feature; that has been done for you. The feature should already be ready to use, and the alias will be <tt>dropbox@[yoursubdomain].x2vps.com</tt>. You will only need to configure the Email Dropbox if you are using Download/On Premise.<br />
<br />
Setting it up requires a Linux/Unix server environment, a MTA, and knowledge of how to set up email aliases. There are two methods of integration: direct, and via API.<br />
<br />
== Using the Email Dropbox ==<br />
As of the current version, there are four ways of using the email dropbox:<br />
;CC<br />
: When sending an email to a contact, include the email alias in the CC field, and the email will be attached to the contact as an outgoing email.<br />
;Forward<br />
: After receiving an email from a contact, forward it to the email alias. It will be attached to the contact as an incoming email.<br />
;Direct<br />
: Send an email directly to the alias, '''without "Fwd:" in the subject'''. A post in the social feed will be created using the contents of the email's body.<br />
;Case Attachment<br />
: Same as forward or CC, but include a user-defined code in the email body to specify that the email should be converted to/attached to a case in the Services module.<br />
<br />
== Direct Integration ==<br />
This set-up method works by locally sending the email contents directly to X2Engine through the system console. It is more straightforward to configure, but requires that the MTA and web server hosting X2Engine be on the same machine, virtual or physical. Furthermore, it can run into file permissions/ownership issues.<br />
<ol><br />
<li>Using CPanel, Webmin/Virtualmin, or any other web host administrative tool, create a mail alias on the server that begins with "dropbox@", i.e. "dropbox@yourdomain.com". (Note: you can also do this manually by editing the mail server's configuration files, although doing that is beyond the scope of this guide.)</li><br />
<li>Find the absolute path on the web server's filesystem to X2Engine, if you're not sure where it is already:<br />
<ol style="list-style-type:lower-alpha"><br />
<li>Paste the following into a PHP file called "path.php", in the same directory as X2Engine: <syntaxhighlight lang="php"><?php echo realpath(dirname(__FILE__)); ?></syntaxhighlight></li><br />
<li>Navigate to the file using a web browser</li><br />
<li>Record the path, append "/email.php", and use the resulting string as the path to the capture script in the mail alias.</li><br />
<li>Delete the file "path.php" from the server.</li><br />
</ol></li><br />
<li>Create an email alias. Set its destination a pipe to a program: '''<tt>/usr/bin/php /path/to/X2Engine/email.php</tt>''' (versions up to 2.5.2) or '''<tt>/path/to/X2Engine/protected/yiic emaildropbox</tt>''' (versions 2.7 and later)</li><br />
<li>(X2Engine versions 2.7 and later) Change the permissions on the <tt>protected/runtime</tt> folder to '''777''' (all users can read/write).<ref>If you cannot do this for security purposes, but are a system administrator, you can instead change them to 770. If you do so, however, you must change the group ownership of the directory to the group under which the MTA spawns processes (typically "nobody" or "nogroup").</ref></li><br />
<li>Test the alias by sending a message to a fictitious name/email address (i.e. <tt>"Sue Doenimm" <test@example.com></tt>) and CC-ing the mail alias. </li><br />
<li>Check in the Contacts module after sending the email, and if the fictitious contact was created, check to see that the email was attached properly to the contact record. If the contact was not created or the email was not imported, check the logs of the MTA for any errors.<br />
<li>Test to see if your email software's forwarded message format is supported by forwarding an email from a contact to the mail alias.</li><br />
</ol><br />
<br />
== API Integration ==<br />
This method works by sending the email contents to X2Engine through the remote API, and thus permits the MTA and X2Engine to live on different servers. Similar to the direct method, an alias will need to be constructed to receive the email and send it to a program, but in this case the program acts as a proxy by sending the contents to X2Engine via a web request. This method is more flexible, and it is far less likely to result in file permission issues (i.e. when the MTA attempts to run the Yii console application), but takes slightly more effort to set up.<br />
<br />
For information on API authentication, see [[Remote_API#Authenticating|Remote API: Authenticating]]. <br />
<br />
The setup process is as follows: <br />
<br />
<ol><br />
<li>'''Obtain the API script.''' Using your file/FTP manager of choice, navigate to the <tt>protected</tt> folder inside X2Engine, find the <tt>integration</tt> folder, and inside of that, navigate to the "MTA" folder.</li><br />
<li>Download/copy <tt>emailImport.php</tt></li><br />
<li>Open the file in your editor of choice, and look for the following lines of code:<br />
<syntaxhighlight lang="php"><br />
///////////////////////////<br />
// Configuration details //<br />
///////////////////////////<br />
// Set this to the IP address or domain name of the server<br />
$host = '';<br />
// Set this to the protocol (use "https://" for an SSL-enabled web server)<br />
$proto = 'http://';<br />
// Set this to the URI on the web server of X2Engine, without the trailing slash.<br />
// So, if the login URL is "http://example.com/X2Engine/index.php/site/login",<br />
// this variable should be "/X2Engine"<br />
$baseUri = '';<br />
// Leave this null if the host specified by $host will resolve correctly.<br />
// Otherwise, if in an environment where (for instance) the domain does not resolve<br />
// properly, and the IP address must be used, but the CRM is on a specifically-named<br />
// virtual host on a shared IP, set this to the domain name of that host, and set<br />
// $host to the IP address of the web server.<br />
$hostName = '';<br />
$data = array(<br />
'user' => '',<br />
'userKey' => '',<br />
);</syntaxhighlight><br />
</li><br />
<li>Change the variables according to their comments by putting the appropriate values in the (currently empty) pairs of single quotes. For instance, if your CRM is hosted at "https://crm.domain.com/crm", you will put "https://" in for <tt>$proto</tt>, "crm.domain.com" for <tt>$host</tt>, and "/crm" for <tt>$baseUri</tt>.</li><br />
<li>In the <tt>$data</tt> array, set the <tt>user</tt> and <tt>userKey</tt> to the API user and API key to be used (see: [[Remote_API#Authenticating|Remote API: Authenticating]]). So, for "admin" and key "aabbccddee": <br />
<syntaxhighlight lang="php"><br />
$data = array(<br />
'user' => 'admin',<br />
'userKey' => 'aabbccddee',<br />
);</syntaxhighlight><br />
</li><br />
<li>Upload the script to the server to be handling email.</li><br />
<li>Ensure the script has proper ownership and permissions to be run by the MTA user. If you're not sure what user the MTA will run under when executing mail handling scripts, set the permissions on emailImport.php to '''755''' (all can read, owner/group can write, all can execute)</li><br />
<li>Create an email alias on the server to be handling email, which pipes to the program: '''/usr/bin/php /path/to/emailImport.php''' (replacing "/path/to" with the actual filesystem path where you uploaded the script).</li><br />
</ol><br />
<br />
== Troubleshooting ==<br />
Due to how this feature requires interaction between multiple software systems, a non-functioning email dropbox can have a very wide variety of possible causes.<br />
=== Email is not captured ===<br />
It may be that mail is not actually getting transmitted to X2Engine in the first place. You should see something in the MTA logs that looks a bit like this (i.e. if you're using Postfix and the "direct" integration method):<br />
<br />
<tt>Feb 10 10:28:21 HOSTNAME postfix/local[2546]: D8C0B13613CB: to=<dropbox-USER@DOMAIN>, orig_to=<USER@DOMAIN>, relay=local, delay=1.2, delays=0.86/0/0/0.39, dsn=2.0.0, status=sent (delivered to command: /home/USER/public_html/protected/yiic emaildropbox)</tt><br />
<br />
In the case of API integration, substitute the "yiic" command with "/usr/bin/php /path/to/emailImport.php"; one way or another, what's happening on the most basic level is that email is getting handled by a program rather than going into a mailbox.<br />
<br />
If you can verify that this event happened (delivery to a local mail handling program), and no error email response is received, but the dropbox "silently" fails (by not capturing the email), verify the following:<br />
<ul><br />
<li>Your user profile email address matches the address from which you are sending email.</li><br />
<li>If using the API method of integration, check:<br />
<ul><br />
<li>The domain name of the X2Engine-hosting server is resolvable on the mail host</li><br />
<li>The host with X2Engine on it can be accessed from the mail host via a HTTP (web) request</li><br />
<li>If X2Engine is on a server that is behind a firewall, port 80 (or 443, if using HTTPS) on the external IP address is forwarded to the X2Engine web host</li><br />
<li>The API user and user key are correct in the API configuration</li><br />
</ul></li><br />
<li>If you have "Create Contacts from Emails" disabled, the recipient address of the email (or original sender, if you are forwarding the email) matches that of an existing contact in X2Engine</li><br />
</ul><br />
<br />
=== Email rejected with error message ===<br />
Verify:<br />
* Correct email server settings, DNS, and email alias<br />
* Correct permissions/ownership on all scripts run by the MTA<br />
<br />
=== (API Method) No request to X2Engine showed up in the web server logs ===<br />
This is due to one of the following problems:<br />
# The mail handling server never received the message<br />
# Any of the same conditions that would cause the MTA to bounce the email back to the sender with an error message<br />
# The mail handling server couldn't connect to X2Engine's server<br />
Ultimately, it boils down to there being no network connection between the mail handling server and X2Engine to begin with.<br />
<br />
To investigate if #1 and #2, first look at the MTA's logs. They are typically stored in /var/log, and named <tt>maillog</tt> (RHEL/CentOS) or <tt>mail.log</tt> and <tt>mail.err</tt> (Debian/Ubuntu). If there is no record of mail received:<br />
* Does the mail handling server have a fully-qualified domain name?<br />
* Check the MTA's configuration. Is it configured to allow receiving mail for the system's FQDN?<br />
* Is there a route to the mail host from the internet? And, are the necessary ports open for receiving mail?<br />
<br />
Otherwise, if there are lines in the mail log(s) matching the alias, they will most often immediately indicate what went wrong. In such cases, the issue will typically be one of an improperly typed pipe alias (i.e. feeding directly to the script, rather than the PHP interpreter which runs the script), or permissions (meaning, Postfix child processes cannot read the import script file). Note that each node (parent directory) in the path to the script must be executable by the Postfix subprocess; else it cannot access anything within.<br />
<br />
If, on the other hand (#3) there were no errors reported in the mail log for delivery to the alias, the problem is at the level of connecting between servers. Thus, you must verify:<br />
* Is the domain name of the web server correct?<br />
* Can the web server of the CRM be resolved to the proper IP address on the mail server?<br />
* Can HTTP connections can be made to the server (i.e. verify the port isn't blocked, a valid route exists to the host, etc.)?<br />
* Is the php_curl extension available in the command line environment on the mail handling server?<br />
<br />
=== Response email regarding an unsupported forwarded message format ===<br />
Forward the the email to [mailto:customersupport@x2engine.com X2Engine Customer Support], using the same email software that was used to test the feature. Due to the great diversity of email software and the inconsistency of forwarded/attached message formatting across platforms, the forwarded message capture may not immediately work with your email software of choice. However, per request, support for new forwarded message formats will be added in the next release.<br />
<br />
= Notes =<br />
<references /></div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Installation&diff=1933Installation2014-12-05T19:02:47Z<p>Raymond Colebaugh: /* Installing Without All Requirements: What Won't Work */</p>
<hr />
<div>[[Category:Support]]<br />
This article covers manual installation of X2Engine on a webserver. If this is your first time working with a web server, it is recommended that you first try installing via an automatic full-stack installer that will set up a self-contained web server environment for you. See [http://bitnami.org/stack/X2CRM Bitnami's X2Engine stack installers] for download links.<br />
<br />
= Before Installation =<br />
Before installing X2Engine, you should first ensure that<br />
* You have a web server<br />
* You understand the basics of installing a PHP web application<br />
* Your hosting environment is properly configured and meets all the minimum requirements<br />
<br />
== Required Knowledge ==<br />
Installing X2Engine requires you are able to perform the following taks (and have basic knowledge of how to perform them):<br />
# Upload files to a web server via a hosting file manager, FTP/SFTP, or otherwise<br />
# Creating a MySQL database and a database user, if they aren't available already<br />
# Using a web browser, and knowing what URL to use to access a location on the server<br />
<br />
If you are unsure of how to perform any of these, please ask for assistance on [http://x2community.com The X2Engine Forums].<br />
<br />
== System Requirements ==<br />
You can quickly determine if your web server can run X2Engine by downloading the requirements checking script (link: [[requirements:|requirements.php]]), uploading it to your web server, and opening it in a web browser. When finished using the script, delete it; it displays detailed information about the server's PHP configuration. Displaying it publicly for indefinite time can pose a security risk; it could be useful to attackers. In general, the following are the absolute minimum requirements for installation:<br />
<br />
* A web server that can execute PHP.<br />
* A password-protected '''MySQL''' database server connection, and a database on which the user of the connection has all rights (i.e. select, drop, create and update).<br />
* '''PHP version 5.3 or later'''<br />
* PHP must be run as the same system user that owns the directory where X2Engine will be installed.<br />
<br />
== If Requirements Are Not Met ==<br />
=== Directory Ownership ===<br />
If you are running a dedicated web server and/or have administrative access, please refer to [[Preparing_a_Linux_Server_Environment#Server_Preparation|Preparing a Webserver]].<br />
<br />
This is a hosting environment misconfiguration wherein files uploaded to the server are owned by a different system user than the user as whom PHP executes. It is an ease-of-use issue that many shared hosting providers still haven't gotten right. It can in most cases be resolved by changing the PHP gateway interface, i.e. from SuPHP to FCGI, using your hosting control panel ('''To correct this issue in CPanel''': see [http://docs.cpanel.net/twiki/bin/vief/EasyApache3/ApachePHPRequestHandling Apache PHP Request Handling]).<br />
<br />
You should also use a method of uploading files to the server that results in them having the same ownership as the "domain owner" (this terminology is used by most shared hosting providers). FTP services have been known to fail in this regard, whereas web-based file managers available in hosting control panels are often much more consistent. Uploading files with proper ownership will completely circumvent file permission and ownership issues.<br />
<br />
In any case, refer to your hosting provider's documentation. If the option to change the PHP execution mode does not appear to be available, contact the system administrator or customer service department of the hosting provider. If they don't give you a satisfactory answer, consider switching to a better web host. <ref>In a discussion on our forums, [http://x2community.com/topic/703-hosting-that-works/ "Hosting that works"], some users propose or advocate web hosts that they have had experiences with. A general consensus on GoDaddy (and our own professional opinion, based on repeated bad experiences) is that it is a very poor choice for hosting X2Engine.</ref><br />
<br />
=== PHP Version ===<br />
In some cases, it may be possible to enable PHP 5.3+ using an [http://httpd.apache.org/docs/2.2/howto/htaccess.html Apache override]; see <br />
* [http://www.velvetblues.com/web-development-blog/activate-php-5-3-hostgator-godaddy/ Velvet Blues: How to Activate PHP 5.3 on HostGator and GoDaddy]<br />
* [http://kb.siteground.com/article/How_to_have_different_Php__MySQL_versions.html SiteGround knowledge base: How to switch to a different PHP version]<br />
By adding the appropriate directives to the <tt>.htaccess</tt> file in the web root of X2Engine (NOT replacing the file, but changing it), the PHP version can ideally be set. You can test whether this method succeeds by re-visiting the requirements checker script and verifying that the PHP version in use is 5.3 or later.<br />
<br />
In other cases, it may be possible to enable later versions of PHP via the web hosting control interface (i.e. CPanel or Webmin). Otherwise, the only option will be to contact the hosting provider and request that version 5.3 be made available.<br />
<br />
=== PHP Extensions ===<br />
If your server does not meet the minimum system requirements for running X2Engine, and you are a system administrator of your server, you will be able to install the necessary modules. Note, however, that as of the most recent version, the MySQL PDO extension is the only extension used by X2Engine that isn't included by default and always enabled in PHP 5.3; the reflection class and extensions SPL, PCRE and Ctype should all be available if PHP is at version 5.3 or later.<br />
<br />
In most distributions of Linux, PHP extensions can be easily installed by the distribution's default [[wikipedia:Package management system|Package management system]]. That is the recommended method of installing them; in most cases, the package manager will automatically configure and reload the HTTP server to enable them.<br />
<br />
On Ubuntu &amp; Debian: the extension <tt>mbstring</tt> will typically be included in the Apache module package.<br />
<pre>sudo apt-get install php5-mysql php5-curl</pre><br />
<br />
<br />
On CentOS (6+), the mbstring extension must be installed separately with other missing modules:<br />
<pre>sudo yum install php-pdo php-mbstring php-common curl</pre><br />
<br />
== Installing Without All Requirements: What Won't Work ==<br />
'''This section is obsolete as of 3/26/2013.''' The severity of each missing requirement, in addition to explanation of what functionality will be unavailable/broken for each missing requirement, has been written directly into the requirements check script and should be displayed there. Thus, it is no longer necessary to list them here, although the list as it was last maintained is left here for archival/demonstrative purposes.<br />
<br />
If your server environment does not meet the minimum system requirements, and it is not possible to add PHP extensions, you can still install X2Engine, though it is '''not''' recommended. Note the following issues that can occur:<br />
<br />
{|class="wikitable" align="left"<br />
|-<br />
! scope="col" | Deficiency<br />
! scope="col" | Severity<br />
! scope="col" | Problems/Notes<br />
|-<br />
! scope="row" | IMAP extension missing<br />
|<br />
<span style="background-color:yellow">Minor</span><br />
|<br />
The email manager depends on the IMAP extension.<br />
|-<br />
! scope="row" | Zip extension missing<br />
|<br />
<span style="background-color:yellow">Minor</span><br />
|<br />
Cannot import or export custom modules.<br />
|-<br />
! scope="row" | cURL extension missing<br />
|<br />
<span style="background-color:yellow">Minor</span><br />
|<br />
* Google integration will not work<br />
* Time zone widget will not work<br />
* Contact views may be inaccessible <ref>[http://x2community.com/index.php?/topic/386-cant-open-contact-view/ X2Community Forums: "Can't open contact View"]</ref><br />
* Cannot use local scripts that make API calls<br />
* Cannot use built-in error reporter<br />
|-<br />
! scope="row" | allow_url_fopen set to "No"/0 in PHP configuration<br />
|<br />
<span style="background-color:orange">Major</span><br />
|<br />
Cannot receive software updates using the built-in update utility, and cannot receive notifications of new software versions from within the app.<br />
|-<br />
! scope="row" | Directory ownership mismatch<br />
|<br />
<span style="background-color:orange">Major</span><br />
|<br />
Application cannot be updated and cannot run unless the permissions on all files and directories are set to allow any system user to read/write <strong>(not recommended)</strong>. The application uses file-based caching and also creates files and folders during software updates, both of which require write access to the filesystem.<br />
|-<br />
! scope="row" | json extension missing<br />
|<br />
<span style="background-color:orange">Major</span><br />
|<br />
Numerous components and features will not work; the json_encode function is used throughout the application<br />
|-<br />
! scope="row" | PHP version earlier than 5.3<br />
|<br />
<span style="background-color:red; color:white; font-weight:bold;">Fatal</span><br />
|Numerous fatal errors, including errors that inhibit installation<br />
|-<br />
! scope="row" | mbstring extension missing<br />
|<br />
<span style="background-color:red; color:white; font-weight:bold;">Fatal</span><br />
|<br />
Application crashes upon login with "invalid unicode sequence" error.<br />
|-<br />
! scope="row" | pdo_mysql extension missing<br />
|<br />
<span style="background-color:red; color:white; font-weight:bold;">Fatal</span><br />
|<br />
Application cannot run; no database connection is possible. It is a requirement of [http://www.yiiframework.com/ Yii Framework].<br />
|-<br />
! scope="row" | Any other PHP extensions listed as required but missing<br />
|<br />
<span style="background-color:red; color:white; font-weight:bold;">Fatal</span><br />
|<br />
Application cannot run (requirements of Yii)<br />
|-<br />
! scope="row" | Outdated PCRE library version<br />
|<br />
<span style="background-color:red; color:white; font-weight:bold;">Fatal</span><br />
|<br />
Application cannot run. Regex used in URL rules requires the "?J" group type, which was added to PCRE in version 7.4 (September 21, 2007)<ref>[http://www.pcre.org/changelog.txt PCRE Changelog]</ref><br />
|}<br />
<br />
== Recommended System ==<br />
The following attributes of the hosting environment are by no means required. However, they are the same as the primary servers on which X2Engine is most commonly developed and tested, and thus would be the most likely to never cause problems:<br />
* PHP 5.3.10 and later<br />
* MySQL 5.5<br />
* Apache 2.2 with [http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html mod_rewrite] enabled. <br />
* Ubuntu 12.04 LTS, CentOS 6.3, or Amazon Linux<br />
<br />
If only Windows is available, the environment provided by [http://www.wampserver.com/ WampServer] is recommended, especially for development.<br />
<br />
= Installing =<br />
== Using The Installation Page ==<br />
Browser-based installation generally proceeds as follows:<br />
# Make sure a MySQL database and a database user with full permissions to that database are available from the web server.<br />
# Upload the contents of the <tt>x2engine</tt> folder to the document root of the web server, or a subdirectory if desired.<br />
# Navigate to the webroot (or subdirectory) where the contents of the folder were uploaded.<br />
# Fill out the installation form. '''Note the following:'''<br />
#* If you leave "Create Sample Data" checked, the installer will insert fictitious contact, user, account and action records into the initial installation for testing purposes. Uncheck the box if this is not desired.<br />
#* You can first test the database connection without losing the installation form by using the "Test Connection" button. Doing this before clicking the install button is highly recommended.<br />
<br />
== Using The Command Line Installer == <br />
It is possible to install X2Engine from the command line via SSH or otherwise. This is performed as follows:<br />
# There is a script in the root of the web application named <tt>installConfig.php</tt>. Fill it with the same information that would be submitted by the installation page form. If you are installing a commercial edition of X2Engine, fill the variable <tt>$unique_id</tt> with your product key.<br />
# Change directory into the root folder of the web application.<br />
# Run: <pre>php initialize.php silent</pre><br />
<br />
The configuration variables in <tt>installConfig.php</tt> are as follows:<br />
;host<br />
: The (MySQL) database hostname<br />
;db<br />
: The database name<br />
;user<br />
: The database username<br />
;pass<br />
: The database password<br />
;app<br />
: The application name (i.e. "Company X CRM") that shows up in various places throughout the app.<br />
;currency<br />
: The 3-letter code for the currency to be used in quotes and opportunities. Currently supported currencies include: USD,EUR,GBP,CAD,JPY,CNY,CHF,INR, and BRL.<br />
;lang<br />
: The application's language.<br />
;timezone<br />
: The time zone. It must be a valid timezone alias; see [http://php.net/manual/en/timezones.php List of supported time zones] for more info.<br />
;adminEmail<br />
: The email address of the application's owner<br />
;adminPassword<br />
: The administrator's application password<br />
;adminUsername<br />
: The username of the administrator<br />
;dummyData<br />
: Whether to include sample data in the installation, for evaluative purposes<br />
;webLeadUrl<br />
: The base URL of the web application.<br />
;unique_id<br />
: The product key, if installing a commercial edition. In Open Source Edition, it can simply be left "none". If it is "none" in Open Source Edition, the user will need to enable software updates separately by going to "Updater Settings" in the administrative index, in the "System Settings" section.<br />
;visibleModules<br />
: Initial module visibility setting; a comma-delineated list of modules to be displayed in the menu at the top of the CRM.<br />
;test_db<br />
: Set this to 1 if constructing a unit/functional testing environment (see [[Test-Driven_Development#Preparing_a_testing_database|Test-driven Development: preparing a testing database]])<br />
;test_url<br />
: If installing a testing environment, the URL to the "index-test.php" entry script that web tests will use.<br />
;installType<br />
: Installation type to report to the updates server. This is a setting that is used for internal reporting/statistical purposes and should be left as-is.<br />
<br />
= Miscellaneous Post-Installation Tasks =<br />
== Configuring the "Email Dropbox" ==<br />
This section has been moved to the article: "[[E-Mail Configuration]]"<br />
<br />
= References =<br />
<references /></div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Known_Issues&diff=1932Known Issues2014-12-05T01:30:45Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
<br />
The following is a list of problems that both arise under very specific circumstances and cannot be fixed by updating to the latest version (due to how they either prevent updating or cause fatal errors, or because updates cannot fix the issue).<br />
<br />
{|class="wikitable" align="left"<br />
|-<br />
! scope="col" | Description<br />
! scope="col" | Affected Instances<br />
! scope="col" | How to Fix<br />
|-<br />
! scope="row" | Failed to update with "ResponseUtil.php: No such file or directory" before 3.7.5<br />
| Instances between versions 3.0.1 and 3.7.5<br />
| Modify protected/components/UpdaterBehavior.php, and add the value "components/util/ResponseUtil.php" to the array $updaterFiles and proceed to update:<br />
<syntaxhighlight lang="php"><br />
public $updaterFiles = array(<br />
// ...<br />
"components/util/ResponseUtil.php",<br />
);<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Primary administrator user has lost permissions<br />
| After updating to 4.0.1, having a username that isn't "admin"<br />
| Run the following database command (SQL) on X2Engine's database, '''REPLACING <tt>{user}</tt> WITH THE ADMINISTRATOR'S USERNAME''':<br />
<syntaxhighlight lang="sql"><br />
UPDATE `x2_auth_item` SET `bizrule`='return Yii::app()->user->name === "{user}";' WHERE `name`='admin';<br />
DELETE FROM `x2_auth_cache` WHERE 1;<br />
</syntaxhighlight><br />
Once administrative access has been restored, update to 4.0.2 or later. ''Alternatively, you can use the [[Software_Updates_and_Upgrades#Using_the_Command_Line_Updater|command line update method]].''<br />
|-<br />
! scope="row" | "failed to copy from tmp" error message on update page<br />
| Versions 3.5.5, and in some cases, from 3.5.6 to 3.7.5<br />
| There was a bug in 3.5.5 caused this particular error, but it was later fixed. If you are on 3.5.5, you can fix this by [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|updating the updater utility to the latest version.]] In all other versions, the only two legitimate causes of this error that could be found were:<br />
* The list of updater dependency files to download is actually empty (this should almost never happen when the updater utility refreshes)<br />
* (In X2Engine Professional Edition): the installation's number of users exceeds the amount of users for which the license is authorized. This causes the server to return something other than the list of file digests, and the updater interprets it as being empty.<br />
In both of these cases, the "tmp" folder simply does not get created due to the file list being empty (which was problematic). In the case that you are using X2Engine Open Source Edition, or the number of users does not exceed the number that your license is authorized for, you can attempt to circumvent this by creating the folder manually. In both cases, you can get past this issue if you have access to the CRM's files.<br />
|-<br />
! scope="row" | Cannot update, and requirements check script passes<br />
| Installed at or updated to version 3.5.5<br />
| [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|Update the updater utility to the latest version.]]<br />
|-<br />
! scope="row" | Prompt to update doesn't go away after updating<br />
| X2Engine versions before 2.1 (check for the version number being the same as before the update)<br />
| '''After updating,''' edit the file <tt>protected/config/X2Config.php</tt>. For the <tt>$version</tt> variable, put the current version between the pair of single quotes. For example, for version x.y.z, it should look like this: <syntaxhighlight lang="php">$version = 'x.y.z';</syntaxhighlight><br />
|-<br />
! scope="row" | Cannot log in after update<br />
| X2Engine versions before 2.0, after updating to a version after 2.0<br />
| Empty the x2_sessions table (delete all records)<br />
<syntaxhighlight lang="sql"><br />
DELETE FROM `x2_sessions`;<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Invalid path in "protected/runtime/..."<br />
|<br />
* PHP built without <tt>posix</tt> library<br />
* Windows-based servers<br />
|<br />
This is caused by installing with a directory ownership mismatch that wasn't caught and reported in the requirements check. In some PHP builds, the necessary POSIX functions for running this check are unavailable due to one of the following reasons:<br />
* The <tt>--disable-posix</tt> flag is enabled and the POSIX library hasn't been separately installed<br />
* The server's operating system is Windows<br />
To see if this is the case, upload [[requirements:|requirements.php]] to your server again and search for "posix" in the <tt>phpinfo()</tt> output.<br />
|-<br />
! scope="row" | Installation error: deprecated function "mysql_escape_string"<br />
|<br />
All versions up to 2.2.1 on some servers<br />
|<br />
'''Install a newer version of X2Engine''' (recommended) or replace all instances of "mysql_escape_string" with "addslashes" in initialize.php, or simply remove those function calls. Note that if you perform the latter action, you must ensure that the values you put in the "Application Name" and "Administrator Email" fields of the installation form do not contain any apostrophes (otherwise, the resulting installation will be broken).<br />
|-<br />
! scope="row" | "No input file specified"<br />
|<br />
All versions, on GoDaddy hosting<br />
|<br />
All web applications featuring human-friendly URLs (i.e. Joomla!, Drupal, Wordpress) will have this issue on GoDaddy. The error is caused by the URL rewriting engine being disabled in GoDaddy's hosting environment. See the following articles on how to rectify the issue using <tt>.htaccess</tt> overrides: <br />
* [http://www.magentocommerce.com/wiki/groups/227/error/no_input_file_specified Magento: "No Input File Specified"] <br />
* [http://support.godaddy.com/help/article/6656/enabling-joomla-search-engine-friendly-urls GoDaddy &mdash; Enabling Joomla! Search Engine Friendly URLs]<br />
|-<br />
! scope="row" | Update notifications do not work, cannot enable them<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 2.0<br />
|<br />
* Set value of the column <tt>unique_id</tt> in table <tt>x2_admin</tt> to "none", i.e. <syntaxhighlight lang="sql">UPDATE `x2_admin` SET `unique_id`='none'</syntaxhighlight> Then, go to "Updater Settings" ("General Settings" in versions earlier than 2.0), ensure "Enable Software Update Notifications" is checked, and submit the form.<br />
* Run the update manually if all else fails<br />
|-<br />
! scope="row" | Cannot view or create quotes<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 1.6.6<br />
|<br />
Run the following SQL:<br />
<br />
'''Versions earlier than 2.0:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, ""), <br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "")<br />
</syntaxhighlight><br />
'''Versions 2.0 and later:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance,isVirtual) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1),<br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1)<br />
</syntaxhighlight><br />
|-<br />
|}</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Known_Issues&diff=1931Known Issues2014-12-05T01:28:04Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
<br />
The following is a list of problems that both arise under very specific circumstances and cannot be fixed by updating to the latest version (due to how they either prevent updating or cause fatal errors, or because updates cannot fix the issue).<br />
<br />
{|class="wikitable" align="left"<br />
|-<br />
! scope="col" | Description<br />
! scope="col" | Affected Instances<br />
! scope="col" | How to Fix<br />
|-<br />
! scope="row" | Failed to update with error "ResponseUtil.php: failed to open stream: No such file or directory" before 3.7.5<br />
| Instances between versions 3.0.1 and 3.7.5<br />
| Modify protected/components/UpdaterBehavior.php, and add the value "components/util/ResponseUtil.php" to the array $updaterFiles and proceed with the update:<br />
<syntaxhighlight lang="php"><br />
public $updaterFiles = array(<br />
// ...<br />
"components/util/ResponseUtil.php",<br />
);<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Primary administrator user has lost permissions<br />
| After updating to 4.0.1, having a username that isn't "admin"<br />
| Run the following database command (SQL) on X2Engine's database, '''REPLACING <tt>{user}</tt> WITH THE ADMINISTRATOR'S USERNAME''':<br />
<syntaxhighlight lang="sql"><br />
UPDATE `x2_auth_item` SET `bizrule`='return Yii::app()->user->name === "{user}";' WHERE `name`='admin';<br />
DELETE FROM `x2_auth_cache` WHERE 1;<br />
</syntaxhighlight><br />
Once administrative access has been restored, update to 4.0.2 or later. ''Alternatively, you can use the [[Software_Updates_and_Upgrades#Using_the_Command_Line_Updater|command line update method]].''<br />
|-<br />
! scope="row" | "failed to copy from tmp" error message on update page<br />
| Versions 3.5.5, and in some cases, from 3.5.6 to 3.7.5<br />
| There was a bug in 3.5.5 caused this particular error, but it was later fixed. If you are on 3.5.5, you can fix this by [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|updating the updater utility to the latest version.]] In all other versions, the only two legitimate causes of this error that could be found were:<br />
* The list of updater dependency files to download is actually empty (this should almost never happen when the updater utility refreshes)<br />
* (In X2Engine Professional Edition): the installation's number of users exceeds the amount of users for which the license is authorized. This causes the server to return something other than the list of file digests, and the updater interprets it as being empty.<br />
In both of these cases, the "tmp" folder simply does not get created due to the file list being empty (which was problematic). In the case that you are using X2Engine Open Source Edition, or the number of users does not exceed the number that your license is authorized for, you can attempt to circumvent this by creating the folder manually. In both cases, you can get past this issue if you have access to the CRM's files.<br />
|-<br />
! scope="row" | Cannot update, and requirements check script passes<br />
| Installed at or updated to version 3.5.5<br />
| [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|Update the updater utility to the latest version.]]<br />
|-<br />
! scope="row" | Prompt to update doesn't go away after updating<br />
| X2Engine versions before 2.1 (check for the version number being the same as before the update)<br />
| '''After updating,''' edit the file <tt>protected/config/X2Config.php</tt>. For the <tt>$version</tt> variable, put the current version between the pair of single quotes. For example, for version x.y.z, it should look like this: <syntaxhighlight lang="php">$version = 'x.y.z';</syntaxhighlight><br />
|-<br />
! scope="row" | Cannot log in after update<br />
| X2Engine versions before 2.0, after updating to a version after 2.0<br />
| Empty the x2_sessions table (delete all records)<br />
<syntaxhighlight lang="sql"><br />
DELETE FROM `x2_sessions`;<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Invalid path in "protected/runtime/..."<br />
|<br />
* PHP built without <tt>posix</tt> library<br />
* Windows-based servers<br />
|<br />
This is caused by installing with a directory ownership mismatch that wasn't caught and reported in the requirements check. In some PHP builds, the necessary POSIX functions for running this check are unavailable due to one of the following reasons:<br />
* The <tt>--disable-posix</tt> flag is enabled and the POSIX library hasn't been separately installed<br />
* The server's operating system is Windows<br />
To see if this is the case, upload [[requirements:|requirements.php]] to your server again and search for "posix" in the <tt>phpinfo()</tt> output.<br />
|-<br />
! scope="row" | Installation error: deprecated function "mysql_escape_string"<br />
|<br />
All versions up to 2.2.1 on some servers<br />
|<br />
'''Install a newer version of X2Engine''' (recommended) or replace all instances of "mysql_escape_string" with "addslashes" in initialize.php, or simply remove those function calls. Note that if you perform the latter action, you must ensure that the values you put in the "Application Name" and "Administrator Email" fields of the installation form do not contain any apostrophes (otherwise, the resulting installation will be broken).<br />
|-<br />
! scope="row" | "No input file specified"<br />
|<br />
All versions, on GoDaddy hosting<br />
|<br />
All web applications featuring human-friendly URLs (i.e. Joomla!, Drupal, Wordpress) will have this issue on GoDaddy. The error is caused by the URL rewriting engine being disabled in GoDaddy's hosting environment. See the following articles on how to rectify the issue using <tt>.htaccess</tt> overrides: <br />
* [http://www.magentocommerce.com/wiki/groups/227/error/no_input_file_specified Magento: "No Input File Specified"] <br />
* [http://support.godaddy.com/help/article/6656/enabling-joomla-search-engine-friendly-urls GoDaddy &mdash; Enabling Joomla! Search Engine Friendly URLs]<br />
|-<br />
! scope="row" | Update notifications do not work, cannot enable them<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 2.0<br />
|<br />
* Set value of the column <tt>unique_id</tt> in table <tt>x2_admin</tt> to "none", i.e. <syntaxhighlight lang="sql">UPDATE `x2_admin` SET `unique_id`='none'</syntaxhighlight> Then, go to "Updater Settings" ("General Settings" in versions earlier than 2.0), ensure "Enable Software Update Notifications" is checked, and submit the form.<br />
* Run the update manually if all else fails<br />
|-<br />
! scope="row" | Cannot view or create quotes<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 1.6.6<br />
|<br />
Run the following SQL:<br />
<br />
'''Versions earlier than 2.0:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, ""), <br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "")<br />
</syntaxhighlight><br />
'''Versions 2.0 and later:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance,isVirtual) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1),<br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1)<br />
</syntaxhighlight><br />
|-<br />
|}</div>Raymond Colebaughhttp://wiki.x2crm.com/index.php?title=Known_Issues&diff=1930Known Issues2014-12-05T01:23:49Z<p>Raymond Colebaugh: </p>
<hr />
<div>[[Category:Support]]<br />
<br />
The following is a list of problems that both arise under very specific circumstances and cannot be fixed by updating to the latest version (due to how they either prevent updating or cause fatal errors, or because updates cannot fix the issue).<br />
<br />
{|class="wikitable" align="left"<br />
|-<br />
! scope="col" | Description<br />
! scope="col" | Affected Instances<br />
! scope="col" | How to Fix<br />
|-<br />
! scope="row" | Failed to update before 3.7.5<br />
| Instances between versions 3.0.1 and 3.7.5<br />
| Modify protected/components/UpdaterBehavior.php, and add the value "components/util/ResponseUtil.php" to the array $updaterFiles and proceed with the update:<br />
<syntaxhighlight lang="php"><br />
public $updaterFiles = array(<br />
// ...<br />
"components/util/ResponseUtil.php",<br />
);<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Primary administrator user has lost permissions<br />
| After updating to 4.0.1, having a username that isn't "admin"<br />
| Run the following database command (SQL) on X2Engine's database, '''REPLACING <tt>{user}</tt> WITH THE ADMINISTRATOR'S USERNAME''':<br />
<syntaxhighlight lang="sql"><br />
UPDATE `x2_auth_item` SET `bizrule`='return Yii::app()->user->name === "{user}";' WHERE `name`='admin';<br />
DELETE FROM `x2_auth_cache` WHERE 1;<br />
</syntaxhighlight><br />
Once administrative access has been restored, update to 4.0.2 or later. ''Alternatively, you can use the [[Software_Updates_and_Upgrades#Using_the_Command_Line_Updater|command line update method]].''<br />
|-<br />
! scope="row" | "failed to copy from tmp" error message on update page<br />
| Versions 3.5.5, and in some cases, from 3.5.6 to 3.7.5<br />
| There was a bug in 3.5.5 caused this particular error, but it was later fixed. If you are on 3.5.5, you can fix this by [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|updating the updater utility to the latest version.]] In all other versions, the only two legitimate causes of this error that could be found were:<br />
* The list of updater dependency files to download is actually empty (this should almost never happen when the updater utility refreshes)<br />
* (In X2Engine Professional Edition): the installation's number of users exceeds the amount of users for which the license is authorized. This causes the server to return something other than the list of file digests, and the updater interprets it as being empty.<br />
In both of these cases, the "tmp" folder simply does not get created due to the file list being empty (which was problematic). In the case that you are using X2Engine Open Source Edition, or the number of users does not exceed the number that your license is authorized for, you can attempt to circumvent this by creating the folder manually. In both cases, you can get past this issue if you have access to the CRM's files.<br />
|-<br />
! scope="row" | Cannot update, and requirements check script passes<br />
| Installed at or updated to version 3.5.5<br />
| [[Software_Updates_and_Upgrades#Refreshing_and_Updating_the_Updater_Utility|Update the updater utility to the latest version.]]<br />
|-<br />
! scope="row" | Prompt to update doesn't go away after updating<br />
| X2Engine versions before 2.1 (check for the version number being the same as before the update)<br />
| '''After updating,''' edit the file <tt>protected/config/X2Config.php</tt>. For the <tt>$version</tt> variable, put the current version between the pair of single quotes. For example, for version x.y.z, it should look like this: <syntaxhighlight lang="php">$version = 'x.y.z';</syntaxhighlight><br />
|-<br />
! scope="row" | Cannot log in after update<br />
| X2Engine versions before 2.0, after updating to a version after 2.0<br />
| Empty the x2_sessions table (delete all records)<br />
<syntaxhighlight lang="sql"><br />
DELETE FROM `x2_sessions`;<br />
</syntaxhighlight><br />
|-<br />
! scope="row" | Invalid path in "protected/runtime/..."<br />
|<br />
* PHP built without <tt>posix</tt> library<br />
* Windows-based servers<br />
|<br />
This is caused by installing with a directory ownership mismatch that wasn't caught and reported in the requirements check. In some PHP builds, the necessary POSIX functions for running this check are unavailable due to one of the following reasons:<br />
* The <tt>--disable-posix</tt> flag is enabled and the POSIX library hasn't been separately installed<br />
* The server's operating system is Windows<br />
To see if this is the case, upload [[requirements:|requirements.php]] to your server again and search for "posix" in the <tt>phpinfo()</tt> output.<br />
|-<br />
! scope="row" | Installation error: deprecated function "mysql_escape_string"<br />
|<br />
All versions up to 2.2.1 on some servers<br />
|<br />
'''Install a newer version of X2Engine''' (recommended) or replace all instances of "mysql_escape_string" with "addslashes" in initialize.php, or simply remove those function calls. Note that if you perform the latter action, you must ensure that the values you put in the "Application Name" and "Administrator Email" fields of the installation form do not contain any apostrophes (otherwise, the resulting installation will be broken).<br />
|-<br />
! scope="row" | "No input file specified"<br />
|<br />
All versions, on GoDaddy hosting<br />
|<br />
All web applications featuring human-friendly URLs (i.e. Joomla!, Drupal, Wordpress) will have this issue on GoDaddy. The error is caused by the URL rewriting engine being disabled in GoDaddy's hosting environment. See the following articles on how to rectify the issue using <tt>.htaccess</tt> overrides: <br />
* [http://www.magentocommerce.com/wiki/groups/227/error/no_input_file_specified Magento: "No Input File Specified"] <br />
* [http://support.godaddy.com/help/article/6656/enabling-joomla-search-engine-friendly-urls GoDaddy &mdash; Enabling Joomla! Search Engine Friendly URLs]<br />
|-<br />
! scope="row" | Update notifications do not work, cannot enable them<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 2.0<br />
|<br />
* Set value of the column <tt>unique_id</tt> in table <tt>x2_admin</tt> to "none", i.e. <syntaxhighlight lang="sql">UPDATE `x2_admin` SET `unique_id`='none'</syntaxhighlight> Then, go to "Updater Settings" ("General Settings" in versions earlier than 2.0), ensure "Enable Software Update Notifications" is checked, and submit the form.<br />
* Run the update manually if all else fails<br />
|-<br />
! scope="row" | Cannot view or create quotes<br />
|<br />
Installed at 1.6.1 or earlier and updated to a version in the range 1.6.5 - 1.6.6<br />
|<br />
Run the following SQL:<br />
<br />
'''Versions earlier than 2.0:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, ""), <br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "")<br />
</syntaxhighlight><br />
'''Versions 2.0 and later:'''<br />
<syntaxhighlight lang="sql"><br />
INSERT INTO x2_fields (modelName, fieldName, attributeLabel, modified, custom, type, required, readOnly, linkType, searchable, relevance,isVirtual) <br />
VALUES ("Quote", "products", "Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1),<br />
("Quote", "existingProducts", "Existing Products", 0, 0, "varchar", 0, 0, NULL, 0, "", 1)<br />
</syntaxhighlight><br />
|-<br />
|}</div>Raymond Colebaugh