Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playlists: dynamic playlists should filter media by folderId #2732

Merged
merged 7 commits into from
Oct 21, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/*
* Copyright (C) 2024 Xibo Signage Ltd
*
* Xibo - Digital Signage - http://www.xibo.org.uk
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/

use Phinx\Migration\AbstractMigration;

/**
* Add a new connector (Open Weather Map) to connectors table
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
class AddFolderFilterToPlaylistTableMigration extends AbstractMigration
{
public function change()
{
$this->table('playlist')
->addColumn('filterFolderId', 'integer', ['after' => 'filterMediaTagsLogicalOperator', 'default' => null,
'null' => true])
->save();
}
}
28 changes: 22 additions & 6 deletions lib/Controller/Playlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -703,16 +703,17 @@ public function add(Request $request, Response $response)
$playlist->updateTagLinks($tags);
}

// Do we have a tag or name filter?
// Do we have a tag, name or folder filter?
$nameFilter = $sanitizedParams->getString('filterMediaName');
$nameFilterLogicalOperator = $sanitizedParams->getString('logicalOperatorName');
$tagFilter = $this->getUser()->featureEnabled('tag.tagging') ? $sanitizedParams->getString('filterMediaTag') : null;
$logicalOperator = $this->getUser()->featureEnabled('tag.tagging') ? $sanitizedParams->getString('logicalOperator') : 'OR';
$exactTags = $this->getUser()->featureEnabled('tag.tagging') ? $sanitizedParams->getCheckbox('exactTags') : 0;
$folderIdFilter = $this->getUser()->featureEnabled('folder.view') ? $sanitizedParams->getInt('filterFolderId') : null;

// Capture these as dynamic filter criteria
if ($playlist->isDynamic === 1) {
if (empty($nameFilter) && empty($tagFilter)) {
if (empty($nameFilter) && empty($tagFilter) && empty($folderIdFilter)) {
throw new InvalidArgumentException(__('No filters have been set for this dynamic Playlist, please click the Filters tab to define'));
}
$playlist->filterMediaName = $nameFilter;
Expand All @@ -722,18 +723,24 @@ public function add(Request $request, Response $response)
$playlist->filterExactTags = $exactTags;
$playlist->filterMediaTagsLogicalOperator = $logicalOperator;
}

if ($this->getUser()->featureEnabled('folder.view')) {
$playlist->filterFolderId = $folderIdFilter;
}

$playlist->maxNumberOfItems = $sanitizedParams->getInt('maxNumberOfItems', ['default' => $this->getConfig()->getSetting('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER')]);
}

$playlist->save();

// Should we assign any existing media
if (!empty($nameFilter) || !empty($tagFilter)) {
if (!empty($nameFilter) || !empty($tagFilter) || !empty($folderIdFilter)) {
$media = $this->mediaFactory->query(
null,
[
'name' => $nameFilter,
'tags' => $tagFilter,
'folderId' => $folderIdFilter,
'assignable' => 1,
'exactTags' => $exactTags,
'logicalOperator' => $logicalOperator,
Expand Down Expand Up @@ -960,17 +967,26 @@ public function edit(Request $request, Response $response, $id)
// Do we have a tag or name filter?
// Capture these as dynamic filter criteria
if ($playlist->isDynamic === 1) {
if (empty($sanitizedParams->getString('filterMediaName')) && empty($sanitizedParams->getString('filterMediaTag'))) {
$filterMediaName = $sanitizedParams->getString('filterMediaName');
$filterMediaTag = $sanitizedParams->getString('filterMediaTag');
$filterFolderId = $sanitizedParams->getString('filterFolderId');

if (empty($filterMediaName) && empty($filterMediaTag) && empty($filterFolderId)) {
throw new InvalidArgumentException(__('No filters have been set for this dynamic Playlist, please click the Filters tab to define'));
}
$playlist->filterMediaName = $sanitizedParams->getString('filterMediaName');
$playlist->filterMediaName = $filterMediaName;
$playlist->filterMediaNameLogicalOperator = $sanitizedParams->getString('logicalOperatorName');

if ($this->getUser()->featureEnabled('tag.tagging')) {
$playlist->filterMediaTags = $sanitizedParams->getString('filterMediaTag');
$playlist->filterMediaTags = $filterMediaTag;
$playlist->filterExactTags = $sanitizedParams->getCheckbox('exactTags');
$playlist->filterMediaTagsLogicalOperator = $sanitizedParams->getString('logicalOperator');
}

if ($this->getUser()->featureEnabled('folder.view')) {
$playlist->filterFolderId = $filterFolderId;
}

$playlist->maxNumberOfItems = $sanitizedParams->getInt('maxNumberOfItems');
}

Expand Down
13 changes: 11 additions & 2 deletions lib/Entity/Playlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ class Playlist implements \JsonSerializable
*/
public $filterMediaTagsLogicalOperator;

/**
* @SWG\Property(description="The ID of the folder to filter media items by")
* @var int
*/
public $filterFolderId;

