diff --git a/cypress/e2e/folder.cy.js b/cypress/e2e/folder.cy.js
index 5e8ac2c8c0..8b0d2a82fd 100644
--- a/cypress/e2e/folder.cy.js
+++ b/cypress/e2e/folder.cy.js
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2023 Xibo Signage Ltd
*
- * Xibo - Digital Signage - http://www.xibo.org.uk
+ * Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
@@ -19,189 +19,188 @@
* along with Xibo. If not, see .
*/
-describe('Folders', function () {
-
- beforeEach(function () {
- cy.login();
- });
-
- it('creating a new folder and rename it', () => {
- cy.visit('/folders/view');
- cy.contains('Root Folder').rightclick();
- cy.contains('Create').should('be.visible').click();
-
- cy.visit('/folders/view');
- cy.contains('New Folder').should('be.visible').rightclick();
- cy.contains('Rename').type('Folder123{enter}');
- });
-
- it('Moving an image from Root Folder to another folder', () => {
- // Create and alias for load folders
- cy.server();
- cy.route('/library?*').as('mediaLoad');
-
- // Go to library
- cy.visit('/library/view');
- cy.get('#media').type('child_folder_media');
-
- cy.wait('@mediaLoad');
- cy.get('#libraryItems tbody tr').should('have.length', 1);
-
- cy.get('#libraryItems tr:first-child .dropdown-toggle').click();
- cy.get('#libraryItems tr:first-child .library_button_selectfolder').click();
-
- cy.get('#container-folder-form-tree>ul>li>i').click();
- cy.get('#container-folder-form-tree>ul>li:not(.jstree-loading)>i').click();
- cy.contains('ChildFolder').click();
- cy.get('.save-button').click();
- });
-
- it('Sharing', () => {
- cy.server();
-
- // Create and alias for load folders
- cy.route('/folders').as('loadFolders');
-
- // Create and alias for load user permissions for folders
- cy.route('/user/permissions/Folder/*').as('permissionsFolders');
-
- cy.visit('/folders/view');
-
- cy.wait('@loadFolders');
-
- cy.contains('ShareFolder').rightclick();
- cy.get('ul.jstree-contextmenu >li:nth-child(6) > a').click(); // Click on Share Link
- cy.get('#name').type('folder_user');
-
- cy.wait('@permissionsFolders');
-
- cy.get('#permissionsTable tbody tr').should('have.length', 1);
- cy.get('#permissionsTable tbody tr:nth-child(1) td:nth-child(1)').contains('folder_user');
- cy.get('#permissionsTable tbody tr:nth-child(1) td:nth-child(2)> input').click();
- cy.get('.save-button').click();
- });
-
- it('Set Home Folders for a user', () => {
- cy.server();
- // Create and alias for load users
- cy.route('/user*').as('loadUsers');
-
- cy.visit('/user/view');
- cy.get('#userName').type('folder_user');
-
- cy.wait('@loadUsers');
- cy.get('#users tbody tr').should('have.length', 1);
- cy.get('#users tr:first-child .dropdown-toggle').click();
- cy.get('#users tr:first-child .user_button_set_home').click();
- cy.get('#home-folder').should('be.visible');
- cy.contains('FolderHome').click({force: true} );
- cy.get('.save-button').click();
-
- // Check
- cy.visit('/user/view');
- cy.get('#userName').clear();
- cy.get('#userName').type('folder_user');
-
- cy.wait('@loadUsers');
- cy.get('#users tbody tr').should('have.length', 1);
- cy.get('#users tbody tr:nth-child(1) td:nth-child(1)').contains('folder_user');
- cy.get('#users tbody tr:nth-child(1) td:nth-child(3)').contains('FolderHome');
- });
-
- it('Remove an empty folder', () => {
- // Create and alias for load folders
- cy.server();
- cy.route('/folders').as('loadFolders');
-
- cy.visit('/folders/view');
- cy.contains('EmptyFolder').rightclick();
- cy.contains('Remove').click();
-
- cy.visit('/folders/view');
- cy.wait('@loadFolders');
- cy.contains('EmptyFolder').should('not.exist');
- });
-
- it('cannot remove a folder with content', () => {
- // Create and alias for load folders
- cy.server();
- cy.route('/folders').as('loadFolders');
-
- cy.visit('/folders/view');
- cy.contains('FolderWithContent').rightclick();
- cy.contains('Remove').click();
-
- // Check folder still exists
- cy.visit('/folders/view');
-
- cy.wait('@loadFolders');
- cy.contains('FolderWithContent').should('exist');
- });
-
- it('search a media in a folder', () => {
- // Create and alias for load folders
- cy.server();
- cy.route('/folders').as('loadFolders');
-
- // Go to library
- cy.visit('/library/view');
-
- cy.wait('@loadFolders');
-
- // Click on All folders
- cy.get('#folder-tree-clear-selection-button').click();
- cy.get('.jstree-ocl').click();
- cy.contains('FolderWithImage').click();
-
- cy.get('#libraryItems tbody tr').should('have.length', 1);
- cy.get('#libraryItems tbody').contains('media_for_search_in_folder');
- });
-
- it('Hide Folder tree', () => {
- // Go to library
- cy.visit('/library/view');
- // The Folder tree is open by default on a grid
- cy.get('#folder-tree-select-folder-button').click();
- // clicking on the folder icon hides it
- cy.get('#grid-folder-filter').should('have.css', 'display', 'none');
- });
-
- it('Move folders and Merge', () => {
- // Move a folder (MoveFromFolder) to another (MoveToFolder) and merge
- cy.server();
- cy.route('/library?*').as('mediaLoad');
- // Create and alias for load folders
- cy.route('/folders').as('loadFolders');
-
- // Go to folders
- cy.visit('/folders/view');
- cy.contains('MoveFromFolder').rightclick();
- cy.contains('Move Folder').click();
-
- cy.get('#container-folder-form-tree>ul>li>i').click();
- cy.get('#container-folder-form-tree>ul>li:not(.jstree-loading)>i').click();
- cy.contains('MoveToFolder').click({force: true});
- cy.get('.form-check input').click();
- cy.get('.save-button').click();
-
- // Validate test34 image exist in MoveToFolder
- cy.visit('/folders/view');
-
- cy.wait('@loadFolders');
- cy.contains('MoveFromFolder').should('not.exist');
-
- // Validate test34 image exist in MoveToFolder
- // Go to library
- cy.visit('/library/view');
- cy.wait('@mediaLoad');
- cy.wait('@loadFolders');
-
- // Click on All folders from Folder Tree
- cy.get('#folder-tree-clear-selection-button').click();
- cy.get('.jstree-ocl').click();
- cy.contains('MoveToFolder').click();
-
- cy.wait('@mediaLoad');
- cy.get('#libraryItems tbody').contains('test34');
- });
+describe('Folders', function() {
+ beforeEach(function() {
+ cy.login();
+ });
+
+ it('creating a new folder and rename it', () => {
+ cy.visit('/folders/view');
+ cy.contains('Root Folder').rightclick();
+ cy.contains('Create').should('be.visible').click();
+
+ cy.visit('/folders/view');
+ cy.contains('New Folder').should('be.visible').rightclick();
+ cy.contains('Rename').type('Folder123{enter}');
+ });
+
+ it('Moving an image from Root Folder to another folder', () => {
+ // Create and alias for load folders
+ cy.intercept('/library?*').as('mediaLoad');
+ cy.intercept('/user/pref').as('userPref');
+
+ // Go to library
+ cy.visit('/library/view');
+ cy.get('#media').type('child_folder_media');
+
+ cy.wait('@mediaLoad');
+ cy.get('#libraryItems tbody tr').should('have.length', 1);
+ cy.wait('@mediaLoad');
+ cy.wait('@userPref');
+ cy.get('#datatable-container').should('contain', 'child_folder_media');
+
+ cy.get('#libraryItems tr:first-child .dropdown-toggle').click();
+ cy.get('#libraryItems tr:first-child .library_button_selectfolder').click();
+
+ cy.get('#container-folder-form-tree>ul>li>i').click();
+ cy.get('#container-folder-form-tree>ul>li:not(.jstree-loading)>i').click();
+ cy.contains('ChildFolder').click();
+ cy.get('.save-button').click();
+ });
+
+ it('Sharing', () => {
+ // Create and alias for load folders
+ cy.intercept('/folders').as('loadFolders');
+
+ // Create and alias for load user permissions for folders
+ cy.intercept('/user/permissions/Folder/*').as('permissionsFolders');
+
+ cy.visit('/folders/view');
+
+ cy.wait('@loadFolders');
+
+ cy.contains('ShareFolder').rightclick();
+ cy.get('ul.jstree-contextmenu >li:nth-child(6) > a').click(); // Click on Share Link
+ cy.get('#name').type('folder_user');
+
+ cy.wait('@permissionsFolders');
+
+ cy.get('#permissionsTable tbody tr').should('have.length', 1);
+ cy.get('#permissionsTable tbody tr:nth-child(1) td:nth-child(1)').contains('folder_user');
+ cy.get('#permissionsTable tbody tr:nth-child(1) td:nth-child(2)> input').click();
+ cy.get('.save-button').click();
+ });
+
+ it('Set Home Folders for a user', () => {
+ // Create and alias for load users
+ cy.intercept('/user*').as('loadUsers');
+
+ cy.visit('/user/view');
+ cy.get('#userName').type('folder_user');
+
+ cy.wait('@loadUsers');
+ cy.get('#users tbody tr').should('have.length', 1);
+ cy.get('#users tr:first-child .dropdown-toggle').click();
+ cy.get('#users tr:first-child .user_button_set_home').click();
+ cy.get('#home-folder').should('be.visible');
+ cy.contains('FolderHome').click({force: true} );
+ cy.get('.save-button').click();
+
+ // Check
+ cy.visit('/user/view');
+ cy.get('#userName').clear();
+ cy.get('#userName').type('folder_user');
+
+ cy.wait('@loadUsers');
+ cy.get('#users tbody tr').should('have.length', 1);
+ cy.get('#users tbody tr:nth-child(1) td:nth-child(1)').contains('folder_user');
+ cy.get('#users tbody tr:nth-child(1) td:nth-child(3)').contains('FolderHome');
+ });
+
+ it('Remove an empty folder', () => {
+ // Create and alias for load folders
+ cy.intercept('/folders').as('loadFolders');
+
+ cy.visit('/folders/view');
+ cy.contains('EmptyFolder').rightclick();
+ cy.contains('Remove').click();
+
+ cy.visit('/folders/view');
+ cy.wait('@loadFolders');
+ cy.contains('EmptyFolder').should('not.exist');
+ });
+
+ it('cannot remove a folder with content', () => {
+ // Create and alias for load folders
+ cy.intercept('/folders').as('loadFolders');
+
+ cy.visit('/folders/view');
+ cy.contains('FolderWithContent').rightclick();
+ cy.contains('Remove').click();
+
+ // Check folder still exists
+ cy.visit('/folders/view');
+
+ cy.wait('@loadFolders');
+ cy.contains('FolderWithContent').should('exist');
+ });
+
+ it('search a media in a folder', () => {
+ // Create and alias for load folders
+ cy.intercept('/folders').as('loadFolders');
+ cy.intercept('/library?*').as('mediaLoad');
+ cy.intercept('/user/pref').as('userPref');
+
+ // Go to library
+ cy.visit('/library/view');
+
+ cy.wait('@loadFolders');
+ cy.wait('@mediaLoad');
+ cy.wait('@userPref');
+
+ // Click on All folders
+ cy.get('#folder-tree-clear-selection-button').click();
+ cy.get('.jstree-ocl').click();
+ cy.contains('FolderWithImage').click();
+
+ cy.get('#libraryItems tbody tr').should('have.length', 1);
+ cy.get('#libraryItems tbody').contains('media_for_search_in_folder');
+ });
+
+ it('Hide Folder tree', () => {
+ // Go to library
+ cy.visit('/library/view');
+ // The Folder tree is open by default on a grid
+ cy.get('#folder-tree-select-folder-button').click();
+ // clicking on the folder icon hides it
+ cy.get('#grid-folder-filter').should('have.css', 'display', 'none');
+ });
+
+ it('Move folders and Merge', () => {
+ // Move a folder (MoveFromFolder) to another (MoveToFolder) and merge
+ cy.intercept('/library?*').as('mediaLoad');
+ // Create and alias for load folders
+ cy.intercept('/folders').as('loadFolders');
+
+ // Go to folders
+ cy.visit('/folders/view');
+ cy.contains('MoveFromFolder').rightclick();
+ cy.contains('Move Folder').click();
+
+ cy.get('#container-folder-form-tree>ul>li>i').click();
+ cy.get('#container-folder-form-tree>ul>li:not(.jstree-loading)>i').click();
+ cy.contains('MoveToFolder').click({force: true});
+ cy.get('.form-check input').click();
+ cy.get('.save-button').click();
+
+ // Validate test34 image exist in MoveToFolder
+ cy.visit('/folders/view');
+
+ cy.wait('@loadFolders');
+ cy.contains('MoveFromFolder').should('not.exist');
+
+ // Validate test34 image exist in MoveToFolder
+ // Go to library
+ cy.visit('/library/view');
+ cy.wait('@mediaLoad');
+ cy.wait('@loadFolders');
+
+ // Click on All folders from Folder Tree
+ cy.get('#folder-tree-clear-selection-button').click();
+ cy.get('.jstree-ocl').click();
+ cy.contains('MoveToFolder').click();
+
+ cy.wait('@mediaLoad');
+ cy.get('#libraryItems tbody').contains('test34');
+ });
});
diff --git a/cypress/e2e/layout_dataset.cy.js b/cypress/e2e/layout_dataset.cy.js
index 12f168da89..7b83fc0158 100644
--- a/cypress/e2e/layout_dataset.cy.js
+++ b/cypress/e2e/layout_dataset.cy.js
@@ -57,10 +57,7 @@ describe('Layout Designer', function() {
cy.get('.select2-container--open').contains('test');
cy.get('.select2-container--open .select2-results > ul > li').should('have.length', 1);
cy.get('.select2-container--open .select2-results > ul > li:first').contains('test').click();
-
- cy.get('[contenteditable="true" ]').should('exist');
- cy.get('.cke_editable_inline').clear();
- cy.get('.cke_editable_inline').type('No data to show').trigger('change');
+
cy.get('[name="lowerLimit"]').clear().type('1');
cy.get('[name="upperLimit"]').clear().type('10');
cy.get('.order-clause-row > :nth-child(2) > .form-control').select('Text', {force: true});
diff --git a/lib/Widget/MastodonProvider.php b/lib/Widget/MastodonProvider.php
index 786044f1c7..5ca78a4478 100644
--- a/lib/Widget/MastodonProvider.php
+++ b/lib/Widget/MastodonProvider.php
@@ -23,6 +23,8 @@
namespace Xibo\Widget;
use Carbon\Carbon;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Xibo\Widget\DataType\SocialMedia;
use Xibo\Widget\Provider\DataProviderInterface;
@@ -45,42 +47,55 @@ public function fetchData(DataProviderInterface $dataProvider): WidgetProviderIn
try {
$httpOptions = [
'timeout' => 20, // wait no more than 20 seconds
- 'query' => [
- 'limit' => $dataProvider->getProperty('numItems', 15)
- ]
+ ];
+
+ $queryOptions = [
+ 'limit' => $dataProvider->getProperty('numItems', 15)
];
if ($dataProvider->getProperty('searchOn', 'all') === 'local') {
- $httpOptions['query']['local'] = true;
+ $queryOptions['local'] = true;
} elseif ($dataProvider->getProperty('searchOn', 'all') === 'remote') {
- $httpOptions['query']['remote'] = true;
+ $queryOptions['remote'] = true;
}
// Media Only
if ($dataProvider->getProperty('onlyMedia', 0)) {
- $httpOptions['query']['only_media'] = true;
+ $queryOptions['only_media'] = true;
}
if (!empty($dataProvider->getProperty('serverUrl', ''))) {
- $uri = $dataProvider->getProperty('serverUrl', '');
+ $uri = $dataProvider->getProperty('serverUrl');
}
- // Hashtag: When empty we should do a public search, when filled we should do a hashtag search
+ // Hashtag
$hashtag = trim($dataProvider->getProperty('hashtag', ''));
- if (!empty($hashtag)) {
- $uri = rtrim($uri, '/').'/api/v1/timelines/tag/'. trim($hashtag, '#');
+
+ // when username is provided do not search in public timeline
+ if (!empty($dataProvider->getProperty('userName', ''))) {
+ // username search: get account ID, always returns one record
+ $accountId = $this->getAccountId($uri, $dataProvider->getProperty('userName'), $dataProvider);
+ $queryOptions['tagged'] = trim($hashtag, '#');
+ $uri = rtrim($uri, '/') . '/api/v1/accounts/' . $accountId . '/statuses?';
} else {
- $uri = rtrim($uri, '/').'/api/v1/timelines/public';
+ // Hashtag: When empty we should do a public search, when filled we should do a hashtag search
+ if (!empty($hashtag)) {
+ $uri = rtrim($uri, '/') . '/api/v1/timelines/tag/' . trim($hashtag, '#');
+ } else {
+ $uri = rtrim($uri, '/') . '/api/v1/timelines/public';
+ }
}
- $this->getLog()->debug('Mastodon: uri: ' . $uri . ' httpOptions: '. json_encode($httpOptions));
-
$response = $dataProvider
->getGuzzleClient($httpOptions)
- ->get($uri);
+ ->get($uri, [
+ 'query' => $queryOptions
+ ]);
$result = json_decode($response->getBody()->getContents(), true);
+ $this->getLog()->debug('Mastodon: uri: ' . $uri . ' httpOptions: ' . json_encode($httpOptions));
+
$this->getLog()->debug('Mastodon: count: ' . count($result));
// Expiry time for any media that is downloaded
@@ -157,4 +172,29 @@ public function getDataModifiedDt(DataProviderInterface $dataProvider): ?Carbon
{
return null;
}
+
+ /**
+ * Get Mastodon Account Id from username
+ * @throws GuzzleException
+ */
+ private function getAccountId(string $uri, string $username, DataProviderInterface $dataProvider)
+ {
+ $uri = rtrim($uri, '/').'/api/v1/accounts/lookup?';
+
+ $httpOptions = [
+ 'timeout' => 20, // wait no more than 20 seconds
+ 'query' => [
+ 'acct' => $username
+ ],
+ ];
+ $response = $dataProvider
+ ->getGuzzleClient($httpOptions)
+ ->get($uri);
+
+ $result = json_decode($response->getBody()->getContents(), true);
+
+ $this->getLog()->debug('Mastodon: getAccountId: ID ' . $result['id']);
+
+ return $result['id'];
+ }
}
diff --git a/modules/mastodon.xml b/modules/mastodon.xml
index 5b340b88fa..20d426c9a7 100644
--- a/modules/mastodon.xml
+++ b/modules/mastodon.xml
@@ -26,7 +26,7 @@
\Xibo\Widget\MastodonProvider
mastodon
social-media
- %hashtag%_%numItems%_%searchOn%_%onlyMedia%_%serverUrl%
+ %hashtag%_%numItems%_%searchOn%_%onlyMedia%_%serverUrl%_%userName%
1
1
1
@@ -70,6 +70,11 @@
Leave empty to use the one from settings.
+
+ Username
+ Provide Mastodon username to get public statuses from the account.
+
+
Count
The number of posts to return (default = 15).