diff --git a/core/controllers/media.php b/core/controllers/media.php index 07ce688f..3d858190 100644 --- a/core/controllers/media.php +++ b/core/controllers/media.php @@ -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') + ]]; } /** @@ -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 diff --git a/core/models/media_model.php b/core/models/media_model.php index b511d76b..58421663 100644 --- a/core/models/media_model.php +++ b/core/models/media_model.php @@ -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 * diff --git a/core/updates/20260219.php b/core/updates/20260219.php new file mode 100644 index 00000000..d212958a --- /dev/null +++ b/core/updates/20260219.php @@ -0,0 +1,37 @@ +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; + } +} diff --git a/public/html/sidebar/my_searches.html b/public/html/sidebar/my_searches.html index 7c959950..2e2e73ae 100644 --- a/public/html/sidebar/my_searches.html +++ b/public/html/sidebar/my_searches.html @@ -11,6 +11,14 @@
+ +