Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion core/controllers/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public function formats_save()
public function media_my_searches()
{
$this->user->require_authenticated();
return [true,'Searches',['saved' => $this->models->media('search_get_saved', ['type' => 'saved']), 'history' => $this->models->media('search_get_saved', ['type' => 'history'])]];
return [true,'Searches',[
'saved' => $this->models->media('search_get_saved', ['type' => 'saved']),
'history' => $this->models->media('search_get_saved', ['type' => 'history']),
'shared' => $this->models->media('search_get_shared')
]];
}

/**
Expand Down Expand Up @@ -178,6 +182,55 @@ public function media_my_searches_unset_default()
}
}

/**
* Share a saved search with users and/or groups.
*
* @param id
* @param user_ids
* @param group_ids
*
* @route POST /v2/media/searches/share
*/
public function media_my_searches_share()
{
$this->user->require_authenticated();

$user_ids = $this->data('user_ids');
$group_ids = $this->data('group_ids');

if ($this->models->media('search_share', [
'id' => $this->data('id'),
'user_id' => $this->user->param('id'),
'user_ids' => is_array($user_ids) ? $user_ids : [],
'group_ids' => is_array($group_ids) ? $group_ids : [],
])) {
return [true,'Search shared successfully.'];
} else {
return [false,'Error sharing search.'];
}
}

/**
* Remove sharing for a saved search.
*
* @param id
*
* @route DELETE /v2/media/searches/share/(:id:)
*/
public function media_my_searches_unshare()
{
$this->user->require_authenticated();

if ($this->models->media('search_unshare', [
'id' => $this->data('id'),
'user_id' => $this->user->param('id'),
])) {
return [true,'Search sharing removed.'];
} else {
return [false,'Error removing search sharing.'];
}
}

/**
* Returns a boolean value determining whether the current user can edit the
* provided media item. Private method used by a number of other methods in
Expand Down
161 changes: 161 additions & 0 deletions core/models/media_model.php
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,167 @@ public function search_edit($args = [])
return true;
}

/**
* Share a saved search with users and/or groups.
*
* @param id Search ID to share.
* @param user_id Owner of the search (for validation).
* @param user_ids Array of user IDs to share with.
* @param group_ids Array of group IDs to share with.
*
* @return success
*/
public function search_share($args = [])
{
OBFHelpers::require_args($args, ['id', 'user_id']);
OBFHelpers::default_args($args, ['user_ids' => [], 'group_ids' => []]);

// verify this search belongs to the user and is saved
$this->db->where('id', $args['id']);
$this->db->where('user_id', $args['user_id']);
$this->db->where('type', 'saved');
$search = $this->db->get_one('media_searches');

if (!$search) {
return false;
}

// remove existing shares for this search
$this->db->where('search_id', $args['id']);
$this->db->delete('media_searches_shared');

// share with users
if (!empty($args['user_ids'])) {
foreach ($args['user_ids'] as $share_user_id) {
$share_user_id = (int) $share_user_id;

// don't share with self
if ($share_user_id == $args['user_id']) {
continue;
}
$this->db->insert('media_searches_shared', [
'search_id' => $args['id'],
'shared_by' => $args['user_id'],
'shared_with_user_id' => $share_user_id,
]);
}
}

// share with groups
if (!empty($args['group_ids'])) {
foreach ($args['group_ids'] as $group_id) {
$this->db->insert('media_searches_shared', [
'search_id' => $args['id'],
'shared_by' => $args['user_id'],
'shared_with_group_id' => (int) $group_id,
]);
}
}

return true;
}

/**
* Remove all sharing for a saved search.
*
* @param id Search ID to unshare.
* @param user_id Owner of the search (for validation).
*
* @return success
*/
public function search_unshare($args = [])
{
OBFHelpers::require_args($args, ['id', 'user_id']);

// verify this search belongs to the user
$this->db->where('id', $args['id']);
$this->db->where('user_id', $args['user_id']);
$search = $this->db->get_one('media_searches');

if (!$search) {
return false;
}

$this->db->where('search_id', $args['id']);
return $this->db->delete('media_searches_shared');
}

