Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
typo3
qfq
Commits
faf44578
Commit
faf44578
authored
Sep 06, 2020
by
Carsten Rose
Browse files
Fixes #11119 - restClient via ... AS _link
parent
b54975ba
Pipeline
#3779
passed with stages
in 4 minutes and 9 seconds
Changes
8
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Documentation/REST.rst
View file @
faf44578
...
...
@@ -38,8 +38,11 @@ REST
====
Via `REST <https://en.wikipedia.org/wiki/Representational_state_transfer>`_ it's possible to access the QFQ based
application. Each REST API endpoint has to be defined as a QFQ Form. The QFQ REST api implements the
four most used REST HTTP methods:
application. Each REST API endpoint has to be defined as a QFQ Form.
This describes the server side (=QFQ is server). For client access check :ref:`rest_client`.
The QFQ REST api implements the four most used REST HTTP methods:
GET - Read
Shows a list of database records or a single record. The QFQ form holds the definition which and what to show.
...
...
@@ -77,14 +80,14 @@ Endpoint
``<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/<level1>/<id1>/<level2>/<id2>/.../?<var1>=<value1>&...``
Append level names and ids after `.../rest.php/`, each separated by '/' .
Append level names and ids after
`
`.../rest.php/`
`
, each separated by '/' .
E.g.:
1. List of all persons: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person`
2. Data of person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123`
3. Adresses of person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address`
4. Adress details of address 45 from person 123: `<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/45`
1. List of all persons:
`
`<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person`
`
2. Data of person 123:
`
`<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123`
`
3. Adresses of person 123:
`
`<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address`
`
4. Adress details of address 45 from person 123:
`
`<domain>/typo3conf/ext/qfq/Classes/Api/rest.php/person/123/address/45`
`
QFQ 'Forms' are used as a 'container' (to define all details).
...
...
@@ -96,14 +99,14 @@ Only the last <level> of an URI will be processed. The former ones are just to f
.. note::
Each level name (=form name) is available via STORE_CLIENT and name `_formX`. E.g. in example
(1) `{{_form1:C:alnumx}}=person` and `{{_form2:C:alnumx}}=address`.
Each level name (=form name) is available via STORE_CLIENT and name
`
`_formX`
`
. E.g. in example
(1)
`
`{{_form1:C:alnumx}}=person`
`
and
`
`{{_form2:C:alnumx}}=address`
`
.
Each level id is available via STORE_CLIENT and name `_idX`. E.g. in example
(2) `{{_id1:C}}=123` and `{{_id2:C}}=45`.
(2)
`
`{{_id1:C}}=123`
`
and
`
`{{_id2:C}}=45`
`
.
Also the `id` after the last `level` in the URI path, 123 in example (2) and 45 in example (4), is copied to
variable `
r
` in STORE_TYPO3, access it via `{{r:T}}`.
Also the
`
`id`
`
after the last
`
`level`
`
in the URI path, 123 in example (2) and 45 in example (4), is copied to
variable `
`r`
` in STORE_TYPO3, access it via
`
`{{r:T}}`
`
.
GET - Read
...
...
@@ -124,8 +127,8 @@ list
There are *no* native-FormElements necessary or loaded. Action FormElements will be processed.
To simplify access to id parameter of the URI, a mapping is possible via 'form.parameter.restParam'.
E.g. `restParam=pId,adrId` with example d) makes `{{pId:C}}=123` and `{{adrId:C}}=45`. The order of variable
names corresponds to the position in the URI. `_id1` is always mapped to the first parameter name, `_id2` to
E.g.
`
`restParam=pId,adrId`
`
with example d) makes
`
`{{pId:C}}=123`
`
and
`
`{{adrId:C}}=45`
`
. The order of variable
names corresponds to the position in the URI.
`
`_id1`
`
is always mapped to the first parameter name,
`
`_id2`
`
to
the second one and so on.
GET Variables provided via URL are available via STORE_CLIENT as usual.
...
...
@@ -156,12 +159,12 @@ GET Variables provided via URL are available via STORE_CLIENT as usual.
+-------------------+----------------------------------------------------------------------------------+
| restParam | Optional. CSV list of variable names. E.g.: ``restParam=pId,adrId`` |
+-------------------+----------------------------------------------------------------------------------+
| restToken | Optional. User defined string or dynamic token (see :ref:`restAuthorization`).
|
| restToken | Optional. User defined string or dynamic token (see :ref:`
`
restAuthorization`
`
). |
+-------------------+----------------------------------------------------------------------------------+
.. note::
There are no :ref:`special-column-names` available in `restSqlData` or `restSqlList`. Also there are no
There are no :ref:`special-column-names` available in
`
`restSqlData`
`
or
`
`restSqlList`
`
. Also there are no
SIPs possible, cause REST typically does not offer sessions/cookies (which are necessary for SIPs).
...
...
@@ -305,7 +308,7 @@ exist. In case of multiple tokens, replace the static string against a SQL query
.. tip::
The HTML Header Authorization token is available in STORE_CLIENT via '`{{Authorization:C:alnumx}}`.
The HTML Header Authorization token is available in STORE_CLIENT via '`
`
{{Authorization:C:alnumx}}`
`
.
Best Practice: For example all created tokens are saved in a table 'Auth' with a column 'token'. Define::
...
...
Documentation/Report.rst
View file @
faf44578
...
...
@@ -880,8 +880,8 @@ Link Examples
.. _question:
Question
^^^^^^^^
Alert:
Question
^^^^^^^^
^^^^^^^
**Syntax**
...
...
@@ -1614,6 +1614,8 @@ API Call QFQ Report (e.g. AJAX)
General use API call to fire a specific QFQ tt-content record. Useful for e.g. AJAX calls. No Typo3 is involved.
*No FE-Group access control*.
This describes the client side (=QFQ is client). For server function check :ref:`restApi`.
Example QFQ record JS::
# Register SIP with given arguments.
...
...
@@ -1640,6 +1642,67 @@ Example QFQ record called by above AJAX::
10.sql = SELECT '{{arg1:S}} {{arg2:S}} {{arg3:C}} {{arg4:C}}', NOW()
.. _rest_client:
REST Client
^^^^^^^^^^^
.. note::
POST and GET data to external REST interfaces or other API services.
Access to external services via HTTP / HTTPS is triggered via special column name *restClient*. The received data might
be processed in subsequent calls.
Example::
# Retrieve information. Received data is delivered in JSON and decoded / copied on the fly to STORE_CLIENT
10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123' AS _restClient
20.sql = SELECT 'Status: {{http-status:C}}
<br>
Name: {{name:C:alnumx}}
<br>
Surname: {{surname:C:alnumx}}'
# Simple POST request via https. Result is printed on the page.
10.sql = SELECT 'n:https://www.dummy.ord/rest/person/id/123|method:POST|content:{"name":"John";"surname":"Doe"}' AS _restClient
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| Token | Example | Comment |
+===================+================================+============================================================================+
| n | n:https://www.dummy.ord/rest/person | |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| method | method:POST | GET or POST |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| content | content:{"name":"John";"surname":"Doe"} | Depending on the REST server JSON might be expected |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| header | *see below* | |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| timeout | timeout:5 | Default: 5 seconds. |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
| ssl | ssl:{"verify_peer":true,"allow_self_signed":false} | JSON config for SSL settings |
+-------------------+----------------------------------------------------+--------------------------------------------------------+
**Header**
* Each header must be separated by ``\r\n``.
* An explicit given header will overwrite the named default header.
* Default header:
* *content-type: application/json* - if *content* starts with a ``{``.
* *content-type: text/plain* - if *content* does not start with a ``{``.
* *connection: close* - Necessary for HTTP 1.1.
**Result received**
* After a *REST client* call is fired, QFQ will wait up to *timeout* seconds for the answer.
* By default, the whole received answer will be shown. To suppress the output: ``... AS '_restClient|_hide'``
* The variable ``{{http-status:C}}`` shows the `HTTP status code
<https:
//
en.wikipedia.org
/
wiki
/
List_of_HTTP_status_codes
>
`_.
A value starting with '2..' shows success.
* In case of an error, ``{{error-message:C:allbut}}`` shows some details.
* In case the returned answer is a valid JSON string, it's automatically copied STORE_CLIENT with corresponding key names.
JSON answer example::
Answer from Server: { 'name' : 'John'; 'street': 'Milky road' }
Retrieve the values via: {{name:C:alnumx}}, {{street:C:alnumx}}
.. _special-sql-functions:
Special SQL Functions (prepared statements)
...
...
extension/Classes/Api/rest.php
View file @
faf44578
...
...
@@ -11,7 +11,6 @@ namespace IMATHUZH\Qfq\Api;
require_once
(
__DIR__
.
'/../../vendor/autoload.php'
);
use
IMATHUZH\Qfq\Core\QuickFormQuery
;
use
IMATHUZH\Qfq\Core\Helper\OnString
;
$restId
=
array
();
...
...
extension/Classes/Core/Constants.php
View file @
faf44578
...
...
@@ -1605,6 +1605,7 @@ const COLUMN_MAILTO = "mailto";
const
COLUMN_SENDMAIL
=
"sendmail"
;
const
COLUMN_VERTICAL
=
"vertical"
;
const
COLUMN_WEBSOCKET
=
"websocket"
;
const
COLUMN_REST_CLIENT
=
"restClient"
;
const
COLUMN_NO_WRAP
=
"noWrap"
;
const
COLUMN_HIDE
=
"hide"
;
...
...
@@ -1761,6 +1762,7 @@ const TOKEN_DOWNLOAD = 'd';
const
TOKEN_COPY_TO_CLIPBOARD
=
'y'
;
const
TOKEN_DROPDOWN
=
'z'
;
const
TOKEN_WEBSOCKET
=
'w'
;
const
TOKEN_REST_CLIENT
=
'n'
;
const
TOKEN_TEXT
=
't'
;
const
TOKEN_ALT_TEXT
=
'a'
;
...
...
@@ -1810,6 +1812,12 @@ const TOKEN_L_APPEND = 'append';
const
TOKEN_L_INTERVAL
=
'interval'
;
const
TOKEN_L_HTML_ID
=
'htmlId'
;
const
TOKEN_L_METHOD
=
'method'
;
const
TOKEN_L_HEADER
=
'header'
;
const
TOKEN_L_CONTENT
=
'content'
;
const
TOKEN_L_TIMEOUT
=
'timeout'
;
const
TOKEN_L_SSL
=
'ssl'
;
const
MONITOR_MODE_APPEND_0
=
'0'
;
const
MONITOR_MODE_APPEND_1
=
'1'
;
const
MONITOR_SESSION_FILE_SEEK
=
'monitor-seek-file'
;
...
...
@@ -2009,3 +2017,5 @@ const I_ATTRIBUTE = 'attribute';
const
I_CHECKED
=
'checked'
;
const
I_UNCHECKED
=
'unchecked'
;
const
HTTP_STATUS
=
'http-status'
;
const
ERROR_MESSAGE
=
'error-message'
;
\ No newline at end of file
extension/Classes/Core/Database/Database.php
View file @
faf44578
...
...
@@ -8,7 +8,6 @@
namespace
IMATHUZH\Qfq\Core\Database
;
use
IMATHUZH\Qfq\Core\Helper\BindParam
;
use
IMATHUZH\Qfq\Core\Helper\HelperFile
;
use
IMATHUZH\Qfq\Core\Helper\HelperFormElement
;
...
...
extension/Classes/Core/Report/Link.php
View file @
faf44578
...
...
@@ -58,7 +58,7 @@ use IMATHUZH\Qfq\Core\Store\Store;
* L:
* m:mailto
* M:Mode
* n:
* n:
GET/POST Rest Call
* N:new
* o:ToolTip
* O:Monitor
...
...
@@ -518,6 +518,16 @@ class Link {
return
$this
->
renderLink
(
KeyValueStringParser
::
unparse
(
$paramArr
,
PARAM_TOKEN_DELIMITER
,
PARAM_DELIMITER
));
}
/**
* @param $str
* @return string
* @throws \UserFormException
* @throws \UserReportException
*/
public
function
processRestClient
(
$str
)
{
}
/**
* @param $str
* @return string
...
...
@@ -599,6 +609,11 @@ class Link {
case
TOKEN_WEBSOCKET
:
return
$this
->
processWebSocket
(
$str
);
break
;
case
TOKEN_REST_CLIENT
:
$restClient
=
new
RestClient
();
return
$restClient
->
process
(
$str
);
break
;
default
:
break
;
}
...
...
extension/Classes/Core/Report/Report.php
View file @
faf44578
...
...
@@ -939,6 +939,7 @@ class Report {
switch
(
$columnName
)
{
case
COLUMN_LINK
:
case
COLUMN_WEBSOCKET
:
case
COLUMN_REST_CLIENT
:
$content
.
=
$this
->
link
->
renderLink
(
$columnValue
);
break
;
...
...
extension/Classes/Core/Report/RestClient.php
0 → 100644
View file @
faf44578
<?php
namespace
IMATHUZH\Qfq\Core\Report
;
use
Exception
;
use
IMATHUZH\Qfq\Core\Helper\KeyValueStringParser
;
use
IMATHUZH\Qfq\Core\Store\Store
;
/**
* Class RestClient
* @package IMATHUZH\Qfq\Core\Report
*/
class
RestClient
{
/**
* @var Store
*/
private
$store
=
null
;
/**
* RestClient constructor.
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public
function
__construct
()
{
$this
->
store
=
Store
::
getInstance
();
}
/**
* @param string $str
* @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public
function
process
(
$str
)
{
$recvBuffer
=
''
;
$recv
=
array
();
$param
=
$this
->
parseArgument
(
$str
);
$options
=
array
(
'http'
=>
array
(
'header'
=>
$param
[
TOKEN_L_HEADER
],
'method'
=>
strtoupper
(
$param
[
TOKEN_L_METHOD
]),
'timeout'
=>
$param
[
TOKEN_L_TIMEOUT
],
)
);
if
(
isset
(
$param
[
TOKEN_L_SSL
]))
{
$options
[
'ssl'
]
=
json_decode
(
$param
[
TOKEN_L_SSL
]);
}
// Add content only if there is one.
if
(
!
empty
(
$param
[
TOKEN_L_CONTENT
]))
{
$options
[
'http'
][
'content'
]
=
$param
[
TOKEN_L_CONTENT
];
}
$context
=
stream_context_create
(
$options
);
try
{
if
(
false
===
(
$recvBuffer
=
file_get_contents
(
$param
[
TOKEN_REST_CLIENT
],
false
,
$context
)))
{
$recv
[
HTTP_STATUS
]
=
400
;
$recv
[
ERROR_MESSAGE
]
=
implode
(
", "
,
$http_response_header
);
}
else
{
// If $recBuffer is no json - don't care, $recv will be null then.
$recv
=
json_decode
(
$recvBuffer
,
true
);
$recv
[
HTTP_STATUS
]
=
200
;
}
}
catch
(
Exception
$e
)
{
$recvBuffer
=
''
;
$recv
[
HTTP_STATUS
]
=
$e
->
getCode
();
$recv
[
ERROR_MESSAGE
]
=
$e
->
getMessage
();
}
// Copy new values to STORE_CLIENT
$this
->
store
::
setStore
(
$recv
,
STORE_CLIENT
,
true
);
return
$recvBuffer
;
}
/**
* Parses $str, fill some defaults and returns an array with given arguments.
*
* @param string $str
* @return array
* @throws \UserFormException
* @throws \UserReportException
*/
private
function
parseArgument
(
$str
)
{
// "n:http://antmedia-dev.math.uzh.ch/WebRTCAppEE/rest/v2/broadcasts/create|
// content:{'streamId' => "ASDKLJfdlajfhdkhH"}|method:POST|header:Content-type: application/json\r\n"
// Split string
$param
=
KeyValueStringParser
::
parse
(
$str
,
PARAM_TOKEN_DELIMITER
,
PARAM_DELIMITER
);
if
(
empty
(
$param
[
TOKEN_REST_CLIENT
]))
{
throw
new
\
UserReportException
(
"Missing RestClient target"
,
ERROR_MISSING_VALUE
);
}
$param
[
TOKEN_L_CONTENT
]
=
trim
(
$param
[
TOKEN_L_CONTENT
]);
$param
[
TOKEN_L_HEADER
]
=
trim
(
$param
[
TOKEN_L_HEADER
]);
if
(
empty
(
$param
[
TOKEN_L_METHOD
]))
{
$param
[
TOKEN_L_METHOD
]
=
'GET'
;
}
// Default Timeout
if
(
empty
(
$param
[
TOKEN_L_TIMEOUT
]))
{
$param
[
TOKEN_L_TIMEOUT
]
=
5
;
}
// If 'Host' is missing in header: define - useful for Firewall/ Proxy
// CR: if a header 'host' is given, REST calls fails always.
// $header = KeyValueStringParser::parse($param[TOKEN_L_HEADER], ':', '\r\n');
// if (empty($header['host'])) {
// $urlParts = parse_url($param[TOKEN_REST_CLIENT]);
// $header['host'] = $urlParts['host'];
// }
// If 'Content-type' is missing in header: define.
if
(
empty
(
$header
[
'content-type'
]))
{
// Poor man guess: if no 'content-type' is explicit given and string starts with '{' >> 'application/json'
$mime
=
((
$param
[
TOKEN_L_CONTENT
][
0
]
??
''
)
==
'{'
)
?
'application/json'
:
'text/plain'
;
$header
[
'content-type'
]
=
$mime
.
'; charset: utf-8'
;
}
// If 'Connection' is missing in Header: define
if
(
empty
(
$header
[
'connection'
]))
{
$header
[
'connection'
]
=
'close'
;
}
// Join all header arguments to one string
$param
[
TOKEN_L_HEADER
]
=
KeyValueStringParser
::
unparse
(
$header
,
': '
,
'\r\n'
)
.
'\r\n'
;
return
$param
;
}
}
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment