XenForo 2.1: REST API

Добро пожаловать в ещё один (уже восьмой) обзор нововведений XF2.1, посвящённый опять же разработчикам.
Если Вы ещё не читали предыдущие обзоры, то рекомендуем почитать их здесь.

Поскольку нашим самым популярным запросом был и вот начиная с 2.1, он доспупен. Несмотря на то, что это довольно ориентированная на разработчика функция, она открывает множество дополнительных возможностей интеграции. Это упростит получение данных в XenForo или вне их, без необходимости понимать базовую структуру PHP, на которой построен XF.

API разбивается на несколько отдельных компонентов, поэтому давайте посмотрим на них по очереди.

(Опять же на английском, здесь по большей части техническая информация)

API keys

All API access requires authentication using a key. Keys can only be created in the control panel by a super administrator. Because the ability to create an API key is very powerful, simply accessing the keys may require an additional password check. This is similar to GitHub's "sudo mode".

When accessing the API, you will always be acting as a particular user. This will be used to know who is creating content or what permissions to use, though there are some exceptions to this which we'll get into later. Therefore, there are three types of API keys:

  1. Guest keys. This will only be able to access data that a guest can access. To emphasize, a key must still be created, even for guest API access.
  2. User keys. These keys are tied to a particular user. All actions (such as creating a thread) will be attributed to this user and their permissions will be taken into account.
  3. Super user key. These are the most powerful keys, and can access the API as any user and bypass the user's permissions if needed. These keys are primarily designed for complex integrations. For example, you may integrate with a third-party CMS that creates a thread whenever you post a new article. This type of key would allow you to create a thread with a different user depending on the article author or in a forum that users normally can't post in.
Super user keys (or user keys attached to privileged users) are very powerful, so it is important that you protect any created API keys so that they can't be used in unexpected ways, such as by bulk deleting threads or even whole nodes.

To help limit the amount of damage that could be done by a compromised key, every key has a set of allowed scopes: \


1541071042864.png
This screenshot only shows a few of the available scopes. Each content type or action exposed by the API will be covered by some sort of scope. For example, threads and posts are covered by distinct scopes to limit reading, writing (creating/updating/soft-deleting) and hard deleting. For security, you should only give a key the API scopes that you intend to use.

No API key can take a particular action unless it has the relevant scope. For example, if you have an integration that needs a super user key to post a thread, then you would want to give it thread:write (and likely thread:read as well), but you wouldn't want to give it node:delete or user:delete.

(For those of you keeping score, API scopes are what's using the change as discussed on Tuesday. The type:action convention is in various APIs, so it made sense for us to use it too but that isn't valid as a phrase name.)

To further aid security, whenever an API key is generated or modified, all super admins will be sent an email.

Now that we have an API key, we can start to access the API...

Accessing the API

The API for a particular installation is always found at <XF url>/api/. (Note that to ensure consistent URLs, the API always uses the "friendly URL" style.) All routes will be accessed under this URL.

The API key you generated must be passed in via a custom XF-Api-Key header added to your request.

If you are using a super user key, you may pass the user ID of the effective user via the XF-Api-User header; if you don't pass any user in, the request will default to acting as a guest.

Further request parameters may be sent via the query string or a application/x-www-form-urlencoded body. (Note: multipart/form-data is also supported, but for POST requests only.)

By default, all requests will respect user permissions. Super user keys may bypass this on a request-by-request basis by setting the api_bypass_permissions parameter to 1.

The API always responds with JSON. Errors will always return a response code in the 400 range. Successful requests will return a 200 code. While not commonly used, redirects will return a 300-range code.

Errors always take a standard form. Here's an example:

JSON:
{
    "errors": [
        {
            "code": "api_key_not_found",
            "message": "API key provided in request was not found.",
            "params": []
        }
    ]
}

Note that more than one error may be returned (such as if you have multiple errors when creating a thread).

In most cases, the error code is the name of the phrase used by the error. The message itself may change and should not be used for automated processing.

For successful requests, the response data will vary by route, so let's talk more about the routes...

API routes

As this is a REST-style API, the content you access and the action taken are based on the URL and the HTTP method used. We primarily use GET, POST and DELETE.

There are numerous routes in the API so we won't go into most of them in detail here. Full details of the available routes and their inputs and outputs will be found in the API documentation. (There are tools built-in to help with automatic documentation generation that add-on developers can also use for their own API routes.)

