# Disciple.Tools Technical Documentation

## Introduction

Welcome to the D.T developer documentation. The heart of Disciple Tools as an open source project is to empower ministries and disciple makers to move at the pace of God's vision for them. Technology can help only if it can be made to serve the vision.

Our ambition is to build disciple making software that unleashes your obedience to the vision He gave you.

Follow the side navigation to find documentation for Hosting, Disciple.Tools Theme Development, Getting Setup Locally and more.

Thank you for joining us in contributing to Disciple.Tools!

## Theme Contribution

The theme is the core of Disciple.Tools. It holds the default features and configuration of D.T and everything else is build around it.

The theme is hosted on GitHub [here](https://github.com/DiscipleTools/disciple-tools-theme).

If you are interested in contributing to the theme a good place to start would be to browse our [“Help Wanted”](https://github.com/DiscipleTools/disciple-tools-theme/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) issues.

Clone the Repo and submit a Pull Request when your feature is ready. See the [helpful instructions](/code-contribution/from-fork-to-pull-request). Be sure to follow the [contribution guidelines](/code-contribution/contribution-guidelines).

[Here](/local-setup) is documentation for getting set up in your development environment set up.

## Plugin Contribution

All our plugins are open source, you can see a list of them [here](https://disciple.tools/plugins/)

Use the same instructions for the Theme to Fork and Contribute to a plugin

## Create Your Own Plugin

The [D.T Starter Plugin](https://github.com/DiscipleTools/disciple-tools-plugin-starter-template) sets the structure for what a D.T plugin should look like and comes with useful examples to get started with.

We encourage you to check it out and start developing from there.

### Code of Conduct

Please read our [Code of Conduct](/code-of-conduct)


# Theme Core


# API - Posts


# List of Endpoints

This page covers endpoints available for all post types like

* contacts
* groups
* custom post types. Click [here](/theme-core/customization/custom-post-types) for more information.

## Endpoints

CRU~~D~~

* [Create post](/theme-core/api-posts/create-post): POST /wp-json/dt-posts/v2/{post-type}
* [Get post](/theme-core/api-posts/get-post): GET /wp-json/dt-posts/{post-type}/v2/post-type/{post\_id}
* [Update post](/theme-core/api-posts/update-post): POST /wp-json/dt-posts/v2/{post-type}/{post\_id}

List

* [List posts](/theme-core/api-posts/list-query): GET /wp-json/dt-posts/v2/{post-type}
* [List posts for Typeaheads](/theme-core/api-posts/list-posts-compact): GET /wp-json/dt-posts/v2/{post-type}/compact

Comments

* [Get comments](/theme-core/api-posts/post-comments): GET /wp-json/dt-posts/v2/{post-type}/{post\_id}/comments
* [Create comment](/theme-core/api-posts/post-comments): POST /wp-json/dt-posts/v2/{post-type}/{post\_id}/comments
* [Update comment](/theme-core/api-posts/post-comments): POST /wp-json/dt-posts/v2/{post-type}/{post\_id}/comments/{comment\_id}
* [Delete comment](/theme-core/api-posts/post-comments): DELETE /wp-json/dt-posts/v2/{post-type}/{post\_id}/comments/{comment\_id}

Activity

* [Get activity](/theme-core/api-posts/post-activity): GET /wp-json/dt-posts/v2/{post-type}/{post\_id}/activity
* [Get one activity](/theme-core/api-posts/post-activity): GET /wp-json/dt-posts/v2/{post-type}/{post\_id}/activity/{activity\_id}

Shares

* [Get shares](/theme-core/api-posts/post-sharing): GET /wp-json/dt-posts/v2/{post-type}/{post\_id}/shares
* [Add shares](/theme-core/api-posts/post-sharing): POST /wp-json/dt-posts/v2/{post-type}/{post\_id}/shares
* [Remove shares](/theme-core/api-posts/post-sharing): DELETE /wp-json/dt-posts/v2/{post-type}/{post\_id}/shares

Following

* [Get users following](/theme-core/api-posts/post-following): GET /wp-json/dt-posts/v2/{post-type}/{post\_id}/following

Settings

* [Get settings](/theme-core/api-posts/post-settings): GET /wp-json/dt-posts/v2/{post-type}/settings
* Multi\_select values: GET /wp-json/dt-posts/v2/{post-type}/multi-select-values

Global Search

* [Global search](/theme-core/api-posts/global-search): GET /wp-json/dt-posts/v2/posts/search/advanced\_search


# Fields Format

Fields have different types. Each type will need it's own syntax. The list of fields will changed based on your D.T instance. For a list of available fields have a look at: the field explorer tool under Utilities in your wp-admin.

> **Note:**\
> For creation and update endpoints, the `do_not_overwrite_existing_fields` parameter prevents overwriting existing field values. See the [Create Post](/theme-core/api-posts/create-post) and [Update Post](/theme-core/api-posts/update-post) documentation for details.

## text

Field examples:

* title

```php
fields = [
  "title" => "John Doe"
]
```

## boolean

Field examples:

* update\_required

```php
fields = [
   "update_required" => true
]
```

## key\_select

Field examples:

* overall\_status
* seeker\_path

The key is used to set save the field instead of the value.

```php
fields = [
   "overall_status" => "active"
]
```

## multi\_select

Field examples:

* sources
* milestones

```php
$fields = [
  "sources" => [
    "values" => [
      [ "value" => "web" ],  //set a value, the value must be predefined in the field options
      [ "value" => "phone", "delete" => true ] //remove existing
    ],
    "force_values" => false // true will set source to the values entries. removing all others
  ]
]
```

## link

Field examples:

* none as yet

```php
$fields = [
  "social_links" => [
    "values" => [ 
      [  "type" => "fb", "value" => "facebook.com" ],  //set a value, the type must be predefined in the field options
      [  "type" => "twitter", "value" => "twitter.com", "meta_id" => "1234" ],  //update a value
      [ "meta_id" => "1234", "delete" => true ] //remove existing
    ],
  ]
]
```

## tags

Field examples:

* tags

Functions just like `multi_select`, but without requiring a pre-defined list of values.

```php
$fields = [
  "tags" => [
    "values" => [
      [ "value" => "web" ],
      [ "value" => "phone", "delete" => true ] //remove existing
    ],
    "force_values" => false // true will set tags to the values entries. removing all others
  ]
]
```

## communication\_channel

* contact\_phone
* contact\_email
* contact\_address
* contact\_facebook
* etc

Note: The field name must start with `contact_`

There are three actions you can take:

* Create, if you don't include a key, a new field will be created
* Update, include the key and value to update
* Delete, including the key and the delete flag will remove the Phone number

```php
$fields = [
  "contact_phone" => [
    ["value" => "94 39 29 39"], //create
    ["key" => "contact_phone_123", "value" => "43 42 45 43"],  //update
    ["key" => "contact_phone_123", "delete" => true] //delete
  ]
]
```

To change a detail on a contact method:

```php
$fields = [
  "contact_phone" => [
    ["key" => "contact_phone_123", "verified" => true],  //update verified flag
  ]
]
```

If either **Mapbox** or **Google Geocode** APIs are available, the `contact_address` field can also be instructed to support the auto-geolocating of manually entered addresses, with the use of a `geolocate` boolean flag.

```php
$fields = [
  "contact_address" => [
    ["value" => "Poland", "geolocate" => true] //create
  ]
]
```

## date

* baptism\_date

Can be either a date string or a numeric timestamp.

```php
$fields = [
  "baptism_date" => "2018-12-31" //format yyyy-mm-dd
]
```

```php
$fields = [
  "baptism_date" => 946720800 //timestamp
]
```

## datetime

* meeting\_time

Can be either a date string or a numeric timestamp.

If sent as a date string, care must be taken with timezones etc.

If a timezone isn't sent in the time string, the timezone will be assumed, so it is recommended to include the timezone information in the date string to get a precise timestamp.

Use the time formats on here [PHP DateTime Formats](https://www.php.net/manual/en/datetime.formats.time.php) for the timestring format

```php
$fields = [
  "meeting_time" => "2018-12-31 12:15 pm GMT-06:00" //format yyyy-mm-dd hh:MM timezone-adjustment
]
```

```php
$fields = [
  "meeting_time" => 946720800 //timestamp
]
```

## user\_select

* assigned\_to //int, a user id

```php
$fields = [
   "assigned_to" => 4  //the id of the user
]
```

## number

* quick\_button\_no\_answer
* quick\_button\_contact\_established
* quick\_button\_meeting\_scheduled
* quick\_button\_meeting\_complete
* quick\_button\_no\_show

```php
$fields = [
  "quick_button_no_answer" => 3
]
```

If the number is greater than the max\_option or less than the min\_option for this field, an error will be returned

## location\_grid

* location\_grid

```php
$fields = [
  "location_grid" => [ 
    "values" => [ 
      [ "value" => '100089589' ] //France
    ],
    "force_values" => false // true will set locations to the values entries. removing all others
  ] 
]
```

## location\_grid\_meta (Mapbox)

* location\_grid\_meta

You can submit geolocation information to the API using the Mapbox service in three ways.

(1) Submit using a known grid\_id

```php
$fields = [
  'location_grid_meta' => [
    'values' => [
      [
        'grid_id' => 100000020
      ]
    ],
    "force_values" => false // true will set locations to the values entries. removing all others
  ]
];
```

(2) Submit using longitude, latitude, location label, and location level information

```php
$fields = [
  'location_grid_meta' => [
    'values' => [
      [
        "label" => "Kunduz, Afganistan",
        "level" => "admin1",
        "lng" => 68.7514,
        "lat" => 36.8396,
      ]
    ],
    "force_values" => false // true will set locations to the values entries. removing all others
  ]
];
```

(3) Submit using just longitude and latitude

```php
$fields = [
  'location_grid_meta' => [
    'values' => [
      [
        "lng" => 68.7514,
        "lat" => 36.8396,
      ]
    ],
    "force_values" => false // true will set locations to the values entries. removing all others
  ]
];
```

Submitting location\_grid\_meta will trigger the mapping service to geocode the information to the location grid and install records in the correct tables. This allows for more advanced location storage and mapping.

## connection

* groups
* people\_groups
* baptized\_by
* baptized
* coaching
* coached\_by
* subassigned
* relation

Let's say our contact is connected to groups with IDs 1, 3 and 43. This example will add the group with ID 1 and will remove the group with ID 43. The contact will then be connected to group 1 and 3

```php
$fields = [
  "groups" => [
    "values" => [
      [ "value" => 1 ],
      [ "value" => 43, "delete" => true ]
    ],
    "force_values" => false // true will set groups to the values entries. removing all others
  ]
]
```

This example will remove groups 1 and 3 and leave the contact connected to group 5:

```php
$fields = [
  "groups" => [
    "values" => [
      [ "value" => 5 ],
    ],
    "force_values" => true // true will set groups to the values entries. removing all others
  ]
]
```

### connection meta

Add meta data when creating or updating a connection value. Here we add the role and date meta to the group member.

```php
$fields = [
  "groups" => [
    "values" => [
      [
        "value" => 5,
        "meta" => [
          "role" => "treasurer",
          "date" => time()
        ]
      ],
    ],
  ]
]
```

Use the same syntax for creating or updating a connection. To delete a connection meta send an empty string:

```php
$fields = [
  "groups" => [
    "values" => [
      [
        "value" => 5,
        "meta" => [
          "role" => "",
          "date" => ""
        ]
      ],
    ],
  ]
]
```

### Creating a post from a connection field

Example: Cantact A baptized someone (not yet a contact in the system). We can use the `additional_meta` parameter to create the new contact and set the `baptized_by` field by passing in the `baptized` connection field key. When creating the new contact, the API with translate `baptized` to `baptized_by` and set the new contact to be baptized by Contact A.

```php
$fields = [
  "additional_meta": [
    "created_from": 45, //the id of the existing record you want use in the connection
    "add_connection": "baptized", //the connection field where the new record is created from
  ]
]
```

## post\_user\_meta

Meta for a post for a specific user. This meta is only accessible by the user who created it and is stored in its own table.

* reminders

```php
$fields = [
  "reminders" => [
    "values" => [
      [ "value" => "Call again" ],
      [ "value" => "Call again", date => "2018-01-01" ], //optional date value
      [ "id" => 43, "delete" => true ] //delete user the meta id.
    ]
  ]
]
```

## Fields example together

```php
$fields = [
  "title" => "Bob",
  "overall_status" => "active",
  "contact_phone" => [
    ["value" => "43 42 45 43"],
    ["value" => "94 39 29 39"]
  ],
  "locations" => [
    "values" => [
      [ "value" => "9" ]
    ]
  ],
  "quick_button_no_answer" => 3,
  "baptism_date" => "2017-11-34",
]
DT_Posts::create_post( 'contacts', $fields )
```


# Get Post

`GET` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/>

Requires permission: `access_{post_type}`

## Returns

(json object): the contact. Each field type will show in a different way:

* **"ID"**
* **"name"**:"John Doe",
* **"created\_date"**:"2018-06-05 16:07:16",
* **"last\_modified"**:"1552987784", //date the contact has last been modified
* **text fields**

```
"field_key": "text"
```

* **multi\_select fields**

  ```javascript
  "field_key": [ 
    "option_key", 
    "option_key",
    ... 
  ]
  ```
* **key\_select fields**

  ```javascript
  "field_key": {
   "key":"option_key",
   "label":"option_label"
  },
  ```
* **connection fields**

  ```javascript
  "field_key": [ 
   { 
       "ID":{post_id},
       "post_type":"{post_type}",
       "post_date_gmt":"2018-05-29 13:12:01",
       "post_date":"2018-05-29 13:12:01",
       "post_title":"{post_title}"
   },
   ...
  ]
  ```
* **date fields**

  ```javascript
  "field_key": {
    timestamp: "1552953600",  //unix timestamp and the date
    formatted: "March 19, 2019" // date formatted base on selected date format in WP settings
  }
  ```

## Return Example

```javascript
{
"ID":72,
"title":"Mojiz Chra\u00efbi",
"created_date":"2018-06-05 16:07:16",
"last_modified":"1552987784",
"geonames":[
    {"id":123456,"label":"World"}
],
"groups":[
    {"ID":83,"post_type":"groups","post_date_gmt":"2018-07-02 15:05:53","post_date":"2018-07-02 15:05:53","post_title":"Local Christian Church"}
],
"people_groups":[],
"baptized":[
    {"ID":105,"post_type":"contacts","post_date_gmt":"2018-07-17 14:54:35","post_date":"2018-07-17 14:54:35","post_title":"Jessica Blue"}
],
"baptized_by":[],
"coaching":[],
"coached_by":[],
"subassigned":[],
"relation":[],
"seeker_path":{"key":"none","label":"Contact Attempt Needed"},

"type":{"key":"media","label":"Media"},
"assigned_to":{"id":"8","type":"user","display":"Anthony Palacio (multiplier)","assigned-to":"user-8"},
"overall_status":{"key":"assigned","label":"Waiting to be accepted"},
"contact_phone":[
    {"verified":false,"value":"555-5555","key":"contact_phone_ca2"}
],
"contact_email":[
    {"verified":false,"value":"","key":"contact_email_313"}
],
"sources":["facebook"],
"accepted":false,
"gender":{"key":"male","label":"Male"},
"age":{"key":"<19","label":"Under 18 years old"},
"milestones":[
    "milestone_has_bible",
    "milestone_can_share",
    "milestone_reading_bible"
],
"baptism_generation":"0",
"baptism_date": {"timestamp": "1552953600", "formatted": "March 19, 2019"}
}
```


# Create Post

`POST` <https://example.com/wp-json/dt-posts/v2/{post_type}/>

Requires permission: `create_{post_type}`

## Parameters

Body params: See [Fields Format](/theme-core/api-posts/post-types-fields-format)

Query params: add `?silent=true` to disable notifications

Query param: `check_for_duplicates`.\
Check for duplicates on a field before creating an new post. If a duplicate is found, then the existing post will be updated instead of a new one created.\
ex: `check_for_duplicates=contact_phone,contact_email`

### do\_not\_overwrite\_existing\_fields (boolean)

When enabled, existing field values on a post will not be overwritten during creation:

* **Single-value fields:** If the field already has a value, it will be preserved and not updated.
* **Multi-value fields:** Only new values that do not already exist will be added; existing values are preserved.

## Returns

Will return the same content as: [Get Post](/theme-core/api-posts/get-post)


# Update Post

`Post` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/>

Requires permission: `update_any_{post_type}` or the record to be shared with the user.

## Parameters

Body params: See [Post Types Fields Format](/theme-core/api-posts/post-types-fields-format)

Query params: add `?silent=true` to disable notifications

### do\_not\_overwrite\_existing\_fields (boolean)

When enabled, existing field values on a post will not be overwritten during update:

* **Single-value fields:** If the field already has a value, it will be preserved and not updated.
* **Multi-value fields:** Only new values that do not already exist will be added; existing values are preserved.

## Returns

Will return the same content as: [Get Post](/theme-core/api-posts/get-post)


# Post Comments

## Get comments

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/comments>

### Parameters

* **number** (int) optional. How many comments to return
* **offset** (int) optional. How many comments to skip (for pagination)

### Returns

```javascript
[ 
   comments: (array) An array of comments.
   total: (int) the number of comment in total
]
```

Includes comment meta data and reactions along with default comment:

```javascript
{
   "comments": [{
       ...
       "comment_reactions": {
           "reaction_thumbs_up": [
               { "name": "admin", "user_id": "1" },
               { "name": "user1", "user_id": "2" }
           ],
           "reaction_heart": [
               { "name": "admin", "user_id": "1" }
           ]
       },
       "comment_meta": {
           "audio_url": [
               { "id": "1", "value": "https://my.path/to/audio.mp3" },
               { "id": "2", "value": "https://my.path/to/audio.ogg" },
           ]
       }
   }]
}
```

## Create a comment

`POST` <https://example.com/wp-json/dt/v2/{post_type}/{post_id}/comments>

### Parameters

* **comment** (string) the body of the comment.
* **date** (string) optional. format "Y-m-d H:i:s"
* **comment\_type** (string) optional. The comment type. Default: 'comment'
* **meta** (object) optional. Additional meta data

Query params: add `?silent=true` to disable notifications

**@mentions** Mention are used to make sure a user sees a comment and gets a notification. This example @mentions user with id 46 and will display bob as the name of the user.

```javascript
{
    "comment": "@[bob](46) this is a mention notification"
}
```

**links** Create a link to another record, page or site

```javascript
{
    "comment": "See changes on [link text](link url)
}
```

**meta data** Create additional meta data - such as reactions or audio files - by passing a meta data object with the key/value pairs to be created. Values can be primitive types (string, int, etc) or arrays.

```javascript
{
    "meta": {
        "audio_url": [
            "https://my.path/to/audio.mp3",
            "https://my.path/to/audio.ogg"
        ]
    }
}
```

```javascript
{
    "meta": {
        "audio_url": "https://my.path/to/audio.ogg"
    }
}
```

### Returns

(object) The default wordpress comment. See <https://developer.wordpress.org/reference/functions/get_comment/>

Includes comment meta data along with default comment:

```javascript
{
    "comment_meta": {
        "audio_url": [
            "https://my.path/to/audio.mp3",
            "https://my.path/to/audio.ogg"
        ]
    }
}
```

## Update a comment

`POST` <https://example.com/wp-json/dt/v2/{post_type}/{post_id}/comments/{comment_id}>

### Parameters

* **comment** (string) the body of the comment.
* **meta** (object) optional. Additional meta data

### Returns

(object) The default wordpress comment. See <https://developer.wordpress.org/reference/functions/get_comment/>

Includes comment meta data along with default comment:

```javascript
{
    "comment_meta": {
        "audio_url": [
            "https://my.path/to/audio.mp3",
            "https://my.path/to/audio.ogg"
        ]
    }
}
```

## Delete a comment

`DELETE` <https://example.com/wp-json/dt/v2/{post_type}/{post_id}/comments/{comment_id}>

### Returns

(bool) true if the contact was deleted


# Post Activity

## Get Activity

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/activity>

### Parameters

* **number** (int) optional. How many activities to return
* **offset** (int) optional. How many activities to skip (for pagination)

### Returns

```
[ 
   activity: (array) An array of activities. See below for format.
   total: (int) the number of activities in total
]
```

Activity list format:

```javascript
[
  {
    "meta_key":"overall_status",
    "gravatar":"http:\/\/2.gravatar.com\/avatar\/id?s=16&d=mm&r=g",
    "name":"Me", //name of the user who did the activity
    "object_note":"Overall Status: Active",
    "hist_time":"1559128822", //when the activity happened
    "meta_id":"150", // the ID of the related post_meta field
    "histid":"179" // the ID of the activity
  },
  {
    ...activity2...
  }
]
```

## Get Singe Activity

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/activity/{activity_id}>

### Returns

(array) The activity array.

Activity list format:

```javascript
{
  "meta_key":"overall_status",
  "gravatar":"http:\/\/2.gravatar.com\/avatar\/id?s=16&d=mm&r=g",
  "name":"Me", //name of the user who did the activity
  "object_note":"Overall Status: Active",
  "hist_time":"1559128822", //when the activity happened
  "meta_id":"150", // the ID of the related post_meta field
  "histid":"179" // the ID of the activity
}
```


# List Query

## Get a list of contacts, groups or another post type, with filtering and sorting parameters

## Endpoint

`GET` <https://example.com/wp-json/dt-posts/v2/{post_type}/>

## Parameters

**sort** (string)

* Options:
  * name //name or title of the record
  * post\_date //creation date of the record
  * any field\_key

Add a `-` before any of these options to return them in descending order

Example:

```javascript
// get records assigned to me, ordered by creation date from newest to oldest
let searchParameters = {
  assigned_to: [ 'me' ],
  sort: `-post_date`
}
```

### `user_select`

Parameters: (array) of presets or ids.

* `me` // records assigned to the user making the query
* `83` // records assigned to user of ID 83
* `-84` // exclude records assigned to user 84

Example:

```javascript
// get records assigned to me
let searchParameters = {
  assigned_to: [ 'me' ]
}
// get records assigned_to user 22 and user 48
let searchParameters = {
  assigned_to: [ 22 ]
}
```

### `key_select`, `multi_select`, `tags`

Parameters: (array) of keys.

* overall\_stats (key\_select)
* milestones (mutli\_select)
* gender (key\_select)
* tags (tags)
* etc

Example:

```javascript
// get contacts that have the 'Has Bible' or 'Reading Bible' milestones and that are at the 'Meeting Scheduled' stage.
let searchParameters = {
  milestones: [ 'milestone_has_bible', 'milestone_reading_bible' ],
  seeker_path: [ 'scheduled' ],
  tags: [ 'open' ]
}
```

```javascript
// get contacts that have the 'Has Bible' milestone but not the 'Reading Bible' milestone.
let searchParameters = {
  milestones: [ 'milestone_has_bible', '-milestone_reading_bible' ],
}
```

### `connection`

Parameters (array) of IDs

* subassigned
* groups
* etc

Example:

```javascript
// get contacts subassigned to contact 93. Exclude contacts subassigned to contact 23
let searchParameters = {
  subassinged: [ 93, -23 ]
}
```

Example:

```javascript
// get contacts assigned_to user 22 **OR** subassigned to contact 93
let searchParameters = {
  [ assigned_to => [ 22 ], subassinged: [ 93 ] ]
}
```

Example:

```javascript
// get contacts with no groups connected
let searchParameters = {
   groups: [] 
}
```

Example:

```javascript
// get all contact with any connected group
let searchParameters = {
   groups: [*] 
}
```

### `location`

Parameters: (array) of location\_grid IDs

* location\_grid

Example:

```javascript
// get contacts in location with location_grid (in the dt_location_grid table grid_id) id 123456
// but exclude location 5678
let searchParameters = {
  location_grid: [ 123456, -5678 ]
}
```

### `date`

Parameters: **start** and **end**

* created\_on // date the record was created
* baptism\_date
* etc

Example:

```javascript
// get the records created between in 2018
let searchParameters = {
  created_on : {
    start: "2018-01-01",
    end: "2019-01-01"
  }
}
// get contacts baptized before Feb 2019
let searchParameters = {
  baptism_date : {
    end: "2019-02-01"
  }
}
```

### `boolean`

Parameters (array). "1" for true, "0" for false

* requires\_update
* etc

Example:

```javascript
// get records that need an update
let searchParameters = {
  requires_update: [ "1" ]
}
```

### `number`

Parameters (array):

* **operator** options: `<`, `>`, `<=`, `>=` or `=`
* **number**

Field examples:

* baptism\_generations
* quick actions

Example:

```javascript
// get records that are baptism generation greater than 4
let searchParameters = {
  baptism_generation => [ "operator" => ">", "number" => 4 ],
}
```

### `text` `communication_channel`

Parameters: (array) or text to search for.

* contact\_phone
* name
* nickname
* etc

Examples:

```javascript
// search phone numbers matching 123 anywhere in the number
let searchParameters = {
  contact_phone: ["123"]
}

// search phone numbers matching 123 exactly
let searchParameters = {
  contact_phone: ["^123"]
}

// search records for names; which do not match "Bob"
let searchParameters = {
  name: ["-Bob"]
}

// search phone numbers matching 123 but don't match 234
let searchParameters = {
  contact_phone: ["123", "-234"]
}

// search records for any phone number
let searchParameters = {
  contact_phone: ["*"]
}

// search for records with no phone numbers
let searchParameters = {
  contact_phone: []
}
```

### Record `Text` dynamic Search

* **text** (string).
* **fields\_to\_search** (array). Default is \["name", "comms"].

fields\_to\_search options:

* all
* comment
* name
* `text_field_key` // any text field key
* comms //communication channels

Example:

```javascript
// search for "Bob" in name and communication channel fields
let searchParameters = {
  text: "Bob"
}

// search across all fields
let searchParameters = {
  text: "Bob",
  fields_to_search: ["all"], //search all fields in the listed options
}

// search specific field for any text field like "nickname"
let searchParameters = {
  text: "Bob",
  fields_to_search: ["nickname"]
}

// search multiple fields for given text query
let searchParameters = {
  text: "Bob",
  fields_to_search: ["nickname", "name"]
}

// search for "Bob" in comments
let searchParameters = {
  text: "Bob",
  fields_to_search: ["comment"]
}

```

### Combining with AND/OR logic

Wrapping parameters in arrays with switch add AND/OR logic. The first level of values has AND logic. Wrapping them in an array gives them an OR logic. 1st layer: AND 2nd layer: OR 3rd layer: AND etc

Note that the query is sent in the fields array and thath the structure is a bit different.

Examples:

```javascript
// records that are of field `type` `personal` AND coached by me
let searchParameters = {
  fields: [
    {
      type: ["personal"],  
    },
    // AND
    {
      coached_by: [ "me" ]
    }
  ],
  sort: "name"
}
```

```javascript
// records that are of type "personal" OR coached by me
let searchParameters = {
  fields: [
    {
      type: ["personal"],
      //OR
      coached_by: [ "me" ]
    }
  ],
  sort: "name"
}
```

```javascript
// records that are ( ( type "personal" AND coached_by me ) OR ( assigned to me and active ) ) AND shared with me
let searchParameters = {
  fields: [

    [

      {
        type: ["personal"],
        // AND
        coached_by: [ "me" ]
      },
      // OR
      {
        assigned_to: [ "me" ],
        //AND
        overall_status: [ "active" ]
      }
    ],
    //AND
    {
      shared_with: [ "me" ]
    }
}
```

### Recently viewed posts

**dt\_recent** (bool) true. Cannot be combined with other parameters except: **fields\_to\_return**

Example:

```javascript
//Get the 30 most recently viewed posts by the user making the request.
let searchParameters = {
  dt_recent: true
}
```

### Paging Parameters

**offset** (integer) the number of records to skip. Optional. **limit** (integer) the number of records to include in the response. Default is 100, Maximum: 1000. Warning: a large number may cause a server memory error. Optional.

Example:

```javascript
// get second page of records with each page having 100.
let searchParameters = {
  offset: 100,
  limit: 100
}
```

### Specifying and limiting returned fields

**fields\_to\_return** (array) the fields to return. Optional.

Example:

```javascript
let searchParameters = {
  fields_to_return: [ 'group_status', 'group_type', 'member_count', 'leaders', 'location_grid', 'last_modified', 'requires_updated' ]
}
```

## Bringing it all together

After building the filter parameters, we need to transform the searchParameters object in the query parameters string. The query string needs to be the same format that jQuery.param() outputs. See [here](https://stackoverflow.com/questions/22582795/jquery-param-alternative-for-javascript) for a plain js alternative

```javascript
let searchParameters = {
  overall_status: ["active", "-closed"], // -closed filters out the closed records
  seeker_path: ["none"],
  sort: "post_date",
  sources: ["instagram"]
}

let queryParametersString = jQuery.param(searchParameters)
// this gives a string that looks like this:
// seeker_path%5B%5D=none&overall_status%5B%5D=active&overall_status%5B%5D=-closed&sources%5B%5D=instagram&sort=post_date

//query away with:
let queryString = `https://example.com/wp-json/dt-posts/v2/contacts/?${queryParametersString}`;
```

### Returns

```javascript
//for contacts
{
  posts: [
   { ... contact1 ... },
   { ... contact2 ... }
  ],
  total: 339 // the total number of contacts available to page (see offset)
}
//for groups
{
  posts: [
   { ... group1 ... },
   { ... group2 ... }
  ],
  total: 34 // the total number of groups available to page (see offset)
}
```


# Global Search

## Advanced Search

`Get` <https://example.com/wp-json/dt-posts/v2/posts/search/advanced_search>

### Parameters

* **query** (string) mandatory. URI encoded search query.
* **post\_type** (string) mandatory. Post type name to be searched. Set to `all` for multiple post type searches.
* **offset** (int) mandatory. How many initial search result hits to skip (for load more operations). Set to `0` as default.

### Returns

```
{
   hits: (array) An array of result hits. See below for format.
   total_hits: (int) the number of result hits in total.
}
```

Result hits format:

```javascript
[
  {
    "post_type":"contacts",
    "posts":[
      "ID": "56",
      "post_title": "Ali XYZ",
      "post_type": "contacts",
      "post_date": "2018-05-29 14:19:36",
      "post_hit": "N", //post hit type indicator (Y/N)
      "comment_hit": "Y", //comment hit type indicator (Y/N)
      "meta_hit": "N", //meta hit type indicator (Y/N)
      "comment_hit_content": "Wow! He is willing to meet today!",
      "meta_hit_value": ""
    ],
    "total": 1,
    "offset": 2 //current offset value for result post type
  },
  {
    ...hit2...
  }
]
```


# Posts in Typeaheads

`GET` <https://example.com/wp-json/dt-posts/v2/{post_type}/compact/>

Requires permission: `access_{post_type}`

## Parameters

* `s` (string): the string to filter the list to. Or the id of the target record

## Returns

```javascript
{
  posts: [
   {
     ID : 1,
     name: 'Bob',
     user: 432, //if the record corresponds to a user, only useful for contacts
     status: active //the overall status, only if the record is a contact
   },
   { ... post2 ... }
  ], 
  total: 339 // the total number of posts matching the search
}
```


# Post-Sharing

## Get shares

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/shares>

Requires permission: `view_any_{post_type}` or post is shared with user.

### Return

(array) An array of shares

Example format:

```javascript
[
  {
    "id":"10", // the id of the share
    "user_id":"1", // user the post is shared with
    "post_id":"27", // the id of the post
    "meta":null, // meta related to the share
    "display_name":"Me" // display name of the user
  },
  {
    ...share2...
  }
]
```

## Share the post with a user

`POST` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/shares>

### Parameters

* **user\_id** (int): the id of the user to share the post with

### Returns

`1` if successful

## Unshare the post with a user

`DELETE` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/shares>

### Parameters

* **user\_id** (int): the id of the user to unshare the post with

### Returns

`1` if successful


# Get Following

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/{post_id}/following>

## Returns

(array) An array of user ids.

Shares list format:

```javascript
[
  1, 2, 34
]
```


# Settings

## Get Settings

`Get` <https://example.com/wp-json/dt-posts/v2/{post_type}/settings>

### Return

(object) the settings object

Example format:

```json
{
  "tiles":
  {
    "status":{"label":"Status","tile_priority":10,"order":["overall_status","assigned_to","subassigned"]},
    "details":{"label":"Details","tile_priority":20,"order":["name","nickname","contact_phone","contact_email","location_grid","location_grid_meta","contact_address","contact_facebook","contact_twitter","contact_other","gender","age","baptism_date","sources","campaigns","people_groups"]},
    //etc
  },
  "fields":{ 
    "name":{"name":"Name","type":"text","tile":"details","in_create_form":true,"required":true,"icon":"http:\/\/example.com\/wp-content\/themes\/disciple-tools-theme\/dt-assets\/images\/name.svg","show_in_table":5},
    "last_modified":{"name":"Last Modified","type":"date","default":0,"customizable":false,"show_in_table":100},
    "post_date":{"name":"Creation Date","type":"date","default":0,"customizable":false},
    "favorite":{"name":"Favorite","type":"boolean","default":false,"private":true,"show_in_table":6,"icon":"http:\/\/example.com\/wp-content\/themes\/disciple-tools-theme\/dt-assets\/images\/star.svg"}
    //etc
  },
    
  "channels":{
    "email":{"name":"Email","icon":"http:\/\/example.com\/wp-content\/themes\/disciple-tools-theme\/dt-assets\/images\/email.svg?v=2","type":"communication_channel","tile":"details","customizable":false,"in_create_form":["access"],"label":"Email"},
    "address":{"name":"Address","icon":"http:\/\/example.com\/wp-content\/themes\/disciple-tools-theme\/dt-assets\/images\/house.svg?v=2","type":"communication_channel","tile":"details","mapbox":false,"customizable":false,"in_create_form":["access"],"label":"Address"},
    "twitter":{"name":"Twitter","icon":"http:\/\/example.com\/wp-content\/themes\/disciple-tools-theme\/dt-assets\/images\/twitter.svg?v=2","hide_domain":true,"type":"communication_channel","tile":"details","customizable":false,"label":"Twitter"}
    //etc
  },
  "connection_types":["relation","subassigned","subassigned_on","coaching","coached_by","baptized_by","baptized","people_groups","groups","group_leader","group_coach"],
  "label_singular":"Contact",
  "label_plural":"Contacts",
  "post_type":"contacts"
}
```


# API - Other


# Users

## Get My User Info

`Get` <https://example.com/wp-json/dt/v1/user/my>

### Parameters

### Returns

```php
[
    "ID": 1,
    "user_email": "user@example.com",
    "display_name": "BOB JOE",
    "locale": "fr_FR",
    "locations": {
      "location_grid": [{
        "id": 100306693,
        "label": "Poland"
      }],
      "location_grid_meta": [{
        "grid_id": "100306693",
        "grid_meta_id": "66",
        "label": "Poland",
        "lat": "52.124609907545",
        "level": "admin0",
        "lng": "19.30063630556",
        "post_id": "1",
        "post_type": "users",
        "postmeta_id_location_grid": "573",
        "source": "user"
      }]
    },
    "apps": [
      {
        "description": "An update summary of assigned contacts.",
        "label": "User Contact Updates",
        "link": "http://...."
      }
    ],
    "notifications": {
      "email_preference": {
        "daily": false,
        "hourly": false,
        "realtime": true
      },
      "follow_all": false,
      "notify_types": [
        {
          "channels": [
            {
              "enabled": true,
              "key": "email",
              "label": "Email"
            },
            {
              "enabled": true,
              "key": "web",
              "label": "Web"
            },
            {
              "enabled": false,
              "key": "push_notifications",
              "label": "Push Notifications"
            }
          ],
          "key": "new_assigned",
          "label": "Newly Assigned Contact"
        }
      ]
    },
    "preferences": {
      "languages": [
        {
          "key": "fr",
          "label": "French"
        },
        {
          "key": "es",
          "label": "Spanish"
        }
      ],
      "locations": {
        "location_grid": [{
          "id": 100306693,
          "label": "Poland"
        }],
        "location_grid_meta": [{
          "grid_id": "100306693",
          "grid_meta_id": "66",
          "label": "Poland",
          "lat": "52.124609907545",
          "level": "admin0",
          "lng": "19.30063630556",
          "post_id": "1",
          "post_type": "users",
          "postmeta_id_location_grid": "573",
          "source": "user"
        }]
      },
      "people_groups": [
        {
          "ID": "13",
          "post_title": "Arab Egyptian"
        },
        {
          "ID": "20",
          "post_title": "Algerian, Arabic-speaking"
        }
      ],
      "workload": {
        "color": "#4caf50",
        "id": "active",
        "label": "Accepting new contacts"
      }
    },
    "profile": {
      "ID": 1,
      "address": [],
      "bio": "This has been my journey so far....",
      "display_name": "admin",
      "email": [
        {
          "label": "admin@dtdev.local",
          "value": "admin@dtdev.local"
        }
      ],
      "gender": "male",
      "language": "English (United States)",
      "locale": "en_US",
      "name": "Administrator",
      "nickname": "admin",
      "other": [],
      "phone": [],
      "roles": {
        "administrator": "Administrator"
      },
      "social": [],
      "username": "admin"
    },
    "unavailability": [
      {
        "id": 1,
        "start_date": "2022-06-13",
        "end_date": "2022-06-17"
      },
      {
        "id": 2,
        "start_date": "2022-06-20",
        "end_date": "2022-06-24"
      }
    ]
]
```

## Update My User Details

`POST` <https://example.com/wp-json/dt/v1/user/update>

### Parameters

* **locale** (string) optional. The new user locale

### Returns

`true` on success. WP\_ERROR if not

## List Users

`Get` <https://example.com/wp-json/dt/v1/users/get_users>

### Parameters

* **s** (string) optional. Search user display names
* **get\_all** (string) optional. Return all the users. Default false. "1" for true

### Returns

```php
[
    "ID": 1,
    "name": "BOB JOE",
    "avatar": "http://2.gravatar.com/avatar/x?s=16&d=mm&r=g"
]
```


# Locations

## Changes

Locations appear as the `location_grid` field on contacts and groups. The old `locations` field has been removed.

`location_grid` is a field of type 'location'. It is updated the same way connections are. See [Post Types Fields Format](https://github.com/DiscipleTools/disciple-tools-theme/wiki/Post-Types-Fields-Format) for more details

## Endpoints

### Search Locations

`GET` <https://example.com/wp-json/dt/v1/mapping_module/search_location_grid_by_name>

Requires permission: `access_{post_type}`

#### Parameters

* `s` (string): the string to filter the list to.
* `filter` (string): Options: `[ 'all' | 'focus' | 'used' ]`
  * `all`: return all geoname locations
  * `focus`: Anly return the locations included in the selected Mapping Focus
  * `used`: Only return locations currently used by contacts, groups (and other)

#### Returns

```javascript
{
  location_grid: [
   {
     ID : 6255147,
     name: 'Asia',
   },
   { ... location_grid_id 2 ... }
  ], 
  total: 339 // the total number of location_grid_ids matching the search
}
```


# Settings

## Get Settings

`Get` <https://example.com/wp-json/dt-core/v1/settings>

### Return

(object) the settings object

* available\_translations: list of languages available for the user to select
* post\_types: list of post types and the post type settings. See [Post Type Settings](/theme-core/api-posts/post-settings)

Example format:

```json5
{
  "available_translations":[
    {"language":"en_US","english_name":"English (United States)","native_name":"English (United States)","site_default":true},
    {"language":"am_ET","native_name":"Amharic (Ethiopia)","english_name":"Amharic (Ethiopia)","site_default":false},
    //etc
  ],
  "post_types": {
    "peoplegroups": {}, //see post type settings
    "contacts": {},
    "groups": {}
    //etc
  },
  "plugins": {
    "disciple-tools-dashboard": {
      "name": "Disciple.Tools - Dashboard",
      "plugin_url": "http://multisite.local/wp-content/plugins/disciple-tools-dashboard/",
      "version":  "1.0.5"
    },
    "disciple-tools-trainings": {...},
    //etc  
  }
}
```


# Hooks


# Record Page Hooks

**dt\_record\_picture** Filter Filter for adding an avatar or image for a record. Expects a URL as an output to be used in an \<img tag. A provided image will be used instead of the default icon.\
Parameters 3: string $picture, string $post\_type, string $post\_id

**dt\_record\_icon** Filter Filter for changing the icon for a record. Expects a class name pertaining to an icon in <https://zurb.com/playground/foundation-icon-fonts-3\\>
Parameters 3: string $icon, string $post\_type, array $post\_id

**dt\_record\_top\_full\_with** Action\
Section for full width content above the details and comment tiles\
Parameters 2: string $post\_type, array $post

**dt\_record\_top\_above\_details** Action\
Section above the details tile.\
Parameters 2: string $post\_type, array $post

**dt\_details\_additional\_section** Action\
Action for displaying content within a tile\
Parameters 2: string $tile\_key, string $post\_type

**dt\_details\_additional\_tiles** Filter\
Declare other tiles to display.\
Parameters 2: array $tiles, string $post\_type


# API-Hooks

Documentation for theme version 1.0.0 or greater

## Post Create

**dt\_create\_post\_check\_proceed** filter\
Extra permission check before the fields are processed\
Parameters 2: bool continue, array $fields

**dt\_post\_create\_fields** filter\
Add, remove or modify fields before the fields are processed.\
Parameters 2: array $fields, string $post\_type

**dt\_post\_create\_allow\_fields** filter\
Add extra field keys that are allowed. The API will reject requests with fields that are not declared or allowed\
Parameters 2: array $field\_keys, string $post\_type

*post created*\
*fields processed*

**dt\_post\_created** action\
Runs after post is created and fields are processed\
Parameters 3: string $post\_type, int $post\_id, array $initial\_request\_fields

## Update Post

**dt\_post\_update\_fields** filter\
Add, remove or modify fields before the fields are processed.\
Parameters 4: array $fields, string $post\_type, int $post\_id, array $existing\_post

**dt\_post\_create\_allow\_fields** filter\
Add extra field keys that are allowed. The API will reject requests with fields that are not declared or allowed\
Parameters 2: array $field\_keys, string $post\_type

*fields processed*

**dt\_post\_updated** action\
Runs after fields are processed\
Parameters 5: string $post\_type, int post\_id, array $initial\_request\_fields, array $post\_fields\_before\_update, array $post\_fields\_after\_update

## Get Post

**dt\_after\_get\_post\_fields\_filter** filter\
Add, modify or remove fields from the response\
Parameters 2: array $fields, string $post\_type

## Delete Post

**dt\_before\_post\_deleted** action\
Runs before a post is deleted, allowing you to inspect the post before it's deleted.\
Parameters 2: string $post\_type, int $post\_id

**dt\_post\_deleted** action\
Runs after post is deleted.\
Parameters 3: string $post\_type, int $post\_id, string $post\_title

## List Post

**dt\_search\_viewable\_posts\_query** filter\
Add or remove query parameters\
Parameters 1: array $query\_fields

**dt\_adjust\_post\_custom\_fields** filter\
Runs an each post in the query result. Add, remove, or modify the post fields Parameters 2: array $fields, string $post\_type

**dt\_list\_posts\_custom\_fields** filter\
Modify the response before the list of posts is returned.\
Parameters 2: array $request\_response, string $post\_type

## Comments

**dt\_comment\_created** Action\
Runs after a comment is created\
Parameters 4: string $post\_type, int $post\_id, int $created\_comment\_id, string $comment\_type

**dt\_filter\_post\_comments** Filter\
Modify the response before the list of comments is returned.\
Parameters 3: array $request\_body, sting $post\_type, int $post\_id


# Public settings

Filter `dt_core_public_endpoint_settings`, in D.T v1.0.7\
Expose settings publicly to world. To not use unless it is for setting that must be accessed before the user is logged in. Settings are available at <https://dt-instance/wp-json/dt-public/dt-core/v1/settings>

Usage example:

```php
add_filter( "dt_core_public_endpoint_settings", function ( $settings ){
    $settings["login_settings"]["google"] = [ "login_url" => "https://google.com/login?redirect=https://dt-instance.com/wp-json/google-auth" ];
    return $settings;
} );
```


# Adding menu navigation links

```php
// Hook for adding a menu item to the desktop view
add_filter( 'desktop_navbar_menu_options', 'add_navigation_links', 35 );

// Hook for adding a menu item to the desktop view
add_filter( 'off_canvas_menu_options', 'add_navigation_links', 35);



function add_navigation_links( $tabs ) {
    //check user permissions
    if ( current_user_can( 'access_' . $this->post_type ) ) {

        $tabs[] = [
            "link" => site_url( "/awesome_page/" ), // the link where the user will be directed when they click
            "label" => __( "Awesome Page", "disciple_tools" )  // the label the user will see
        ];

    }
    return $tabs;
}
```


# Customization


# Fields

## Field Types

* multi\_select
* key\_select
* tags
* communication\_channel
* connection
* user\_select
* text
* textarea
* link
* date
* number
* array
* tasks
* boolean
* location
* location\_meta

## Adding fields

```php
add_filter( "dt_custom_fields_settings", "dt_contact_fields", 1, 2 );
function dt_contact_fields( array $fields, string $post_type = ""){
    //check if we are dealing with a contact
    if ($post_type === "contacts"){
        //check if the language field is already set
        if ( !isset( $fields["language"] )){
            //define the language field
            $fields["language"] = [
                "name" => __( "Spoken Language", "disciple_tools_language" ),
                "type" => "key_select",
                "default" => [
                    "english" => __( "English", "disciple_tools_language" ),
                    "french" => __( "French", "disciple_tools_language" )
                ],
                "tile" => "contact_language"
            ];
        }
    }
    //don't forget to return the update fields array
    return $fields;
}
```

### Parameters

* **name**: (string). The name you want the user to see for the field. Required.
* **type**: (string). the field type. Required.
* **description**: (string). Extra context for the field. May show up in the help modals. Optional.
* **default**: (array, string). Options for the field. Required for key\_select\_and multi\_select fields.
* **icon**: (string). The url of the icon to display next to the name.
* **font-icon**: (string). Font icon like `mdi mdi-robot-outline` using material icons. See [mdi](https://pictogrammers.com/library/mdi/)
* **tile**: (string). Which tile this field should be displayed on.
* **customizable**: (bool, string). If this field is customizable by the user in the wp\_admin settings page. Options: false, 'add\_only'
* **in\_create\_form**: (bool, array). Whether this field should be visible by default on the create post page. Either true or an array of (post) types to display on ex: \[ "personal", "access" ].
* **hidden**: (bool). If this field should show on the front end in filter options or new record pages.
* **show\_in\_table**: (int). If this field should by default show in the list table. Lower number means higher priority.
* **custom\_display**: (bool). If this field should be ignored by D.T field display generator.
* **only\_for\_types**: (array) array of the record types the field should be visible on.

**Extra Parameters for connections fields**

* **post\_type**: the post\_type of the connected posts
* **p2p\_key**: the p2p connection key. See Declaring Connection Fields below.
* **p2p\_direction**: the p2p direction. See Declaring Connection Fields below.
* **create-icon**: url for the icon user in the typeahead create button.

**Extra parameters for communication\_channel fields**

* **hide\_domain** whether to hide the url domain name when displaying a like. Ex "<https://facebook.com/person>" would show "person" as a link.

**Extra parameters for key\_select and multi\_select field**

* **default\_color**: (string). Default color for key\_select and multi\_select options. This triggers the color mode for the select field
* **select\_cannot\_be\_empty**: (bool). Selects the first value instead of an empty value by default
* **default**: options:

  ```php
  [
    "option_key" => [
        "label" => "" // name of the option
        "description" => "" //option description
        "color" => "#3F729B" //color used to display the option as.
        "hidden" => bool //don't show this option on the front end.
        "icon" => "" The url of the icon to display next to the option.
    ]
  ]
  ```

**Extra parameters for link field**

This field type is similar to the multi-select field type.

```php
[
    "option_key" => [
        "label" => "" // name of the option
        "description" => "" //option description
        "icon" => "" The url of the icon to display next to the option.
        "deleted" => bool, // don't show this option on the front end
    ]
]
```

**Extra parameters for number field**

* **min\_option**: (number). Optional parameter to define a minimum for this field
* **max\_option**: (number). Optional parameter to define a maximum for this field

## Declaring Connection Fields

Here the connection is from the contacts post type to the groups post\_type.

Declaring the field on the contacts post\_type looks like:

```php
$fields["groups"] = [
    "name" => __( 'Groups', 'disciple_tools' ),        
    "type" => "connection",
    "post_type" => "groups",
    "p2p_direction" => "from",
    "p2p_key" => "contacts_to_groups",
    'tile'     => 'details',
];
```

Note the direction and post\_type change on the field declaration on the group post\_type:

```php
$fields["members"] = [
    "name" => __( 'Member List', 'disciple_tools' ),
    "type" => "connection",
    "post_type" => "contacts",
    "p2p_direction" => "to",
    "p2p_key" => "contacts_to_groups"
];
```

## Function to have D.T display a field:

`render_field_for_display( $field_key, $fields_options, $post, $show_extra_controls = false, $show_hidden = false )`


# Custom Post Types

Contacts and Groups are post types. With custom post types you can add a post type gaining:

* a menu tab next to metrics
* a menu option to create records
* a list page
* a details page.
* a set of endpoints ready to use.
* some cool to show your friends.

To create a custom post type see our "starters" plugin: <https://github.com/DiscipleTools/disciple-tools-plugin-starter-template>


# Post Type Modules

Modules extend the functionality of a post type like Contacts or Groups. A modules can be used to add:

* Fields
* Workflows
* List filters
* Roles
* Other functionality

A module resembles what can be done through a plugin. The big difference is the instance admin can enable/disable the modules they want and the theme/plugins can package multiple modules.

With v1.0 the D.T theme has 2 main modules available by default: the DMM module and the Access modules.\
The DMM adds fields, filters and workflows that go with: coaching, faith milestones, baptism date, baptisms etc. These are fields needed for any DMM.\
The Access module focuses more on contact followup and come with fields like the seeker path, the assigned\_to and subassigned. It also adds our Follow-up filter tab on the lists page.

## Getting The Module List

```php
$modules = dt_get_option( "dt_post_type_modules" );
// check if the access module is enabled
if ( empty( $modules["access_module"]["enabled"] ) ){
    return;
}
```

## How To Add A Modules

### Declaring The Module

Hook into the `dt_post_type_modules` filter.

```php
add_filter( 'dt_post_type_modules', function( $modules ){
    $modules["module_key"] = [
        "name" => "Module Name",
        "enabled" => true, // default if the module is enabled. The admin's preference in the settings will take precedence.
        "prerequisites" => [ "dmm_module", "contacts_base" ], //don't load this module unless these other modules are also loaded
        "post_type" => "contacts", //the post type the module extends
        "description" => "Field and workflows for follow-up ministries" //displayed on the wp-admin settings page.
    ];
    return $modules;
}, 20, 1 );
```

### The Module Class

See dt-contacts/access-module.php as an example.

Keep in mind:

* extend `DT_Module_Base`.
* declare the $post\_type and $module public variables.
* call `self::check_enabled_and_prerequisites()` to only load if:
  * the module is enabled in the settings.
  * the prerequisites are also enabled.

```php
class DT_Contacts_Access extends DT_Module_Base {
    public $post_type = "contacts";
    public $module = "access_module";

    public function __construct(){
        parent::__construct();
        if ( !self::check_enabled_and_prerequisites() ){
            return;
        }
        //your filters
    }

    //your functions
}
```


# Adding Fields and Tiles.

## Adding a field to track

You need tell Disciple.tools that you want to track something new. Fields are defined in the theme’s custom post type file. And we give you a way to add to that list using the dt\_custom\_fields\_settings filter.

Here is an example on how to add a spoken language field on the contact:

```php
add_filter( "dt_custom_fields_settings", "dt_contact_fields", 1, 2 );
function dt_contact_fields( array $fields, string $post_type = ""){
    //check if we are dealing with a contact
    if ($post_type === "contacts"){
        //check if the language field is already set
        if ( !isset( $fields["language"] )){
            //define the language field
            $fields["language"] = [
                "name" => __( "Spoken Language", "disciple_tools_language" ),
                "type" => "key_select",
                "default" => [
                    "english" => __( "English", "disciple_tools_language" ),
                    "french" => __( "French", "disciple_tools_language" )
                ],
                "tile" => "contact_language"
            ];
        }
    }
    //don't forget to return the update fields array
    return $fields;
}
```

See [Field Options](/theme-core/customization/fields) for documentation on field types.

## Adding a Tile

```php
add_filter( 'dt_details_additional_tiles', 'dt_details_additional_tiles', 10, 2 );
public function dt_details_additional_tiles( $tiles, $post_type = "" ){
    if ( $post_type === "contacts" ){
        $tiles["contact_language"] = [ "label" => __( "Language", 'disciple_tools' ) ];
    }
    return $tiles;
}
```

Since the language field is already declared in the fields list with the "contact\_language" tile, D.T will take care of displaying the field.

## Add custom content

If you desire to display a field or element your own way you can also do so: Change the filter priority to determine the order of this code

```php
add_action( "dt_details_additional_section", "dt_add_section", 30, 2 );
function dt_add_section( $section, $post_type ) {
    if ( $section === "contact_language" && $post_type === "contacts" ) {
        ?>
        <!-- need you own css? -->
        <style type="text/css">
            .required-style-example {
                color: red
            }
        </style>

        <p class="required-style-example"> Wanna know something cool? D.T is translated into multiple languages. <a href="https://disciple.tools/translation/">Check it out!</a></p>

        <script type="application/javascript">
            //enter jquery here if you need it
            jQuery(($) => {
            })
        </script>
        <?php
    }
}
```

## End result

![End result](/files/vIk35SsJpWCiAK6ioO0f)


# Authentication


# JWT-Authentication-for-the-mobile-app

The mobile app plugin includes JWT authentication. Some hosting setups require additional configuration.

For full documentation <https://github.com/Tmeister/wp-api-jwt-auth/blob/develop/README.md>

Here is the basic usage:

## Namespace and Endpoints

When the plugin is activated, a new namespace is added.

```
/jwt-auth/v1
```

Also, two new endpoints are added to this namespace.

| Endpoint                              | HTTP Verb |
| ------------------------------------- | --------- |
| */wp-json/jwt-auth/v1/token*          | POST      |
| */wp-json/jwt-auth/v1/token/validate* | POST      |

## Usage

### /wp-json/jwt-auth/v1/token

This is the entry point for the JWT Authentication.

Validates the user credentials, *username* and *password*, and returns a token to use in a future request to the API if the authentication is correct or error if the authentication fails.

#### Sample request using AngularJS

```javascript
( function() {
  var app = angular.module( 'jwtAuth', [] );

  app.controller( 'MainController', function( $scope, $http ) {

    var apiHost = 'http://yourdomain.com/wp-json';

    $http.post( apiHost + '/jwt-auth/v1/token', {
        username: 'admin',
        password: 'password'
      } )

      .then( function( response ) {
        console.log( response.data )
      } )

      .catch( function( error ) {
        console.error( 'Error', error.data[0] );
      } );

  } );

} )();
```

Success response from the server:

```javascript
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9qd3QuZGV2IiwiaWF0IjoxNDM4NTcxMDUwLCJuYmYiOjE0Mzg1NzEwNTAsImV4cCI6MTQzOTE3NTg1MCwiZGF0YSI6eyJ1c2VyIjp7ImlkIjoiMSJ9fX0.YNe6AyWW4B7ZwfFE5wJ0O6qQ8QFcYizimDmBy6hCH_8",
    "user_display_name": "admin",
    "user_email": "admin@localhost.dev",
    "user_nicename": "admin"
}
```

Error response from the server:

```javascript
{
    "code": "jwt_auth_failed",
    "data": {
        "status": 403
    },
    "message": "Invalid Credentials."
}
```

Once you get the token, you must store it somewhere in your application, e.g. in a **cookie** or using **localstorage**.

From this point, you should pass this token to every API call.

Sample call using the Authorization header using AngularJS:

```javascript
app.config( function( $httpProvider ) {
  $httpProvider.interceptors.push( [ '$q', '$location', '$cookies', function( $q, $location, $cookies ) {
    return {
      'request': function( config ) {
        config.headers = config.headers || {};
        //Assume that you store the token in a cookie.
        var globals = $cookies.getObject( 'globals' ) || {};
        //If the cookie has the CurrentUser and the token
        //add the Authorization header in each request
        if ( globals.currentUser && globals.currentUser.token ) {
          config.headers.Authorization = 'Bearer ' + globals.currentUser.token;
        }
        return config;
      }
    };
  } ] );
} );
```

The **wp-api-jwt-auth** will intercept every call to the server and will look for the authorization header, if the authorization header is present, it will try to decode the token and will set the user according with the data stored in it.

If the token is valid, the API call flow will continue as always.

**Sample Headers**

```
POST /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_s9.B5f-4.1JqM
```

### Errors

If the token is invalid an error will be returned. Here are some samples of errors:

**Invalid Credentials**

```javascript
[
  {
    "code": "jwt_auth_failed",
    "message": "Invalid Credentials.",
    "data": {
      "status": 403
    }
  }
]
```

**Invalid Signature**

```javascript
[
  {
    "code": "jwt_auth_invalid_token",
    "message": "Signature verification failed",
    "data": {
      "status": 403
    }
  }
]
```

**Expired Token**

```javascript
[
  {
    "code": "jwt_auth_invalid_token",
    "message": "Expired token",
    "data": {
      "status": 403
    }
  }
]
```

## Usage

Usage:

```php
$token = "token retrieved above"
$args = [
  'method' => 'GET',
  'headers' => [
      'Authorization' => 'Bearer ' . $token,
  ],
];
return wp_remote_get( 'https://example.disciple.tools/wp-json/dt-posts/v2/contacts', $args );
```

### /wp-json/jwt-auth/v1/token/validate

This is a simple helper endpoint to validate a token; you only will need to make a POST request sending the Authorization header.

Valid Token Response:

```javascript
{
  "code": "jwt_auth_valid_token",
  "data": {
    "status": 200
  }
}
```


# Site-to-Site-Link

To authenticate with D.T from another server.

### Creating the token

Go to the wp-admin. Open **Site Links** in the menu on the left. Click the **Add New** button.

In **Site 1** add the D.T domain. Example: example.disciple.tools

In **Site 2** add the remote server's. Example: example.com

note: both domains need to be HTTPS. You can disable this for local testing by enabling `WP_DEBUG`

**Connection type**: You will create a connection type and associate permissions to it. See below.

Since you are not connecting to another D.T instance, under **DT Site** choose 'No, connection for a non-Disciple Tools system.'.

See more documentation on creating a site link [here](https://disciple.tools/user-docs/getting-started-info/admin/site-links/)

### Remote authentication

We'll use the token and the 2 domains we just defined to authenticate with D.T form another server.

Here are example for creating a contact. Note we are not using the endpoints that include "dt\_public" in the url. Those most likely will not work.

PHP Example

```php
function create_contact( $fields ) {
  $token = "token from the Site to Site link";
  $site_key = md5($token . "example.disciple.tools" . "example.com");
  $transfer_token = md5($site_key . date('Y-m-dH'));

  $url = 'https://example.disciple.tools/wp-json/dt-posts/v2/contacts';
  $req = curl_init();
  curl_setopt_array($req, array(
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => json_encode($fields),
    CURLOPT_HTTPHEADER => array(
      "Content-Type: application/json",
      "cache-control: no-cache",
      "Authorization: Bearer " . $transfer_token
    ),
  ));

  $response = curl_exec($req);
  return json_decode($response);
}
```

Wordpress Example

```php
function create_contact( $fields ){
  $token = "token from the Site to Site link"
  $site_key = md5( $token . "example.disciple.tools" . "example.com" );
  $transfer_token = md5( $site_key . current_time( 'Y-m-dH', 1 ) );
  $args = [
    'method' => 'POST',
    'body' => $fields,
    'headers' => [
        'Authorization' => 'Bearer ' . $transfer_token,
    ],
  ];
  return wp_remote_post( 'https://example.disciple.tools/wp-json/dt-posts/v2/contacts', $args );
}
```

Node example

```javascript
import moment from 'moment'
import request from "request-promise"
let CryptoJS = require('crypto-js')

create_contact = (fields)=>{
  let token = "token from the Site to Site link"
  let key =  CryptoJS.MD5(token + "example.disciple.tools" + "example.com")
  let date = moment().utc().format("Y-MM-DDHH")
  let transfer_token = CryptoJS.MD5(key + date).toString()

  let options = {
    method: "POST",
    uri: 'https://example.disciple.tools/wp-json/dt/dt-posts/v2/contacts',
    body: fields,
    headers: {
      Authorization: 'Bearer ' + transfer_token
    },
    json: true
  }

  return request(options)
}
```

## Connection Types

Here is how you add a connection type and the right permissions from you D.T plugin.

For a list of permissions see [Permissions](/theme-core/capabilities). In the `site_link_capabilities` function you can add your own permission that you use later on in your plugin.

```php
add_filter( 'site_link_type', 'site_link_type', 10, 1 );
add_filter( 'site_link_type_capabilities', 'site_link_capabilities', 10, 1 );

function site_link_type( $types ){
    if ( !isset( $types["connection_to_system_x"] ) ){
        $types["connection_to_system_x"] = "Connection to System X";
    }
    return $types;
}
function site_link_capabilities( $args ){
    if ( $args['connection_type'] === "connection_to_system_x" ){
         $args['capabilities'][] = 'view_x_metrics';
    }
    return $args;
}
```


# Easy-Example

## Create a "Token" Site Link.

![image](https://github.com/DiscipleTools/Documentation/assets/24901539/0c92e588-974c-46e1-8f8e-97c5dccb9759)

Notes:

* Site 1: Your D.T instance
* Site 2: Where you expect the webform to be used. It can be any url is this is not verified).
* Connection Type: At leate "Create Contact" to give the permision needed.
* Use Token As API KEY: This is required to keep the Authentication simple.

## Create a Contact

Create a Post Request to your instance

`POST`: `https://example.disciple.tools/wp-json/dt-posts/v2/contacts`

Use the site key in the Authorization header `Authorization: Bearer 14c5f0f1ebbc3c4b1042e884c1cf4e04410e274198928197c9ae726cbbe15b19`

Usage:

```php
$token = "token from the Site to Site link"
$args = [
  'method' => 'POST',
  'body' => $fields,
  'headers' => [
      'Authorization' => 'Bearer ' . $token,
  ],
];
return wp_remote_post( 'https://example.disciple.tools/wp-json/dt-posts/v2/contacts', $args );
```

$fields need to be in the format specified in [Fields Format](https://github.com/DiscipleTools/Documentation/blob/master/theme-core/authentication/theme-core/api-posts/post-types-fields-format.md)


# Permissions

This is a list of the capabilities used by D.T roles within the theme core. Each role is assigned certain capabilities which determines what the user is able to do.

## WP capabilities

**edit\_posts**\
give the user access and update ability for site links

**edit\_page**\
give the user access and update ability for site links

**read**\
let the user access the wp-admin interface

## D.T Admin capabilities

**manage\_dt**\
let the user update all D.T settings

## User management

**promote\_users**\
Ability to add/update/remove a user roles. Note only and administrator can give or take away the administrator role.

**edit\_users**\
Edit user data in the wp-admin interface

**create\_users**\
Create new users or invite users to the instance

**delete\_users**\
Ability to delete users

**list\_users**\
WP Admin list users

**dt\_list\_users**\
D.T Front End list users

## Metrics

**view\_project\_metrics**\
view more metrics for the whole project

## Locations

**read\_location**\
list locations

## People Groups

**access\_peoplegroups**\
Basic permission list and update all people groups.

**list\_peoplegroups**\
List all people groups in typeaheads.

## Post Types

Replace `post_type` with `contacts`, `groups` or your custom post type.

**access\_\[post\_type]**\
Give the user access to view their or all records of the post\_type

**create\_\[post\_type]**\
Gives the user the ability to create a record of the post\_type

**list\_all\_\[post\_type]**\
List all post\_type record names in typeaheads

**assign\_any\_\[post\_type]**\
Ability to assign any post\_type record to a user.

**access\_specific\_sources**\
Ability to list and update all contacts of source x

**dt\_all\_access\_contacts** >= v1.0.0\
List and update all contact of type 'access'

**view\_any\_\[post\_type]**\
gives full permission to view and update all records of the post type. Recommended only for API/script use.


# Roles and Permissions

Documentation for theme version 1.0.0 or greater

## Permissions

Permissions guide what a user can and cannot see and what records the user has access to. What Roles a user has determines what a user is permitted to do.

### Records Access

By default when a user creates contact, group or any record, that record is shared with the user.\
If a record is shared with a user, that user has permission to view and update that record and can share the record with other users.\
The list page looks up all the records that user has access to and displays them. By default this means the list page will ask for all the records shared with the user.

### Expanding access

Some roles like the Administrator and the Dispatcher can see more than the records they created. This is because their roles has additional capabilities which expands their access to records.\
An example is the Dispatcher role has has permission to see and update all access contacts. The dispatcher role has the `dt_all_access_contacts` capability. The access module looks for users which that capability and gives them access to all contacts of type 'access'.

## Roles

Since version 1.0.0 Roles are modular. The theme sets declares a set of roles and their capabilities. Plugin can add roles and modify capabilities on all roles.

Let's build out the Dispatcher role.

First we hook into the `dt_set_roles_and_permissions` filter to declare role and set the capabilities.

```php
add_filter( 'dt_set_roles_and_permissions', [ $this, 'dt_set_roles_and_permissions' ], 10, 1 );
public function dt_set_roles_and_permissions( $expected_roles ){

    $expected_roles['dispatcher'] = [
        "label" => __( "Dispatcher", "disciple_tools" ), // the displayed name of the role
        "descriptions" => "Monitor new D.T contacts and assign the to waiting Multipliers", //description shown to help the admin choose what role to assign a user.
        "permissions" => [
            'dt_all_access_contacts' => true, //gives permission to all contacts with the 'access' type.
            'view_project_metrics' => true, //view all poject chart is the metrics tab
            'list_users' => true, //list users in the wp-admin
            'dt_list_users' => true, //list user in the theme (needed for assigning to multipliers)
        ]
    ];
    return $expected_roles;
}
```

If you want to add capabilities to an existing role from your plugin make sure the filter priority is higher that the number user when declaring the role in the first place. Let's add a capability to the dispatcher role. They priority used by the filter when declaring it was 10. So we'll used 20.

```php
add_filter( 'dt_set_roles_and_permissions', [ $this, 'dt_set_roles_and_permissions' ], 20, 1 );
public function dt_set_roles_and_permissions( $expected_roles ){
    //check if the role is declared. You don't need to reset the label and description.
    if ( isset( $expected_roles["dispatcher"]["permissions"] ) ){
        $expected_roles["dispatcher"]["permissions"]["my_custom_capability"] = true;
    }
    return $expected_roles;
}
```

## Linking capabilities to permissions

### In listing records

**dt\_filter\_access\_permissions** filter\
This filter is called when querying a list of records. Here you can expand or restrict the records the user has access to based on their role and capabilities.

Example, giving the dispatcher permission to all contacts of type 'access':

```php
public static function dt_filter_access_permissions( $permissions, $post_type ){
    if ( $post_type === "contacts" ){
        //give user permission to all contacts af type 'access'
        if ( current_user_can( "dt_all_access_contacts" ) ){
            $permissions[] = [ "type" => [ "access" ] ];
        }
    }
    return $permissions;
}
```

### In viewing records

**dt\_can\_view\_permission** filter

Example, giving the Dispatcher permission to view any contact of type 'access':

```php
add_filter( "dt_can_view_permission", [ $this, 'can_view_permission_filter' ], 10, 3 );
public function can_view_permission_filter( $has_permission, $post_id, $post_type ){
    if ( $post_type === "contacts" ){
        if ( current_user_can( 'dt_all_access_contacts' ) ){
            $contact_type = get_post_meta( $post_id, "type", true );
            if ( $contact_type === "access" ){
                return true;
            }
        }
    }
    return $has_permission;
}
```

### In updating records

**dt\_can\_update\_permission** filter

Example, giving the Dispatcher permission to update any contact of type 'access':

```bash
add_filter( "dt_can_update_permission", [ $this, 'can_update_permission_filter' ], 10, 3 );
public function can_update_permission_filter( $has_permission, $post_id, $post_type ){
    if ( $post_type === "contacts" ){
        if ( current_user_can( 'dt_all_access_contacts' ) ){
            $contact_type = get_post_meta( $post_id, "type", true );
            if ( $contact_type === "access" ){
                return true;
            }
        }
    }
    return $has_permission;
}
```

### In deleting records

**dt\_can\_delete\_permission** filter

Would be similar to the code above.


# Database Tables

### Here is a sumarry of the tabels D.T uses

#### wp\_posts

* D.T records like contacts and groups
* The post row contains limited data like: post ID, name, author, creation date

#### wp\_postmeta

* Stores field data abot the recod
* Example: status, milestones

#### wp\_dt\_post\_user\_meta

* D.T custom table
* Stores record data that is only visible to one user (private fields)
* Example: my favorite contacts

#### wp\_comments

* Stores record comments

#### wp\_commentmeta

* Stores comment meta like
* Example: reactions

#### wp\_dt\_share

* D.T custom table
* Stores who a record is shared with (which users have access to a record)

#### wp\_p2p

* Stores connections to other records
* Example: contact records that form a group's members

#### wp\_p2pmeta

* Stores p2p meta. Currently unused

#### [wp\_dt\_activity\_log](/theme-core/tables/wp-dt-activity-log)

* D.T custom table, stores activity on records
* Example: Milestone "praying" selected on July 27 2020 at 7:23pm

#### wp\_dt\_location\_grid

* D.T custom table.
* Stores the base location grid project. Used in the location field
* Example: Paris, France, grid\_id: 100089652, level: admin2

#### wp\_dt\_location\_grid\_meta

* D.T custom table
* Stores extra geocoding data when using a geocoder from mapbox or google

#### wp\_dt\_notifications

* D.T custom table
* Stores web notifications for user to see when they log in
* Example: You were assigned on contact John Doe

#### wp\_dt\_reports

* D.T custom table
* Stores lightweight events
* Possible example: meeting times

#### wp\_dt\_reportmeta

* D.T custom table
* Store extra information on a report

#### wp\_dt\_movement\_log

* D.T custom table
* Stores geocoded event that can more easily be queried

#### wp\_options

* WP table
* Stores settings and configurations used by D.T

#### wp\_users

* WP table
* Each user that user the D.T system has a user record

#### wp\_usermeta

* WP table
* Stores user information
* Example: prefered language

### Tables that D.T does not use:

* wp\_links
* wp\_term\_relationships
* wp\_term\_taxonomy
* wp\_termmeta
* wp\_terms


# Activity Table

### \[ wp\_dt\_activity\_log ]

D.T custom table; which captures a wide array of system events, such as field updates.

### Field Update Events

The following field types are currently captured:

#### text:

```php
(
    [action] => field_update
    [object_type] => contacts
    [object_subtype] => nickname
    [object_id] => 536
    [object_name] => MAKE Contact
    [meta_id] => 8604
    [meta_key] => nickname
    [meta_value] => MAKE Stuff Pls
    [meta_parent] => unknown
    [object_note] => Nickname changed from "MAKE Stuff" to "MAKE Stuff Pls"
    [old_value] => MAKE Stuff
    [field_type] => text
)
```

#### date:

```php
(
    [action] => field_update
    [object_type] => contacts
    [object_subtype] => baptism_date
    [object_id] => 536
    [object_name] => MAKE Contact
    [meta_id] => 8667
    [meta_key] => baptism_date
    [meta_value] => 1673395200
    [meta_parent] => unknown
    [object_note] => Added Baptism Date: 1673395200
    [old_value] => 
    [field_type] => date
)
```

#### key\_select:

```php
 (
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => gender
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8668
     [meta_key] => gender
     [meta_value] => male
     [meta_parent] => unknown
     [object_note] => Added Gender: Male
     [old_value] => 
     [field_type] => key_select
 )
```

#### multi\_select:

```php
 (
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => milestones
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8669
     [meta_key] => milestones
     [meta_value] => milestone_has_bible
     [meta_parent] => unknown
     [object_note] => Added Faith Milestones: Has Bible
     [old_value] => 
     [field_type] => multi_select
 )
```

#### connection:

```php
(
     [action] => connected to
     [object_type] => contacts
     [object_subtype] => $from_field_key
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 572
     [meta_key] => contacts_to_groups
     [meta_value] => 317
     [meta_parent] => 
     [object_note] => connection from
     [field_type] => connection
 )
 (
     [action] => connected to
     [object_type] => groups
     [object_subtype] => $to_field_key
     [object_id] => 317
     [object_name] => Metric Group
     [meta_id] => 572
     [meta_key] => contacts_to_groups
     [meta_value] => 536
     [meta_parent] => 
     [object_note] => connection to
     [field_type] => connection
 )
```

#### number:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => number_test
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8671
     [meta_key] => number_test
     [meta_value] => 5
     [meta_parent] => unknown
     [object_note] => Added Number field: 5
     [old_value] => 
     [field_type] => number
 )
```

#### communication\_channel:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => contact_other_c8a
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8682
     [meta_key] => contact_other_c8a
     [meta_value] => @other3
     [meta_parent] => unknown
     [object_note] => Added contact_other_c8a: @other3
     [old_value] => 
     [field_type] => communication_channel
 )
 (
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => contact_other_c8a_details
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8683
     [meta_key] => contact_other_c8a_details
     [meta_value] => a:1:{s:8:"verified";b:0;}
     [meta_parent] => unknown
     [object_note] => contact_other_c8a "@other3" not verified
     [old_value] => 
     [field_type] => details
 )
```

#### tags:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => tags_test
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8674
     [meta_key] => tags_test
     [meta_value] => random
     [meta_parent] => unknown
     [object_note] => Added Random Tags: random
     [old_value] => 
     [field_type] => tags
 )
```

#### user\_select:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => user_select_test
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8675
     [meta_key] => user_select_test
     [meta_value] => user-12
     [meta_parent] => unknown
     [object_note] => Added User Select: user-12
     [old_value] => 
     [field_type] => user_select
 )
```

#### location:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => location_grid
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8676
     [meta_key] => location_grid
     [meta_value] => 100134548
     [meta_parent] => unknown
     [object_note] => Added Locations: 100134548
     [old_value] => 
     [field_type] => location
 )
```

#### location\_meta:

```php
(
     [action] => field_update
     [object_type] => contacts
     [object_subtype] => location_grid_meta
     [object_id] => 536
     [object_name] => MAKE Contact
     [meta_id] => 8677
     [meta_key] => location_grid_meta
     [meta_value] => 123
     [meta_parent] => unknown
     [object_note] => Hungary
     [old_value] => 
     [field_type] => location_meta
 )
```


# Disciple.Tools Translation

## Overview

Disciple.Tools is built on WordPress and uses the WordPress translation strategy. Extensive resources can be found on WordPress.org giving explanations and help for translators. [WordPress Translation Resources](https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/)

We invite you to [contribute a new translation](https://poeditor.com/join/project/KcPvw3oaKD) to Disciple.Tools, and it does not require writing code! You can submit completed translations through Github or through email, and our commit team will review it and add it to the project.

## Current Available Translations

Disciple.Tools is available in 30+ languages. See [Translation](https://disciple.tools/translation/) for more details.

As Disciple.Tools develops, additional translation commits will be needed.

## How to contribute

We are using an online tool called [POEditor](https://poeditor.com/). No downloading, changing, or uploading of files necessary. No coding skills needed either.

To get started visit the main [Disciple.Tools WordPress theme translation project](https://poeditor.com/join/project/KcPvw3oaKD) and the much smaller [D.T app translation project](https://poeditor.com/join/project/dQzfAs5uNc). Or contribute to the translation of some D.T plugins [here](https://poeditor.com/join/project?hash=ts4yDqkDSW)

Either select an existing language from the displayed list or click **“Click here to suggest a new language”** link to add the language you want Disciple.Tools to be translated in to. Enter your email and name and then click “Join this project”

We will receive your request and approve your account as soon as possible. Once approved, you will be free to start translating.

Your translations will become available to everyone when we push a release for the theme

## POEditor: How to do a translation

Log in to <https://poeditor.com/login/> and find the translation you have access to. You will arrive at a page like the following:

![POEditor projects](/files/gJ4inIXnpZI4l7J2n24n)

Click on the **flag** the represents the language you want to translate. You will arrive at a screen that looks like the following:

![POEditor translations](/files/wVrNaQY1SLLTE2YIclXW)

In the “big empty box”, type in the translation of “the string” (a word or phrase) that is displayed on the left side.

**For example**: The first string is `Location Grid Meta`. Type your translation of that phrase into the “big empty box” to the right. Once you are happy with what you have typed, click out side of the box, and the translation will be saved automatically. If you need to change it, simply click on the string, and the box will become editable again.

On the far right side of each row, there are a few icons that can be used to sort the list.

The `A` icon means “automatic translation” when highlighted will be set to orange in colour and means that the string was automatically translated. When you make a change to the string, the `A` will be unset. When you review a string that was marked as translated automatically, please untick the orange `A` icon to indicate that the string is correct.

The `Comments` icon (represented by the speech bubbles icon) indicates if a comment has been written by any translators relating to this string. If you have a question or comment to make about the string, click the `Comments` icon and write your comment (or question) in the popup window. Your comment will be sent to the main editor for the language project who will reply ASAP, if needed.

The `F` icon means “Fuzzy” which you can toggle to indicate that you aren’t sure if this string has been translated correctly. You can later review or have others easily find the strings that need revision.

## What are those wonky characters?

In POEditor, you will see some strings that look like this:

`Sorry, you don't have permission to view the %1$s with id %2$s.`

What do I do with the `%1$s` and `%2$s` and what do they mean?

These are placeholders that will be replaced with a something else.

Here this sentence in English could be :

Sorry, you don’t have permission to view the contact with id 4344. Sorry, you don’t have permission to view the group with id 493. In this case, `%1$s` corresponds to “contact” or “group”. `%2$s` corresponds to the id of the record

This message can be displayed for a contact or a group. And we don’t know before hand the ID of the record. This lets you, the translator, make a sentence that is gramatically correct while still using placeholders.

To translate the sentence, just copy and paster the characters ( `%s`, `%1$s`, `%2$s` ) to into your translation.

In french this sentence would give:

`Désolé, vous n'avez pas l'autorisation d'afficher le %1$s avec l'id %2$s.`


# Revisions


# v1.0.0-dev-changes

## Code Changes Overview

* Contact and group v1 endpoints removed and API v1 removed (Disciple\_Tools\_Contacts and Disciple\_Tools\_Groups).
* Contact and Group Record and list files removed and replace with the custom post type structure.
  * All tiles are modular including details tile.
  * Page for creating posts is more flexible.
* Channels merged into fields list.
* List query upgraded.
* List page upgraded.
  * Dynamic list columns.
  * Dynamic list columns order.
* Roles and capabilities are now built by the post-types.
* Support for deleting a post.
* Contacts are divided into `types`.
* Modules to add to a post-type.
  * Some field are only available if the module is active. Ex: the seeker\_path field is only available when the access module is enabled.

## Structure changes

Files removed:

* dt-contacts/contacts.php
* dt-contacts/contact-endpoints.php
* dt-contacts/contacts-template.php
* archive-contacts.php
* template-contacts-new\.php
* single-contacts.php
* dt-assets/parts/contact-details.php
* dt-assets/parts/content-contacts.php
* dt-groups/groups.php
* dt-groups/group-endpoints.php
* dt-groups/groups-template.php
* archive-groups.php
* template-groups-new\.php
* single-groups.php
* dt-assets/parts/group-details.php
* dt-assets/parts/content-groups.php

## API changes

### v1 Function removed

**Search for these**

* Disciple\_Tools\_Contact\_Post\_Type::
* Disciple\_Tools\_Contacts::
* Disciple\_Tools\_Groups\_Post\_Type::
* Disciple\_Tools\_Groups::

And replace as follows:

| Find                                                                          | Replace                                            | Note                                                                            |
| ----------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------- |
| `Disciple_Tools_Contacts::get_contact(`                                       | `DT_Posts::get_post( "contacts",`                  | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Contacts::create_contact(`                                    | `DT_Posts::create_post( "contacts",`               | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Contacts::update_contact(`                                    | `DT_Posts::update_post( "contacts",`               | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Contacts::add_comment(`                                       | `DT_Posts::add_post_comment( "contacts",`          |                                                                                 |
| `Disciple_Tools_Contacts::search_viewable_contacts(`                          | `DT_Posts::search_viewable_post( "contacts",`      |                                                                                 |
| `Disciple_Tools_Contacts::get_contact_fields();`                              | `DT_Posts::get_post_field_settings( "contacts" );` |                                                                                 |
| `Disciple_Tools_Contact_Post_Type::instance()->get_custom_fields_settings();` | `DT_Posts::get_post_field_settings( "contacts" );` |                                                                                 |
| `Disciple_Tools_Groups::get_group(`                                           | `DT_Posts::get_post( "groups",`                    | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Groups::create_group(`                                        | `DT_Posts::create_post( "groups",`                 | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Groups::update_group(`                                        | `DT_Posts::update_post( "groups",`                 | The order of parameters has also changed. $check\_permissions is before $silent |
| `Disciple_Tools_Groups_Post_Type::instance()->get_custom_fields_settings();`  | `DT_Posts::get_post_field_settings( "groups" );`   |                                                                                 |

### Hooks Removed:

* `dt_contact_update`
* `dt_contact_created`
* `dt_group_created`
* `dt_group_updated`
* `dt_pre_contacts_connections_section`
* `dt_post_contacts_progress_section`
* `dt_pre_contacts_progress_section`
* `dt_pre_contacts_other_section`
* `dt_post_contacts_other_section`
* `dt_contact_detail_notification` -> `dt_record_top_above_details`

[API Hooks documentation](/theme-core/hooks/api-hooks) [Record Page Hooks documentation](/theme-core/hooks/record-page-hooks)

### Consistently using post\_date in the API

The post date was represented in 4 different ways:

* create\_date (in creating a post)
* created\_date (in getting a post)
* created\_on (in list query)
* post\_date (in getting a post in the list)

We've changed them all to be `post_date`

* create\_date => post\_date
* created\_date => post\_date
* created\_on -> post\_date

### Consistently using name for record title/name

* List response included: **post\_title**. Now includes **name** and **post\_title**
* Create post accepted: **title** and now accepts **title** and **name**
* Get post returned: **title** and now returns **name** and **title**
* Search posts was N/A -> You can now search for **name**

### ID returned from `create_contact`

`create_contact` used to return the contact\_id.\
`create_post` returns the full post.\
If the user has permissions to create posts but not read posts the function will return only an array with the ID of the new post:

```php
[ "ID" => 123 ]
```

### Field settings changes

* Contacts
  * `name`: new field. Type: `text`
    * See [Consistently using name for record title/name](#consistently-using-name-for-record-titlename)
  * `nickname`: new field. Type: `text`
  * `faith_status`: new field (DMM module). Type: `multi_select`
  * `post_date`: repurposed. Type: `date`
    * See [Consistently using post\_date in the API](#consistently-using-post_date-in-the-api)
  * `type`: repurposed. Type: `multi_select`
    * Contact `type` field value `media` is now `access` and a migration converts them all.
  * `languages`: new field. Type: `multi_select`
  * `contact_phone`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)
  * `contact_email`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)
  * `contact_address`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)
  * `contact_facebook`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)
  * `contact_twitter`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)
  * `baptism_generation`: Changed from `text` to `number`. Type: `number`
  * `last_modified`: Changed from `number` to `date`. Type: `date`
* Groups
  * `name`: new field. Type: `text`
  * `post_date`: repurposed. Type: `date`
    * See [Consistently using post\_date in the API](#consistently-using-post_date-in-the-api)
  * `member_count`: Changed from `text` to `number`. Type: `number`
  * `address`: Channel migration. Type: `communication_channel`
    * See [Channels](#channels)

### List query

* `assigned_to => 'all'` is now not a query
* `assigned_to => 'shared'` is now "shared\_with" => \[ "me"]
* The `combine` parameter no longer works.
* Maximum query limit is 1000.

The list query is now more flexible with:

* More fields types supported: communication\_channel, text, number
* Negative queries to exclude data.
* Exact match for text and communication\_channel fields.
* [Better AND/OR capabilities](https://github.com/DiscipleTools/Documentation/tree/d05945fa8e00435ee251dd92ababf88b593c14cc/theme-core/api/posts/list-query.md#combining-with-andor-logic).
* [Get recently viewed posts](https://github.com/DiscipleTools/Documentation/tree/d05945fa8e00435ee251dd92ababf88b593c14cc/theme-core/api/posts/list-query.md#recently-viewed-posts)

[See List Query Documentation](https://github.com/DiscipleTools/Documentation/tree/d05945fa8e00435ee251dd92ababf88b593c14cc/theme-core/api/posts/list-query.md).

### Transfer Contact API

The Endpoint used by the front end to send a contact to another instance has changed:\
POST `dt/v1/contact/transfer` -> POST `dt-posts/v2/contacts/transfer`

The endpoint to receive a transfer has also changed:\
POST `dt-public/v1/contact/transfer` -> POST `dt-posts/v2/contact/receive-transfer`

### Channels

Upgrade to the function to get the channels list:\
`Disciple_Tools_Contact_Post_Type::instance()->get_channels_list();` -> `DT_Posts::get_post_settings( "contacts" )["channels"];`

Channels continue to be available in the "channels" key in the post type settings array.

Channel are now also part of the field list and follow the new format in the field list.\
So Instead of:

```php
"phone" => [
    'label' => "Phone
]
```

the field will look like

```php
"contact_phone => [
   'name' => "Phone"
]
```

* `dt_custom_channels` filter was removed
* `dt_custom_channels` option was removed

### Javascript changes

`contactsDetailsWpApiSettings` and `wpApiGroupsSettings` are no longer available. Look for `window.detailsSettings` or `window.wpApiShare` instead

Examples: `window.contactsDetailsWpApiSettings.contacts_custom_fields_settings` -> `window.detailsSettings.post_settings.fields` `window.contactsDetailsWpApiSettings.contact` -> `window.detailsSettings.post_fields`

## DT\_Posts Functions

### Removed:

* removed function `find_contacts_by_title()`
* removed function `get_viewable_post()`

### New Functions:

//get the list of tiles for a post\_type\
`DT_Posts::get_post_tiles( $post_type );`

//get the list of fields for a post\_type\
`DT_Posts::get_post_field_settings( $post_type );`

//Get all the post\_type settings\
`DT_Posts::get_post_settings( $post_type )`

//get modules\
`dt_get_option( 'dt_post_type_modules' );`\
[See modules documentation](/theme-core/customization/modules)

//Duplicate checker

#### Notes

Please submit a PR if you find something missing from this page.


# v1.0

This page has moved to: <https://disciple.tools/news/disciple-tools-theme-version-1-0-changes-and-new-features/>


# Hosting


# 404 Errors on new install

## Permalinks

If you installed Disciple.Tools and are getting 404 errors on `/contacts` or other pages try this: Log in to wp-admin and go to Settings > Permalinks. You don’t need to change anything, just click **save** at the bottom. Click **save** again because WordPress.

## Windows Hosting

**Special Note for hosting on Windows servers**: If you receive a 404 error message when trying to preview or run DT after installing WordPress and the DT theme, check the root WordPress folder (e.g., `C:\inetpub\wwwroot\wordpress`) , and verify the `web.config` file exists. If not, create a text file named `web.config` and include the following code block in the file:

```markup
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="WordPress" stopProcessing="true">
          <match url="^(.*)$" />
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="index.php" appendQueryString="true" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>
```

For more information, please refer to <https://www.smarterasp.net/support/kb/a1433/how-to-enable-wordpress-iis-rewrite-wordpress-iis-rewrite-example.aspx>.


# CRON

Disciple.Tools relies on cron jobs for certain activities. Some of these are sending scheduled emails and creating update needed notifications.

WordPress’s default scheduling strategy depends on traffic. So then if no one comes to the site, the task may not run for a while. It will wait until the next visitor opens the site. A normal Disciple.Tools instance will not generate much traffic. This also slows down the server for this visitor as all these background tasks are now running.

For WPEngine.com: see this section in their documentatian <https://wpengine.com/support/wp-cron-wordpress-scheduling/#WP_Engine_Alternate_Cron> They will do everything for you.

For other hosting services: we recommend googling the name of you hosting service along with "Replace WordPress Cron"

## Doing it manually:

The solution is to disable the WP cron strategy. To do this, open the wp-config.php file and add the following line before the “/ *That’s all, stop editing! Happy blogging.* /” line:

`define('DISABLE_WP_CRON', true);`

Then,

### Option 1 - Server Cron

you want to setup a cron on your server or with your hosting service to run every 5 mins:

`*/5 * * * * wget -q -O - http://yourdomain.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1`

**Note:** change `yourdomain.com` to you disciple.tools domain

### Option 2 - Outsite Cron service

An alternative to setting up a cron job is to use a service like [Uptime Robot](https://uptimerobot.com/) to ping `http://yourdomain.com/wp-cron.php?doing_wp_cron` every 5 minutes.

## On Multisites

### Option 1

Create a cron job for each subsite

* <http://yourdomain.com/wp-cron.php?doing\\_wp\\_cron>
* <http://subsite1.yourdomain.com/wp-cron.php?doing\\_wp\\_cron>
* <http://subsite2.yourdomain.com/wp-cron.php?doing\\_wp\\_cron>

### Option 2

Create a script to call each subsite. Create a file `custom-cron.sh` in the same folder as your wp-config.php

custom-cron.sh contents:

```
/usr/local/bin/php /usr/local/bin/wp site list --field=domain --archived=0 --deleted=0 | xargs -i -n1 /usr/local/bin/php /usr/local/bin/wp cron event run --due-now --url="https://{}";
```

create the cron job, replace `/path/to/wp/install` with the path to you wordpress installating.

`*/15 * * * * cd /path/to/wp/install && /path/to/wp/install/custom-cron.sh`

On a large multisite you might want to make sure cron jobs don't run at the same time. For this we create a lock file while custom-cron.sh is running.

`*/15 * * * * cd /path/to/wp/install && /usr/bin/flock -n /tmp/multisitecron.lock /path/to/wp/install/custom-cron.sh`


# Hosting on WPEngine

## Create an account

Create an account on <https://wpengine.com> and pick a plan - an annual $300+ expense. See <https://wpengine.com/plans>

## Create a site.

You have the choice between a single instance and a multisite instance.

A single instance is fine if you have one team in one location.

You'll want a multisite if you have multiple teams, multiple locations, or need more control over who has access to what. We suggest using the subdirectly multisite install instead of the subdirectly install. The main benefit is that you can use one ssl cert for the whole site.

We recommend starting with a multisite. This makes it easier to add instances later as your ministry grows.

See [single or multisite](https://developers.disciple.tools/hosting/single-or-multisite) for more information.

Any plan is good for the single site. For multisite you need to start with a plan that supports the "WordPress Multisite available for purchase" option. Currently starting at the "MANAGED HOSTING PROFESSIONAL" plan.

To use multisite you'll need to first purchase the "Wordpress Multisite" Add-on from Billing > Purchase Add-ons (<https://my.wpengine.com/modify_plan>). It will cost \~$200 (one time expense).

You now have a WordPress instance at \[instance\_name].wpengine.com

## Setup Custom Domain

Purchase custom domain (your-domain.com)

Set up DNS to access your instance from your-domain.com or subsite.your-domain.com DNS instructions?

## Install Theme

Download the theme <https://github.com/DiscipleTools/disciple-tools-theme/releases/latest/download/disciple-tools-theme.zip> - or link to the downloads page.

Log in to your new wordpress instance.

Install the theme (link to how to install themes) Single/Multisite

## Setup TLS (SSL)

After your custom domain is set up

### Single Site

Click SSL > Add Certificates > Get FREE certificate using the Let's Encrypt option

### Multisite

If you selected "subdirectory" as you multisite install then it is the same single site strategy.

For "subdomain insteals":

If you add each subdomain to the domains panel then it is the single site process.

If you have many sub-domains and will be adding and removing them often:

* Buy the wildcard SSL cert from WPEngine - an annual $200 expense

### Restrict all traffic to use https.

Under SSL, select the certificate and choose “Secure All URLs”.

## Backups

Have a strategy for offsite backups. If WPEngine accidentally deletes your account or it gets frozen (GDPR?) you want to have access to your data. See <https://developers.disciple.tools/hosting/backups>

## Additional Configuration

### CRON

Enable system schedule processes:

-enable cron <https://wpengine.com/support/wp-cron-wordpress-scheduling/>

### Caching and Bots

-<https://wpengine.com/support/redirecting-bots-how-this-benefits-you/>

-We initially had to contact support to disable caching on GET api requests. This has not been an issue recently.

### Multisites

Install the multisite plugins:

Multisite Tools and helpful functions: <https://github.com/DiscipleTools/disciple-tools-multisite>

Show update notifications if your main site is not an instance of Disciple.Tools: <https://github.com/DiscipleTools/disciple-tools-multisite-mu-plugin>

### Cloudflare

Consider using cloudflare in front of your hosting for additional security. Create a free account and point the name servers for your domain to cloudflare.

## Notes

* WPEngine does have a publicly accessible error log (though you need to know the link to access it). Error logs have the potential to dump personal contact info.
* WPEngine has a small storage limit per account, so don't store a lot of backups locally.
* With a multisite the option “Secure all URLs” with HTTPS does not always work.
* When you want to add another WPEngine instance, you can stay on the cheapest plan and under Billing > Add-ons add a site for $200
* cairocoder01 has instructions on how to expedite setting up multiple WPEngine instances: [DT Setup Automation](https://github.com/cairocoder01/dt-setup-automation)


# Backups

You know that you need to keep your data backed up. Here are some things to keep in mind. Not all backups are equal. You need to have a backup that you can access if your website goes down or if your hosting provider accidentally deletes your account (this happens). This means that any backup that stays on the server your site is on isn’t a reliable backup. You must have a secure remote backup of you Disciple.Tools instance. This can be with Amazon S3, Google Drive or any other secure storage location.

## UpdraftPlus

We recommend and use [UpdraftPlus](https://updraftplus.com/) for our backups. Multisite support is only available on the paid version.

## BackWPup

We’ve also tested BackWPup: <https://wordpress.org/plugins/backwpup/>. This plugin is free but more difficult to set up.


# Single Site or MultiSite

## A Wordpress Tool

In setting up Wordpress on a new server we have two options. The default way as single site, or as a multisite.

### Single site

This is the default when you install Wordpress on your server and set it up on your domain or subdomain. Your site will be available at example.com or site.example.com.

### Multisite

During install (preferred) or after Wordpress is installed you can configure Wordpress as a Multisite. This lets you set up multiple sites on ones server.

* example.com
* site1.example.com
* site2.example.com
* site3.example.com

## What does this mean for Disciple.Tools?

A single site will give you an instance where you and your users can collaborate on contacts, groups, and more. All your contacts will be in one place and are managed by the Admin and Dispatchers.

This is a great starting point if you are a small team working in together over one region. But suppose you have a team in New York with a Facebook Ministry and a team in Chicago with a cool Website and another team in a different location doing campus ministry.\
It soon become overwhelming to have all the contacts one spot. This is why you might want to separate the teams out into different instances using Wordpress as a multisite.

The Serve could be set up like this:

* ministry.com - a D.T instance, or a front facing webpage
* new-york.ministry.com - instance for the New York team
* chicago.ministry.com - instance for the Chicago team
* etc

You may choose to have a different instance for each location you are in. You can also separate based on teams, language, media page, etc.

Note: On multisites, only Super Admins can modify a user's email address or password. This makes supporting users harder for the subsite administrator. Note: We'd love to support team functionality inside one instance. Follow us at <https://disciple.tools/news/>

## Global Metrics - Network Dashboard

If you have gone with the multisite route, you still would like to see what is happening in one place. We've built the Network Dashboard to collect data form many instances and **show metrics in one place**.\
You can set it up to collect data from all the multisite instances on your server and you can connect it to other instances on others servers as well.

Find out more about the Network Dashboard here <https://disciple.tools/plugins/network-dashboard/>

## Site Links - Collaborate Between Instances

D.T allows users to send contacts between instances. This can be the different instances on your multisite, or with other D.T servers.

A **Site Link** is needed to collaborate between instances. Here are instructions on how to set them up: <https://disciple.tools/user-docs/getting-started-info/admin/site-links/>

## Managing a Multisite

### Setup

If you are hosting your own instance check out the Wordpress documentation for enabling multisite [here](https://wordpress.org/support/article/create-a-network/).

If you are using a hosting service like WPEngine google "WPEngine multisite" or contact your hosting support. They will help you get it set up. Sometimes hosting services charge extra for multisites.

### Maintenance - D.T Multisite Plugin

Once your multisite is set up, you can install the Disciple.Tools multisite plugin: <https://disciple.tools/plugins/multisite/>.

It offers growing support for updating and managing all your instances at one time.


# Disciple.Tools Development Setup

## Contents

* **Hosting on a Local Computer**
  * [LocalWP Based Setup](/local-setup/localwp-setup)
  * [Alternative Docker Based Setup](/local-setup/dt-docker)
    * [Enable Debugging](/local-setup/dt-docker#enable-debugging)
    * [Docker Multi-site Setup](/local-setup/dt-docker#docker-multi-site-setup)
* [**Mobile App Setup**](/local-setup/mobile-app-setup)


# LocalWP

[LocalWP](https://localwp.com/#) provides a simple way to configure a WordPress development environment.

1. Install LocalWP on your machine:

   a. Download the appropriate installer for your platform from the list on: <https://localwp.com/community/>

   * *If a dialog asking for your platform type appears*, you must have pressed the DOWNLOAD button in the upper right of the web page *instead* - You do not have to fill in all the information, but you must give your email address.
   * The installer should download automatically, if not then follow the instructions

   b. Installing LocalWP on **MacOS** and **Linux** is straight forward. For Windows, especially with a 3rd-party antivirus, it requires more effort.

   > ***Note*****:** Some VPNs set firewalls on your network configuration, which will likely conflict with LocalWP.
   >
   > ### Installing on Windows
   >
   > * Select install for **all users** (requires Administrator privileges).
   > * **Run Local but do not create a website yet**, LocalWP needs to be able to write to your `c:\Windows\System32\Drivers\etc\hosts` file and set up SSL. Your antivirus will not like this so you **must** first “whitelist” Local.
   > * As an example: if you use Kapersky 2020, these are the steps: [*(source)*](https://localwp.com/community/t/how-to-run-local-5-0-7-windows-10-antivirus-software-kaspersky/15290) 1. In the main windows go to More Tools -> Manage applications -> Application Controls -> Manage applications 2. In the search box type “local” 3. Double click on Local.exe – **Be careful it is the right application!** 4. Under Exclusions check:
   >
   >   <img src="/files/5nbLS6TzhuGUF1iN555C" alt="Kapersky Settings" data-size="original">
   >
   >   * \[X] Do not monitor application activity
   >   * \[X] Do not inherit restriction from the (application's) parent process
   >   * \[X] Do not monitor the activity of child applications
   >   * Press "Save"
2. LocalWP will ask if you want to create a new site, or press the large **✚** in the lower left corner.

   a. Provide a site name and choose ADVANCED OPTIONS to change defaults 1. Change the site domain and browse for where you want your site’s code to be stored. (You may need to create the folder)\
   **e.g.** Name: D.T Local site path: *D:\sandbox\DT (e.g. as a Windows path)* 2. Select “CONTINUE”

   b. Choose “Preferred” environment 1. Select “CONTINUE”

   > * Select the custom option with different values if you run into an issue.

   c. Provide WordPress Username, Password and Email address

   * **If** you wish the site to be **Multisite**, select this under ADVANCED OPTIONS
   * Select “ADD SITE”

   d. Wait for WordPress, etc. to be downloaded and the site to be created

   > * ***Warning***: This can fail if there is no write access to your hosts file, or your antivirus does not give LocalWP sufficient permissions.
   > * ***In Windows:*** if you are not running as an administrator you will need to give it permission to update the hosts file.
   > * This and other changes will need admin approval. An alternative is to change the LocalWP shortcut’s Properties -> Advanced… so it always runs as Administrator, but this *can* cause security issues.

   e. You can access your site via **both** http\:// and https\://

   * LocalWP will create an SSL certificate if you select the **TRUST** button (which will bypass your browser’s security warning)
   * e.g. If the site’s name is D.T, then both addresses will work `http://dt.local` and `https://dt.local`
3. Install Theme. See <https://github.com/DiscipleTools/disciple-tools-theme>
   1. Follow installation instructions:

      <https://github.com/DiscipleTools/disciple-tools-theme#how-to-install>
   2. Download latest release:

      <https://github.com/DiscipleTools/disciple-tools-theme/releases/latest/download/disciple-tools-theme.zip>
4. Some plugins are available for installation from the **Extensions** tab

   If you need the latest plugin, download the zip files from GitHub, and install plugins using these instructions:\
   <https://wordpress.org/support/article/managing-plugins/#manual-upload-via-wordpress-admin>

   * e.g. <https://github.com/DiscipleTools/disciple-tools-demo-content>


# Mobile App Setup

This [YouTube playlist](https://www.youtube.com/playlist?list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE) shows how to set up a local development environment for the D.T. Mobile App and contains more information about the environment than this guide.

However, the [playlist](https://www.youtube.com/playlist?list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE) uses Docker for the local Wordpress server and executes the mobile app setup slightly differently. There are links in the steps below to pertinent sections in the video, although they are not necessarily in the same order.

Where the details differ please follow these instructions instead.

## Contents

* [1. Install Mobile App Plugin](#1-install-mobile-app-plugin)
* [2. Configure WordPress Address](#2-configure-wordpress-address)
  * [Use Live Link (LocalWP only)](#use-live-link-localwp-only)
  * [*OR* Change Site Domain to Your Server’s IP Address](#or-change-site-domain-to-your-servers-ip-address)
* [3. Setup Source Code](#3-setup-source-code)
  * [Https Workaround](#https-workaround)
  * [Install Expo](#install-expo)
  * [Install Dependencies](#install-dependencies)
* [4. Run the Development Environment](#4-run-the-development-environment)

## 1. Install Mobile App Plugin

Download the *latest* mobile app plugin zip file, and install the plugin, by following these instructions: <https://wordpress.org/support/article/managing-plugins/#manual-upload-via-wordpress-admin>

* <https://github.com/DiscipleTools/disciple-tools-mobile-app-plugin/releases/latest/download/disciple-tools-mobile-app-plugin.zip>

## 2. Configure WordPress Address

Changes need to be made to the *Wordpress* configuration for the mobile app to connect to it.

If you are using **LocalWP** Setup, then there are two choices: Local Live Link address, *or* change the D.T site’s domain to an address reachable from your mobile device. The latter is the only choice if you are using **Docker**

### Use Live Link (LocalWP only)

1. **Enable** ngrok.io Live Link from the centre of the bottom line of the D.T’s Local Sites in the Local app.
2. “**Enable**” button’s text will change to “**Copy**”
3. Transfer address to mobile device – it will look like: `http://0f0f0f0f0f0f.ngrok.io`

### *OR* Change Site Domain to Your Server’s IP Address

> ***Warning*****: With Docker, if your local Wordpress site has NOT been setup to use http (i.e. NOT https) then you will need to reinitialize it.**
>
> * Adding “—volumes” to docker-compose down will remove your previous configuration
>
>   ```bash
>   > docker-compose down  --volumes
>   > docker-compose up -d
>   ```
> * Go to <http://localhost:8000> and repeat the configuration steps from [1.3 Start Wordpress](https://developers.disciple.tools/local-setup/pages/-MU8GvCEOn00R7_9gTH1###start-wordpress)

1. Discover the IP address of your local server

   There are multiple commands to do this, and your computer may have multiple network interfaces and it will depend if it uses a wired or wireless connection.

   * The address would likely be of the form “192.168.1.2” (as an example)
     * On Linux you can use: *i**f**config*
     * On Windows you can use: *i**p**config*
2. **If** you are using **LocalWP**,

   * In the D.T’s Local Sites in the **Local** app, change the Site Domain to your IP address

   **If** you are using **Docker**,

   * login to wp-admin and go to Settings > General and update the WordPress and Site Addresses to your servers IP address.
   * As examples, if your address is `192.168.1.2` and no port number, then:

     * WordPress Address (URL): `http://192.168.1.2`

     * Site Address (URL): `http://192.168.1.2`

     > NOTE: This [YouTube video](https://www.youtube.com/watch?v=1KJEOY5J3Sw\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=5\&t=6m12s) shows the basic procedure.

## 3. Setup Source Code

Clone the *development* or *master* branch of <https://github.com/DiscipleTools/disciple-tools-mobile-app> to the folder where you want the source code to be stored.

This [YouTube video](https://www.youtube.com/watch?v=gdeJHI19F7A\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=4\&t=2m35) goes into much more detail.

```bash
> git clone https://github.com/DiscipleTools/disciple-tools-mobile-app.git
> cd ./disciple-tools-mobile-app/
> git checkout development
```

### Https Workaround

Your local copy of DT does not have the correct https certificates so the mobile app will not be able to connect to it. To work around this, use the http connection instead.

This is shown in this [YouTube video](https://www.youtube.com/watch?v=gdeJHI19F7A\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=4\&t=9m24s).

> **NOTE:** The app code is able to connect to the official demo server if you have an account, but will no longer be able to after these changes.
>
> **For Windows:** These are Linux commands. You will need execute these commands from the **git bash shell**, or another **Linux** shell

From within the *disciple-tools-mobile-app* folder:

```bash
> cd store/sagas
> grep -rl https: . | xargs sed -i "s/https:/http:/g"
```

> **WARNING:** **Do not include these changes with any pull requests** to the main repository! They will be rejected.

### Install Expo

See <https://expo.io/learn>

1. Expo requires NPM to be installed. Download and setup from <https://nodejs.org/>
2. Use NPM to install expo:

   ```bash
   > npm install expo-cli -–global
   ```
3. *Create an account* if you do not have one (You do not have to create a project)
4. Install the Expo client on your mobile device (iOS or Android app stores)

### Install Dependencies

From within the ***disciple-tools-mobile-app*** cloned folder install project dependencies based on the package.json file

This is shown in this [YouTube video](https://www.youtube.com/watch?v=gdeJHI19F7A\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=4\&t=5m53s)

Use the NPM install command:

```bash
   > npm install
```

This may take a while

## 4. Run the Development Environment

Use the NPM start command:

```bash
    > npm start
```

1. Wait for the GUI to display a QR code and then on your mobile device:
   * Again, this may take a while
   * **From iOS:** take picture of QR code
   * **From Android:** launch Expo app and take a picture of the QR code.
2. Wait for the DT android app to start and ask for you to connect to the server.
   * This may a while as well, but the main Expo GUI will provide a status, as will the terminal you started it on.
   * Log into the mobile app using the local DT Wordpress server ip address you set the server to [previously](#2-configure-wordpress-address).

> **NOTE:** The end of Part 5 as well as part 6 of the [YouTube videos](https://www.youtube.com/watch?v=1KJEOY5J3Sw\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=5\&t=8m6s) give additional information about using the mobile development environment:
>
> [Part 7](https://www.youtube.com/watch?v=eM2zxplCaOE\&list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE\&index=7) shows you what is happening on the phone while this is taking place:


# Unit Tests

## Setup testing environment

In the theme root within your environment (localWP: "Open site shell" option) run:

```bash
./tests/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host]
```

* `<db-name>` is the name of the db you want the run the tests in. We suggest creating a separate db form your dev db for testing.
* `<db-user>` Database username
* `<db-pass>` Database password
* `[db-host]` Database url and port. For localWP get the port from the url when opening Adminer.

Note for localWP on linux the command looks like this for localWP:\
`./tests/install-wp-tests.sh local-test root root localhost:10063`

Notes:

* On localWP, make sure you are running from the "Open site shell" menu option, so the environment is loaded
* On localWP, remove `--protocal=tcp` on line #141 of ./tests/install-wp-tests.sh
* On localWP, you can get the database port from the browser url after opening adminer
* You may need to install svn: `sudo apt install subversion`
* If you get an error that looks like: `Could not find /{path}/wordpress-tests-lib /includes/functions.php, have you run tests/install-wp-tests.sh ?` delete the temp folder and run the install again.

## Running the tests

Install phpunit and phpunit-polyfills form the theme root.

```bash
composer require "phpunit/phpunit=7.5.*"
composer require "yoast/phpunit-polyfills"
```

Run `./vendor/bin/phpunit`

The tests need phpunit v7. v8 and above currently don't work.

## Writing tests

* tests are located in the theme ./tests folder
* unit test files need to start with 'unit-test'
* unit test function need to start with 'test\_'
* phpunit documentation <https://phpunit.readthedocs.io/en/7.5>
* assertions: <https://phpunit.readthedocs.io/en/7.5/assertions.html>


# Cypress Tests

Support for the [Cypress Testing Framework](https://www.cypress.io) has now been integrated into D.T.

With Cypress, you can easily create declarative tests, debug them visually and automatically run them in your continuous integration builds.

## Getting started

From the terminal, navigate to the `disciple-tools-theme` directory and execute `npm run cy:open`; which should display the Cypress Launchpad.

From there, select the `E2E Testing` option; which should then display the following browser selection view.

![Cypress Browser Selection](/files/lwh2pvpKx5xnFFshh2KU)

See [Cyress Open The App](https://docs.cypress.io/app/get-started/open-the-app) guide for more details.

On browser selection, you should then be taken to the End To End (E2E) view; as shown below.

![Cypress E2E View](/files/gV1PNvER8HiUAZs96dWP)

## E2E Folder Structure

The view from the previous section, displays all E2E tests created under the `./cypress/e2e` directory; which is organised as follows:

* **./cypress/e2e/contacts/**
  * Siloed unit tests, covering the testing of `contacts` post type related functionality; such as creating, updating and deleting records, searching, etc.
  * Please follow the pattern shown, when introducing tests for new features.
* **./cypress/support/commands.js**
  * A holding area for custom functions; typically duplicate code; which can be encapsulated into a global function.
  * The following custom functions have been created:
    * *dtLogin()*
      * D.T frontend login.

## Running Environment

Currently, all tests are run locally; so, you'll need to update the base url and admin credentials within the `./cypress.config.js` file.

```js
import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: '<LOCAL_DEV_INSTANCE_HTTP_URL>'
  },
  dt: {
    credentials: {
      admin: {
        username: '<LOCAL_DEV_ADMIN_USR>',
        password: '<LOCAL_DEV_ADMIN_PWD>'
      }
    }
  }
});
```

Lastly, the following Cypress user guides also provide useful information, for gaining a better understanding of the framework.

* [Cypress - Your First Test](https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test)
* [Cypress - Testing Your App](https://docs.cypress.io/app/end-to-end-testing/testing-your-app)


# D.T on Docker Setup

[Docker](https://www.docker.com/) is a container system that can be used to set up all of the infrastructure needed to run a web site. The below will setup containers locally needed to run a MySQL database and an Apache + PHP web server

All of this will be running on a Linux virtual machine in order to duplicate as close as possible the production hosting environment.

## Contents

* [Three Choices](#three-choices)
* [Setup Docker](#setup-docker)
  1. [Install Docker](#install-docker)
  2. [Configure SSL](#configure-ssl)
  3. [Configure Docker](#configure-docker)
     * [*Prepackaged Image*](#prepackaged-image)
     * [*Video Walk-through*](#video-walk-through)
     * [*Example Template*](#example-template)
  4. [Start Wordpress](#start-wordpress)
  5. [Install Theme](#install-theme)
  6. [Install Plugins](#install-plugins)
* [Enable Debugging](#enable-debugging)
* [Docker Multi-site Setup](#docker-multi-site-setup)

## Three Choices

There are currently three sets of instructions for setting up a Disciple Tools server locally using Docker. Choose the one that suites your needs best.

1. Prepackaged Image

   <https://github.com/zdmc23/dt-docker> is the easiest of the three to install (similar effort to setting up a [LocalWP](/local-setup/localwp-setup) environment), but it is difficult to update preinstalled plugins.
2. Video Walk-through

   This [YouTube playlist](https://www.youtube.com/playlist?list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE) shows how to set up for D.T. Mobile App development on a local machine, which includes setting up the Docker server environment

   The videos do not follow these instructions exactly, but it is better to use them to help understand these instructions, instead of following them by themselves.
3. Example Template

   <https://github.com/cairocoder01/dt-docker> which these instructions were originally based on, and provides a downloadable set configuration files, which can be updated.

## Setup Docker

### Install Docker

* Install the correct Docker download for your platform <https://docs.docker.com/get-docker/>

> **For Windows:**
>
> * When you start the Docker Desktop, **if necessary** it will provide you with additional instructions to update WSL 2 (Windows Subsystem for Linux)
> * If the user account you commonly use does not have admin privileges, you can add it to the docker-users group so you can run docker directly **without** requiring “runas” (similar to “sudo” in Linux/MacOS)
>
>   From the `Computer Management` app: open System Tools > Local Users and Groups > Groups, double click the "docker-users" group and from the "Add..." dialog add the desired user account.
>
>   <img src="/files/KjsmmkAhJVrWkRQBg7YS" alt="Configure Windows Docker Users" data-size="original">

### Configure SSL

Set up the self-signed SSL certificate. (The instructions are explained [here](https://medium.com/@nh3500/how-to-create-self-assigned-ssl-for-local-docker-based-lamp-dev-environment-on-macos-sierra-ab606a27ba8a) in detail)

> **Warning:** If you will be working on the **Mobile app** code, it [does not currently support Https](https://developers.disciple.tools/local-setup/pages/-MU8GvBzSmovtve9l8dv##https-workaround) when connecting to a local server, so this step is not required.
>
> **For Windows:**
>
> * The instructions above for MacOs work if you have OpenSSL installed
> * OpenSSL is available via
>
>   [Git Bash](https://git-scm.com/download/win),
>
>   [Ubuntu for Windows](https://www.microsoft.com/en-us/store/p/ubuntu/9nblggh4msv6?SilentAuth=1),
>
>   [Cygwin](https://www.cygwin.com/),
>
>   or [Chocolatey](https://www.cygwin.com/)
>
> **Note:** Already updated *dev.conf* and *dockerfile* configuration files are available from <https://github.com/cairocoder01/dt-docker>

1. From command line in the project root directory (your copy of the Github repository) run:

   ```bash
   > openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
   ```
2. Specify the Common Name as 'local.disciple.tools'. You may answer the other questions however you wish.
3. Site will be available on <https://local.disciple.tools> (if you add the needed hosts file mapping) or <https://localhost:44300>

### Configure Docker

Configure Docker according to which one of the [3 approaches](#three-choices) you choose:

#### ***Prepackaged Image***

1. Download the `docker-compose.yml` file from <https://github.com/zdmc23/dt-docker> into your working directory (or clone it from GitHub)
2. Create a `.env` file and copy/paste the following, and then update the values:

   ```
   DOMAIN=mydomain
   EMAIL=noreply@mydomain
   MYSQL_USER=wordpress
   MYSQL_PASSWORD=wordpress
   MYSQL_ROOT_PASSWORD=somewordpress
   ```

   (`DOMAIN` value can be an IP address - e.g., `127.0.0.1` or `192.168.1.2`)

#### ***Video Walk-through***

* As mentioned [above](#three-choices), the [YouTube playlist](https://www.youtube.com/playlist?list=PLNZnizaetELN6_2k3_iRxBhJuyavhqawE) works best to help understand these instructions, rather than following it alone.

#### ***Example Template***

* These instructions were originally based on <https://github.com/cairocoder01/dt-docker>
* Download the files, or clone the repository from GitHub, to use as a template.

### Start Wordpress

> **Warning:** The configuration files were up to date the last time they were edited, but *always* make sure to double check what the most up to date versions of the software components are, or make certain to install mutually compatible versions.
>
> For example, the *dockerfile* should have up to date version numbers for Wordpress and PHP.
>
> * The first line will look like:
>
>   `FROM wordpress:5.4-php7.4-apache`
> * Check <https://github.com/DiscipleTools/disciple-tools-theme/releases/latest> for which version of Wordpress disciple-tools-theme has been tested with.>

1. Run `docker-compose up -d` from the project root directory (or `npm run docker-start`).
   1. The first time this is run, it will need to download all of the machine images, so it may take a little while.
   2. There will be some warning messages that can be ignored, unless Docker cannot bring the Wordpress and Mysql containers up.
2. You should be able to access the site via <https://local.disciple.tools> (if you add the needed hosts file mapping), <https://localhost:44300> or <http://localhost:8000>

   > **For Windows:** You will need to add a “security exception” in your browser from its warning dialog (depending on browser).
   >
   > **Warning:** When you configure WordPress from “<https://localhost:44300>” or “<http://localhost:8000>” you will have to continue to use that address or reconfigure it from the …/wp-admin/options-general.php settings page to switch.
3. Step through the WordPress installation process.
   * Language:
   * Site Title:
   * Username:
   * Password:
   * Your Email:
   * Press “Install WordPress”

> **Note:** If you cannot access DT’s home page: <https://localhost:44300/contacts> or other pages try this:
>
> * Login to *wp-admin* and go to ![icon](/files/R9qHFgK5bAu3qE8ZuICo) *Settings > Permalinks*. *You don’t need to change anything*, just click *Save* at the bottom.
>
>   (Source: <https://developers.disciple.tools/> section: [Errors on New Installation](https://developers.disciple.tools/hosting/404))

### Install Theme

> **NOTE:** If you used the [**Prepackaged Image**](#prepackaged-image) this has already been done for you.\
> **However,** if you need to update the theme you must do it manually from within the container!

1. Go to <https://github.com/DiscipleTools/disciple-tools-theme>
2. Download latest release: <https://github.com/DiscipleTools/disciple-tools-theme/releases/latest/download/disciple-tools-theme.zip>
3. Follow installation instructions: <https://github.com/DiscipleTools/disciple-tools-theme#how-to-install>

### Install Plugins

> **NOTE:** If you used the [**Prepackaged Image**](#prepackaged-image) some plugins have already been installed for you.\
> **However,** if you need to update a pre-installed plugin you must do it manually from within the container!

Some production plugins are available for installation from the **Extensions** tab. For developement, or unlisted plugins:

1. Download the latest plugin zip files from below, and install plugins using these instructions: <https://wordpress.org/support/article/managing-plugins/#manual-upload-via-wordpress-admin>
   1. <https://github.com/DiscipleTools/disciple-tools-demo-content>
   2. <https://github.com/WP-API/Basic-Auth>
2. Download JWT Authentication for WP REST API from: <https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/>
   1. Follow directions on plugin page to add auth header config to .htaccess
   2. Follow directions on plugin page to add 2 values to wp-config.php

## Enable Debugging

* Edit wp-config.php to add the following values:

  ```php
   define( 'WP_DEBUG', true ); // Enable WP_DEBUG mode
   define( 'WP_DEBUG_LOG', true ); // Enable Debug logging to the /wp-content/debug.log file
  ```

## Docker Multi-site Setup

See <https://www.wpbeginner.com/glossary/multisite/>

1. Add the following to `wp-config.php`

   ```php
   /* Multisite */
   define('WP_ALLOW_MULTISITE', true);
   ```
2. Go to Tools -> Network Setup and follow on-screen directions, adding the necessary code to .htaccess and wp-config.php
3. After changes, login again and return to wp-admin.
4. A new My Sites item appears in the top menu. Go to My Sites -> Network Admin -> Dashboard
   * Copy `disciple-tools-multisite.php` into `wp-content/plugins/disciple-tools-multisite`
5. Add all of the sites as you desire.
   * For each site you add, add the needed entry to your local hosts file.\
     For example:

     ```
      127.0.0.1   site1.local.disciple.tools
     ```


# Gulp - CSS and JS

## Setting Up the Build Process

D.T Uses gulp to compile and minify the css and javascript. If you will be contributing styling or JS changes you will need to use gulp.

* First you need to have Node.js installed on your computer. You can download and install Node.js from [here](https://nodejs.org/)
* Second from your terminal enter the Disciple Tools theme folder.
* From your Disciple Tools theme folder run npm install. You should only have to do this once.
* Once the install process is complete you should be able to type `gulp` in the command line and see an output that looks something like

```
[15:09:08] Using gulpfile /wp-content/themes/disciple-tools-theme/gulpfile.js
[15:09:08] Starting 'default'...
[15:09:08] Starting 'styles'...
[15:09:08] Starting 'scripts'...
[15:09:15] Finished 'scripts' after 7.14 s
[15:09:16] Finished 'styles' after 8.02 s
[15:09:16] Finished 'default' after 8.02 s
```

* If you will making multiple changes to css or js files you will probably not want to have to run gulp after every change. In this case you can use the command `gulp watch` and gulp will watch for any changes to scss or js files and will automatically run when the file is changed.

## CSS

D.T uses SCSS for CSS styiling. The SCSS files can be found under `dt-assets > scss.`

You probably want to modify:

* `_details.scss` for contact, group etc record page
* `_list.scss` for the list page
* `_main.scss` most other cases

Compile your changes to css by running `gulp` in your terminal in the theme root.\
This generates the `dt-assets/build/css/style.min.css` file that is used by the browser

## JS

If you modify `dt-assets/js/footer-scripts.js` you will also need to run `gulp` to apply the changes.\
The outpet is `dt-assets/build/js/scripts.min.js`


# Code Contribution


# Theme Contribution Guidelines

Thank you for joining us in contributing to Disciple.Tools! These are the guidelines we expect you to follow in writing code that will be used in or with D.T

## How to Contribute to the theme

Follow these steps.

1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request

## Setup for Develope

### Install Composer

Download and install composer via [their instructions](https://getcomposer.org/download/) or via Homebrew:

```
$ brew update
$ brew install composer
```

Install o Run Composer to install dependencies

```
$ composer install
```

### Install NPM dependencies

```
$ npm install
```

## Translations

D.T is already being used in multiple languages. Please help us make D.T translable by taking full advantage of Wordpress’ translatable strings. Any string that will be read by the user must be marked as translatable. Ex: `<label class="section-header"><?php esc_html_e( 'Other', 'disciple_tools' )?></label>`

Make sure you look for these in PHP, HTML and JavaScript code.

## PHPCS

We use [PHPCS](https://github.com/squizlabs/PHP_CodeSniffer) and [PHPCS WordPress Coding Standards](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) to test for syntax errors, security vulnerabilities and some styling rules. We expect your commits to pass these tests.

Run `composer install` or `composer update` first.

In the theme you can run `./tests/test_phpcs.sh` or create a pull request to our repo and Travis CI will run the tests for you.

If you are working on a plugin based off our starter plugin run `./includes/admin/test/test_phpcs.sh`

Note: rules for PHPCS are located in the `phpcs.xml` file. We sometimes update the rule list as PHPCS updates. We’ll update the [starter plugin](https://github.com/DiscipleTools/disciple-tools-starter-plugin) `phpcs.xml`, you might want to look there to get the latest version.

Run phpcbf to auto-fix some phpcs issues: `vendor/bin/phpcbf --standard="phpcs.xml" dt-core/`

## Missing WP functions errors

If all of the WP functions are showing up as errors saying that the function is undefined, you can try adding the entire WP site to your editor, so that it can automatically pick up the WP function definitions from `wp-include`, `wp-admin` etc.

## PHPCS sniff errors

If you get errors such as, `phpcs Referenced sniff "WordPress" does not exist` this could be due to your editor not using the correct `phpcs` or `phpcs.xml` file. It could be using a globally installed version instead.

Make sure that you point your editor to the local phpcs file in `vendor/bin/phpcs` and phpcs config file `phpcs.xml` in the `disciple-tools-theme` directory.

In vscode the settings look roughly like this, depending on your setup and where the settings are being kept. In this example a directory specific settings.json file is being used in the root of the website. The paths, may need to be the full absolute paths or relative paths to where the settings file is.

```
    "phpcs.executablePath": "wp-content/themes/disciple-tools-theme/vendor/bin/phpcs",
    "phpcs.standard": "wp-content/themes/disciple-tools-theme/phpcs.xml"
```

## GitHub and Commits

For new plugins copy our [starter plugin](https://github.com/DiscipleTools/disciple-tools-starter-plugin).

To commit to the theme or an existing plugin start by creating a fork of the repository. When you are ready, create a pull request into our repo.

Note: Depending on your context you may wish to use an anonymous GitHub account.

## `WP_DEBUG`

Enable `WP_DEBUG` in your `wp-config.php`: `define('WP_DEBUG', true);` Checking out a PR and seeing the orange debug table is disappointing.

We look forward to hearing from you!


# How to Correctly Contribute to the Disciple Tools Repository

Keeping track of upstream, origin, and local can be a bit overwhelming sometimes. Here's a step-by-step process that shows you how to contribute to DiscipleTools and not go mad in the process. :)

Follow these steps to make sure your code gets neatly added to the Disciple Tools Theme Repository.

## 1. Fork the repo

The fork will be called called **origin**

In order to fork the main DT repository, go to it and click the `Fork` button on the top-right corner of the page.

## 2. Clone the fork

The cloned fork will be called **local**

`git clone https://github.com/[YOUR_GITHUB_USERNAME]/disciple-tools-theme.git`

## 3. Keep your fork up to date with upstream

The original Disciple.Tools repo will be called **upstream**, but first we have to set it up.

`git remote add upstream https://github.com/DiscipleTools/disciple-tools-theme.git`

To check that everything has been set up correctly, run:

`git remote -v`

**You should see something like this:** &#x20;

`origin https://github.com/[your username]/disciple-tools-theme.git (fetch)`

`origin https://github.com/[your username]/disciple-tools-theme.git (push)`

`upstream https://github.com/DiscipleTools/disciple-tools-theme.git (fetch)`

`upstream https://github.com/DiscipleTools/disciple-tools-theme.git (push)`

We're almost there! Now we need to create a local branch directly from DT's upstream repository's master branch (the latest stable version of the code).

## 4. Create a new local branch from upstream

Say you want to create a new branch for the feature you want to create or for the bug you want to fix. First, we need to make sure your forked repo has the latest stable version of the code from DiscipleTools. In other words, we need to fetch the upstream code. To do this, type:

`git fetch upstream`

And then create a new branch from upstream master:

`git checkout -b new-branch upstream/master`

This new branch will not contain remnants of previous commits that were merged in via the squash method. You should receive a message like the following:

`Branch 'new-branch' set up to track remote branch 'master' from 'upstream'.`

`Switched to a new branch 'new-branch'`

## All set!

The 'new-feature' branch should be a mirror of the upstream repository's master branch. Check your branches by running the following command:

`git branch`

## 5. Commit your code

After you work your code-writing magic, commit the changes to your fork (AKA, origin), making sure you specify the branch name that doesn't yet exist on your fork.

`git commit -am 'my new awesome commit description goes here'`

`git push origin new-branch`

## 6. Send the code over to DT

In order to get your code into the official Disciple.Tools repository for all the world to enjoy, you need to create a pull request to the upstream repository.

To do this, go to your fork's Github page, select the *new-feature* branch and Click 'Compare & pull request' and then follow the steps shown on the page.

If there are no problems, the Disciple Tools team with merge your code with the main repository.

## 7. Cleaning up

After your code has been pulled into DT, you should clean up your local repo because branches can start to clutter your workspace. To do this, we recommend deleting the `new-feature` branch by typing:

`git branch -d new-feature`

And then push those changes to your fork:

`git push origin --delete new-feature`

## Done!

Thanks for contributing your time and skill on this project. We greatly appreciate every line of code you send our way.


# How to Translate Your Plugin

You finished coding your Disciple.Tools plugin and now you want to make it accessible to the whole community. Before uploading it, you can add translations in order to make the content understadable for every user.

There are a few different ways of translating your plugin. Here are a few.

## Using Poedit Software

1. Download Poedit: [link here](https://poedit.net/download/)
2. Open the software and select the `Edit a translation` option
3. Select the `default.pot` file in your plugin's `/languages` folder
4. Go to the `Catalog` menu and click on `Update from Source Code`. You should see the strings used in your plugin's code.
5. Click the `Create New Translation` button at the bottom and select the language you want to translate into
6. Translate each string by clicking on them and add its respective translation in the `Translation` text area
7. After translating every string, save the file. Poedit will suggest a file name that is the language code you selected for your translation. Don't delete or modify that text. Write your plugin name before it making sure you only use lowercase and replace spaces with hyphens. (`eg. my-plugin-name-es_ES.po`)
8. Make sure your code echoes the translated strings with the following format: `<?php echo esc_html__( 'Hello, World!', 'my-plugin-name' ); ?>`
9. Done! Your translation should appear for your plugin on the Disciple.Tools theme.

## Using Poedit & Poeditor Website for community translations

1. Register in [www.poeditor.com](http://www.poeditor.com) and create a new project for your plugin
2. Add a new language
3. In Poedit, load the `default.pot` file in your plugin's `/languages` folder
4. Go to the `Catalog` menu and click on `Update from Source Code`. You should see the strings used in your plugin's code.
5. Save the `default.pot` file with the imported strings
6. In poeditor.com, go to the translation project for your plugin and upload the `default.pot` file by clicking the `Import` button. Your plugin's terms should have been added automatically.
7. Translate all the terms and export the `.po` and `.mo` files from the project language menu. These files should be saved in your plugin's `/languages` folder. The file name should be your plugin's name in lowercase and with hyphens instead of spaces followed by the language code. (eg. `my-plugin-name-es_ES.po`)
8. Make sure your code echoes the translated strings with the following format: `<?php echo esc_html__( 'Hello, World!', 'my-plugin-name' ); ?>`
9. Done! Your translation should appear for your plugin on the Disciple.Tools theme.

## How to translate strings in JS files

1. Open `dt-assets/functions/enqueue_scripts.php`.
2. Search for the if statement that looks for the PHP file that calls your JS script. If it doesn't appear in `enqueue_scripts.php`, you will have to create an if statement that looks for it. Live examples of these if statements already exist in this file. You can use them as a reference.
3. Inside the if statement, make sure that your JS script is added with `dt_theme_enqueue_script()`. We're going to need the handle parameter for the next step (the first parameter in the `dt_theme_enqueue_script()` function).

### Example:

```
dt_theme_enqueue_script('my_js_script', 'path/to/script/my-js-script.js' );
```

1. Create a `$translations` array that contains the keys and values for what you want the string to show.

### Example:

```
$translations = [
		'hello_world' => __( 'Hello, World!', 'disciple_tools' ),
		'hello_user', => _x( 'Hello, %s!', 'Hello, John!', disciple_tools' ),
		];
```

1. If it's not already there, add a `wp_localize_script()` function to localize your script. Script localization passes values obtained through PHP to your JS script.
2. The `wp_localize_script()` should have the handle from step 3 passed as the first parameter. The second parameter should be `new_record_localized` and the third parameter should be the information you want to localize. See example below.

```
wp_localize_script( 'my_js_script', 'new_record_localized', array(
            'translations'  =>  $translations,
        ) );
```

1. Now we move to the JS file, find the place that contains the string you wish to translate and replace it with the translated string. Translated strings are inside the `window.new_record_localized.translations` object.

### Example:

```
function say_hello_world() {
    return window.new_record_localized.translations['hello_world'];
    // This returns 'Hello, World!'
}

function say_hello_to_user(username) {
    return window.new_record_localized.translations['hello_user'].replace('%s', username);
    // username = 'Bob', this returns: 'Hello, Bob!'
}
```


# Style Guide

## Branding

Please use "Disciple.Tools" or "D.T" and avoid "Disciple Tools", or "DiscipleTools" or other variants.

## Logos

| File                                                                                               | Description          | Link                                                                                                  |
| -------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------- |
| ![Disciple.Tools Logo Black](/files/8GJq7EnxZ94rvywIUzoS)                                          | Black logo with text | [Download](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/DT-black-text.png) |
| ![Disciple.Tools Logo White](/files/h5bJj6Ts5xXKnOigZxO2)                                          | White logo with text | [Download](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/DT-white-text.png) |
| ![](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/dt-caret.png?raw=true) | Icon in png format   | [Download](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/dt-caret.png)      |
| ![](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/dt-caret.svg?raw=true) | Icon in svg format   | [Download](https://github.com/DiscipleTools/Documentation/blob/master/assets/logos/dt-caret.svg)      |

## Theme Colors

![#3f729b](https://via.placeholder.com/15/3f729b/000000?text=+) Main color blue: #3f729b\
![#8BC34A](https://via.placeholder.com/15/8BC34A/000000?text=+) Secondary color: #8BC34A\
![#224f72](https://via.placeholder.com/15/224f72/000000?text=+) Darker blue: #224f72\
![#4caf50](https://via.placeholder.com/15/4caf50/000000?text=+) Success/New green: #4caf50\
![#00897B](https://via.placeholder.com/15/00897B/000000?text=+) Action butons: #00897B

Status Colors\
![#4CAF50](https://via.placeholder.com/15/4CAF50/000000?text=+) Active: #4CAF50\
![#FF9800](https://via.placeholder.com/15/FF9800/000000?text=+) Paused: #FF9800\
![#F43636](https://via.placeholder.com/15/F43636/000000?text=+) New: #F43636\
![#366184](https://via.placeholder.com/15/366184/000000?text=+) other: #366184\
![#808080](https://via.placeholder.com/15/808080/000000?text=+) Archived: #808080

## CSS filters for svg icons.

Add this css to an icon to change its color.

To #3F729B ![#3F729B](https://via.placeholder.com/15/3F729B/000000?text=+) `filter: invert(41%) sepia(42%) saturate(518%) hue-rotate(164deg) brightness(94%) contrast(100%);`

To find a good filter see <https://codepen.io/sosuke/pen/Pjoqqp>

Use the existing `dt-white-icon`,`dt-blue-icon` and`dt-green-icon` classes to quitly change an icon color.

Example: `<img src="settings.svg">` gives ![image](https://user-images.githubusercontent.com/24901539/134213152-5dd422c6-f6c7-411a-9289-77e6cdc32fa0.png)

`<img class="dt-blue-icon" src="settings.svg">` gives ![image](https://user-images.githubusercontent.com/24901539/134213328-1afde89c-a7ea-45cf-b5bd-6faedd371ed0.png)


# Disciple.Tools Code of Conduct

Like the technical community as a whole, the DiscipleTools team and community is made up of volunteers from all over the world. Diversity is a strength, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to.

**Be friendly and patient**.

**Be welcoming**. We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.

**Be considerate**. Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language.

**Be respectful**. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the DiscipleTools community should be respectful when dealing with other members as well as with people outside the DiscipleTools community.

**Be careful in the words that you choose**. We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to:

* Violent threats or language directed against another person.
* Discriminatory jokes and language.
* Posting sexually explicit or violent material.
* Posting (or threatening to post) other people’s personally identifying information (“doxing”).
* Personal insults, especially those using racist or sexist terms.
* Unwelcome sexual attention.
* Advocating for, or encouraging, any of the above behavior.
* Repeated harassment of others. In general, if someone asks you to stop, then stop.

**When we disagree, try to understand why**. Disagreements, both social and technical, happen all the time and DiscipleTools is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.

This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate. This code of conduct applies to all spaces of the DiscipleTools community.

Attribution

Original text courtesy of the Sphinx project: <https://www.sphinx-doc.org/en/master/internals/code-of-conduct.html>