/**
* Get searches shared with the current user (directly or via groups).
*
* @return searches
*/
public function search_get_shared()
{
if (!$this->user->param('id')) {
return [];
}

$user_id = $this->db->escape($this->user->param('id'));

$this->db->query("
SELECT DISTINCT ms.id, ms.query, ms.description, ms.`default`,
mss.shared_by,
u.display_name AS shared_by_name
FROM media_searches ms
JOIN media_searches_shared mss ON mss.search_id = ms.id
LEFT JOIN users_to_groups utg ON utg.group_id = mss.shared_with_group_id
LEFT JOIN users u ON u.id = mss.shared_by
WHERE mss.shared_with_user_id = \"{$user_id}\"
OR utg.user_id = \"{$user_id}\"
");

$searches = $this->db->assoc_list();
if (!is_array($searches)) {
return [];
}

foreach ($searches as $index => $search) {
$searches[$index]['query'] = unserialize($search['query']);
}

return $searches;
}

/**
* Get the recipients (users and groups) a search is shared with.
*
* @param id Search ID.
* @param user_id Owner of the search (for validation).
*
* @return recipients Array with 'user_ids' and 'group_ids'.
*/
public function search_get_shared_recipients($args = [])
{
OBFHelpers::require_args($args, ['id', 'user_id']);

// verify ownership
$this->db->where('id', $args['id']);
$this->db->where('user_id', $args['user_id']);
$search = $this->db->get_one('media_searches');

if (!$search) {
return false;
}

$this->db->where('search_id', $args['id']);
$shares = $this->db->get('media_searches_shared');

$user_ids = [];
$group_ids = [];

foreach ($shares as $share) {
if (!empty($share['shared_with_user_id'])) {
$user_ids[] = (int) $share['shared_with_user_id'];
}
if (!empty($share['shared_with_group_id'])) {
$group_ids[] = (int) $share['shared_with_group_id'];
}
}

return ['user_ids' => $user_ids, 'group_ids' => $group_ids];
}

/**
* Get captions URL for media item
*
Expand Down
37 changes: 37 additions & 0 deletions core/updates/20260219.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace OpenBroadcaster\Updates;

use OpenBroadcaster\Base\Update;

class OBUpdate20260219 extends Update
{
public function items()
{
$updates = [];
$updates[] = "Create media_searches_shared table for sharing saved searches with users and groups.";
return $updates;
}

public function run()
{
$this->db->query('CREATE TABLE IF NOT EXISTS `media_searches_shared` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`search_id` INT(10) UNSIGNED NOT NULL,
`shared_by` INT(10) UNSIGNED NOT NULL,
`shared_with_user_id` INT(10) UNSIGNED DEFAULT NULL,
`shared_with_group_id` INT(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `search_id` (`search_id`),
KEY `shared_by` (`shared_by`),
KEY `shared_with_user_id` (`shared_with_user_id`),
KEY `shared_with_group_id` (`shared_with_group_id`),
FOREIGN KEY (`search_id`) REFERENCES `media_searches`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`shared_by`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`shared_with_user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`shared_with_group_id`) REFERENCES `users_groups`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;');

return true;
}
}
13 changes: 13 additions & 0 deletions public/html/sidebar/my_searches.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<div id="my_searches_saved"></div>
</fieldset>

<fieldset>
<legend data-t>Shared With Me</legend>

<p id="my_searches_shared_nosearches" class="hidden" data-t>No shared searches found.</p>

<div id="my_searches_shared"></div>
</fieldset>

<fieldset>
<legend data-t>Search History</legend>

Expand Down Expand Up @@ -44,6 +52,11 @@
<div class="hidden" id="my_searches_context_menu_unset_default">
<a class="context_menu_item" onclick="OB.Sidebar.mySearchesUnsetDefault();" data-t>Unset Default</a>
</div>
<div><a class="context_menu_item" onclick="OB.Sidebar.mySearchesShareWindow();" data-t>Share</a></div>
<div><a class="context_menu_item" onclick="OB.Sidebar.mySearchesDelete();" data-t>Delete</a></div>
</div>

<div class="my_searches_context_menu" id="my_searches_shared_context_menu">
<div><a class="context_menu_item" onclick="OB.Sidebar.mySearchesSearch($('.my_searches_item.context_menu_on').attr('data-id'));" data-t>Search</a></div>
</div>
</div>
40 changes: 40 additions & 0 deletions public/html/sidebar/share_search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<obwidget id="share_search_message" type="message"></obwidget>

<div>
<p data-t>Select users and/or groups to share this search with.</p>

<fieldset>
<legend data-t>Share With Users</legend>
<div class="fieldrow">
<select id="share_search_users" multiple size="6" style="width: 100%;"></select>
</div>
</fieldset>

<fieldset>
<legend data-t>Share With Groups</legend>
<div class="fieldrow">
<select id="share_search_groups" multiple size="4" style="width: 100%;"></select>
</div>
</fieldset>

<fieldset>
<div class="fieldrow">
<ob-element-button
data-text="Share"
data-icon-name="share-nodes"
data-icon-style="solid"
onclick="OB.Sidebar.mySearchesShareSubmit();"
data-t
>Share</ob-element-button
>
<ob-element-button
data-text="Cancel"
data-icon-name="xmark"
data-icon-style="solid"
onclick="OB.UI.closeModalWindow();"
data-t
>Cancel</ob-element-button
>
</div>
</fieldset>
</div>
Loading
Loading