1.8 Alpha Api - Add Media to Region of Layout

I was looking through the swagger documentation trying to figure out how to add media to a region of a layout. I did not see a way to do that yet. Am I correct that this has just not been documented yet?

I went through the documentation again and it looks like a playlist needs to be created, then the playlist assigned to a region and the region assigned to a layout. If you take that one step farther, you can then assign the layout to a campaign.

Does that mean that Regions can be assigned to more than one Layout? Also, does that mean that a playlist can be assigned to multiple regions?

Update: Answered the question about assigning a region to multiple layouts, as seen in the swagger documentation under:
POST /playlist/library/assign/{playlistId}

It looks like it is possible

But I do not see a way to remove content from a playlist. Does this mean that when you want to make a change to a playlist that you will need to recreate the playlist/upload all the data for the playlist again?

Update 2: When trying to add media to a playlist I get:

( ! ) Fatal error: Uncaught exception 'GuzzleHttp\Exception\ClientException' with message 'Client error: 422' in C:\wamp\www\xibo_interface_c\3rdParty\oauth2-xibo-cms-master\vendor\guzzlehttp\guzzle\src\Middleware.php on line 69
( ! ) GuzzleHttp\Exception\ClientException: Client error: 422 in C:\wamp\www\xibo_interface_c\3rdParty\oauth2-xibo-cms-master\vendor\guzzlehttp\guzzle\src\Middleware.php on line 69
Call Stack
#	Time	Memory	Function	Location
1	0.0009	214152	{main}( )	..\index.php:0
2	0.0152	824648	include( 'C:\wamp\www\xibo_interface_c\client_portal.php' )	..\index.php:605
3	0.0246	854192	include( 'C:\wamp\www\xibo_interface_c\active_media.php' )	..\client_portal.php:127
4	0.0294	930432	updateContractMedia( )	..\active_media.php:120
5	0.0410	944712	postPlaylistLibrary( )	..\database_insert_request.php:414
6	0.0422	945504	callService( )	..\index.php:564
7	0.0588	1299624	GuzzleHttp\Client->request( )	..\index.php:357
8	0.0825	1617808	GuzzleHttp\Promise\Promise->wait( )	..\Client.php:129.

$playListId:
int 3

$mediaArray:
array (size=4)
0 => int 57
1 => int 57
2 => int 57
3 => int 61

Code index.php, Line 564 is the last line. (Same code structure works fine on all other api calls):

   $params = array(
		'api_method' => 'POST',
		'api_path' => 'api/playlist/library/assign/'.$playListId,
		'api_data' => [
			'form_params' =>[
				'media' => $mediaArray
			]
		]
	);
	callService($params, true);

The following code is working fine for all other api calls:
indent preformatted text by 4 spaces

function callService($params, $echo = false) {
	if(isset($_SESSION['user_id']) && isset($params['api_path'])){
	
		$client = new Client();
		if(!empty($params['info'])) {

			$request = $client->request($params['api_method'], SERVER_BASE.'/'.$params['api_path'], [
					'headers' => [
						'Authorization' => 'Bearer '.$_SESSION['client_access_token']
					],
					
					'multipart' => [
						[
							'name' => 'name[]',
							'contents' => basename($params['info']['uri'])
						],
						[
							'name' => 'files[]',
							'contents' => fopen($params['info']['uri'], 'r')
						]
					]
				]
			);		
		
		}else{
			
			$request = $client->request($params['api_method'], SERVER_BASE.'/'.$params['api_path'], [
					'headers' => [
						'Authorization' => 'Bearer '.$_SESSION['client_access_token']
					],
						$params['api_data']
				]
			);
		}

		$full_response = $request;
		
		$GLOBALS['statusCode'] = $request->getStatusCode();
		$GLOBALS['responseMessage'] = $request->getStatusCode().' '.$request->getReasonPhrase();
		$GLOBALS['return'] = $full_response->getBody();
	}
}

I will attempt to explain :smiley:

Layouts/Regions are one to many - one layout can have many regions, but a region can only be on one layout.

In 1.7 media was assigned to a region, in 1.8 media is assigned to a playlist and a playlist assigned to a region. Regions/Playlists are many to many - but the UI only supports 1 to 1 (one region and one playlist), which mimics the original behaviour.

We hope to have a full implementation of regions/playlists available in 1.8.0-alpha2.


POST /playlist/library/assign/{playlistId} is used to assign library items to a Playlist - the UI behaviour for this is when you are Editing a Timeline and you select library, pick a number of files and click assign.

Assigning anything to a playlist creates a widget which is an instance of a module.

Removing something from a playlist is deleting a widget, editing is editing a widget, etc. Likewise adding some text, ticker, etc is adding a widget.