Let's look at /api/threads/123/ as an example. When you request this URL, what happens depends on the method used:

  • GET - this will get information about the thread with ID 123. If you pass the with_posts parameter, we'll also include a page of posts.
  • POST - this will update the thread's information, such as the title or whether it's sticky. Any elements you don't want to update can be omitted as parameters. Note that this is not for replying to this thread.
  • DELETE - as you might expect, this allows you to delete the thread. Parameters can be passed in to control the type of deletion (hard/soft), the reason provided, etc.
Each route may also have more specific actions. For example, GET /api/threads/123/posts?page=2 will get the second page of posts for this thread.

Here is some example output from GET /api/threads/2/:

JSON:
{
    "thread": {
        "thread_id": 2,
        "node_id": 2,
        "title": "Test API thread",
        "reply_count": 2,
        "view_count": 19,
        "user_id": 1,
        "username": "Example",
        "post_date": 1538157093,
        "sticky": false,
        "discussion_state": "visible",
        "discussion_open": true,
        "discussion_type": "",
        "first_post_id": 3,
        "last_post_date": 1540908968,
        "last_post_id": 21,
        "last_post_user_id": 1,
        "first_post_reaction_score": 0,
        "prefix_id": 0,
        "Forum": {
            "node_id": 2,
            "title": "Main forum",
            "node_name": null,
            "description": "",
            "node_type_id": "Forum",
            "parent_node_id": 1,
            "display_order": 100,
            "display_in_list": true,
            "breadcrumbs": [
                {
                    "node_id": 1,
                    "title": "Main category",
                    "node_type_id": "Category"
                }
            ],
            "type_data": {
                "allow_posting": true,
                "allow_poll": true,
                "require_prefix": false,
                "min_tags": 0,
                "discussion_count": 6,
                "message_count": 20,
                "last_post_id": 22,
                "last_post_date": 1540982158,
                "last_post_username": "test",
                "last_thread_id": 3,
                "last_thread_title": "Test API thread",
                "last_thread_prefix_id": 0,
                "can_create_thread": true,
                "can_upload_attachment": true
            }
        },
        "User": {
            "user_id": 1,
            "username": "Example",
            "email": "[email protected]",
            "visible": true,
            "activity_visible": true,
            "user_group_id": 2,
            "secondary_group_ids": [
                3,
                4
            ],
            "message_count": 16,
            "register_date": 1536666416,
            "trophy_points": 0,
            "user_state": "valid",
            "is_moderator": true,
            "is_admin": true,
            "is_staff": true,
            "is_banned": false,
            "reaction_score": 0,
            "custom_title": "",
            "warning_points": 0,
            "is_super_admin": true,
            "user_title": "Administrator",
            "age": 43,
            "dob": {
                "year": 1975,
                "month": 1,
                "day": 1
            },
            "signature": "",
            "location": "Somewhere",
            "website": "http://xenforo.com",
            "last_activity": 1540988599,
            "avatar_urls": {
                "o": null,
                "h": null,
                "l": null,
                "m": null,
                "s": null
            },
            "custom_fields": {
                "skype": "",
                "facebook": "",
                "twitter": ""
            },
            "is_ignored": false,
            "is_following": false,
            "can_edit": true,
            "can_ban": false,
            "can_warn": false,
            "can_view_profile": true,
            "can_view_profile_posts": true,
            "can_post_profile": true,
            "can_follow": false,
            "can_ignore": false,
            "can_converse": false
        },
        "is_watching": true,
        "visitor_post_count": 3,
        "can_edit": true,
        "can_edit_tags": false,
        "can_reply": true,
        "can_soft_delete": true,
        "can_hard_delete": false,
        "can_view_attachments": true
    }
}

These are derived from the entity system. For the most part, the top-level entry is a Thread entity, but you can also see a node (Forum) and a User. The API structure of a particular entity is defined by opting-in to specific fields and by a new method that allows you to define other custom elements to include (such as the results of the various can_xyz outputs).

API internals

People you want to use the API won't need to understand how it works internally. However, add-on developers that want to expand the data that's returned or add their own actions will need to know a bit about how it's built. This will be a high level overview.

The API is defined via a new route type. The API system uses essentially the same routing system as public or admin URLs, though there are a few tweaks. By convention, threads/ would map to the XF:Threads controller and threads/123/ would map to XF:Thread. The latter's route is defined with a sub-name of - and the route format includes the + modifier which requires the parameter to be present. For ease, when link building, if data is passed in, we will attempt to automatically match the correct route.

When determining the name of an action to call, the HTTP request method will be prepended. For example, GET /api/threads/123/ would call XF:Thread and a method called actionGet(). Similarly, POST /api/threads/123/watch would call XF:Thread :: actionPostWatch().

