Unverified Commit 2907860c authored by Adrien Clairembault's avatar Adrien Clairembault Committed by GitHub
Browse files

Massive actions API

parent a6377269
......@@ -24,6 +24,10 @@
* [Add item(s)](#add-items)
* [Update item(s)](#update-items)
* [Delete item(s)](#delete-items)
* [Get available massive actions for an itemtype](#get-available-massive-actions-for-an-itemtype)
* [Get available massive actions for an item](#get-available-massive-actions-for-an-item)
* [Get massive action parameters](#get-massive-action-parameters)
* [Apply massive action](#apply-massive-action)
* [Special cases](#special-cases)
* [Errors](#errors)
* [Servers configuration](#servers-configuration)
......@@ -1220,6 +1224,336 @@ $ curl -X DELETE \
[{"16":true, "message": ""},{"17":false, "message": "Item not found"}]
```
## Get available massive actions for an itemtype
* **URL**: apirest.php/getMassiveActions/:itemtype/
* **Description**: Show the availables massive actions for a given itemtype.
* **Method**: GET
* **Parameters**: (Headers)
* *Session-Token*: session var provided by [initSession](#init-session) endpoint. Mandatory.
* *App-Token*: authorization string provided by the GLPI API configuration. Optional.
* **Parameters**: (query string)
* *is_deleted* (default false): Show specific actions for items in the trashbin.
* **Returns**:
* 200 (OK).
* 400 (Bad Request) with a message indicating an error in input parameter.
* 401 (UNAUTHORIZED).
Example usage (CURL):
```bash
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActions/Computer'
< 200 OK
[
{
"key": "MassiveAction:update",
"label": "Update"
},
{
"key": "MassiveAction:clone",
"label": "Clone"
},
{
"key": "Infocom:activate",
"label": "Enable the financial and administrative information"
},
{
"key": "MassiveAction:delete",
"label": "Put in trashbin"
},
{
"key": "MassiveAction:add_transfer_list",
"label": "Add to transfer list"
},
{
"key": "Appliance:add_item",
"label": "Associate to an appliance"
},
{
"key": "Item_OperatingSystem:update",
"label": "Operating systems"
},
{
"key": "Computer_Item:add",
"label": "Connect"
},
{
"key": "Item_SoftwareVersion:add",
"label": "Install"
},
{
"key": "KnowbaseItem_Item:add",
"label": "Link knowledgebase article"
},
{
"key": "Document_Item:add",
"label": "Add a document"
},
{
"key": "Document_Item:remove",
"label": "Remove a document"
},
{
"key": "Contract_Item:add",
"label": "Add a contract"
},
{
"key": "Contract_Item:remove",
"label": "Remove a contract"
},
{
"key": "MassiveAction:amend_comment",
"label": "Amend comment"
},
{
"key": "MassiveAction:add_note",
"label": "Add note"
},
{
"key": "Lock:unlock",
"label": "Unlock components"
}
]
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActions/Computer?is_deleted=1'
< 200 OK
[
{
"key": "MassiveAction:purge_item_but_devices",
"label": "Delete permanently but keep devices"
},
{
"key": "MassiveAction:purge",
"label": "Delete permanently and remove devices"
},
{
"key": "MassiveAction:restore",
"label": "Restore"
},
{
"key": "Lock:unlock",
"label": "Unlock components"
}
]
```
## Get available massive actions for an item
* **URL**: apirest.php/getMassiveActions/:itemtype/:id
* **Description**: Show the availables massive actions for a given item.
* **Method**: GET
* **Parameters**: (Headers)
* *Session-Token*: session var provided by [initSession](#init-session) endpoint. Mandatory.
* *App-Token*: authorization string provided by the GLPI API configuration. Optional.
* **Parameters**: (query string)
* **Returns**:
* 200 (OK).
* 400 (Bad Request) with a message indicating an error in input parameter.
* 401 (UNAUTHORIZED).
Example usage (CURL):
```bash
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActions/Computer/3'
< 200 OK
[
{
"key": "MassiveAction:update",
"label": "Update"
},
{
"key": "MassiveAction:clone",
"label": "Clone"
},
{
"key": "Infocom:activate",
"label": "Enable the financial and administrative information"
},
{
"key": "MassiveAction:delete",
"label": "Put in trashbin"
},
{
"key": "MassiveAction:add_transfer_list",
"label": "Add to transfer list"
},
{
"key": "Appliance:add_item",
"label": "Associate to an appliance"
},
{
"key": "Item_OperatingSystem:update",
"label": "Operating systems"
},
{
"key": "Computer_Item:add",
"label": "Connect"
},
{
"key": "Item_SoftwareVersion:add",
"label": "Install"
},
{
"key": "KnowbaseItem_Item:add",
"label": "Link knowledgebase article"
},
{
"key": "Document_Item:add",
"label": "Add a document"
},
{
"key": "Document_Item:remove",
"label": "Remove a document"
},
{
"key": "Contract_Item:add",
"label": "Add a contract"
},
{
"key": "Contract_Item:remove",
"label": "Remove a contract"
},
{
"key": "MassiveAction:amend_comment",
"label": "Amend comment"
},
{
"key": "MassiveAction:add_note",
"label": "Add note"
},
{
"key": "Lock:unlock",
"label": "Unlock components"
}
]
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActions/Computer/4'
< 200 OK
[
{
"key": "MassiveAction:purge_item_but_devices",
"label": "Delete permanently but keep devices"
},
{
"key": "MassiveAction:purge",
"label": "Delete permanently and remove devices"
},
{
"key": "MassiveAction:restore",
"label": "Restore"
},
{
"key": "Lock:unlock",
"label": "Unlock components"
}
]
```
## Get massive action parameters
* **URL**: apirest.php/getMassiveActionParameters/:itemtype/
* **Description**: Show the availables parameters for a given massive action.
* Warning: experimental endpoint, some required parameters may be missing from the returned content.
* **Method**: GET
* **Parameters**: (Headers)
* *Session-Token*: session var provided by [initSession](#init-session) endpoint. Mandatory.
* *App-Token*: authorization string provided by the GLPI API configuration. Optional.
* **Parameters**: (query string)
* **Returns**:
* 200 (OK).
* 400 (Bad Request) with a message indicating an error in input parameter.
* 401 (UNAUTHORIZED).
Example usage (CURL):
```bash
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActionParameters/Computer/MassiveAction:add_note'
< 200 OK
[
{
"name": "add_note",
"type": "text"
}
]
$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/getMassiveActionParameters/Computer/Contract_Item:add'
< 200 OK
[
{
"name": "peer_contracts_id",
"type": "dropdown"
}
]
```
## Apply massive action
* **URL**: apirest.php/applyMassiveAction/:itemtype/
* **Description**: Run the given massive action
* **Method**: POST
* **Parameters**: (Headers)
* *Session-Token*: session var provided by [initSession](#init-session) endpoint. Mandatory.
* *App-Token*: authorization string provided by the GLPI API configuration. Optional.
* **Parameters**: (json payload)
* *ids* items to execute the action on. Mandatory.
* *specific action parameters* some actions require specific parameters to run. You can check them through the 'getMassiveActionParameters' endpoint.
* **Returns**:
* 200 (OK) All items were processed.
* 207 (Multi-Status) Not all items were successfully processed.
* 400 (Bad Request) with a message indicating an error in input parameter.
* 401 (UNAUTHORIZED).
* 422 (Unprocessable Entity) All items failed to be processed.
Example usage (CURL):
```bash
$ curl -X POST \
-H 'Content-Type: application/json' \
-H "Session-Token: 83af7e620c83a50a18d3eac2f6ed05a3ca0bea62" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
-d '{"ids": [2, 3], "input": {"amendment": "newtext"}}'
'http://path/to/glpi/apirest.php/applyMassiveAction/Computer/MassiveAction:amend_comment'
< 200 OK
{
"ok": 2,
"ko": 0,
"noright": 0,
"messages": []
}
```
## Special cases
### Upload a document file
......@@ -1436,6 +1770,16 @@ Check the GLPI logs files (in files/_logs directory).
Some of the objects you want to delete triggers an error, maybe a missing field or rights.
You'll find with this error, a collection of results.
### ERROR_MASSIVEACTION_KEY
Missing or invalid massive action key.
Run 'getMassiveActions' endpoint to see available keys.
### ERROR_MASSIVEACTION_NO_IDS
No ids supplied when trying to run a massive action.
## Servers configuration
By default, you can use <http://path/to/glpi/apirest.php> without any additional configuration.
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5da1f9a432394fae361acf06110e3dd0",
"content-hash": "9526dfe06bfdd3d92a73678959de782e",
"packages": [
{
"name": "blueimp/jquery-file-upload",
......@@ -230,7 +230,8 @@
"bin": [
"bin/convert",
"bin/build_hw_jsons",
"bin/refresh_hw_sources"
"bin/refresh_hw_sources",
"bin/validate"
],
"type": "library",
"extra": {
......@@ -3803,6 +3804,81 @@
],
"time": "2021-03-23T23:28:01+00:00"
},
{
"name": "symfony/dom-crawler",
"version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "55fff62b19f413f897a752488ade1bc9c8a19cdd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/55fff62b19f413f897a752488ade1bc9c8a19cdd",
"reference": "55fff62b19f413f897a752488ade1bc9c8a19cdd",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"masterminds/html5": "<2.6"
},
"require-dev": {
"masterminds/html5": "^2.6",
"symfony/css-selector": "^4.4|^5.0"
},
"suggest": {
"symfony/css-selector": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases DOM navigation for HTML and XML documents",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dom-crawler/tree/v5.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-05-26T17:43:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.23.0",
......
......@@ -40,6 +40,7 @@ namespace Glpi\Api;
use APIClient;
use Auth;
use Change;
use CommonDBTM;
use CommonDevice;
use CommonITILObject;
use Config;
......@@ -50,6 +51,7 @@ use Html;
use Infocom;
use Item_Devices;
use Log;
use MassiveAction;
use Michelf\MarkdownExtra;
use NetworkEquipment;
use NetworkPort;
......@@ -60,6 +62,7 @@ use SavedSearch;
use Search;
use Session;
use Software;
use Symfony\Component\DomCrawler\Crawler;
use Ticket;
use Toolbox;
use User;
......@@ -2891,4 +2894,223 @@ abstract class API {
public function isDeprecated(): bool {
return $this->deprecated_item !== null;
}
/**
* "getMassiveActions" endpoint.
* Return possible massive actions for a given item or itemtype.
*
* @param string $itemtype Itemtype for which to show possible massive actions
* @param int $id If >0, will load given item and restrict massive actions to this item
* @param bool $is_deleted Should we show massive action in "deleted" mode ?
* @return array
*/
public function getMassiveActions(
string $itemtype,
?int $id = null,
bool $is_deleted = false
) {
if (is_null($id)) {
// No id supplied, show massive actions for the given itemtype
$actions = $this->getMassiveActionsForItemtype(
$itemtype,
$is_deleted
);
} else {
$item = new $itemtype();
if (!$item->getFromDB($id)) {
// Id was supplied but item can't be loaded -> error
return $this->returnError(
"Failed to load item (itemtype = '$itemtype', id = '$id')",
400,
"ERROR_ITEM_NOT_FOUND"
);
}
// Id supplied and item was loaded, show massive action for this
// specific item
$actions = $this->getMassiveActionsForItem($item);
}
// Build response array
$response = [];
foreach ($actions as $key => $label) {
$response[] = [
'key' => $key,
'label' => $label,
];
}
$this->returnResponse($response);
}
/**
* Return possible massive actions for a given itemtype.
*
* @param string $itemtype Itemtype for which to show possible massive actions
* @param bool $is_deleted Should we show massive action in "deleted" mode ?
* @return array
*/
public function getMassiveActionsForItemtype(
string $itemtype,
bool $is_deleted = false
): array {
// Return massive actions for a given itemtype
return MassiveAction::getAllMassiveActions($itemtype, $is_deleted);
}
/**
* Return possible massive actions for a given item.
*
* @param CommonDBTM $item Item for which to show possible massive actions
* @return array
*/
public function getMassiveActionsForItem(CommonDBTM $item): array {
// Return massive actions for a given item
return MassiveAction::getAllMassiveActions(
$item::getType(),
$item->isDeleted(),
$item,
true
);
}
/**
* "getMassiveActionParameters" endpoint.
* Return required parameters for a given massive action key.
*
* @param string $itemtype Target itemtype
* @param string|null $action_key Target massive action
* @param bool $is_deleted Is this massive action to be used on items in the trashbin ?
* @return array
*/
public function getMassiveActionParameters(
string $itemtype,
?string $action_key,
bool $is_deleted
) {
if (is_null($action_key)) {
return $this->returnError(
"Missing action key, run 'getMassiveActions' endpoint to see available keys",
400,
"ERROR_MASSIVEACTION_KEY"
);
}
$action = explode(':', $action_key);
if (($action[1] ?? "") == 'update') {
// Specific case, update form call "exit" function so we don't want to run the actual code
return $this->returnResponse([]);
}
$actions = MassiveAction::getAllMassiveActions($itemtype, false, null, $is_deleted);
if (!isset($actions[$action_key])) {
return $this->returnError(
"Invalid action key parameter, run 'getMassiveActions' endpoint to see available keys",
400,
"ERROR_MASSIVEACTION_KEY"
);
}
// Get massive action for the given key
$ma = new MassiveAction([
'action' => $action_key,
'actions' => $actions,
'items' => [],
'is_deleted' => $is_deleted
], [], 'specialize');
// Capture form display
ob_start();
$ma->showSubForm();
$html = ob_get_clean();
// Parse html to find all non hidden inputs, textareas and select
$inputs = [];
$crawler = new Crawler($html);
$crawler->filterXPath('//input')->each(function (Crawler $node, $i) use (&$inputs) {
if ($node->attr('type') != "hidden") {
$inputs[] = [
'name' => $node->attr('name'),
'type' => $node->attr('type'),
];
}
});
$crawler->filterXPath('//select')->each(function (Crawler $node, $i) use (&$inputs) {
$type = 'select';