The API does support adding widgets and its actually quite simple, but I am not sure how to document it at the moment and hence it doesn’t appear in swagger.json.

It is difficult to document because while each operation goes through the same API route, the parameters that are required are different for each type of module.

The routes available are:

POST /playlist/widget/{moduleType}/{playlistId}
PUT /playlist/widget/{widgetId}
DELETE /playlist/widget/{widgetId}
PUT /playlist/widget/transition/{moduleType}/{widgetId}

As you can see, once you have posted a new widget to a playlist you then interact with it using the widgetId. The return from the post gives you this widgetId.


Once we have implemented the full set of features for playlists there will be API calls in layout for assigning, ordering and unassigning playlists.

Regarding your error - 422 means it thinks one of the arguments is invalid - this should be logged CMS side?

Forgive me if I am not understanding this. I see in the swagger file that the response to POST /playlist/library/assign/{playlistId} shows that it can return an array of :

Playlist { 
	regions {

		regionId,
		layoutId,
		ownerId,
		name,
		width,
		height, 
		top, 
		left,
		zIndex,
		playlists(array),
		regionOptions(array),
		permissions(array),
		displayOrder,
		duration
	}
}

I see a playlist array shown on the return. :confused: Am I just looking at a future use option? Or is this some how tied to what is to become the ability to assign a region/playlist to multiple layouts?

I understand… No Worries

Need some sort of categorized widget handling

Overall it sounds like we just need to be patient :grin: We love what we are seeing and are grateful for your work.

Yeah I would think so too base on the log saying “Please provide Media to Assign”:

12	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	Storage rollback.
11	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	Please provide Media to Assign
10	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	SQL = SELECT playlist.* FROM `playlist` WHERE 1 = 1 AND playlistId = :playlistId . Params = array ( 'playlistId' => 3, ).
9	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	SQL = SELECT `group`.group, `group`.groupId, `group`.isUserSpecific, `group`.isEveryone, `group`.libraryQuota FROM `group` WHERE 1 = 1 AND `group`.groupId IN (SELECT groupId FROM `lkusergroup` WHERE userId = :userId) AND isUserSpecific = :isUserSpecific AND isEveryone = :isEveryone . Params = array ( 'userId' => 1, 'isUserSpecific' => 0, 'isEveryone' => 0, ).
8	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	Loading 1. All Objects = 0
7	e22a8ea	2015-11-05 21:44	API	POST	DEBUG		/playlist/library/assign/:id	SQL = SELECT `user`.userId, userName, userTypeId, loggedIn, email, `user`.homePageId, pages.title AS homePage, lastAccessed, newUserWizard, retired, CSPRNG, UserPassword AS password, group.groupId, group.group, IFNULL(group.libraryQuota, 0) AS libraryQuota FROM `user` INNER JOIN lkusergroup ON lkusergroup.userId = user.userId INNER JOIN `group` ON `group`.groupId = lkusergroup.groupId AND isUserSpecific = 1 LEFT OUTER JOIN `pages` ON pages.pageId = `user`.homePageId WHERE 1 = 1 AND user.userId = :userId ORDER BY userName. Params = array ( 'userId' => 1, ).

The media was provided, but the code doesn’t seem to know about it. It is suppose to be an array of the media id’s stored in the Xibo Media database correct? (As shown above)

So far I have only been able to trace this back to:
\Lib\Controller\Playlist.php Line 306:

$media = Sanitize::getIntArray('media');

I have not been able to figure out yet how to trace the Media variable from it being sent to the API to where the API picks it up.

From what I can tell we are passing what the swagger doc says and in the format it says.

If we replace line 306 with say this:

$media = array(11);

It works. So I guess for us the problem is either in the format we are passing the data, or somewhere in the sanitization.php the array’s format is changed to something it doesn’t like.

It doesn’t look like ‘media’ has been specified.

It returns you the entire Playlist object - i.e. this is the current structure of the playlist - as it happens it might be necessary to change that as the response will grow and grow as things are added to the playlist.

The regions are the regions that the playlist is linked to - at the moment this will only ever be one (until we complete the functionality).

I would imagine it is the data format - the UI passes it like this:

$.ajax({
    type: "post",
    url: url,
    dataType: "json",
    data: {media: media},
    success: XiboSubmitResponse
});

I must admit, I can’t see anything wrong with your code - according to the Guzzle docs:

form_params: (array) Associative array of form field names to values
where each value is a string or array of strings. Sets the Content-Type
header to application/x-www-form-urlencoded when no Content-Type header
is already present.

You are (in effect) providing an array of strings in this case, which will be passed through intval in the CMS.

Perhaps you can capture a more general log at the CMS end? Add the below into Playlist.php, just before it tries to get the media ids