API controller methods will generally return by calling the XF\Api\Controller\AbstractController::apiResult($result) method. This can receive an array, entity, or one of the API result objects. These will be converted into the necessary JSON representation as needed.

In most cases, your controller results will be made of up some structural elements and the API representation of entities. On a singular entity, you can call $entity->toApiResult() or $collection->toApiResults() on a collection. Both of these take arguments that allow you to pass in a "verbosity" level or other options to control the output level.

For entities, by default, they will throw an exception if you try to get their API representation. Entities must specifically opt-in to be representable in the API. Individual columns are opt-in as well.

To enable the API representation of an entity, you want to override the setupApiResultData() method in your entity type. For a very basic entity, this can be left empty. This method can include arbitrary code to determine what data to expose. For example, some of the results might be dependent on the visitor being registered:

PHP:
if ($visitor->user_id)
{
   $result->is_watching = isset($this->Watch[$visitor->user_id]);
   $result->visitor_post_count = $this->getUserPostCount();
}

Others might be the results from specific methods:

PHP:
$result->can_reply = $this->canReply();
$result->can_soft_delete = $this->canDelete();

Others might require specific permissions:

PHP:
if ($isBypassingPermissions || $visitor->hasAdminPermission('user'))
{
   $result->includeColumn(['email', 'user_group_id', 'secondary_group_ids', 'user_state']);
}

In the entity structure, you can define particular columns and relations as being included in the API results by tagging them as 'api' => true.

On the whole, extending the API shouldn't be significantly different from how you write "standard" XF2 code.

И это все на сегодня. Мы вернемся на следующей неделе с новыми обзорами! А так как мы приближаемся к концу нашей серии обзоров нововведений в XF2.1, то я думаю все будут рады (как и мы), что каждый сможет это опробовать вживую.
 
очень гибкий двиг, можно сделать что угодно.

PHP:
if (COption::GetOptionString("vote", "VOTE_COMPATIBLE_OLD_TEMPLATE", "Y") == "N")
    {
        unset($arDisplayOptions[2]);
        unset($arDisplayOptions[3]);
        unset($arDisplayOptions[4]);
        unset($arDisplayOptions[5]);
        unset($arDisplayOptions[6]);
    }
    elseif ($old_module_version=="Y")
    {
        unset($arDisplayOptions[6]);
    }
    else
    {
        unset($arDisplayOptions[2]);
        unset($arDisplayOptions[3]);
        unset($arDisplayOptions[4]);
        unset($arDisplayOptions[5]);
    }
Не, можно конечно все. Только саппортить это все дорого для заказчика. Как по программерам, так и по железу.
 
Smalesh, вы говорите о "говнокоде", о других косяках разработчиков, я ещё и не такое видел, и на разных движках, про вордпресс например можно написать наверное целый форум о его глюках, но это самый популярный движок.
Проще говоря - ну и что что он местами такой не нормальный, зато он выполняет задачи - приносит деньги, отображает информацию и т.п., делает то что должен, а стремиться к идеальному коду - дак тут жизнь закончится и не добьёшься идеальности.
 
Последнее редактирование:
Во вторник на офф форуме добавили информацию по API .
Помимо этого есть CLI команда по генерации документации и к своим плагинам.
 
Последнее редактирование:
Эх, жаль для поиска нет эндпоинта пока
 
Трамваищи, я в XF-разработке человек новый, ответьте мне пожалуйста на вопрос.

У XF реально такая даунская политика выдавать как можно меньше документации для разработчиков, или где то есть тома документации, только вот мне не по глазам.

Документация разработчика XF2 - сортирная бумажка, из которой примерно следует что можно даже аддоны разрабатывать (реально разрабатывать пришлось опираясь на исходный код XF2).

А вот теперь что касается сабжа. Второй день ищу документацию по API XF2 - очень интересует эндпоинт auth. Везде нахожу только 3 дебильных сообщения на форумах, о том что мне вернет threads (а то я сам этого посмотреть не смогу).

Или наговариваю я зря и ищу плохо?
 
Во вторник на офф форуме добавили информацию по API .
Помимо этого есть CLI команда по генерации документации и к своим плагинам.
На 1 сообщение подняться лень, как и доку почитать походу
 
Благодарю! То что надо!

Вот это мне надо было знать про метод auth "Only available to super user keys " и не более ?

На 1 сообщение подняться лень, как и доку почитать походу
да нет. ко второй странице я уже отчаялся найти документацию совсем) Отсюда неконтролируемый гнев.
 
Последнее редактирование модератором:
Современный облачный хостинг провайдер | Aéza
Назад
Сверху Снизу