/**
* @SWG\Property(description="Maximum number of Media items matching dynamic Playlist filters")
* @var int
Expand Down Expand Up @@ -786,8 +792,8 @@ private function add()
$time = Carbon::now()->format(DateFormatHelper::getSystemFormat());

$sql = '
INSERT INTO `playlist` (`name`, `ownerId`, `regionId`, `isDynamic`, `filterMediaName`, `filterMediaNameLogicalOperator`, `filterMediaTags`, `filterExactTags`, `filterMediaTagsLogicalOperator`, `maxNumberOfItems`, `createdDt`, `modifiedDt`, `requiresDurationUpdate`, `enableStat`, `folderId`, `permissionsFolderId`)
VALUES (:name, :ownerId, :regionId, :isDynamic, :filterMediaName, :filterMediaNameLogicalOperator, :filterMediaTags, :filterExactTags, :filterMediaTagsLogicalOperator, :maxNumberOfItems, :createdDt, :modifiedDt, :requiresDurationUpdate, :enableStat, :folderId, :permissionsFolderId)
INSERT INTO `playlist` (`name`, `ownerId`, `regionId`, `isDynamic`, `filterMediaName`, `filterMediaNameLogicalOperator`, `filterMediaTags`, `filterExactTags`, `filterMediaTagsLogicalOperator`, `filterFolderId`, `maxNumberOfItems`, `createdDt`, `modifiedDt`, `requiresDurationUpdate`, `enableStat`, `folderId`, `permissionsFolderId`)
VALUES (:name, :ownerId, :regionId, :isDynamic, :filterMediaName, :filterMediaNameLogicalOperator, :filterMediaTags, :filterExactTags, :filterMediaTagsLogicalOperator, :filterFolderId, :maxNumberOfItems, :createdDt, :modifiedDt, :requiresDurationUpdate, :enableStat, :folderId, :permissionsFolderId)
';
$this->playlistId = $this->getStore()->insert($sql, array(
'name' => $this->name,
Expand All @@ -799,6 +805,7 @@ private function add()
'filterMediaTags' => $this->filterMediaTags,
'filterExactTags' => $this->filterExactTags ?? 0,
'filterMediaTagsLogicalOperator' => $this->filterMediaTagsLogicalOperator ?? 'OR',
'filterFolderId' => $this->filterFolderId,
'maxNumberOfItems' => $this->isDynamic == 0 ? null : $this->maxNumberOfItems,
'createdDt' => $time,
'modifiedDt' => $time,
Expand Down Expand Up @@ -835,6 +842,7 @@ private function update()
`filterMediaTags` = :filterMediaTags,
`filterExactTags` = :filterExactTags,
`filterMediaTagsLogicalOperator` = :filterMediaTagsLogicalOperator,
`filterFolderId` = :filterFolderId,
`maxNumberOfItems` = :maxNumberOfItems,
`requiresDurationUpdate` = :requiresDurationUpdate,
`enableStat` = :enableStat,
Expand All @@ -855,6 +863,7 @@ private function update()
'filterMediaTags' => $this->filterMediaTags,
'filterExactTags' => $this->filterExactTags ?? 0,
'filterMediaTagsLogicalOperator' => $this->filterMediaTagsLogicalOperator ?? 'OR',
'filterFolderId' => $this->filterFolderId,
'maxNumberOfItems' => $this->maxNumberOfItems,
'modifiedDt' => Carbon::now()->format(DateFormatHelper::getSystemFormat()),
'requiresDurationUpdate' => $this->requiresDurationUpdate,
Expand Down
6 changes: 6 additions & 0 deletions lib/Factory/PlaylistFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ public function query($sortOrder = null, $filterBy = [])
`playlist`.filterMediaTags,
`playlist`.filterExactTags,
`playlist`.filterMediaTagsLogicalOperator,
`playlist`.filterFolderId,
`playlist`.maxNumberOfItems,
`playlist`.requiresDurationUpdate,
`playlist`.enableStat,
Expand Down Expand Up @@ -458,6 +459,11 @@ public function query($sortOrder = null, $filterBy = [])
$params['mediaLike'] = '%' . $parsedFilter->getString('mediaLike') . '%';
}

if ($parsedFilter->getInt('filterFolderId') !== null) {
$body .= " AND `playlist`.filterFolderId = :filterFolderId ";
$params['filterFolderId'] = $parsedFilter->getInt('filterFolderId');
}

if ($parsedFilter->getInt('folderId') !== null) {
$body .= " AND `playlist`.folderId = :folderId ";
$params['folderId'] = $parsedFilter->getInt('folderId');
Expand Down
3 changes: 2 additions & 1 deletion lib/XTR/DynamicPlaylistSyncTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function run()

$this->log->debug('Assessing Playlist: ' . $playlist->name);

if (empty($playlist->filterMediaName) && empty($playlist->filterMediaTags)) {
if (empty($playlist->filterMediaName) && empty($playlist->filterMediaTags) && empty($playlist->filterFolderId)) {
// if this Dynamic Playlist was populated will all Media in the system
// before we introduced measures against it, we need to go through and unassign all Widgets from it.
// if it is fresh Playlist added recently, it will not have any Widgets on it with empty filters.
Expand All @@ -144,6 +144,7 @@ public function run()
'tags' => $playlist->filterMediaTags,
'exactTags' => $playlist->filterExactTags,
'logicalOperator' => $playlist->filterMediaTagsLogicalOperator,
'folderId' => !empty($playlist->filterFolderId) ? $playlist->filterFolderId : null,
'userCheckUserId' => $playlist->getOwnerId(),
'start' => 0,
'length' => $playlist->maxNumberOfItems
Expand Down
18 changes: 18 additions & 0 deletions views/playlist-form-add.twig
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,24 @@
{{ forms.inputWithTags("filterMediaTag", title, "", helpText, "", null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }}
{% endif %}

{% if currentUser.featureEnabled("folder.view") %}
{% set title %}{% trans "Folder Filter" %}{% endset %}
{% set helpText %}{% trans "Select a folder to filter the media items." %}{% endset %}
{% set attributes = [
{ name: "data-width", value: "100%" },
{ name: "data-search-url", value: url_for("folders.search") },
{ name: "data-filter-options", value: '{"gridView":1}' },
{ name: "data-search-term", value: "folderName" },
{ name: "data-id-property", value: "folderId" },
{ name: "data-text-property", value: "text" },
{ name: "data-initial-key", value: "folderId" },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" }
] %}
{{ forms.dropdown("filterFolderId", "single", title, "", null, "", "", helpText, "pagedSelect", "", "", "", attributes) }}
{% endif %}

{% set title %}{% trans "Max number of Items" %}{% endset %}
{% set helpText %}{% trans "The upper limit on number of Media items that can be dynamically assigned to this Playlist" %}{% endset %}
{{ forms.number("maxNumberOfItems", title, settings.DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER, helpText, 'dynamic-message', '', '', settings.DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT) }}
Expand Down
19 changes: 19 additions & 0 deletions views/playlist-form-edit.twig
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,25 @@
{{ forms.inputWithTags("filterMediaTag", title, playlist.filterMediaTags, helpText, "", null, null, "exactTags", exactTagTitle, logicalOperatorTitle, playlist.filterExactTags, playlist.filterMediaTagsLogicalOperator) }}
{% endif %}

{% if currentUser.featureEnabled("folder.view") %}
{% set title %}{% trans "Folder Filter" %}{% endset %}
{% set helpText %}{% trans "Select a folder to filter the media items." %}{% endset %}
{% set attributes = [
{ name: "data-width", value: "100%" },
{ name: "data-search-url", value: url_for("folders.search") },
{ name: "data-filter-options", value: '{"gridView":1}' },
{ name: "data-search-term", value: "folderName" },
{ name: "data-id-property", value: "folderId" },
{ name: "data-text-property", value: "text" },
{ name: "data-initial-key", value: "folderId" },
{ name: "data-initial-value", value: playlist.filterFolderId },
{ name: "data-allow-clear", value: "true" },
{ name: "data-placeholder--id", value: null },
{ name: "data-placeholder--value", value: "" }
] %}
{{ forms.dropdown("filterFolderId", "single", title, playlist.filterFolderId, null, "", "", helpText, "pagedSelect", "", "", "", attributes) }}
{% endif %}

{% set title %}{% trans "Max number of Items" %}{% endset %}
{% set helpText %}{% trans "The upper limit on number of Media items that can be dynamically assigned to this Playlist" %}{% endset %}
{{ forms.number("maxNumberOfItems", title, playlist.maxNumberOfItems, helpText, 'dynamic-message', '', '', settings.DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT) }}
Expand Down
7 changes: 5 additions & 2 deletions views/playlist-page.twig
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
var exactTags;
var logicalOperator;
var logicalOperatorName;
var filterFolderId;

function playlistEditorFormOpen(formData) {

Expand All @@ -320,7 +321,7 @@
playlistFormPopulateMediaTable(dialog);
}, 500));

$(dialog).find("input[name=filterMediaTag], input[name=exactTags], select[name=logicalOperator], select[name=logicalOperatorName]").on("change", function () {
$(dialog).find("input[name=filterMediaTag], input[name=exactTags], select[name=logicalOperator], select[name=logicalOperatorName], select[name=filterFolderId]").on("change", function () {
playlistFormPopulateMediaTable(dialog);
});

Expand Down Expand Up @@ -394,8 +395,9 @@
exactTags = $(dialog).find("input[name=exactTags]").is(':checked')
logicalOperator = $(dialog).find("select[name=logicalOperator]").val();
logicalOperatorName = $(dialog).find("select[name=logicalOperatorName]").val();
filterFolderId = $(dialog).find("select[name=filterFolderId]").val() ?? "";

if (nameFilter === "" && tagFilter === "") {
if (nameFilter === "" && tagFilter === "" && filterFolderId === "") {
if (mediaTable != null) {
mediaTable.destroy();
mediaTable = null;
Expand Down Expand Up @@ -425,6 +427,7 @@
{
media: nameFilter,
tags: tagFilter,
folderId: filterFolderId,
assignable: 1,
exactTags: exactTags,
logicalOperator: logicalOperator,
Expand Down
Loading