Log::debug(var_export($this->getApp()->request()->params(), true));

Does not look to help much, except to show the array is empty:

13	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	Storage rollback.
12	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	Please provide Media to Assign
11	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	array ( )
10	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	SET @playlistId=4; SELECT playlist.* FROM `playlist` WHERE 1 = 1 AND playlistId = @playlistId
9	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	SET @userId=1; SET @isUserSpecific=0; SET @isEveryone=0; SELECT `group`.group, `group`.groupId, `group`.isUserSpecific, `group`.isEveryone, `group`.libraryQuota FROM `group` WHERE 1 = 1 AND `group`.groupId IN (SELECT groupId FROM `lkusergroup` WHERE userId = @userId) AND isUserSpecific = @isUserSpecific AND isEveryone = @isEveryone
8	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	Loading 1. All Objects = 0
7	6ff5ac2	2015-11-10 08:39	API	POST	DEBUG		/playlist/library/assign/:id	SET @userId=1; SELECT `user`.userId, userName, userTypeId, loggedIn, email, `user`.homePageId, pages.title AS homePage, lastAccessed, newUserWizard, retired, CSPRNG, UserPassword AS password, group.groupId, group.group, IFNULL(group.libraryQuota, 0) AS libraryQuota FROM `user` INNER JOIN lkusergroup ON lkusergroup.userId = user.userId INNER JOIN `group` ON `group`.groupId = lkusergroup.groupId AND isUserSpecific = 1 LEFT OUTER JOIN `pages` ON pages.pageId = `user`.homePageId WHERE 1 = 1 AND user.userId = @userId ORDER BY userName

Dan, can you please tell me where the CMS is populating ‘media’ in this line?
Line 306 from \Lib\Controller\Playlist.php

From what I can tell it is just the media from params(‘media’).

We found that if we try to pass form_params via multipart we do see the params(‘media’) but it is still blank, and the documentation for Guzzle says that method is not supported.

Setting the debug option to true on the guzzle request gave us this:

Warning: curl_setopt_array(): cannot represent a stream of type Output as a STDIO FILE* in C:\wamp\www\xibo_interface_c\3rdParty\oauth2-xibo-cms-master\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php on line 57

Not sure what that means yet.

Updated: Searched Google for the error message and found exactly 1 match, and even that did not look promising. I am pulling my hair out on this one. :older_man: I even tried to update guzzle to the dev version to see if that made a difference. Just not sure what to think here. The form_params are passed, but params() always comes up empty. I think this is a Guzzle problem, but we do not have the needed expertise to document this correctly and submit it to Guzzle.

I’ve been through a worked example at this end and put the code here. This looks like the same as what you are doing, but there must be something your end not quite right (very hard to spot from snips).

I didn’t need to make any changes to the CMS, etc… it worked as is. Probably not what you wanted to hear! :disappointed_relieved:

This is populated from $_POST by the Slim framework - but I suspect you will find $_POST is empty.

Dan,

Thank you for taking the time to make the example code. It does look a lot like ours. The only thing that initially sticks out is that you are not using SSL I wonder if the SSL has something to do with our problem. When I get a moment I will try changing to non-ssl and see what happens.

Yeah it is…

Update: So… We found that using a 3rd level multidimensional array seems to be a problem when forming a request for Guzzle. (I thought this was fixed a while back in Guzzle :confused:, then maybe something else)When we trim it down to a 2nd level multidimensional array, we can then get the form_params to pass at least to the point where the extra logging you suggested is showing the media array being passed as shown here:

27157	c1835af	2015-11-11 21:56	API	POST	DEBUG		/playlist/library/assign/:id	array ( 'media' => array ( 0 => '13', ), )

Also changing to non-SSL had no affect on the problem.

After a few checks… :grinning: :grin: :joy: :smiley: :smile: :sweat_smile: … Ok I am good. Thank you Dan, we now have layouts that we can add media to.

Fantastic, i’m glad the example helped point you in the right direction :smile:

All of this debugging is useful for us, as i’m adding and refining the api as we go - so no wasted time (at least from my perspective! :smile:)

Now that we can add media to a playlist as a widget, we need to know how to record the widget id we added so that later we can remove it from the playlist.

Looking at the API response, I only see the ability to get all the widgets assigned to a playlist, after adding media to the playlist. How do we go about getting the widget id that is created when we add the media?

We could pull the last “widgets” and assume that is the one, but I am not sure that will always returned the desired result.

You are quite right of course - it is difficult to determine which IDs are added at present. I’ve submitted an issue for it:

DELETE /playlist/widget/{widgetId}

The response returns an empty array.

It does remove the widgetId

Correct - it returns a 204 (success but empty response) - all DELETE’s do the same

Thank you Dan for the information.

1 Like