? UNION SELECT DISTINCT collection FROM external_access_keys))";
$condparams = array("i",COLLECTION_TYPE_PUBLIC,"i",$user);
} elseif (strlen($find) == 1 && !is_numeric($find)) {
# A-Z search
$condsql = " WHERE c.name LIKE ?";
$condparams = array("s",$find . "%");
} elseif (strlen($find) > 1 || is_numeric($find)) {
$keywords = split_keywords($find);
$keysql = "";
$keyparams = array();
for ($n = 0; $n < count($keywords); $n++) {
$keyref = resolve_keyword($keywords[$n], false);
if ($keyref === false) {
continue;
}
$keysql .= " JOIN collection_keyword k" . $n . " ON k" . $n . ".collection=ref AND (k" . $n . ".keyword=?)";
$keyparams = array_merge($keyparams, ['i', $keyref]);
}
}
$validtypes = [COLLECTION_TYPE_STANDARD, COLLECTION_TYPE_PUBLIC, COLLECTION_TYPE_REQUEST];
if ($themes_in_my_collections) {
$validtypes[] = COLLECTION_TYPE_FEATURED;
}
$condsql .= $condsql == "" ? "WHERE" : " AND";
$condsql .= " c.`type` IN (" . ps_param_insert(count($validtypes)) . ")";
$condparams = array_merge($condparams, ps_param_fill($validtypes, "i"));
if ($themes_in_my_collections) {
// If we show featured collections, remove the categories
$keysql .= " WHERE (clist.`type` IN (?,?,?) OR (clist.`type` = ? AND clist.`count` > 0))";
$keyparams[] = "i";
$keyparams[] = COLLECTION_TYPE_STANDARD;
$keyparams[] = "i";
$keyparams[] = COLLECTION_TYPE_PUBLIC;
$keyparams[] = "i";
$keyparams[] = COLLECTION_TYPE_REQUEST;
$keyparams[] = "i";
$keyparams[] = COLLECTION_TYPE_FEATURED;
}
if (isset($anonymous_login) && ($username == $anonymous_login) && $anonymous_user_session_collection) {
// Anonymous user - only get the user's own collections that are for this session - although we can still join to
// get collections that have been specifically shared with the anonymous user
if ('' == $condsql) {
$extrasql = " WHERE ";
} else {
$extrasql .= " AND ";
}
$extrasql .= " (c.session_id=?)";
$extraparams = array("i",$rs_session);
}
$order_sort = "";
$validsort = array("name","ref","user","created","public","home_page_publish","type","parent");
if ($order_by != "name" && in_array(strtolower($order_by), $validsort)) {
$order_sort = " ORDER BY $order_by $sort";
}
// Control the selected columns. $query_select_columns is for the outer SQL and $collection_select_columns
// is for the inner one. Both have some extra columns from the user & resource table.
$collection_table_columns = [
'ref',
'name',
'user',
'created',
'public',
'allow_changes',
'cant_delete',
'keywords',
'savedsearch',
'home_page_publish',
'home_page_text',
'home_page_image',
'session_id',
'description',
'type',
'parent',
'thumbnail_selection_method',
'bg_img_resource_ref',
'order_by',
];
$query_select_columns = implode(', ', $collection_table_columns) . ', username, fullname, count';
$collection_select_columns = [];
foreach ($collection_table_columns as $column_name) {
$collection_select_columns[] = "c.{$column_name}";
}
$collection_select_columns = implode(', ', $collection_select_columns) . ', u.username, u.fullname, count(r.resource) AS count';
$query = "SELECT {$query_select_columns}
FROM (
SELECT {$collection_select_columns}
FROM user AS u
JOIN collection AS c ON u.ref = c.user AND c.user = ?
LEFT OUTER JOIN collection_resource AS r ON c.ref = r.collection
$condsql
$extrasql
GROUP BY c.ref
UNION
SELECT {$collection_select_columns}
FROM user_collection AS uc
JOIN collection AS c ON uc.collection = c.ref AND uc.user = ? AND c.user <> ?
LEFT OUTER JOIN collection_resource AS r ON c.ref = r.collection
LEFT JOIN user AS u ON c.user = u.ref
$condsql
GROUP BY c.ref
UNION
SELECT {$collection_select_columns}
FROM usergroup_collection AS gc
JOIN collection AS c ON gc.collection = c.ref AND gc.usergroup = ? AND c.user <> ?
LEFT OUTER JOIN collection_resource AS r ON c.ref = r.collection
LEFT JOIN user AS u ON c.user = u.ref
$condsql
GROUP BY c.ref
) AS clist
$keysql
GROUP BY ref $order_sort";
$queryparams = array_merge(
array("i",$user),
$condparams,
$extraparams,
array("i", $user,"i", $user),
$condparams,
array("i", $usergroup,"i",$user),
$condparams,
$keyparams
);
$return = ps_query($query, $queryparams, 'collection_access' . $user);
if ($order_by == "name") {
if ($sort == "ASC") {
usort($return, 'collections_comparator');
} elseif ($sort == "DESC") {
usort($return, 'collections_comparator_desc');
}
}
// To keep Default Collection creation consistent: Check that user has at least one collection of his/her own (not if collection result is empty, which may include shares),
$hasown = false;
for ($n = 0; $n < count($return); $n++) {
if ($return[$n]['user'] == $user) {
$hasown = true;
}
}
if (!$hasown && $auto_create && $find == "") { # User has no collections of their own, and this is not a search. Make a new 'Default Collection'
# No collections of one's own? The user must have at least one Default Collection
global $usercollection;
$usercollection = create_collection($user, "Default Collection", 0, 1); // make not deletable
set_user_collection($user, $usercollection);
# Recurse to send the updated collection list.
return get_user_collections($user, $find, $order_by, $sort, $fetchrows, false);
}
return $return;
}
$GLOBALS['get_collection_cache'] = array();
/**
* Returns all data for collection $ref.
*
* @param int $ref Collection ID
* @param bool $usecache Optionally retrieve from cache
*
* @return array|boolean
*/
function get_collection($ref, $usecache = false)
{
global $lang, $userref,$k;
if (isset($GLOBALS['get_collection_cache'][$ref]) && $usecache) {
return $GLOBALS['get_collection_cache'][$ref];
}
$columns = ", u.fullname, u.username";
$return = ps_query("SELECT " . columns_in('collection', 'c') . $columns . " FROM collection c LEFT OUTER JOIN user u ON u.ref = c.user WHERE c.ref = ?", array("i",$ref));
if (count($return) == 0) {
return false;
} else {
$return = $return[0];
$users = ps_array("SELECT u.username value FROM user u,user_collection c WHERE u.ref=c.user AND c.collection = ? ORDER BY u.username", array("i",$ref));
$return["users"] = join(", ", $users);
$groups = ps_array("SELECT concat('" . $lang["groupsmart"] . ": ',u.name) value FROM usergroup u,usergroup_collection c WHERE u.ref = c.usergroup AND c.collection = ? ORDER BY u.name", array("i",$ref));
$return["groups"] = join(", ", $groups);
$request_feedback = 0;
if ($return["user"] != $userref) {
# If this is not the user's own collection, fetch the user_collection row so that the 'request_feedback' property can be returned.
$request_feedback = ps_value("SELECT request_feedback value FROM user_collection WHERE collection = ? AND user = ?", array("i",$ref,"i",$userref), 0);
if (!$request_feedback && $k == "") {
# try to set via usergroup_collection
global $usergroup;
$request_feedback = ps_value("SELECT request_feedback value FROM usergroup_collection WHERE collection = ? AND usergroup = ?", array("i",$ref,"i",$usergroup), 0);
}
}
if ($k != "") {
# If this is an external user (i.e. access key based) then fetch the 'request_feedback' value from the access keys table
$request_feedback = ps_value("SELECT request_feedback value FROM external_access_keys WHERE access_key = ? AND request_feedback = 1", array("s",$k), 0);
}
$return["request_feedback"] = $request_feedback;
// Legacy property which is now superseded by types. FCs need to be public before they can be put under a category by an admin (perm h)
global $COLLECTION_PUBLIC_TYPES;
$return["public"] = (int) in_array($return["type"], $COLLECTION_PUBLIC_TYPES);
$GLOBALS['get_collection_cache'][$ref] = $return;
return $return;
}
}
/**
* Returns all resources in collection
*
* @param int $collection ID of collection being requested
*
* @return array|boolean
*/
function get_collection_resources($collection)
{
global $userref;
# For many cases (e.g. when displaying a collection for a user) a search is used instead so permissions etc. are honoured.
if (!is_int_loose($collection)) {
return false;
}
# Check if review collection if so delete any resources moved out of users archive status permissions by other users
if ((string)$collection == "-" . $userref) {
collection_cleanup_inaccessible_resources($collection);
}
$plugin_collection_resources = hook('replace_get_collection_resources', "", array($collection));
if (is_array($plugin_collection_resources)) {
return $plugin_collection_resources;
}
return ps_array("SELECT resource value FROM collection_resource WHERE collection = ? ORDER BY sortorder ASC, date_added DESC, resource ASC", array("i",$collection));
}
/**
* Get all resources in a collection without checking permissions or filtering by workflow states.
* This is useful when you want to get all the resources for further subprocessing (@see render_selected_collection_actions()
* as an example)
*
* @param integer $ref Collection ID
*
* @return array
*/
function get_collection_resources_with_data($ref)
{
if (!is_numeric($ref)) {
return array();
}
$result = ps_query(
"
SELECT r.*
FROM collection_resource AS cr
RIGHT JOIN resource AS r ON cr.resource = r.ref
WHERE cr.collection = ?
ORDER BY cr.sortorder ASC , cr.date_added DESC , cr.resource DESC
",
array("i",$ref)
);
if (!is_array($result)) {
return array();
}
return $result;
}
/**
* Add resource $resource to collection $collection
*
* @param integer $resource
* @param integer $collection
* @param boolean $smartadd
* @param string $size
* @param string $addtype
* @param boolean $col_access_control Collection access control. Is user allowed to add to it? You can leave it null
* to allow this function to determine it but it may have performance issues.
* @param array $external_shares List of external share keys. {@see get_external_shares()}. You can leave it null
* to allow this function to determine it but it will affect performance.
* @param string $search Optionsl search string. Used to update resource_node hit count
*
* @param integer $sort_order Sort order of resource in collection
*
* @return boolean | string
*/
function add_resource_to_collection(
$resource,
$collection,
$smartadd = false,
$size = "",
$addtype = "",
?bool $col_access_control = null,
?array $external_shares = null,
string $search = '',
?int $sort_order = null
) {
global $lang;
if (!is_int_loose($collection) || !is_int_loose($resource)) {
return $lang["cantmodifycollection"];
}
global $collection_allow_not_approved_share, $collection_block_restypes;
$addpermitted = $col_access_control ?? (
(collection_writeable($collection) && !is_featured_collection_category_by_children($collection))
|| $smartadd
);
if ($addpermitted && !$smartadd && (count($collection_block_restypes) > 0)) { // Can't always block adding resource types since this may be a single resource managed request
if ($addtype == "") {
$addtype = ps_value("SELECT resource_type value FROM resource WHERE ref = ?", ["i",$resource], 0);
}
if (in_array($addtype, $collection_block_restypes)) {
$addpermitted = false;
}
}
if ($addpermitted) {
$collection_data = get_collection($collection, true);
// If this is a featured collection apply all the external access keys from the categories which make up its
// branch path to prevent breaking existing shares for any of those featured collection categories.
$fc_branch_path_keys = [];
if ($collection_data !== false && $collection_data['type'] === COLLECTION_TYPE_FEATURED) {
$branch_category_ids = array_column(
// determine the branch from the parent because the keys for the collection in question will be done below
get_featured_collection_category_branch_by_leaf((int)$collection_data['parent'], []),
'ref'
);
foreach ($branch_category_ids as $fc_category_id) {
$fc_branch_path_keys = array_merge(
$fc_branch_path_keys,
get_external_shares([
'share_collection' => $fc_category_id,
'share_type' => 0,
'ignore_permissions' => true
])
);
}
}
# Check if this collection has already been shared externally. If it has, we must fail if not permitted or add a further entry
# for this specific resource, and warn the user that this has happened.
$keys = array_merge(
$external_shares ?? get_external_shares(array("share_collection" => $collection,"share_type" => 0,"ignore_permissions" => true)),
$fc_branch_path_keys
);
if (count($keys) > 0) {
$archivestatus = ps_value("SELECT archive AS value FROM resource WHERE ref = ?", ["i",$resource], "");
if ($archivestatus < 0 && !$collection_allow_not_approved_share) {
global $lang;
$lang["cantmodifycollection"] = $lang["notapprovedresources"] . $resource;
return false;
}
// Check if user can share externally and has open access. We shouldn't add this if they can't share externally, have restricted access or only been granted access
if (!can_share_resource($resource)) {
return false;
}
# Set the flag so a warning appears.
global $collection_share_warning;
# Check to see if all shares have expired
$expiry_dates = ps_array("SELECT DISTINCT expires value FROM external_access_keys WHERE collection = ?", ["i",$collection]);
$datetime = time();
$collection_share_warning = true;
foreach ($expiry_dates as $date) {
if ($date != "" && $date < $datetime) {
$collection_share_warning = false;
}
}
for ($n = 0; $n < count($keys); $n++) {
# Insert a new access key entry for this resource/collection.
global $userref;
ps_query(
'INSERT INTO external_access_keys(resource, access_key, user, collection, `date`, expires, access, usergroup, password_hash) VALUES (?, ?, ?, ?, now(), ?, ?, ?, ?)',
[
'i', $resource,
's', $keys[$n]['access_key'],
'i', $userref,
'i', $collection ?: null,
's', $keys[$n]['expires'] ?: null,
'i', $keys[$n]['access'],
'i', $keys[$n]['usergroup'] ?: null,
's', $keys[$n]['password_hash'] ?: null,
]
);
collection_log($collection, LOG_CODE_COLLECTION_SHARED_RESOURCE_WITH, $resource, $keys[$n]["access_key"]);
}
}
ps_query('DELETE FROM collection_resource WHERE collection = ? AND resource = ?', ['i', $collection, 'i', $resource]);
ps_query(
'INSERT INTO collection_resource(collection, resource, sortorder) VALUES (?, ?, ?)',
['i', $collection, 'i', $resource, 'i', $sort_order ?: null]
);
# Update the hitcounts for the search nodes (if search specified)
if (strpos($search, NODE_TOKEN_PREFIX) !== false) {
update_node_hitcount_from_search($resource, $search);
}
if ($collection_data !== false && $collection_data['type'] != COLLECTION_TYPE_SELECTION) {
collection_log($collection, LOG_CODE_COLLECTION_ADDED_RESOURCE, $resource);
}
// Clear theme image cache
clear_query_cache("themeimage");
clear_query_cache('col_total_ref_count_w_perm');
return true;
} else {
return $lang["cantmodifycollection"];
}
}
/**
* Remove resource $resource from collection $collection
*
* @param integer $resource
* @param integer $collection
* @param boolean $smartadd
* @return boolean | string
*/
function remove_resource_from_collection($resource, $collection, $smartadd = false)
{
global $lang;
if ((string)(int)$collection != (string)$collection || (string)(int)$resource != (string)$resource) {
return $lang["cantmodifycollection"];
}
if ($smartadd || collection_writeable($collection)) {
$delparams = ["i",$resource,"i",$collection];
ps_query("DELETE FROM collection_resource WHERE resource = ? AND collection = ?", $delparams);
ps_query("DELETE FROM external_access_keys WHERE resource = ? AND collection = ?", $delparams);
// log this
collection_log($collection, LOG_CODE_COLLECTION_REMOVED_RESOURCE, $resource);
// Clear theme image cache
clear_query_cache("themeimage");
clear_query_cache('col_total_ref_count_w_perm');
return true;
} else {
return $lang["cantmodifycollection"];
}
}
/**
* Add resource(s) $resources to collection $collection
*
* @param mixed $resources
* @param mixed $collection
* @return boolean | string
*/
function collection_add_resources($collection, $resources = '', $search = '', $selected = false)
{
global $USER_SELECTION_COLLECTION,$lang;
if (
!is_int_loose($collection)
|| ($resources == '' && $search == '')
|| !collection_writeable($collection)
|| is_featured_collection_category_by_children($collection)
) {
return $lang["cantmodifycollection"];
}
$access_control = true;
$external_share_keys = get_external_shares([
'share_collection' => $collection,
'share_type' => 0,
'ignore_permissions' => true,
]);
if ($selected) {
$resources = get_collection_resources($USER_SELECTION_COLLECTION);
} elseif ($resources == '') {
$resources = do_search($search);
}
if ($resources === false) {
return $lang["noresourcesfound"];
}
if (!is_array($resources)) {
$resources = explode(",", $resources);
}
if (count($resources) == 0) {
return $lang["noresourcesfound"];
}
$collection_resources = get_collection_resources($collection);
$refs_to_add = array_diff($resources, $collection_resources);
$errors = 0;
foreach ($refs_to_add as $ref) {
if (!add_resource_to_collection($ref, $collection, false, '', '', $access_control, $external_share_keys)) {
$errors++;
}
}
if ($errors == 0) {
return true;
} else {
return $lang["cantaddresourcestocolection"];
}
}
/**
* collection_remove_resources
*
* @param mixed $collection
* @param mixed $resources
* @param mixed $removeall
* @return boolean | string
*/
function collection_remove_resources($collection, $resources = '', $removeall = false, $selected = false)
{
global $USER_SELECTION_COLLECTION,$lang;
if (
(string)(int)$collection != (string)$collection
|| ($resources == '' && !$removeall && !$selected)
|| (!collection_writeable($collection))
|| is_featured_collection_category_by_children($collection)
) {
return $lang["cantmodifycollection"];
}
if ($removeall) {
foreach (get_collection_resources($collection) as $ref) {
remove_resource_from_collection($ref, $collection);
}
return true;
}
if ($selected) {
$resources = get_collection_resources($USER_SELECTION_COLLECTION);
}
if ($resources === false) {
return $lang["noresourcesfound"];
}
$collection_resources = get_collection_resources($collection);
if (!is_array($resources)) {
$resources = explode(",", $resources);
}
$refs_to_remove = array_intersect($collection_resources, $resources);
$errors = 0;
foreach ($refs_to_remove as $ref) {
if (!remove_resource_from_collection($ref, $collection)) {
$errors++;
}
}
if ($errors == 0) {
return true;
} else {
return $lang["cantremoveresourcesfromcollection"];
}
}
/**
* Is the collection $collection writable by the current user?
* Returns true if the current user has write access to the given collection.
*
* @param integer $collection
* @return boolean
*/
function collection_writeable($collection)
{
$collectiondata = get_collection($collection);
if ($collectiondata === false) {
return false;
}
global $userref,$usergroup, $allow_smart_collections;
if (
$allow_smart_collections && !isset($userref)
&& isset($collectiondata['savedsearch']) && $collectiondata['savedsearch'] != null
) {
return false; // so "you cannot modify this collection"
}
if ($collectiondata['type'] == COLLECTION_TYPE_REQUEST && !checkperm('R')) {
return false;
}
# Load a list of attached users
$attached = ps_array("SELECT user value FROM user_collection WHERE collection = ?", ["i",$collection]);
$attached_groups = ps_array("SELECT usergroup value FROM usergroup_collection WHERE collection = ?", ["i",$collection]);
// Can edit if
// - The user owns the collection (if we are anonymous user and are using session collections then this must also have the same session id )
// - The user has system setup access (needs to be able to sort out user issues)
// - Collection changes are allowed and :-
// a) User is attached to the collection or
// b) Collection is public or a theme and the user either has the 'h' permission or the collection is editable
global $usercollection,$username,$anonymous_login,$anonymous_user_session_collection, $rs_session;
debug("collection session : " . $collectiondata["session_id"]);
debug("collection user : " . $collectiondata["user"]);
debug("anonymous_login : " . isset($anonymous_login) && is_string($anonymous_login) ? $anonymous_login : "(no)");
debug("userref : " . $userref);
debug("username : " . $username);
debug("anonymous_user_session_collection : " . (($anonymous_user_session_collection) ? "TRUE" : "FALSE"));
$writable =
// User either owns collection AND is not the anonymous user, or is the anonymous user with a matching/no session
($userref == $collectiondata["user"] && (!isset($anonymous_login) || $username != $anonymous_login || !$anonymous_user_session_collection || $collectiondata["session_id"] == $rs_session))
// Collection is public AND either they have the 'h' permission OR allow_changes has been set
|| ((checkperm("h") || $collectiondata["allow_changes"] == 1) && $collectiondata["public"] == 1)
// Collection has been shared but is not public AND user is either attached or in attached group
|| ($collectiondata["allow_changes"] == 1 && $collectiondata["public"] == 0 && (in_array($userref, $attached) || in_array($usergroup, $attached_groups)))
// System admin
|| checkperm("a")
// Adding to active upload_share
|| upload_share_active() == $collection
// This is a request collection and user is an admin user who can approve requests
|| (checkperm("R") && $collectiondata['type'] == COLLECTION_TYPE_REQUEST && checkperm("t"));
// Check if user has permission to manage research requests. If they do and the collection is research request allow writable.
if ($writable === false && checkperm("r")) {
include_once 'research_functions.php';
$research_requests = get_research_requests();
$collections = array();
foreach ($research_requests as $research_request) {
$collections[] = $research_request["collection"];
}
if (in_array($collection, $collections)) {
$writable = true;
}
}
return $writable;
}
/**
* Returns true if the current user has read access to the given collection.
*
* @param integer $collection
* @return boolean
*/
function collection_readable($collection)
{
global $userref, $usergroup, $ignore_collection_access, $collection_commenting;
$k = getval('k', '');
# Fetch collection details.
if (!is_numeric($collection)) {
return false;
}
$collectiondata = get_collection($collection);
if ($collectiondata === false) {
return false;
}
# Load a list of attached users
$attached = ps_array("SELECT user value FROM user_collection WHERE collection = ?", ["i",$collection]);
$attached_groups = ps_array("SELECT usergroup value FROM usergroup_collection WHERE collection = ?", ["i",$collection]);
# Access if collection_commenting is enabled and request feedback checked
# Access if it's a public collection (or featured collection to which user has access to)
# Access if k is not empty or option to ignore collection access is enabled and k is empty
if (
($collection_commenting && $collectiondata['request_feedback'] == 1)
|| $collectiondata['type'] == COLLECTION_TYPE_PUBLIC
|| ($collectiondata['type'] == COLLECTION_TYPE_FEATURED && featured_collection_check_access_control($collection))
|| $k != ""
|| ($k == "" && $ignore_collection_access)
) {
return true;
}
# Perform these checks only if a user is logged in
# Access if:
# - It's their collection
# - It's a public collection (or featured collection to which user has access to)
# - They have the 'access and edit all collections' admin permission
# - They are attached to this collection
# - Option to ignore collection access is enabled and k is empty
if (
is_numeric($userref)
&& ($userref == $collectiondata["user"]
|| $collectiondata['type'] == COLLECTION_TYPE_PUBLIC
|| ($collectiondata['type'] == COLLECTION_TYPE_FEATURED && featured_collection_check_access_control($collection))
|| checkperm("h")
|| in_array($userref, $attached)
|| in_array($usergroup, $attached_groups)
|| checkperm("R")
|| $k != ""
|| ($k == "" && $ignore_collection_access))
) {
return true;
}
return false;
}
/**
* Sets the current collection of $user to be $collection
*
* @param integer $user
* @param integer $collection
* @return void
*/
function set_user_collection($user, $collection)
{
global $usercollection,$username,$anonymous_login,$anonymous_user_session_collection;
if (!(isset($anonymous_login) && $username == $anonymous_login) || !$anonymous_user_session_collection) {
ps_query("UPDATE user SET current_collection = ? WHERE ref = ?", ["i",$collection,"i",$user]);
}
$usercollection = $collection;
}
/**
* Creates a new collection for user $userid called $name
*
* @param integer $userid
* @param string $name
* @param boolean $allowchanges
* @param boolean $cant_delete
* @param integer $ref
* @param boolean $public
* @return integer
*/
function create_collection($userid, $name, $allowchanges = 0, $cant_delete = 0, $ref = 0, $public = false, $extraparams = array())
{
debug_function_call("create_collection", func_get_args());
global $username,$anonymous_login,$rs_session, $anonymous_user_session_collection;
if (($username == $anonymous_login && $anonymous_user_session_collection) || upload_share_active()) {
// We need to set a collection session_id for the anonymous user. Get session ID to create collection with this set
$rs_session = get_rs_session_id(true);
} else {
$rs_session = "";
}
$setcolumns = array();
$extracolopts = array("type",
"keywords",
"saved_search",
"session_id",
"description",
"savedsearch",
"parent",
"thumbnail_selection_method",
);
foreach ($extracolopts as $coloption) {
if (isset($extraparams[$coloption])) {
$setcolumns[$coloption] = $extraparams[$coloption];
}
}
$setcolumns["name"] = mb_strcut($name, 0, 100);
$setcolumns["user"] = is_numeric($userid) ? $userid : 0;
$setcolumns["allow_changes"] = $allowchanges;
$setcolumns["cant_delete"] = $cant_delete;
$setcolumns["public"] = $public ? COLLECTION_TYPE_PUBLIC : COLLECTION_TYPE_STANDARD;
if ($ref != 0) {
$setcolumns["ref"] = (int)$ref;
}
if (is_int_loose(trim($rs_session))) {
$setcolumns["session_id"] = $rs_session;
}
if ($public) {
$setcolumns["type"] = COLLECTION_TYPE_PUBLIC;
}
$insert_columns = array_keys($setcolumns);
$insert_values = array_values($setcolumns);
$sql = "INSERT INTO collection
(" . implode(",", $insert_columns) . ", created)
VALUES
(" . ps_param_insert(count($insert_values)) . ",NOW())";
ps_query($sql, ps_param_fill($insert_values, 's'));
$ref = sql_insert_id();
index_collection($ref);
clear_query_cache('collection_access' . $userid);
return $ref;
}
/**
* Deletes the collection with reference $ref
*
* @param integer $collection
* @return boolean|void
*/
function delete_collection($collection)
{
global $home_dash, $lang;
if (!is_array($collection)) {
$collection = get_collection($collection);
}
if (!$collection) {
return false;
}
$ref = $collection["ref"];
$type = $collection["type"];
if (!collection_writeable($ref) || is_featured_collection_category_by_children($ref)) {
return false;
}
ps_query("DELETE FROM collection WHERE ref=?", array("i",$ref));
ps_query("DELETE FROM collection_resource WHERE collection=?", array("i",$ref));
ps_query("DELETE FROM collection_keyword WHERE collection=?", array("i",$ref));
ps_query("DELETE FROM external_access_keys WHERE collection=?", array("i",$ref));
if ($home_dash) {
// Delete any dash tiles pointing to this collection
$collection_dash_tiles = ps_array("SELECT ref value FROM dash_tile WHERE link LIKE ?", array("s","%search.php?search=!collection" . $ref . "&%"));
if (count($collection_dash_tiles) > 0) {
ps_query("DELETE FROM dash_tile WHERE ref IN (" . ps_param_insert(count($collection_dash_tiles)) . ")", ps_param_fill($collection_dash_tiles, "i"));
ps_query("DELETE FROM user_dash_tile WHERE dash_tile IN (" . ps_param_insert(count($collection_dash_tiles)) . ")", ps_param_fill($collection_dash_tiles, "i"));
}
}
collection_log($ref, LOG_CODE_COLLECTION_DELETED_COLLECTION, 0, $collection["name"] . " (" . $lang["owner"] . ":" . $collection["username"] . ")");
if ($type === COLLECTION_TYPE_FEATURED) {
clear_query_cache("featured_collections");
} else {
/** {@see create_collection()} */
clear_query_cache("collection_access{$collection['user']}");
}
}
/**
* Adds script to page that refreshes the Collection bar
*
* @param integer $collection Collection id
* @return void
*/
function refresh_collection_frame($collection = "")
{
# Refresh the CollectionDiv
global $baseurl, $headerinsert;
if (getval("ajax", false)) {
echo "";
} else {
$headerinsert .= "";
}
}
/**
* Performs a search for featured collections / public collections.
*
* @param string $search
* @param string $order_by
* @param string $sort
* @param boolean $exclude_themes
* @param boolean $include_resources
* @param boolean $override_group_restrict
* @param integer $fetchrows
* @return array
*/
function search_public_collections($search = "", $order_by = "name", $sort = "ASC", $exclude_themes = true, $include_resources = false, $override_group_restrict = false, $fetchrows = -1)
{
global $userref,$public_collections_confine_group,$userref,$usergroup;
$keysql = "";
$sql = "";
$sql_params = [];
$select_extra = "";
debug_function_call("search_public_collections", func_get_args());
// Validate sort & order_by
$sort = validate_sort_value($sort) ? $sort : 'ASC';
$valid_order_bys = array("fullname", "name", "ref", "count", "type", "created");
$order_by = (in_array($order_by, $valid_order_bys) ? $order_by : "name");
if (strpos($search, "collectiontitle:") !== false) {
// This includes a specific title search from the advanced search page.
$searchtitlelength = 0;
$searchtitleval = "";
$origsearch = $search;
// Force quotes around any collectiontitle: search to support old behaviour
// i.e. to allow split_keywords() to work
// collectiontitle:*ser * collection* simpleyear:2022
// - will be changed to -
// "collectiontitle:*ser * collection*" simpleyear:2022
$searchstart = mb_substr($search, 0, strpos($search, "collectiontitle:"));
$titlepos = strpos($search, "collectiontitle:") + 16;
$searchend = mb_substr($search, $titlepos);
if (strpos($searchend, ":") !== false) {
// Remove any other parts of the search with xxxxx: prefix that relate to other search aspects
$searchtitleval = explode(":", $searchend)[0];
$searchtitleparts = explode(" ", $searchtitleval);
if (count($searchtitleparts) > 1) {
// The last string relates to the next searched field name/attribute
array_pop($searchtitleparts);
}
// Build new string for searched value
$searchtitleval = implode(" ", $searchtitleparts);
$searchtitlelength = strlen($searchtitleval);
if (substr($searchtitleval, -1, 1) == ",") {
$searchtitleval = substr($searchtitleval, 0, -1);
}
// Add quotes
$search = $searchstart . ' "' . "collectiontitle:" . $searchtitleval . '"';
// Append the other search strings
$search .= substr($origsearch, $titlepos + $searchtitlelength);
} else {
// nothing to remove
$search = $searchstart . ' "' . "collectiontitle:" . $searchend . '"';
}
debug("New search: " . $search);
}
$keywords = split_keywords($search, false, false, false, false, true);
if (strlen($search) == 1 && !is_numeric($search)) {
# A-Z search
$sql = "AND c.name LIKE ?";
$sql_params[] = "s";
$sql_params[] = $search . "%";
}
if (strlen($search) > 1 || is_numeric($search)) {
$keyrefs = array();
$keyunions = array();
$unionselect = "SELECT kunion.collection";
for ($n = 0; $n < count($keywords); $n++) {
if (substr($keywords[$n], 0, 1) == "\"" && substr($keywords[$n], -1, 1) == "\"") {
$keywords[$n] = substr($keywords[$n], 1, -1);
}
if (substr($keywords[$n], 0, 16) == "collectiontitle:") {
$newsearch = explode(":", $keywords[$n])[1];
$newsearch = strpos($newsearch, '*') === false ? '%' . trim($newsearch) . '%' : str_replace('*', '%', trim($newsearch));
$sql = "AND c.name LIKE ?";
$sql_params[] = "s";
$sql_params[] = $newsearch;
} elseif (substr($keywords[$n], 0, 16) == "collectionowner:") {
$keywords[$n] = substr($keywords[$n], 16);
$keyref = $keywords[$n];
$sql .= " AND (u.username RLIKE ? OR u.fullname RLIKE ?)";
$sql_params[] = "i";
$sql_params[] = $keyref;
$sql_params[] = "i";
$sql_params[] = $keyref;
} elseif (substr($keywords[$n], 0, 19) == "collectionownerref:") {
$keywords[$n] = substr($keywords[$n], 19);
$keyref = $keywords[$n];
$sql .= " AND (c.user=?)";
$sql_params[] = "i";
$sql_params[] = $keyref;
} elseif (substr($keywords[$n], 0, 10) == "basicyear:" || substr($keywords[$n], 0, 11) == "basicmonth:") {
$dateparts = explode(":", $keywords[$n]);
$yearpart = $dateparts[0] == "basicyear" ? $dateparts[1] : "____";
$monthpart = $dateparts[0] == "basicmonth" ? $dateparts[1] : "__";
$sql .= " AND c.created LIKE ?";
$sql_params[] = "s";
$sql_params[] = $yearpart . "-" . $monthpart . "%";
} else {
if (substr($keywords[$n], 0, 19) == "collectionkeywords:") {
$keywords[$n] = substr($keywords[$n], 19);
}
# Support field specific matching - discard the field identifier as not appropriate for collection searches.
if (strpos($keywords[$n], ":") !== false) {
$keywords[$n] = substr($keywords[$n], strpos($keywords[$n], ":") + 1);
}
$keyref = resolve_keyword($keywords[$n], false);
if ($keyref !== false) {
$keyrefs[] = $keyref;
}
}
}
if ($sql == "" && count($keyrefs) == 0) {
// Not a recognised collection search syntax and no matching keywords
return [];
}
for ($n = 0; $n < count($keyrefs); $n++) {
$select_extra .= ", k.key" . $n;
$unionselect .= ", BIT_OR(key" . $n . "_found) AS key" . $n;
$unionsql = "SELECT collection ";
for ($l = 0; $l < count($keyrefs); $l++) {
$unionsql .= $l == $n ? ",TRUE" : ",FALSE";
$unionsql .= " AS key" . $l . "_found";
}
$unionsql .= " FROM collection_keyword WHERE keyword=" . $keyrefs[$n];
$keyunions[] = $unionsql;
$sql .= " AND key" . $n;
}
if (count($keyunions) > 0) {
$keysql .= " LEFT OUTER JOIN (" . $unionselect . " FROM (" . implode(" UNION ", $keyunions) . ") kunion GROUP BY collection) AS k ON c.ref = k.collection";
}
}
# Restrict to parent, child and sibling groups?
if ($public_collections_confine_group && !$override_group_restrict) {
# Form a list of all applicable groups
$groups = array($usergroup); # Start with user's own group
$usergroupparams = ["i",$usergroup];
$groups = array_merge($groups, ps_array("SELECT ref value FROM usergroup WHERE parent=?", $usergroupparams, 'usergroup')); # Children
$groups = array_merge($groups, ps_array("SELECT parent value FROM usergroup WHERE ref=?", $usergroupparams, 'usergroup')); # Parent
$groups = array_merge($groups, ps_array("SELECT ref value FROM usergroup WHERE parent<>0 AND parent=(SELECT parent FROM usergroup WHERE ref=?)", $usergroupparams, 'usergroup')); # Siblings (same parent)
$sql .= " AND u.usergroup IN (" . ps_param_insert(count($groups)) . ")";
$sql_params = array_merge($sql_params, ps_param_fill($groups, "i"));
}
// Add extra elements to the SELECT statement if needed
if ($include_resources) {
$select_extra .= ", COUNT(DISTINCT cr.resource) AS count";
}
// Filter by type (public/featured collections)
$public_type_filter_sql = "c.`type` = ?";
$public_type_filter_sql_params = ["i",COLLECTION_TYPE_PUBLIC];
if ($exclude_themes) {
$featured_type_filter_sql = "";
$featured_type_filter_sql_params = [];
} else {
$featured_type_filter_sql = "(c.`type` = ?)";
$featured_type_filter_sql_params = ["i",COLLECTION_TYPE_FEATURED];
$fcf_sql = featured_collections_permissions_filter_sql("AND", "c.ref");
if (is_array($fcf_sql)) {
// Update with the extra condition
$featured_type_filter_sql = "(c.`type` = ? " . $fcf_sql[0] . ")";
$featured_type_filter_sql_params = array_merge(["i",COLLECTION_TYPE_FEATURED], $fcf_sql[1]);
}
}
if ($public_type_filter_sql != "" && $featured_type_filter_sql != "") {
$type_filter_sql = "(" . $public_type_filter_sql . " OR " . $featured_type_filter_sql . ")";
$type_filter_sql_params = array_merge($public_type_filter_sql_params, $featured_type_filter_sql_params);
} else {
$type_filter_sql = $public_type_filter_sql . $featured_type_filter_sql;
$type_filter_sql_params = array_merge($public_type_filter_sql_params, $featured_type_filter_sql_params);
}
$where_clause_osql = 'col.`type` = ' . COLLECTION_TYPE_PUBLIC;
if ($featured_type_filter_sql !== '') {
$where_clause_osql .= ' OR (col.`type` = ' . COLLECTION_TYPE_FEATURED . ' AND col.is_featured_collection_category = false)';
}
$main_sql = sprintf(
"SELECT *
FROM (
SELECT DISTINCT c.*,
u.username,
u.fullname,
IF(c.`type` = %s AND COUNT(DISTINCT cc.ref)>0, true, false) AS is_featured_collection_category
%s
FROM collection AS c
LEFT OUTER JOIN collection AS cc ON c.ref = cc.parent
LEFT OUTER JOIN collection_resource AS cr ON c.ref = cr.collection
LEFT OUTER JOIN user AS u ON c.user = u.ref
%s # keysql
WHERE %s # type_filter_sql
%s
GROUP BY c.ref
ORDER BY %s
) AS col
WHERE %s",
COLLECTION_TYPE_FEATURED,
$select_extra,
$keysql,
$type_filter_sql,
$sql, # extra filters
"{$order_by} {$sort}",
$where_clause_osql
);
return ps_query($main_sql, array_merge($type_filter_sql_params, $sql_params), '', $fetchrows);
}
/**
* Search within available collections
*
* @param string $search
* @param string $restypes
* @param integer $archive
* @param string $order_by
* @param string $sort
* @param integer $fetchrows
* @return array
*/
function do_collections_search($search, $restypes, $archive = 0, $order_by = '', $sort = "DESC", $fetchrows = -1)
{
global $search_includes_themes, $default_collection_sort;
if ($order_by == '') {
$order_by = $default_collection_sort;
}
$result = array();
# Recognise a quoted search, which is a search for an exact string
if (substr($search, 0, 1) == "\"" && substr($search, -1, 1) == "\"") {
$search = substr($search, 1, -1);
}
$search_includes_themes_now = $search_includes_themes;
if ($restypes != "") {
$restypes_x = explode(",", $restypes);
$search_includes_themes_now = in_array("FeaturedCollections", $restypes_x);
}
if ($search_includes_themes_now) {
# Same search as when searching within public collections.
$result = search_public_collections($search, "name", "ASC", !$search_includes_themes_now, true, false, $fetchrows);
}
return $result;
}
/**
* Add a collection to a user's 'My Collections'
*
* @param integer $user ID of user
* @param integer $collection ID of collection
*
* @return boolean
*/
function add_collection($user, $collection)
{
// Don't add if we are anonymous - we can only have one collection
global $anonymous_login,$username,$anonymous_user_session_collection;
if (isset($anonymous_login) && ($username == $anonymous_login) && $anonymous_user_session_collection) {
return false;
}
remove_collection($user, $collection);
ps_query("insert into user_collection(user,collection) values (?,?)", array("i",$user,"i",$collection));
clear_query_cache('col_total_ref_count_w_perm');
clear_query_cache('collection_access' . $user);
collection_log($collection, LOG_CODE_COLLECTION_SHARED_COLLECTION, 0, ps_value("select username as value from user where ref = ?", array("i",$user), ""));
return true;
}
/**
* Remove someone else's collection from a user's My Collections
*
* @param integer $user
* @param integer $collection
*/
function remove_collection($user, $collection)
{
ps_query("delete from user_collection where user=? and collection=?", array("i",$user,"i",$collection));
clear_query_cache('col_total_ref_count_w_perm');
collection_log($collection, LOG_CODE_COLLECTION_STOPPED_SHARING_COLLECTION, 0, ps_value("select username as value from user where ref = ?", array("i",$user), ""));
}
/**
* Update the keywords index for this collection
*
* @param integer $ref
* @param string $index_string
* @return integer How many keywords were indexed?
*/
function index_collection($ref, $index_string = '')
{
# Remove existing indexed keywords
ps_query("delete from collection_keyword where collection=?", array("i",$ref)); # Remove existing keywords
# Define an indexable string from the name, themes and keywords.
global $index_collection_titles;
if ($index_collection_titles) {
$indexfields = 'c.ref,c.name,c.keywords,c.description';
} else {
$indexfields = 'c.ref,c.keywords';
}
global $index_collection_creator;
if ($index_collection_creator) {
$indexfields .= ',u.fullname';
}
// if an index string wasn't supplied, generate one
if (!strlen($index_string) > 0) {
$indexarray = ps_query("select $indexfields from collection c left join user u on u.ref=c.user where c.ref = ?", array("i",$ref));
for ($i = 0; $i < count($indexarray); $i++) {
$index_string = "," . implode(',', $indexarray[$i]);
}
}
$keywords = split_keywords($index_string, true);
for ($n = 0; $n < count($keywords); $n++) {
if (trim($keywords[$n]) == "") {
continue;
}
$keyref = resolve_keyword($keywords[$n], true);
ps_query("insert into collection_keyword values (?,?)", array("i",$ref,"i",$keyref));
}
// return the number of keywords indexed
return $n;
}
/**
* Process the save action when saving a collection
*
* @param integer $ref
* @param array $coldata
*
* @return false|void
*/
function save_collection($ref, $coldata = array())
{
if (!is_numeric($ref) || !collection_writeable($ref)) {
return false;
}
if (count($coldata) == 0) {
// Old way
$coldata["name"] = getval("name", "");
$coldata["allow_changes"] = getval("allow_changes", "") != "" ? 1 : 0;
$coldata["public"] = getval('public', 0, true);
$coldata["keywords"] = getval("keywords", "");
$coldata["result_limit"] = getval("result_limit", 0, true);
$coldata["relateall"] = getval("relateall", "") != "";
$coldata["removeall"] = getval("removeall", "") != "";
$coldata["users"] = getval("users", "");
if (checkperm("h")) {
$coldata["home_page_publish"] = (getval("home_page_publish", "") != "") ? "1" : "0";
$coldata["home_page_text"] = getval("home_page_text", "");
$home_page_image = getval("home_page_image", 0, true);
if ($home_page_image > 0) {
$coldata["home_page_image"] = $home_page_image;
}
}
}
$oldcoldata = get_collection($ref);
$sqlset = array();
foreach ($coldata as $colopt => $colset) {
// skip data that is not a collection property (e.g result_limit) otherwise the $sqlset will have an
// incorrect SQL query for the update statement.
if (in_array($colopt, ['result_limit', 'relateall', 'removeall', 'users'])) {
continue;
}
// Set type to public unless explicitly passed
if ($colopt == "public" && $colset == 1 && !isset($coldata["type"])) {
$sqlset["type"] = COLLECTION_TYPE_PUBLIC;
}
// "featured_collections_changes" is determined by collection_edit.php page
// This is meant to override the type if collection has a parent. The order of $coldata elements matters!
if ($colopt == "featured_collections_changes" && !empty($colset)) {
$sqlset["type"] = COLLECTION_TYPE_FEATURED;
$sqlset["parent"] = null;
if (isset($colset["update_parent"])) {
$force_featured_collection_type = isset($colset["force_featured_collection_type"]);
// A FC root category is created directly from the collections_featured.php page so not having a parent, means it's just public
if ($colset["update_parent"] == 0 && !$force_featured_collection_type) {
$sqlset["type"] = COLLECTION_TYPE_PUBLIC;
} else {
$sqlset["parent"] = (int) $colset["update_parent"];
}
}
if (isset($colset["thumbnail_selection_method"])) {
$sqlset["thumbnail_selection_method"] = $colset["thumbnail_selection_method"];
}
if (isset($colset["thumbnail_selection_method"]) || isset($colset["name"])) {
// Prevent the parent from being changed if user only modified the thumbnail_selection_method or name
$sqlset["parent"] = (!isset($colset["update_parent"]) ? $oldcoldata["parent"] : $sqlset["parent"]);
}
// Prevent unnecessary changes
foreach (array("type", "parent", "thumbnail_selection_method") as $puc_to_prop) {
if (isset($sqlset[$puc_to_prop]) && $oldcoldata[$puc_to_prop] == $sqlset[$puc_to_prop]) {
unset($sqlset[$puc_to_prop]);
}
}
continue;
}
if (!isset($oldcoldata[$colopt]) || $colset != $oldcoldata[$colopt]) {
$sqlset[$colopt] = $colset;
}
}
// If collection is set as private by caller code, disable incompatible properties used for COLLECTION_TYPE_FEATURED (set by the user or exsting)
if (isset($sqlset["public"]) && $sqlset["public"] == 0) {
$sqlset["type"] = COLLECTION_TYPE_STANDARD;
$sqlset["parent"] = null;
$sqlset["thumbnail_selection_method"] = null;
$sqlset["bg_img_resource_ref"] = null;
}
/*
Order by is applicable only to featured collections.
Determine if we have to reset and, if required, re-order featured collections at the tree level
----------------------------------------------------------------------------------------------------------------
| Old | Set |
|---------------|-------------------|
Use cases | Type | Parent | Type | Parent | Reset order_by? | Re-order?
------------------------------------------------|------|--------|-----------------------------------------------
Move FC to private | 3 | null | 0 | null | yes | no
Move FC to public | 3 | any | 4 | null | yes | no
Move FC to new parent | 3 | null | not set | X | yes | yes
Save FC but don’t change type or parent | 3 | null | not set | null | no | no
Save a child FC but don’t change type or parent | 3 | X | not set | not set | no | no
Move public to private | 4 | null | 0 | null | no | no
Move public to FC (root) | 4 | null | 3 | not set | yes | yes
Move public to FC (others) | 4 | null | 3 | X | yes | yes
Save public but don’t change type or parent | 4 | null | 4 | not set | no | no
Create FC at root | 0 | null | 3 | not set | yes | yes
Create FC at other level | 0 | null | 3 | X | yes | yes
----------------------------------------------------------------------------------------------------------------
*/
// Saving a featured collection without changing its type or parent
$rob_cond_fc_no_change = (
isset($oldcoldata['type']) && $oldcoldata['type'] === COLLECTION_TYPE_FEATURED
&& !isset($sqlset['type'])
&& (!isset($sqlset['parent']) || is_null($sqlset['parent']))
);
// Saving a public collection without changing it into a featured collection
$rob_cond_public_col_no_change = (
isset($oldcoldata['type'], $sqlset['type'])
&& $oldcoldata['type'] === COLLECTION_TYPE_PUBLIC
&& $sqlset["type"] !== COLLECTION_TYPE_FEATURED
);
if (!($rob_cond_fc_no_change || $rob_cond_public_col_no_change)) {
$sqlset['order_by'] = 0;
if (
// Type changed to featured collection
(isset($sqlset['type']) && $sqlset['type'] === COLLECTION_TYPE_FEATURED)
// Featured collection moved in the tree (ie parent changed)
|| ($oldcoldata['type'] === COLLECTION_TYPE_FEATURED && !isset($sqlset['type']) && isset($sqlset['parent']))
) {
$reorder_fcs = true;
}
}
// Update collection record
if (count($sqlset) > 0) {
$sqlupdate = "";
$clear_fc_query_cache = false;
$collection_columns = [
'name',
'user',
'created',
'public',
'allow_changes',
'cant_delete',
'keywords',
'savedsearch',
'home_page_publish',
'home_page_text',
'home_page_image',
'session_id',
'description',
'type',
'parent',
'thumbnail_selection_method',
'bg_img_resource_ref',
'order_by',
];
$params = [];
foreach ($sqlset as $colopt => $colset) {
// Only valid collection columns should be processed
if (!in_array($colopt, $collection_columns)) {
continue;
}
if ($sqlupdate != "") {
$sqlupdate .= ", ";
}
if (in_array($colopt, array("type", "parent", "thumbnail_selection_method", "bg_img_resource_ref"))) {
$clear_fc_query_cache = true;
}
if (in_array($colopt, array("parent", "thumbnail_selection_method", "bg_img_resource_ref"))) {
$sqlupdate .= $colopt . " = ";
if ($colset == 0) {
$sqlupdate .= 'NULL';
} else {
$sqlupdate .= '?';
$params = array_merge($params, ['i', $colset]);
}
continue;
}
if ($colopt == 'allow_changes') {
$colset = (int) $colset;
}
$sqlupdate .= $colopt . " = ? ";
$params = array_merge($params, ['s', $colset]);
}
if ($sqlupdate !== '') {
$sql = "UPDATE collection SET {$sqlupdate} WHERE ref = ?";
ps_query($sql, array_merge($params, ['i', $ref]));
if ($clear_fc_query_cache) {
clear_query_cache("featured_collections");
}
// Log the changes
foreach ($sqlset as $colopt => $colset) {
switch ($colopt) {
case "public";
collection_log($ref, LOG_CODE_COLLECTION_ACCESS_CHANGED, 0, $colset ? 'public' : 'private');
break;
case "allow_changes";
collection_log($ref, LOG_CODE_UNSPECIFIED, 0, $colset ? 'true' : 'false');
break;
default;
collection_log($ref, LOG_CODE_EDITED, 0, $colopt . " = " . $colset);
break;
}
}
}
}
index_collection($ref);
# If 'users' is specified (i.e. access is private) then rebuild users list
if (isset($coldata["users"])) {
$old_attached_users = ps_array("SELECT user value FROM user_collection WHERE collection=?", array("i",$ref));
$new_attached_users = array();
$removed_users = array();
$collection_owner_ref = ps_value(
"SELECT u.ref value FROM collection c LEFT JOIN user u ON c.user=u.ref WHERE c.ref=?",
array("i",$ref),
""
);
global $userref;
$collection_owner = get_user(($collection_owner_ref == '' ? $userref : $collection_owner_ref));
if ($collection_owner_ref != "") {
$old_attached_users[] = $collection_owner["ref"]; # Collection Owner is implied as attached already
}
ps_query("delete from user_collection where collection=?", array("i",$ref));
$old_attached_groups = ps_array("SELECT usergroup value FROM usergroup_collection WHERE collection=?", array("i",$ref));
ps_query("delete from usergroup_collection where collection=?", array("i",$ref));
# Build a new list and insert
$users = resolve_userlist_groups($coldata["users"]);
$ulist = array_unique(trim_array(explode(",", $users)));
$urefs = ps_array("select ref value from user where username in (" . ps_param_insert(count($ulist)) . ")", ps_param_fill($ulist, "s"));
if (count($urefs) > 0) {
$params = [];
foreach ($urefs as $uref) {
$params[] = $ref;
$params[] = $uref;
}
ps_query("insert into user_collection(collection,user) values " . trim(str_repeat('(?, ?),', count($urefs)), ','), ps_param_fill($params, 'i'));
$new_attached_users = array_diff($urefs, $old_attached_users);
$removed_users = array_diff($old_attached_users, $urefs, $collection_owner_ref != "" ? array($collection_owner["ref"]) : array());
}
# log this only if a user is being added
if ($coldata["users"] != "") {
collection_log($ref, LOG_CODE_COLLECTION_SHARED_COLLECTION, 0, join(", ", $ulist));
}
# log the removal of users / smart groups
$was_shared_with = array();
if (count($old_attached_users) > 0) {
$was_shared_with = ps_array("select username value from user where ref in (" . ps_param_insert(count($old_attached_users)) . ")", ps_param_fill($old_attached_users, "i"));
}
if (count($old_attached_groups) > 0) {
foreach ($old_attached_groups as $old_group) {
$was_shared_with[] = "Group (Smart): " . ps_value("SELECT name value FROM usergroup WHERE ref = ?", array("i", $old_group), "");
}
}
if (count($urefs) == 0 && count($was_shared_with) > 0) {
collection_log($ref, LOG_CODE_COLLECTION_STOPPED_SHARING_COLLECTION, 0, join(", ", $was_shared_with));
}
$groups = resolve_userlist_groups_smart($users);
$groupnames = '';
if ($groups != '') {
$groups = explode(",", $groups);
if (count($groups) > 0) {
foreach ($groups as $group) {
ps_query("insert into usergroup_collection(collection,usergroup) values (?,?)", array("i",$ref,"i",$group));
// get the group name
if ($groupnames != '') {
$groupnames .= ", ";
}
$groupnames .= ps_value("select name value from usergroup where ref=?", array("i",$group), "");
}
$new_attached_groups = array_diff($groups, $old_attached_groups);
if (!empty($new_attached_groups)) {
foreach ($new_attached_groups as $newg) {
$group_users = ps_array("SELECT ref value FROM user WHERE usergroup=?", array("i",$newg));
$new_attached_users = array_merge($new_attached_users, $group_users);
}
}
}
#log this
collection_log($ref, LOG_CODE_COLLECTION_SHARED_COLLECTION, 0, $groupnames);
}
# Clear user specific collection cache if user was added or removed.
if (count($new_attached_users) > 0 || count($removed_users) > 0) {
$user_caches = array_unique(array_merge($new_attached_users, $removed_users));
foreach ($user_caches as $user_cache) {
clear_query_cache('collection_access' . $user_cache);
}
}
}
# Send a message to any new attached user
if (!empty($new_attached_users)) {
global $baseurl, $lang;
$new_attached_users = array_unique($new_attached_users);
$message_text = str_replace(
array('%user%', '%colname%'),
array($collection_owner["fullname"] ?? $collection_owner["username"],getval("name", "")),
$lang['collectionprivate_attachedusermessage']
);
$message_url = $baseurl . "/?c=" . $ref;
message_add($new_attached_users, $message_text, $message_url);
}
# Relate all resources?
if (
isset($coldata["relateall"]) && $coldata["relateall"] != ""
&& allow_multi_edit($ref)
) {
relate_all_collection($ref);
}
# Remove all resources?
if (isset($coldata["removeall"]) && $coldata["removeall"] != "") {
remove_all_resources_from_collection($ref);
}
# Update limit count for saved search
if (isset($coldata["result_limit"]) && (int)$coldata["result_limit"] > 0) {
ps_query("update collection_savedsearch set result_limit=? where collection=?", array("i",$coldata["result_limit"],"i",$ref));
}
// Re-order featured collections tree at the level of this collection (if applicable - only for featured collections)
if (isset($reorder_fcs)) {
$new_fcs_order = reorder_all_featured_collections_with_parent($sqlset['parent'] ?? null);
log_activity("via save_collection({$ref})", LOG_CODE_REORDERED, implode(', ', $new_fcs_order), 'collection');
}
// When a collection is now saved as a Featured Collection (must have resources) under an existing branch, apply all
// the external access keys from the categories which make up that path to prevent breaking existing shares.
if (
isset($sqlset['parent']) && $sqlset['parent'] > 0
&& !empty($fc_resources = array_filter((array) get_collection_resources($ref)))
) {
// Delete old branch path external share associations as they are no longer relevant
$old_branch_category_ids = array_column(get_featured_collection_category_branch_by_leaf((int) $oldcoldata['parent'], []), 'ref');
foreach ($old_branch_category_ids as $fc_category_id) {
$old_keys = get_external_shares([
'share_collection' => $fc_category_id,
'share_type' => 0,
'ignore_permissions' => true
]);
foreach ($old_keys as $old_key_data) {
// IMPORTANT: we delete the keys associated with the collection we've just saved. The key may still be valid for the rest of the branch categories.
delete_collection_access_key($ref, $old_key_data['access_key']);
}
}
// Copy associations of all branch parents and apply to this collection and its resources
$all_branch_path_keys = [];
$branch_category_ids = array_column(get_featured_collection_category_branch_by_leaf($sqlset['parent'], []), 'ref');
foreach ($branch_category_ids as $fc_category_id) {
$all_branch_path_keys = array_merge(
$all_branch_path_keys,
get_external_shares([
'share_collection' => $fc_category_id,
'share_type' => 0,
'ignore_permissions' => true
])
);
}
foreach ($all_branch_path_keys as $external_key_data) {
foreach ($fc_resources as $fc_resource_id) {
if (!can_share_resource($fc_resource_id)) {
continue;
}
ps_query(
'INSERT INTO external_access_keys(resource, access_key, collection, `user`, usergroup, email, `date`, access, expires, password_hash) VALUES (?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?)',
[
'i', $fc_resource_id,
's', $external_key_data['access_key'],
'i', $ref,
'i', $GLOBALS['userref'],
'i', $external_key_data['usergroup'],
's', $external_key_data['email'],
'i', $external_key_data['access'],
's', $external_key_data['expires'] ?: null,
's', $external_key_data['password_hash'] ?: null
]
);
collection_log($ref, LOG_CODE_COLLECTION_SHARED_RESOURCE_WITH, $fc_resource_id, $external_key_data['access_key']);
}
}
}
global $userref;
clear_query_cache('collection_access' . $userref);
refresh_collection_frame();
}
/**
* Case insensitive string comparisons using a "natural order" algorithm for collection names
*
* @param string $a
* @param string $b
*
* @return integer < 0 if $a is less than $b > 0 if $a is greater than $b, and 0 if they are equal.
*/
function collections_comparator($a, $b)
{
return strnatcasecmp(i18n_get_collection_name($a), i18n_get_collection_name($b));
}
/**
* Case insensitive string comparisons using a "natural order" algorithm for collection names
*
* @param string $b
* @param string $a
*
* @return integer < 0 if $a is less than $b > 0 if $a is greater than $b, and 0 if they are equal.
*/
function collections_comparator_desc($a, $b)
{
return strnatcasecmp(i18n_get_collection_name($b), i18n_get_collection_name($a));
}
/**
* Returns a list of smart theme headers, which are basically fields with a 'smart theme name' set.
*
* @return array
*/
function get_smart_theme_headers()
{
return ps_query("SELECT ref, name, smart_theme_name, type FROM resource_type_field WHERE length(smart_theme_name) > 0 ORDER BY smart_theme_name", array(), "featured_collections");
}
/**
* get_smart_themes_nodes
*
* @param integer $field
* @param boolean $is_category_tree
* @param integer $parent
* @param array $field_meta - resource type field metadata
* @return array
*/
function get_smart_themes_nodes($field, $is_category_tree, $parent = null, array $field_meta = array())
{
$return = array();
// Determine if this should cascade onto children for category tree type
$recursive = false;
if ($is_category_tree) {
$recursive = true;
}
$nodes = get_nodes($field, ((0 == $parent) ? null : $parent), $recursive);
if (isset($field_meta['automatic_nodes_ordering']) && (bool) $field_meta['automatic_nodes_ordering']) {
$nodes = reorder_nodes($nodes);
$nodes = array_values($nodes); // reindex nodes array
}
if (0 === count($nodes)) {
return $return;
}
/*
Tidy list so it matches the storage format used for keywords
The translated version is fetched as each option will be indexed in the local language version of each option
*/
$options_base = array();
for ($n = 0; $n < count($nodes); $n++) {
$options_base[$n] = trim(mb_convert_case(i18n_get_translated($nodes[$n]['name']), MB_CASE_LOWER, 'UTF-8'));
}
// For each option, if it is in use, add it to the return list
for ($n = 0; $n < count($nodes); $n++) {
$cleaned_option_base = preg_replace('/\W/', ' ', $options_base[$n]); // replace any non-word characters with a space
$cleaned_option_base = trim($cleaned_option_base); // trim (just in case prepended / appended space characters)
$tree_node_depth = 0;
$parent_node_to_use = 0;
$is_parent = false;
if (is_parent_node($nodes[$n]['ref'])) {
$parent_node_to_use = $nodes[$n]['ref'];
$is_parent = true;
$tree_node_depth = get_tree_node_level($nodes[$n]['ref']);
if (!is_null($parent) && is_parent_node($parent)) {
$tree_node_depth--;
}
}
$c = count($return);
$return[$c]['name'] = trim(i18n_get_translated($nodes[$n]['name']));
$return[$c]['indent'] = $tree_node_depth;
$return[$c]['node'] = $parent_node_to_use;
$return[$c]['is_parent'] = $is_parent;
$return[$c]['ref'] = $nodes[$n]['ref'];
}
return $return;
}
/**
* E-mail a collection to users
*
* - Attempt to resolve all users in the string $userlist to user references.
* - Add $collection to these user's 'My Collections' page
* - Send them an e-mail linking to this collection
* - Handle multiple collections (comma separated list)
*
* @param mixed $colrefs
* @param string $collectionname
* @param string $fromusername
* @param string $userlist
* @param string $message
* @param string $feedback
* @param integer $access
* @param string $expires
* @param string $useremail
* @param string $from_name
* @param string $cc
* @param boolean $themeshare
* @param string $themename
* @param string $themeurlsuffix
* @param boolean $list_recipients
* @param boolean $add_internal_access
* @param string $group
* @param string $sharepwd
*/
function email_collection($colrefs, $collectionname, $fromusername, $userlist, $message, $feedback, $access = -1, $expires = "", $useremail = "", $from_name = "", $cc = "", $themeshare = false, $themename = "", $themeurlsuffix = "", $list_recipients = false, $add_internal_access = false, $group = "", $sharepwd = ""): string
{
global $baseurl,$email_from,$applicationname,$lang,$userref,$usergroup;
if ($useremail == "") {
$useremail = $email_from;
}
if ($group == "") {
$group = $usergroup;
}
if (trim($userlist) == "") {
return $lang["mustspecifyoneusername"];
}
$userlist = resolve_userlist_groups($userlist);
if (strpos($userlist, $lang["groupsmart"] . ": ") !== false) {
$groups_users = resolve_userlist_groups_smart($userlist, true);
if ($groups_users != '') {
if ($userlist != "") {
$userlist = remove_groups_smart_from_userlist($userlist);
if ($userlist != "") {
$userlist .= ",";
}
}
$userlist .= $groups_users;
}
}
$ulist = trim_array(explode(",", $userlist));
$emails = array();
$key_required = array();
if ($feedback) {
$feedback = 1;
} else {
$feedback = 0;
}
$reflist = trim_array(explode(",", $colrefs));
// Take out the FC category from the list as this is more of a dummy record rather than a collection we'll be giving
// access to users. See generate_collection_access_key() when collection is a featured collection category.
$fc_category_ref = ($themeshare ? array_shift($reflist) : null);
$emails_keys = resolve_user_emails($ulist);
if (0 === count($emails_keys)) {
return $lang['email_error_user_list_not_valid'];
}
# Make an array of all emails, whether internal or external
$emails = $emails_keys['emails'];
# Make a corresponding array stating whether keys are necessary for the links
$key_required = $emails_keys['key_required'];
# Make an array of internal userids which are unexpired approved with valid emails
$internal_user_ids = $emails_keys['refs'] ?? array();
if (count($internal_user_ids) > 0) {
# Delete any existing collection entries
ps_query("DELETE FROM user_collection WHERE collection IN (" . ps_param_insert(count($reflist)) . ")
AND user IN (" . ps_param_insert(count($internal_user_ids)) . ")", array_merge(ps_param_fill($reflist, "i"), ps_param_fill($internal_user_ids, "i")));
# Insert new user_collection row(s)
#loop through the collections
for ($nx1 = 0; $nx1 < count($reflist); $nx1++) {
#loop through the users
for ($nx2 = 0; $nx2 < count($internal_user_ids); $nx2++) {
ps_query("INSERT INTO user_collection(collection,user,request_feedback) VALUES (?,?,?)", ["i",$reflist[$nx1],"i",$internal_user_ids[$nx2],"i",$feedback ]);
if ($add_internal_access) {
foreach (get_collection_resources($reflist[$nx1]) as $resource) {
if (get_edit_access($resource)) {
open_access_to_user($internal_user_ids[$nx2], $resource, $expires);
}
}
}
#log this
clear_query_cache('collection_access' . $internal_user_ids[$nx2]);
collection_log($reflist[$nx1], LOG_CODE_COLLECTION_SHARED_COLLECTION, 0, ps_value("select username as value from user where ref = ?", array("i", $internal_user_ids[$nx2]), ""));
}
}
}
# Send an e-mail to each resolved email address
# htmlbreak is for composing list
$htmlbreak = "\r\n";
global $use_phpmailer;
if ($use_phpmailer) {
$htmlbreak = "
";
$htmlbreaksingle = "
";
}
if ($fromusername == "") {
$fromusername = $applicationname;
} // fromusername is used for describing the sender's name inside the email
if ($from_name == "") {
$from_name = $applicationname;
} // from_name is for the email headers, and needs to match the email address (app name or user name)
$templatevars['message'] = str_replace(array("\\n","\\r","\\"), array("\n","\r",""), $message);
if (trim($templatevars['message']) == "") {
$templatevars['message'] = $lang['nomessage'];
$message = "lang_nomessage";
}
$templatevars['fromusername'] = $fromusername;
$templatevars['from_name'] = $from_name;
// Create notification message
$notifymessage = new ResourceSpaceUserNotification();
if (count($reflist) > 1) {
$notifymessage->set_subject($applicationname . ": ");
$notifymessage->append_subject("lang_mycollections");
} else {
$notifymessage->set_subject($applicationname . ": " . $collectionname);
}
if ($fromusername == "") {
$fromusername = $applicationname;
}
$externalmessage = str_replace('[applicationname]', $applicationname, $lang["emailcollectionmessageexternal"]);
$internalmessage = "lang_emailcollectionmessage";
$viewlinktext = "lang_clicklinkviewcollection";
if ($themeshare) { // Change the text if sharing a theme category
$externalmessage = str_replace('[applicationname]', $applicationname, $lang["emailthemecollectionmessageexternal"]);
$internalmessage = "lang_emailthememessage";
$viewlinktext = "lang_clicklinkviewcollections";
}
## loop through recipients
for ($nx1 = 0; $nx1 < count($emails); $nx1++) {
## loop through collections
$list = "";
$list2 = "";
$origviewlinktext = $viewlinktext; // Save this text as we may change it for internal theme shares for this user
if ($themeshare && !$key_required[$nx1]) { # don't send a whole list of collections if internal, just send the theme category URL
$notifymessage->set_subject($applicationname . ": " . $themename);
$url = $baseurl . "/pages/collections_featured.php" . $themeurlsuffix;
$viewlinktext = "lang_clicklinkviewthemes";
$notifymessage->url = $url;
$emailcollectionmessageexternal = false;
if ($use_phpmailer) {
$link = '' . $themename . '';
$list .= $htmlbreak . $link;
// alternate list style
$list2 .= $htmlbreak . $themename . ' -' . $htmlbreaksingle . $url;
$templatevars['list2'] = $list2;
} else {
$list .= $htmlbreak . $url;
}
for ($nx2 = 0; $nx2 < count($reflist); $nx2++) {
#log this
collection_log($reflist[$nx2], LOG_CODE_COLLECTION_EMAILED_COLLECTION, 0, $emails[$nx1]);
}
} else {
// E-mail external share, generate the access key based on the FC category. Each sub-collection will have the same key.
if ($key_required[$nx1] && $themeshare && !is_null($fc_category_ref)) {
$k = generate_collection_access_key($fc_category_ref, $feedback, $emails[$nx1], $access, $expires, $group, $sharepwd, $reflist);
$fc_key = "&k={$k}";
}
for ($nx2 = 0; $nx2 < count($reflist); $nx2++) {
$key = "";
$emailcollectionmessageexternal = false;
# Do we need to add an external access key for this user (e-mail specified rather than username)?
if ($key_required[$nx1] && !$themeshare) {
$k = generate_collection_access_key($reflist[$nx2], $feedback, $emails[$nx1], $access, $expires, $group, $sharepwd);
$key = "&k=" . $k;
$emailcollectionmessageexternal = true;
}
// If FC category, the key is valid across all sub-featured collections. See generate_collection_access_key()
elseif ($key_required[$nx1] && $themeshare && !is_null($fc_category_ref)) {
$key = $fc_key;
$emailcollectionmessageexternal = true;
}
$url = $baseurl . "/?c=" . $reflist[$nx2] . $key;
$collection = array();
$collection = ps_query("SELECT name,savedsearch FROM collection WHERE ref = ?", ["i",$reflist[$nx2]]);
if ($collection[0]["name"] != "") {
$collection_name = i18n_get_collection_name($collection[0]);
} else {
$collection_name = $reflist[$nx2];
}
if ($use_phpmailer) {
$link = '' . escape($collection_name) . '';
$list .= $htmlbreak . $link;
// alternate list style
$list2 .= $htmlbreak . $collection_name . ' -' . $htmlbreaksingle . $url;
$templatevars['list2'] = $list2;
} else {
$list .= $htmlbreak . $collection_name . $htmlbreak . $url . $htmlbreak;
}
#log this
collection_log($reflist[$nx2], LOG_CODE_COLLECTION_EMAILED_COLLECTION, 0, $emails[$nx1]);
}
}
$templatevars['list'] = $list;
$templatevars['from_name'] = $from_name;
if (isset($k)) {
if ($expires == "") {
$templatevars['expires_date'] = $lang["email_link_expires_never"];
$templatevars['expires_days'] = $lang["email_link_expires_never"];
} else {
$day_count = round((strtotime($expires) - strtotime('now')) / (60 * 60 * 24));
$templatevars['expires_date'] = $lang['email_link_expires_date'] . nicedate($expires);
$templatevars['expires_days'] = $lang['email_link_expires_days'] . $day_count;
if ($day_count > 1) {
$templatevars['expires_days'] .= " " . $lang['expire_days'] . ".";
} else {
$templatevars['expires_days'] .= " " . $lang['expire_day'] . ".";
}
}
} else {
# Set empty expiration templatevars
$templatevars['expires_date'] = '';
$templatevars['expires_days'] = '';
}
$body = "";
if ($emailcollectionmessageexternal) {
$template = ($themeshare) ? "emailthemeexternal" : "emailcollectionexternal";
// External - send email
if (is_array($emails) && (count($emails) > 1) && $list_recipients === true) {
$body = $lang["list-recipients"] . "\n" . implode("\n", $emails) . "\n\n";
$templatevars['list-recipients'] = $lang["list-recipients"] . "\n" . implode("\n", $emails) . "\n\n";
}
if (substr($viewlinktext, 0, 5) == "lang_") {
$langkey = substr($viewlinktext, 5);
if (isset($lang[$langkey])) {
$viewlinktext = $lang[$langkey];
}
}
$body .= $templatevars['fromusername'] . " " . $externalmessage . "\n\n" . $templatevars['message'] . "\n\n" . $viewlinktext . "\n\n" . $templatevars['list'];
$emailsubject = $notifymessage->get_subject();
$send_result = send_mail($emails[$nx1], $emailsubject, $body, $fromusername, $useremail, $template, $templatevars, $from_name, $cc);
if ($send_result !== true) {
return $send_result;
}
} else {
$template = ($themeshare) ? "emailtheme" : "emailcollection";
}
$viewlinktext = $origviewlinktext;
}
if (count($internal_user_ids) > 0) {
// Internal share, send notifications
$notifymessage->append_text($templatevars['fromusername'] . " ");
$notifymessage->append_text($internalmessage);
$notifymessage->append_text("
" . $templatevars['message'] . "
");
$notifymessage->append_text($viewlinktext);
$notifymessage->url = $url;
send_user_notification($internal_user_ids, $notifymessage);
}
hook("additional_email_collection", "", array($colrefs,$collectionname,$fromusername,$userlist,$message,$feedback,$access,$expires,$useremail,$from_name,$cc,$themeshare,$themename,$themeurlsuffix,$template,$templatevars));
# Identify user accounts which have been skipped
$candidate_users = ps_query("SELECT ref, username FROM user
WHERE username IN (" . ps_param_insert(count($ulist)) . ")", ps_param_fill($ulist, "s"));
$skipped_usernames = array();
if (count($candidate_users) != count($internal_user_ids)) {
foreach ($candidate_users as $candidate_user) {
if (!in_array($candidate_user['ref'], $internal_user_ids)) {
$skipped_usernames[] = $candidate_user['username'];
}
}
}
# Report skipped accounts
if (count($skipped_usernames) > 0) {
return $lang['email_error_user_list_some_skipped'] . ' ' . implode(', ', $skipped_usernames);
}
# Return an empty string (all OK).
return "";
}
/**
* Generate an external access key to allow external people to view the resources in this collection.
*
* @param integer $collection Collection ref -or- collection data structure
* @param integer $feedback
* @param string $email
* @param integer $access
* @param string $expires
* @param string $group
* @param string $sharepwd
* @param array $sub_fcs List of sub-featured collections IDs (collection_email.php page has logic to determine
* this which is carried forward to email_collection())
*
* @return string The generated key used for external sharing
*/
function generate_collection_access_key($collection, $feedback = 0, $email = "", $access = -1, $expires = "", $group = "", $sharepwd = "", array $sub_fcs = array())
{
global $userref, $usergroup, $scramble_key;
// Default to sharing with the permission of the current usergroup if not specified OR no access to alternative group selection.
if ($group == "" || !checkperm("x")) {
$group = $usergroup;
}
if (!is_array($collection)) {
$collection = get_collection($collection);
}
if (!empty($collection) && $collection["type"] == COLLECTION_TYPE_FEATURED && !isset($collection["has_resources"])) {
$collection_resources = get_collection_resources($collection["ref"]);
$collection["has_resources"] = (is_array($collection_resources) && !empty($collection_resources) ? 1 : 0);
}
$is_featured_collection_category = is_featured_collection_category($collection);
// We build a collection list to allow featured collections children that are externally shared as part of a parent,
// to all be shared with the same parameters (e.g key, access, group). When the collection is not COLLECTION_TYPE_FEATURED
// this will hold just that collection
$collections = array($collection["ref"]);
if ($is_featured_collection_category) {
$collections = (!empty($sub_fcs) ? $sub_fcs : get_featured_collection_categ_sub_fcs($collection));
}
// Generate the key based on the original collection. For featured collection category, all sub featured collections
// will share the same key
$k = generate_share_key($collection["ref"]);
if ($expires != '') {
$expires = date_format(date_create($expires), 'Y-m-d') . ' 23:59:59';
}
$main_collection = $collection; // keep record of this info as we need it at the end to record the successful generation of a key for a featured collection category
$created_sub_fc_access_key = false;
foreach ($collections as $collection) {
$r = get_collection_resources($collection);
$shareable_resources = array_filter($r, function ($resource_ref) {
return can_share_resource($resource_ref);
});
foreach ($shareable_resources as $resource_ref) {
$sql = '';
$params = [];
if ($expires == '') {
$sql = 'NULL, ';
} else {
$sql = '?, ';
$params[] = 's';
$params[] = $expires;
}
if (!($sharepwd != "" && $sharepwd != "(unchanged)")) {
$sql .= 'NULL';
} else {
$sql .= '?';
$params[] = 's';
$params[] = hash("sha256", $k . $sharepwd . $scramble_key);
}
ps_query(
"INSERT INTO external_access_keys(resource, access_key, collection, `user`, usergroup, request_feedback, email, `date`, access, expires, password_hash) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), ?, {$sql})",
array_merge(
[
'i', $resource_ref,
's', $k,
'i', $collection,
'i', $userref,
'i', $group,
's', $feedback,
's', $email,
'i', $access
],
$params
)
);
$created_sub_fc_access_key = true;
}
}
if ($is_featured_collection_category && $created_sub_fc_access_key) {
$sql = '';
$params = [];
if ($expires == '') {
$sql = 'NULL, ';
} else {
$sql = '?, ';
$params[] = 's';
$params[] = $expires;
}
if (!($sharepwd != "" && $sharepwd != "(unchanged)")) {
$sql .= 'NULL';
} else {
$sql .= '?';
$params[] = 's';
$params[] = hash("sha256", $k . $sharepwd . $scramble_key);
}
// add for FC category. No resource. This is a dummy record so we can have a way to edit the external share done
// at the featured collection category level
ps_query(
"INSERT INTO external_access_keys(resource, access_key, collection, `user`, usergroup, request_feedback, email, `date`, access, expires, password_hash) VALUES (NULL, ?, ?, ?, ?, ?, ?, NOW(), ?, {$sql})",
array_merge(
[
's', $k,
'i', $main_collection["ref"],
'i', $userref,
'i', $group,
's', $feedback,
's', $email,
'i', $access
],
$params
)
);
}
return $k;
}
/**
* Returns all saved searches in a collection
*
* @param integer $collection
*/
function get_saved_searches($collection): array
{
return ps_query("select " . columns_in("collection_savedsearch") . " from collection_savedsearch where collection= ? order by created", ['i', $collection]);
}
/**
* Add a saved search to a collection
*
* @param integer $collection
* @return void
*/
function add_saved_search($collection)
{
ps_query("insert into collection_savedsearch(collection,search,restypes,archive) values (?,?,?,?)", array("i",$collection,"s",getval("addsearch", ""),"s",getval("restypes", ""),"s",getval("archive", "")));
}
/**
* Remove a saved search from a collection
*
* @param integer $collection
* @param integer $search
* @return void
*/
function remove_saved_search($collection, $search)
{
ps_query("delete from collection_savedsearch where collection=? and ref=?", array("i",$collection,"i",$search));
}
/**
* Greate a new smart collection using submitted values
*
* @return void
*/
function add_smart_collection()
{
global $userref, $search_all_workflow_states, $lang;
$search = getval("addsmartcollection", "");
$restypes = getval("restypes", "");
if ($restypes == "Global") {
$restypes = "";
}
# archive can be a string of values
$archive = getval('archive', 0, false);
if ($search_all_workflow_states && $archive == "") {
$archive = 'all';
}
if ($archive == "") {
$archive = 0;
}
// more compact search strings should work with get_search_title
$searchstring = array();
if ($search != "") {
$searchstring[] = "search=$search";
}
if ($restypes != "") {
$searchstring[] = "restypes=$restypes";
}
if ($archive !== 0) {
if ($archive === 'all') {
$archive_label = $lang['all_workflow_states'];
} else {
$archive_label = $archive;
}
$searchstring[] = "archive=$archive_label";
}
$searchstring = implode("&", $searchstring);
$newcollection = create_collection($userref, get_search_title($searchstring), 1);
ps_query("insert into collection_savedsearch(collection,search,restypes,archive,starsearch)
values (?,?,?,?,?)", array("i",$newcollection,"s",$search,"s",$restypes,"s",$archive,"i",DEPRECATED_STARSEARCH));
$savedsearch = sql_insert_id();
ps_query("update collection set savedsearch=? where ref=?", array("i",$savedsearch,"i",$newcollection));
set_user_collection($userref, $newcollection);
refresh_collection_frame($newcollection);
}
/**
* Get a display friendly name for the given search string
* Takes a full searchstring of the form 'search=restypes=archive=' and
* uses search_title_processing to autocreate a more informative title
*
* @param string $searchstring Search string
*
* @return string Friendly name for search
*/
function get_search_title($searchstring)
{
$order_by = "";
$sort = "";
$offset = "";
$k = getval("k", "");
$search_titles = true;
$search_titles_searchcrumbs = true;
$use_refine_searchstring = true;
global $lang,$userref,$baseurl,$collectiondata,$result,$display,$pagename,$collection,$userrequestmode;
parse_str($searchstring, $searchvars);
if (isset($searchvars["archive"])) {
$archive = $searchvars["archive"];
} else {
$archive = 0;
}
if (isset($searchvars["search"])) {
$search = $searchvars["search"];
} else {
$search = "";
}
if (isset($searchvars["restypes"])) {
$restypes = $searchvars["restypes"];
} else {
$restypes = "";
}
include __DIR__ . "/search_title_processing.php";
if ($restypes != "") {
$resource_types = get_resource_types($restypes, true, false, true);
foreach ($resource_types as $type) {
$typenames[] = $type['name'];
}
$search_title .= " [" . implode(', ', $typenames) . "]";
}
return str_replace(">", "", strip_tags(htmlspecialchars_decode($search_title)));
}
/**
* Adds all the resources in the provided search to $collection
*
* @param integer $collection
* @param string $search
* @param string $restypes
* @param string $archivesearch
* @param string $order_by
* @param string $sort
* @param string $daylimit
* @param int $res_access The ID of the resource access level
* @param boolean $editable_only If true then only editable resources will be added
* @return boolean
*/
function add_saved_search_items(
$collection,
$search = "",
$restypes = "",
$archivesearch = "",
$order_by = "relevance",
$sort = "desc",
$daylimit = "",
$res_access = "",
$editable_only = false
) {
if ((string)(int)$collection != $collection) {
// Not an integer
return false;
}
global $collection_share_warning, $collection_allow_not_approved_share, $userref, $collection_block_restypes, $search_all_workflow_states;
# Adds resources from a search to the collection.
if ($search_all_workflow_states && trim($archivesearch) !== "" && $archivesearch != 0) {
$search_all_workflow_states = false;
}
$results = do_search($search, $restypes, $order_by, $archivesearch, [0,-1], $sort, false, DEPRECATED_STARSEARCH, false, false, $daylimit, false, true, false, $editable_only, false, $res_access);
if (!is_array($results) || (isset($results["total"]) && $results["total"] == 0)) {
return false;
}
// To maintain current collection order but add the search items in the correct order we must first move the existing collection resources out the way
$searchcount = $results["total"];
if ($searchcount > 0) {
ps_query(
"UPDATE collection_resource SET sortorder = if(isnull(sortorder), ?,sortorder + ?) WHERE collection= ?",
[
'i', $searchcount,
'i', $searchcount,
'i', $collection
]
);
}
// If this is a featured collection apply all the external access keys from the categories which make up its
// branch path to prevent breaking existing shares for any of those featured collection categories.
$fc_branch_path_keys = [];
$collection_data = get_collection($collection, true);
if ($collection_data !== false && $collection_data['type'] === COLLECTION_TYPE_FEATURED) {
$branch_category_ids = array_column(
// determine the branch from the parent because the keys for the collection in question will be done below
get_featured_collection_category_branch_by_leaf((int)$collection_data['parent'], []),
'ref'
);
foreach ($branch_category_ids as $fc_category_id) {
$fc_branch_path_keys = array_merge(
$fc_branch_path_keys,
get_external_shares([
'share_collection' => $fc_category_id,
'share_type' => 0,
'ignore_permissions' => true,
])
);
}
}
# Check if this collection has already been shared externally. If it has, we must add a further entry
# for this specific resource, and warn the user that this has happened.
$keys = array_merge(
get_external_shares([
'share_collection' => $collection,
'share_type' => 0,
'ignore_permissions' => true,
]),
$fc_branch_path_keys
);
$resourcesnotadded = array(); # record the resources that are not added so we can display to the user
$blockedtypes = array();# Record the resource types that are not added
foreach ($results["data"] as $result) {
$resource = $result["ref"];
$archivestatus = $result["archive"];
if (in_array($result["resource_type"], $collection_block_restypes)) {
$blockedtypes[] = $result["resource_type"];
continue;
}
if (count($keys) > 0) {
if (($archivestatus < 0 && !$collection_allow_not_approved_share) || !can_share_resource($resource)) {
$resourcesnotadded[$resource] = $result;
continue;
}
for ($n = 0; $n < count($keys); $n++) {
$sql = '';
$params = [];
if ($keys[$n]["expires"] == '') {
$sql .= 'NULL, ';
} else {
$sql .= '?, ';
$params[] = 's';
$params[] = $keys[$n]["expires"];
}
if ($keys[$n]["usergroup"] == '') {
$sql .= 'NULL';
} else {
$sql .= '?';
$params[] = 'i';
$params[] = $keys[$n]["usergroup"];
}
# Insert a new access key entry for this resource/collection.
ps_query(
"INSERT INTO external_access_keys(resource,access_key,user,collection,date,access,password_hash,expires,usergroup) VALUES (?, ?, ?, ?,NOW(), ?, ?, {$sql})",
array_merge([
'i', $resource,
's', $keys[$n]["access_key"],
'i', $userref,
'i', $collection,
's', $keys[$n]["access"],
's', $keys[$n]["password_hash"]
], $params)
);
#log this
collection_log($collection, LOG_CODE_COLLECTION_SHARED_RESOURCE_WITH, $resource, $keys[$n]["access_key"]);
# Set the flag so a warning appears.
$collection_share_warning = true;
}
}
}
if (is_array($results["data"])) {
$n = 0;
foreach ($results["data"] as $result) {
$resource = $result["ref"];
if (!isset($resourcesnotadded[$resource]) && !in_array($result["resource_type"], $collection_block_restypes)) {
ps_query("DELETE FROM collection_resource WHERE resource=? AND collection=?", array("i",$resource,"i",$collection));
ps_query("INSERT INTO collection_resource(resource,collection,sortorder) VALUES (?,?,?)", array("i",$resource,"i",$collection,"s",$n));
#log this
collection_log($collection, LOG_CODE_COLLECTION_ADDED_RESOURCE, $resource);
$n++;
}
}
}
// Clear theme image cache
clear_query_cache('themeimage');
clear_query_cache('col_total_ref_count_w_perm');
if (!empty($resourcesnotadded) || count($blockedtypes) > 0) {
# Translate to titles only for displaying them to the user
global $view_title_field;
$titles = array();
foreach ($resourcesnotadded as $resource) {
$titles[] = i18n_get_translated($resource['field' . $view_title_field]);
}
if (count($blockedtypes) > 0) {
$blocked_restypes = array_unique($blockedtypes);
// Return a list of blocked resouce types
$titles["blockedtypes"] = $blocked_restypes;
}
return $titles;
}
return array();
}
/**
* Returns true or false, can all resources in this collection be edited by the user?
*
* @param array|int $collection Collection IDs
* @param array $collectionid
*
* @return boolean
*/
function allow_multi_edit($collection, $collectionid = 0)
{
global $resource;
if (is_array($collection) && $collectionid == 0) {
// Do this the hard way by checking every resource for edit access
for ($n = 0; $n < count($collection); $n++) {
$resource = $collection[$n];
if (!get_edit_access($collection[$n]["ref"], $collection[$n]["archive"], $collection[$n])) {
return false;
}
}
# All have edit access
return true;
} else {
// Instead of checking each resource we can do a comparison between a search for all resources in collection and a search for editable resources
$resultcount = 0;
$all_resource_refs = array();
if (!is_array($collection)) {
// Need the collection resources so need to run the search
$collectionid = $collection;
# Editable_only=false (so returns resources whether editable or not)
$collection = do_search("!collection{$collectionid}", '', '', 0, -1, '', false, 0, false, false, '', false, false, true, false);
}
if (is_array($collection)) {
$resultcount = count($collection);
}
$editcount = 0;
# Editable_only=true (so returns editable resources only)
$editresults = do_search("!collection{$collectionid}", '', '', 0, -1, '', false, 0, false, false, '', false, false, true, true);
if (is_array($editresults)) {
$editcount = count($editresults);
}
if ($resultcount == $editcount) {
return true;
}
# Counts differ meaning there are non-editable resources
$all_resource_refs = array_column($collection, "ref");
$editable_resource_refs = array_column($editresults, "ref");
$non_editable_resource_refs = array_diff($all_resource_refs, $editable_resource_refs);
# Is grant edit present for all non-editables?
foreach ($non_editable_resource_refs as $non_editable_ref) {
if (!hook('customediteaccess', '', array($non_editable_ref))) {
return false;
}
}
# All non_editables have grant edit
return true;
}
}
/**
* Get featured collection resources (including from child nodes). For normal FCs this is using the collection_resource table.
* For FC categories, this will check within normal FCs contained by that category. Normally used in combination with
* generate_featured_collection_image_urls() but useful to determine if a FC category is full of empty FCs.
*
* @param array $c Collection data structure similar to the one returned by {@see get_featured_collections()}
* @param array $ctx Extra context used to get FC resources (e.g smart FC?, limit on number of resources returned). Context
* information should take precedence over internal logic (e.g determining the result limit)
*
* @return array
*/
function get_featured_collection_resources(array $c, array $ctx)
{
global $usergroup, $userref, $CACHE_FC_RESOURCES, $themes_simple_images,$collection_allow_not_approved_share;
global $FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS, $theme_images_number;
if (!isset($c["ref"]) || !is_int((int) $c["ref"])) {
return array();
}
$CACHE_FC_RESOURCES = (!is_null($CACHE_FC_RESOURCES) && is_array($CACHE_FC_RESOURCES) ? $CACHE_FC_RESOURCES : array());
// create a unique ID for this result set as the context for the same FC may differ
$cache_id = $c["ref"] . md5(json_encode($ctx));
if (isset($CACHE_FC_RESOURCES[$cache_id])) {
return $CACHE_FC_RESOURCES[$cache_id];
}
$limit = (isset($ctx["limit"]) && (int) $ctx["limit"] > 0 ? (int) $ctx["limit"] : null);
$use_thumbnail_selection_method = (isset($ctx["use_thumbnail_selection_method"]) ? (bool) $ctx["use_thumbnail_selection_method"] : false);
// Smart FCs
if (isset($ctx["smart"]) && $ctx["smart"] === true) {
// Root smart FCs don't have an image (legacy reasons)
if (is_null($c["parent"])) {
return array();
}
$node_search = NODE_TOKEN_PREFIX . $c['ref'];
$limit = (!is_null($limit) ? $limit : 1);
// Access control is still in place (i.e. permissions are honoured)
$smart_fc_resources = do_search($node_search, '', 'hit_count', 0, $limit, 'desc', false, 0, false, false, '', true, false, true);
$smart_fc_resources = (is_array($smart_fc_resources) ? array_column($smart_fc_resources, "ref") : array());
$CACHE_FC_RESOURCES[$cache_id] = $smart_fc_resources;
return $smart_fc_resources;
}
// Access control
$rca_where = '';
$rca_where_params = array();
$rca_joins = array();
$rca_join_params = array();
$fc_permissions_where = '';
$fc_permissions_where_params = [];
$union = "";
$unionparams = [];
if (!checkperm("v")) {
// Add joins for user and group custom access
$rca_joins[] = 'LEFT JOIN resource_custom_access AS rca_u ON r.ref = rca_u.resource AND rca_u.user = ? AND (rca_u.user_expires IS NULL OR rca_u.user_expires > now())';
$rca_join_params [] = "i";
$rca_join_params [] = $userref;
$rca_joins[] = 'LEFT JOIN resource_custom_access AS rca_ug ON r.ref = rca_ug.resource AND rca_ug.usergroup = ?';
$rca_join_params [] = "i";
$rca_join_params [] = $usergroup;
$rca_where = 'AND (r.access < ? OR (r.access IN (?, ?) AND ((rca_ug.access IS NOT NULL AND rca_ug.access < ?) OR (rca_u.access IS NOT NULL AND rca_u.access < ?))))';
$rca_where_params = array("i", RESOURCE_ACCESS_CONFIDENTIAL, "i", RESOURCE_ACCESS_CONFIDENTIAL, "i", RESOURCE_ACCESS_CUSTOM_GROUP, "i", RESOURCE_ACCESS_CONFIDENTIAL, "i", RESOURCE_ACCESS_CONFIDENTIAL);
$fcf_sql = featured_collections_permissions_filter_sql("AND", "c.ref");
if (is_array($fcf_sql)) {
$fc_permissions_where = "AND (c.`type` = ? " . $fcf_sql[0] . ")";
$fc_permissions_where_params = array_merge(["i",COLLECTION_TYPE_FEATURED], $fcf_sql[1]);
}
}
if ($use_thumbnail_selection_method && isset($c["thumbnail_selection_method"])) {
if ($c["thumbnail_selection_method"] == $FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["no_image"]) {
return array();
} elseif ($c["thumbnail_selection_method"] == $FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["manual"] && isset($c["bg_img_resource_ref"])) {
$limit = 1;
$union = sprintf(
"
UNION SELECT ref, 1 AS use_as_theme_thumbnail, r.hit_count FROM resource AS r %s WHERE r.ref = ? %s",
implode(" ", $rca_joins),
$rca_where
);
$unionparams = array_merge($rca_join_params, ["i",$c["bg_img_resource_ref"]], $rca_where_params);
}
// For most_popular_image & most_popular_images we change the limit only if it hasn't been provided by the context.
elseif (in_array($c["thumbnail_selection_method"], [$FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["most_popular_image"],$FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["most_recent_image"]]) && is_null($limit)) {
$limit = 1;
} elseif ($c["thumbnail_selection_method"] == $FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["most_popular_images"] && is_null($limit)) {
$limit = $theme_images_number;
}
}
$resource_join = "JOIN resource AS r ON r.ref = cr.resource AND r.ref > 0";
if (!$collection_allow_not_approved_share) {
$resource_join .= " AND r.archive = 0";
}
// A SQL statement. Each array index represents a different SQL clause.
$subquery = array(
"select" => "SELECT r.ref, cr.use_as_theme_thumbnail, r.hit_count",
"from" => "FROM collection AS c",
"join" => array_merge(
array(
"JOIN collection_resource AS cr ON cr.collection = c.ref",
$resource_join,
),
$rca_joins
),
"where" => "WHERE c.ref = ? AND c.`type` = ?",
);
$subquery_params = array_merge($rca_join_params, array("i", $c["ref"], "i", COLLECTION_TYPE_FEATURED), $rca_where_params);
if (is_featured_collection_category($c)) {
$all_fcs = ps_query("SELECT ref, parent FROM collection WHERE `type`=?", array("i",COLLECTION_TYPE_FEATURED), "featured_collections");
$all_fcs_rp = array_column($all_fcs, 'parent', 'ref');
// Array to hold resources
$fcresources = array();
// Create stack of collections to search
// (not a queue as we want to get to the lowest child collections first where the resources are)
$colstack = new SplStack(); //
$children = array_keys($all_fcs_rp, $c["ref"]);
foreach ($children as $child_fc) {
$colstack->push($child_fc);
}
while ((is_null($limit) || count($fcresources) < $limit) && !$colstack->isEmpty()) {
$checkfc = $colstack->pop();
if (!in_array($checkfc, $all_fcs_rp)) {
$subfcimages = get_collection_resources($checkfc);
if (is_array($subfcimages) && count($subfcimages) > 0) {
// The join defined above specifically excludes any resources that are not in the active archive state,
// for the limiting via $ctx to function correctly we'll need to check for each resources state before adding it to fcresources
$resources = get_resource_data_batch($subfcimages);
if (!$collection_allow_not_approved_share) {
$resources = array_filter($resources, function ($r) {
return $r['archive'] == "0";
});
}
$fcresources = array_merge($fcresources, array_column($resources, 'ref'));
}
continue;
}
// Either a parent FC or no results, add sub fcs to stack
$children = array_keys($all_fcs_rp, $checkfc);
foreach ($children as $child_fc) {
$colstack->push($child_fc);
}
}
$fcrescount = count($fcresources);
if ($fcrescount > 0) {
$chunks = [$fcresources];
// Large numbers of query parameters can cause errors so chunking may be required for larger collections.
if ($fcrescount > 20000) {
$chunks = array_chunk($fcresources, 20000);
}
$fc_resources = [];
$subquery["join"] = implode(" ", $subquery["join"]);
foreach ($chunks as $fcresources) {
$subquery["where"] = " WHERE r.ref IN (" . ps_param_insert(count($fcresources)) . ")";
$subquery_params = array_merge($rca_join_params, ps_param_fill($fcresources, "i"), $rca_where_params);
$subquery["where"] .= " {$rca_where} {$fc_permissions_where}";
$subquery_params = array_merge($subquery_params, $fc_permissions_where_params);
$sql = sprintf(
"SELECT DISTINCT ti.ref AS `value`, ti.use_as_theme_thumbnail, ti.hit_count FROM (%s %s) AS ti ORDER BY ti.use_as_theme_thumbnail DESC, ti.hit_count DESC, ti.ref DESC %s",
implode(" ", $subquery),
$union,
sql_limit(null, $limit)
);
$fc_resources = array_merge($fc_resources, ps_array($sql, array_merge($subquery_params, $unionparams), "themeimage"));
}
$CACHE_FC_RESOURCES[$cache_id] = $fc_resources;
return $fc_resources;
}
}
$subquery["join"] = implode(" ", $subquery["join"]);
$subquery["where"] .= " {$rca_where} {$fc_permissions_where}";
$subquery_params = array_merge($subquery_params, $fc_permissions_where_params);
$order_by = "ti.use_as_theme_thumbnail DESC, ti.hit_count DESC, ti.ref DESC";
if ($c["thumbnail_selection_method"] == $FEATURED_COLLECTION_BG_IMG_SELECTION_OPTIONS["most_recent_image"]) {
$order_by = "ti.ref DESC";
}
$sql = sprintf(
"SELECT DISTINCT ti.ref AS `value`, ti.use_as_theme_thumbnail, ti.hit_count FROM (%s %s) AS ti ORDER BY %s %s",
implode(" ", $subquery),
$union,
$order_by,
sql_limit(null, $limit)
);
$fc_resources = ps_array($sql, array_merge($subquery_params, $unionparams), "themeimage");
$CACHE_FC_RESOURCES[$cache_id] = $fc_resources;
return $fc_resources;
}
/**
* Get a list of featured collections based on a higher level featured collection category. This returns all direct/indirect
* collections under that category.
*
* @param array $c Collection data structure
* @param array $ctx Contextual data (e.g disable access control). This param MUST NOT get exposed over the API
*
* @return array
*/
function get_featured_collection_categ_sub_fcs(array $c, array $ctx = array())
{
global $CACHE_FC_CATEG_SUB_FCS;
$CACHE_FC_CATEG_SUB_FCS = (!is_null($CACHE_FC_CATEG_SUB_FCS) && is_array($CACHE_FC_CATEG_SUB_FCS) ? $CACHE_FC_CATEG_SUB_FCS : array());
if (isset($CACHE_FC_CATEG_SUB_FCS[$c["ref"]])) {
return $CACHE_FC_CATEG_SUB_FCS[$c["ref"]];
}
$access_control = (isset($ctx["access_control"]) && is_bool($ctx["access_control"]) ? $ctx["access_control"] : true);
$all_fcs = (isset($ctx["all_fcs"]) && is_array($ctx["all_fcs"]) && !empty($ctx["all_fcs"]) ? $ctx["all_fcs"] : get_all_featured_collections());
$collections = array();
$allowed_fcs = ($access_control ? compute_featured_collections_access_control() : true);
if ($allowed_fcs === false) {
$CACHE_FC_CATEG_SUB_FCS[$c["ref"]] = $collections;
return $collections;
} elseif (is_array($allowed_fcs)) {
$allowed_fcs_flipped = array_flip($allowed_fcs);
// Collection is not allowed
if (!isset($allowed_fcs_flipped[$c['ref']])) {
$CACHE_FC_CATEG_SUB_FCS[$c["ref"]] = $collections;
return $collections;
}
}
$all_fcs_rp = reshape_array_by_value_keys($all_fcs, 'ref', 'parent');
$all_fcs = array_flip_by_value_key($all_fcs, 'ref');
$queue = new SplQueue();
$queue->setIteratorMode(SplQueue::IT_MODE_DELETE);
$queue->enqueue($c['ref']);
while (!$queue->isEmpty()) {
$fc = $queue->dequeue();
$fc_children = array();
if (
$all_fcs[$fc]['has_resources'] > 0
&& (
$allowed_fcs === true
|| (is_array($allowed_fcs) && isset($allowed_fcs_flipped[$fc]))
)
) {
$collections[] = $fc;
} elseif ($all_fcs[$fc]['has_children'] > 0) {
$fc_children = array_keys($all_fcs_rp, $fc);
}
foreach ($fc_children as $fc_child_ref) {
$queue->enqueue($fc_child_ref);
}
}
$CACHE_FC_CATEG_SUB_FCS[$c["ref"]] = $collections;
debug("get_featured_collection_categ_sub_fcs(ref = {$c["ref"]}): returned collections: " . implode(", ", $collections));
return $collections;
}
/**
* Get preview URLs for a list of resource IDs
*
* @param array $resource_refs List of resources
* @param string $size Preview size
*
* @return array List of resource refs and corresponding images URLs
*/
function generate_featured_collection_image_urls(array $resource_refs, string $size)
{
global $baseurl;
$images = array();
$refs_list = array_filter($resource_refs, 'is_numeric');
if (empty($refs_list)) {
return $images;
}
$refs_rtype = ps_query("SELECT ref, resource_type, file_extension FROM resource WHERE ref IN (" . ps_param_insert(count($refs_list)) . ")", ps_param_fill($refs_list, "i"), 'featured_collections');
foreach ($refs_rtype as $ref_rt) {
$ref = $ref_rt['ref'];
$resource_type = $ref_rt['resource_type'];
if (file_exists(get_resource_path($ref, true, $size, false)) && resource_download_allowed($ref, $size, $resource_type, -1, true)) {
$images[] = ["ref" => $ref, "path" => get_resource_path($ref, false, $size, false)];
}
}
if (count($images) == 0 && count($refs_rtype) != 0) {
$images[] = $baseurl . '/gfx/no_preview/default.png';
}
return $images;
}
/**
* Inserts $resource1 into the position currently occupied by $resource2
*
* @param integer $resource1
* @param integer $resource2
* @param integer $collection
* @return void
*/
function swap_collection_order($resource1, $resource2, $collection)
{
// sanity check -- we should only be getting IDs here
if (!is_numeric($resource1) || !is_numeric($resource2) || !is_numeric($collection)) {
exit("Error: invalid input to swap collection function.");
}
$query = "select resource,date_added,sortorder from collection_resource where collection=? and resource in (?,?) order by sortorder asc, date_added desc";
$existingorder = ps_query($query, array("i",$collection,"i",$resource1,"i",$resource2));
$counter = 1;
foreach ($existingorder as $record) {
$rec[$counter]['resource'] = $record['resource'];
$rec[$counter]['date_added'] = $record['date_added'];
if (strlen($record['sortorder']) == 0) {
$rec[$counter]['sortorder'] = "NULL";
} else {
$rec[$counter]['sortorder'] = "'" . $record['sortorder'] . "'";
}
$counter++;
}
ps_query(
"update collection_resource set date_added = ?, sortorder = ? where collection = ? and resource = ?",
[
's', $rec[1]['date_added'],
'i', $rec[1]['sortorder'],
'i', $collection,
'i', $rec[2]['resource']
]
);
ps_query(
"update collection_resource set date_added = ?, sortorder = ? where collection = ? and resource = ?",
[
's', $rec[2]['date_added'],
'i', $rec[2]['sortorder'],
'i', $collection,
'i', $rec[1]['resource']
]
);
}
/**
* Reorder the items in a collection using $neworder as the order by metric
*
* @param array $neworder Array of columns to order by
* @param integer $collection
* @param integer $offset
* @return void
*/
function update_collection_order($neworder, $collection, $offset = 0)
{
if (!is_array($neworder)) {
exit("Error: invalid input to update collection function.");
}
$neworder = array_filter($neworder, 'is_numeric');
if (count($neworder) > 0) {
$updatesql = "update collection_resource set sortorder=(case resource ";
$counter = 1 + $offset;
$params = [];
foreach ($neworder as $colresource) {
$updatesql .= "when ? then ? ";
$params = array_merge($params, ['i', $colresource, 'i', $counter]);
$counter++;
}
$updatesql .= "else sortorder END) WHERE collection= ?";
ps_query($updatesql, array_merge($params, ['i', $collection]));
}
$updatesql = "update collection_resource set sortorder=99999 WHERE collection= ? and sortorder is NULL";
ps_query($updatesql, ['i', $collection]);
}
/**
* Return comments and other columns stored in the collection_resource join.
*
* @param integer $resource
* @param integer $collection
* @return array|bool Returns found record data, false otherwise
*/
function get_collection_resource_comment($resource, $collection)
{
$data = ps_query("select " . columns_in("collection_resource") . " from collection_resource where collection=? and resource=?", array("i",$collection,"i",$resource), "");
if (!isset($data[0])) {
return false;
}
return $data[0];
}
/**
* Save a comment and/or rating for the instance of a resource in a collection.
*
* @param integer $resource
* @param integer $collection
* @param string $comment
* @param integer $rating
* @return boolean
*/
function save_collection_resource_comment($resource, $collection, $comment, $rating)
{
# get data before update so that changes can be logged.
$data = ps_query(
"select comment,rating from collection_resource where resource= ? and collection= ?",
[
'i', $resource,
'i', $collection
]
);
$params = [];
if ($rating != "") {
$sql = '?';
$params = ['i', $rating];
} else {
$sql = 'null';
}
ps_query(
"update collection_resource set rating= {$sql},comment= ?,use_as_theme_thumbnail= ? where resource= ? and collection= ?",
array_merge(
$params,
[
's', $comment,
'i', (getval("use_as_theme_thumbnail", "") == "" ? 0 : 1),
'i', $resource,
'i', $collection
]
)
);
# log changes
if ($comment != $data[0]['comment']) {
collection_log($collection, LOG_CODE_COLLECTION_ADDED_RESOURCE_COMMENT, $resource);
}
if ($rating != $data[0]['rating']) {
collection_log($collection, LOG_CODE_COLLECTION_ADDED_RESOURCE_RATING, $resource);
}
return true;
}
/**
* Relates every resource in $collection to $ref
*
* @param integer $ref
* @param integer $collection
* @return void
*/
function relate_to_collection($ref, $collection)
{
$colresources = get_collection_resources($collection);
ps_query("delete from resource_related where resource= ? and related in (" . ps_param_insert(count($colresources)) . ")", array_merge(['i', $ref], ps_param_fill($colresources, 'i')));
$params = [];
foreach ($colresources as $colresource) {
$params = array_merge($params, ['i', $ref, 'i', $colresource]);
}
ps_query(
"INSERT INTO resource_related (resource,related)
VALUES " . implode(', ', array_fill(0, count($colresources), '(?, ?)')),
$params
);
}
/**
* Fetch all the comments for a given collection.
*
* @param integer $collection
* @return array
*/
function get_collection_comments($collection)
{
return ps_query("select " . columns_in("collection_resource") . " from collection_resource where collection=? and length(comment)>0 order by date_added", array("i",$collection));
}
/**
* Sends the feedback to the owner of the collection
*
* @param integer $collection Collection ID
* @param string $comment Comment text
* @return array|void
*/
function send_collection_feedback($collection, $comment)
{
global $applicationname,$lang,$userfullname,$userref,$k,$feedback_resource_select,$regex_email;
global $userref;
$cinfo = get_collection($collection);
if ($cinfo === false) {
error_alert($lang["error-collectionnotfound"]);
exit();
}
$user = get_user($cinfo["user"]);
$body = $lang["collectionfeedbackemail"] . "\n\n";
if (isset($userfullname)) {
$body .= $lang["user"] . ": " . $userfullname . "\n";
} else {
# External user.
if (!preg_match("/{$regex_email}/", getval("email", ""))) {
$errors[] = $lang["youremailaddress"] . ": " . $lang["requiredfield"];
return $errors;
}
$body .= $lang["fullname"] . ": " . getval("name", "") . "\n";
$body .= $lang["email"] . ": " . getval("email", "") . "\n";
}
$body .= $lang["message"] . ": " . stripslashes(str_replace("\\r\\n", "\n", trim($comment)));
$f = get_collection_comments($collection);
for ($n = 0; $n < count($f); $n++) {
$body .= "\n\n" . $lang["resourceid"] . ": " . $f[$n]["resource"];
$body .= "\n" . $lang["comment"] . ": " . trim($f[$n]["comment"]);
if (is_numeric($f[$n]["rating"])) {
$body .= "\n" . $lang["rating"] . ": " . substr("**********", 0, $f[$n]["rating"]);
}
}
if ($feedback_resource_select) {
$body .= "\n\n" . $lang["selectedresources"] . ": ";
$file_list = "";
$result = do_search("!collection" . $collection);
for ($n = 0; $n < count($result); $n++) {
$ref = $result[$n]["ref"];
if (getval("select_" . $ref, "") != "") {
global $filename_field;
$filename = get_data_by_field($ref, $filename_field);
$body .= "\n" . $ref . " : " . $filename;
# Append to a file list that is compatible with Adobe Lightroom
if ($file_list != "") {
$file_list .= ", ";
}
$s = explode(".", $filename);
$file_list .= $s[0];
}
}
# Append Lightroom compatible summary.
$body .= "\n\n" . $lang["selectedresourceslightroom"] . "\n" . $file_list;
}
$cc = getval("email", "");
get_config_option(['user' => $user['ref'], 'usergroup' => $user['usergroup']], 'email_user_notifications', $send_email);
// Always send a mail for the feedback whatever the user preference, since the feedback may be very long so can then refer to the CC'd email
if (filter_var($cc, FILTER_VALIDATE_EMAIL)) {
send_mail($user["email"], $applicationname . ": " . $lang["collectionfeedback"] . " - " . $cinfo["name"], $body, "", "", "", null, "", $cc);
} else {
send_mail($user["email"], $applicationname . ": " . $lang["collectionfeedback"] . " - " . $cinfo["name"], $body);
}
// Add a system notification message as well
message_add($user["ref"], $lang["collectionfeedback"] . " - " . $cinfo["name"] . "
" . $body, "", (isset($userref)) ? $userref : $user['ref'], MESSAGE_ENUM_NOTIFICATION_TYPE_SCREEN, 60 * 60 * 24 * 30);
}
/**
* Copy a collection contents
*
* @param integer $copied The collection to copy from
* @param integer $current The collection to copy to
* @param boolean $remove_existing Should existing items be removed?
* @return void
*/
function copy_collection($copied, $current, $remove_existing = false)
{
# Get all data from the collection to copy.
$copied_collection = ps_query("select cr.resource, r.resource_type, cr.sortorder from collection_resource cr join resource r on cr.resource=r.ref where collection=?", array("i",$copied), "");
if ($remove_existing) {
#delete all existing data in the current collection
ps_query("delete from collection_resource where collection=?", array("i",$current));
collection_log($current, LOG_CODE_COLLECTION_REMOVED_ALL_RESOURCES, 0);
}
#put all the copied collection records in
foreach ($copied_collection as $col_resource) {
# Use correct function so external sharing is honoured.
add_resource_to_collection($col_resource['resource'], $current, true, "", $col_resource['resource_type'], null, null, '', $col_resource['sortorder']);
}
}
/**
* Returns true if a collection is a research request
*
* @param int $collection Collection ID
*
* @return boolean
*/
function collection_is_research_request($collection)
{
return ps_value("SELECT count(*) value FROM research_request WHERE collection=?", array("i", $collection), 0) > 0;
}
/**
* Generates a HTML link for adding a resource to a collection
*
* @param integer $resource ID of resource
* @param string $extracode Additional code to be run when link is selected
* IMPORTANT: never use untrusted data here!
* @param string $size Resource size if appropriate
* @param string $class Class to be applied to link
* @param string $view_title The title of the field, taken from $view_title_field
*
* @return string
*/
function add_to_collection_link($resource, $extracode = "", $size = "", $class = "", $view_title = ""): string
{
$resource = (int) $resource;
$size = escape($size);
$class = escape($class);
$title = escape($GLOBALS['lang']["addtocurrentcollection"] . (($view_title != "") ? " - " . $view_title : ""));
return "";
}
/**
* Render a "remove from collection" link wherever such a function is shown in the UI
*
* @param integer $resource
* @param string $class
* @param string $onclick Additional onclick code to call before returning false.
* @param bool $notused No longer used
* @param string $view_title The title of the field, taken from $view_title_field
*
*/
function remove_from_collection_link($resource, $class = "", string $onclick = '', $notused = false, $view_title = ""): string
{
# Generates a HTML link for removing a resource from a collection
global $lang, $pagename;
$resource = (int) $resource;
$class = escape($class);
$pagename = escape($pagename);
$title = escape($lang["removefromcurrentcollection"] . (trim($view_title) != "" ? " - " . $view_title : ""));
return "";
}
/**
* Generates a HTML link for adding a changing the current collection
*
* @param integer $collection
* @return string
*/
function change_collection_link($collection)
{
global $lang;
return '' . LINK_CARET . $lang["selectcollection"] . '';
}
/**
* Return all external access given to a collection.
* Users, emails and dates could be multiple for a given access key, an in this case they are returned comma-separated.
*
* @param integer $collection
* @return array
*/
function get_collection_external_access($collection)
{
global $userref;
# Restrict to only their shares unless they have the elevated 'v' permission
$condition = "AND upload=0 ";
$params = array("i",$collection);
if (!checkperm("v")) {
$condition .= "AND user=?";
$params[] = "i";
$params[] = $userref;
}
return ps_query("SELECT access_key,GROUP_CONCAT(DISTINCT user ORDER BY user SEPARATOR ', ') users,GROUP_CONCAT(DISTINCT email ORDER BY email SEPARATOR ', ') emails,MAX(date) maxdate,MAX(lastused) lastused,access,expires,usergroup,password_hash,upload from external_access_keys WHERE collection=? $condition group by access_key order by date", $params);
}
/**
* Delete a specific collection access key, withdrawing access via that key to the collection in question
*
* @param integer $collection
* @param string $access_key
* @return void
*/
function delete_collection_access_key($collection, $access_key)
{
# Get details for log
$users = ps_value("SELECT group_concat(DISTINCT email ORDER BY email SEPARATOR ', ') value FROM external_access_keys WHERE collection=? AND access_key = ? group by access_key ", array("i",$collection,"s",$access_key), "");
# Deletes the given access key.
$params = array("s",$access_key);
$sql = "DELETE FROM external_access_keys WHERE access_key=?";
if ($collection != 0) {
$sql .= " AND collection=?";
$params[] = "i";
$params[] = $collection;
}
ps_query($sql, $params);
# log changes
collection_log($collection, LOG_CODE_COLLECTION_STOPPED_RESOURCE_ACCESS, "", $users . " (" . $access_key . ")");
}
/**
* Add a new row to the collection log (e.g. after an action on that collection)
*
* @param integer $collection
* @param string $type Action type
* @param integer $resource
* @param string $notes
* @return void
*/
function collection_log($collection, $type, $resource, $notes = "")
{
global $userref;
if (!is_numeric($collection)) {
return false;
}
$user = ($userref ?: null);
$resource = ($resource ?: null);
$notes = mb_strcut($notes, 0, 255);
ps_query("INSERT INTO collection_log (date, user, collection, type, resource, notes) VALUES (now(), ?, ?, ?, ?, ?)", array("i",$user,"i",$collection,"s",$type,"i",$resource,"s",$notes));
}
/**
* Return the log for $collection
*
* @param integer $collection
* @param integer $fetchrows How many rows to fetch
* @return array
*/
function get_collection_log($collection, $fetchrows = -1)
{
debug_function_call("get_collection_log", func_get_args());
global $view_title_field;
$extra_fields = hook("collection_log_extra_fields");
if (!$extra_fields) {
$extra_fields = "";
}
$log_query = new PreparedStatementQuery(
"SELECT c.ref,
c.date,
u.username,
u.fullname,
c.type,
r.field{$view_title_field} AS title,
c.resource,
c.notes
{$extra_fields}
FROM collection_log AS c
LEFT OUTER JOIN user AS u ON u.ref = c.user
LEFT OUTER JOIN resource AS r ON r.ref = c.resource
WHERE collection = ?
ORDER BY c.ref DESC",
array("i",$collection)
);
return sql_limit_with_total_count($log_query, $fetchrows, 0, false, null);
}
/**
* Returns the maximum access (the most permissive) that the current user has to the resources in $collection.
*
* @param integer $collection
* @return integer
*/
function collection_max_access($collection)
{
$maxaccess = 2;
$result = do_search("!collection" . $collection);
if (!is_array($result)) {
$result = array();
}
for ($n = 0; $n < count($result); $n++) {
# Load access level
$access = get_resource_access($result[$n]);
if ($access < $maxaccess) {
$maxaccess = $access;
}
}
return $maxaccess;
}
/**
* Returns the minimum access (the least permissive) that the current user has to the resources in $collection.
*
* Can be passed a collection ID or the results of a collection search, the result will be the most restrictive
* access that is found.
*
* @param integer|array $collection Collection ID as an integer or the result of a search as an array
*
* @return integer 0 - Open, 1 - restricted, 2 - Confidential
*/
function collection_min_access($collection)
{
global $k, $internal_share_access, $usersearchfilter;
if (is_array($collection)) {
$result = $collection;
} else {
$result = do_search("!collection{$collection}", '', 'relevance', 0, -1, 'desc', false, '', false, '', '', false, false, true);
}
if (!is_array($result) || empty($result)) {
return 2;
}
if (checkperm("v")) {
// Always has open access
return 0;
}
if (isset($result[0]["resultant_access"])) {
$minaccess = max(array_column($result, "resultant_access"));
} else {
# Reset minaccess and allow get_resource_access to determine the min access for the collection
$minaccess = 0;
$usersearchfilter_original = $usersearchfilter;
# Performance improvement - Don't check search filters again in get_resource_access as $result contains only resources allowed by the search filter.
$usersearchfilter = '';
for ($n = 0; $n < count($result); $n++) {
$access = get_resource_access($result[$n]); // Use the access already calculated if available
if ($access > $minaccess) {
$minaccess = $access;
}
}
$usersearchfilter = $usersearchfilter_original;
}
if ($k != "") {
# External access - check how this was shared. If internal share access and share is more open than the user's access return that
$params[] = "s";
$params[] = $k;
// Don't check each resource as an access key only ever has one level of access
$minextaccess = ps_value("SELECT access value FROM external_access_keys WHERE access_key = ? AND (expires IS NULL OR expires > NOW()) LIMIT 1", $params, -1);
if ($minextaccess != -1 && (!$internal_share_access || ($internal_share_access && ($minextaccess < $minaccess)))) {
return $minextaccess;
}
}
return $minaccess;
}
/**
* Set an existing collection to be public
*
* @param integer $collection ID of collection
*
* @return boolean
*/
function collection_set_public($collection)
{
if (is_numeric($collection)) {
$sql = "UPDATE collection SET `type` = " . COLLECTION_TYPE_PUBLIC . " WHERE ref = ?";
ps_query($sql, array("i",$collection));
return true;
} else {
return false;
}
}
/**
* Remove all resources from a collection
*
* @param integer $ref The collection in question
* @return void
*/
function remove_all_resources_from_collection($ref)
{
$collection_type = ps_value("select type value from collection where ref=?", array("i",$ref), "");
if ($collection_type != COLLECTION_TYPE_SELECTION) {
$removed_resources = ps_array("SELECT resource AS value FROM collection_resource WHERE collection = ?", array("i",$ref));
collection_log($ref, LOG_CODE_COLLECTION_REMOVED_ALL_RESOURCES, 0);
foreach ($removed_resources as $removed_resource_id) {
collection_log($ref, LOG_CODE_COLLECTION_REMOVED_RESOURCE, $removed_resource_id, ' - Removed all resources from collection ID ' . $ref);
}
}
ps_query("DELETE FROM collection_resource WHERE collection = ?", array("i",$ref));
ps_query("DELETE FROM external_access_keys WHERE collection = ? AND upload!=1", array("i",$ref));
}
/**
* Retrieve promoted collections to be displayed on the home page.
*
* This function fetches public collections that are marked for publishing to the home page.
* It returns an array of collection data, including metadata and thumbnail information for the
* home page image if one is assigned.
*
* @return array An array of associative arrays representing each promoted collection, with keys:
* - 'ref' (int): The unique identifier for the collection.
* - 'type' (int): The type identifier for the collection.
* - 'name' (string): The name of the collection.
* - 'home_page_publish' (int): Indicates if the collection is published on the home page.
* - 'home_page_text' (string): Display text for the collection for the home page.
* - 'home_page_image' (int): Resource ID for the image displayed on the home page.
* - 'thumb_height' (int): Thumbnail height of the associated image.
* - 'thumb_width' (int): Thumbnail width of the associated image.
* - 'resource_type' (int): The type of the associated resource.
* - 'file_extension' (string): File extension of the associated resource.
*/
function get_home_page_promoted_collections()
{
global $COLLECTION_PUBLIC_TYPES;
$public_types = join(", ", $COLLECTION_PUBLIC_TYPES); // Note this is a constant and not user input - does not need to be a a parameter in the next line.
return ps_query("select collection.ref, collection.`type`,collection.name,collection.home_page_publish,collection.home_page_text,collection.home_page_image,resource.thumb_height,resource.thumb_width, resource.resource_type, resource.file_extension from collection left outer join resource on collection.home_page_image=resource.ref where collection.`type` IN ({$public_types}) and collection.home_page_publish=1 order by collection.ref desc");
}
/**
* Return an array of distinct archive/workflow states for resources in $collection
*
* @param integer $collection
* @return array
*/
function is_collection_approved($collection)
{
if (is_array($collection)) {
$result = $collection;
} else {
$result = do_search("!collection" . $collection, "", "relevance", 0, -1, "desc", false, "", false, "");
}
if (!is_array($result) || count($result) == 0) {
return true;
}
$collectionstates = array();
global $collection_allow_not_approved_share;
for ($n = 0; $n < count($result); $n++) {
$archivestatus = $result[$n]["archive"];
if ($archivestatus < 0 && !$collection_allow_not_approved_share) {
return false;
}
$collectionstates[] = $archivestatus;
}
return array_unique($collectionstates);
}
/**
* Update an existing external access share
*
* @param string $key External access key
* @param int $access Share access level
* @param string $expires Share expiration date
* @param int $group ID of usergroup that share will emulate permissions for
* @param string $sharepwd Share password
* @param array $shareopts Array of additional share options
* "collection" - int collection ID
* "upload" - bool Set to true if share is an upload link (no visibility of existing resources)
*
* @return boolean
*/
function edit_collection_external_access($key, $access = -1, $expires = "", $group = "", $sharepwd = "", $shareopts = array())
{
global $usergroup, $scramble_key, $lang;
if ($key == "") {
return false;
}
if (
(!isset($shareopts['upload']) || !$shareopts['upload'] )
&& ($group == "" || !checkperm("x"))
) {
// Default to sharing with the permission of the current usergroup if not specified OR no access to alternative group selection.
$group = $usergroup;
}
// Ensure these are escaped as required here
$setvals = array(
"access" => (int)$access,
"usergroup" => (int)$group,
);
if (isset($shareopts['upload']) && $shareopts['upload']) {
$setvals['upload'] = 1;
}
if ($expires != "") {
$expires = date_format(date_create($expires), 'Y-m-d') . ' 23:59:59';
$setvals["expires"] = $expires;
} else {
$setvals["expires"] = null;
}
if ($sharepwd != "(unchanged)") {
$setvals["password_hash"] = ($sharepwd == "") ? "" : hash('sha256', $key . $sharepwd . $scramble_key);
}
$setsql = "";
$params = [];
foreach ($setvals as $setkey => $setval) {
$setsql .= $setsql == "" ? "" : ",";
$setsql .= $setkey . "= ?";
$params = array_merge($params, ['s', $setval]);
}
$setsql .= ', date = now()';
$params = array_merge($params, ['s', $key]);
$condition = '';
if (isset($shareopts['collection'])) {
$condition = ' AND collection = ?';
$params = array_merge($params, ['i', $shareopts['collection']]);
}
ps_query(
"UPDATE external_access_keys
SET " . $setsql . "
WHERE access_key= ?" . $condition,
$params
);
hook("edit_collection_external_access", "", array($key,$access,$expires,$group,$sharepwd, $shareopts));
if (isset($shareopts['collection'])) {
$lognotes = array("access_key" => $key);
foreach ($setvals as $column => $value) {
if ($column == "password_hash") {
$lognotes[] = trim($value) != "" ? "password=TRUE" : "";
} else {
$lognotes[] = $column . "=" . $value;
}
}
collection_log($shareopts['collection'], LOG_CODE_COLLECTION_EDIT_UPLOAD_SHARE, null, "(" . implode(",", $lognotes) . ")");
}
return true;
}
/**
* Hide or show a collection from the My Collections area.
*
* @param integer $colref
* @param boolean $show Show or hide?
* @param integer $user
* @return bool
*/
function show_hide_collection($colref, $show = true, $user = "")
{
global $userref;
if ($user == "" || $user == $userref) {
// Working with logged on user, use global variable
$user = $userref;
global $hidden_collections;
} else {
if (!checkperm_user_edit($user)) {
return false;
}
//Get hidden collections for user
$hidden_collections = explode(",", ps_value("SELECT hidden_collections FROM user WHERE ref=?", array("i",$user), ""));
}
if ($show) {
debug("Unhiding collection " . $colref . " from user " . $user);
if (($key = array_search($colref, $hidden_collections)) !== false) {
unset($hidden_collections[$key]);
}
} else {
debug("Hiding collection " . $colref . " from user " . $user);
if (array_search($colref, $hidden_collections) === false) {
$hidden_collections[] = $colref;
}
}
ps_query("UPDATE user SET hidden_collections = ? WHERE ref= ?", ['s', implode(',', $hidden_collections), 'i', $user]);
return true;
}
/**
* Get an array of collection IDs for the specified ResourceSpace session and user
*
* @param string $rs_session Session id - as obtained by get_rs_session_id()
* @param integer $userref User ID
* @param boolean $create Create new collection?
*
* @return array Array of collection IDs for the specified sesssion
*/
function get_session_collections($rs_session, $userref = "", $create = false)
{
$extrasql = "";
$params = array("s",$rs_session);
if ($userref != "") {
$extrasql = "AND user=?";
$params[] = "i";
$params[] = $userref;
} else {
$userref = 'NULL';
}
$collectionrefs = ps_array("SELECT ref value FROM collection WHERE session_id=? AND type IN ('" . COLLECTION_TYPE_STANDARD . "','" . COLLECTION_TYPE_UPLOAD . "','" . COLLECTION_TYPE_SHARE_UPLOAD . "') " . $extrasql, $params, "");
if (count($collectionrefs) < 1 && $create) {
if (upload_share_active()) {
$collectionrefs[0] = create_collection($userref, "New uploads", 0, 1, 0, false, array("type" => 5)); # Do not translate this string!
} else {
$collectionrefs[0] = create_collection($userref, "Default Collection", 0, 1); # Do not translate this string!
}
}
return $collectionrefs;
}
/**
* Update collection to belong to a new user
*
* @param integer $collection Collection ID
* @param integer $newuser User ID to assign collection to
*
* @return boolean success|failure
*/
function update_collection_user($collection, $newuser)
{
if (!collection_writeable($collection)) {
debug("FAILED TO CHANGE COLLECTION USER " . $collection);
return false;
}
ps_query("UPDATE collection SET user=? WHERE ref=?", array("i",$newuser,"i",$collection));
return true;
}
/**
* Helper function for render_actions(). Compiles actions that are normally valid for collections
*
* @param array $collection_data Collection data
* @param boolean $top_actions Set to true if actions are to be rendered in the search filter bar (above results)
* @param array $resource_data Resource data
*
* @return array
*/
function compile_collection_actions(array $collection_data, $top_actions, $resource_data = array())
{
global $baseurl_short, $lang, $k, $userrequestmode, $zipcommand, $collection_download, $archiver_path,
$manage_collections_share_link, $allow_share, $enable_collection_copy,
$manage_collections_remove_link, $userref, $collection_purge, $result,
$order_by, $sort, $archive, $contact_sheet_link_on_collection_bar,
$show_searchitemsdiskusage, $emptycollection, $count_result,
$download_usage, $home_dash, $top_nav_upload_type, $pagename, $offset, $col_order_by, $find, $default_sort,
$default_collection_sort, $restricted_share, $hidden_collections, $internal_share_access, $search,
$usercollection, $disable_geocoding, $collection_download_settings, $contact_sheet, $pagename,$upload_then_edit, $enable_related_resources,$list, $enable_themes,
$system_read_only, $USER_SELECTION_COLLECTION;
$is_selection_collection = isset($collection_data['ref']) && $collection_data['ref'] == $USER_SELECTION_COLLECTION;
#This is to properly render the actions drop down in the themes page
if (isset($collection_data['ref']) && $pagename != "collections") {
if (!is_array($result)) {
$result = get_collection_resources_with_data($collection_data['ref']);
}
if (('' == $k || $internal_share_access) && is_null($list)) {
$list = get_user_collections($userref);
}
$count_result = count($result);
}
if (isset($search) && substr($search, 0, 11) == '!collection' && ($k == '' || $internal_share_access)) {
# Extract the collection number - this bit of code might be useful as a function
$search_collection = explode(' ', $search);
$search_collection = str_replace('!collection', '', $search_collection[0]);
$search_collection = explode(',', $search_collection); // just get the number
$search_collection = $search_collection[0];
}
// Collection bar actions should always be a special search !collection[ID] (exceptions might arise but most of the
// time it should be handled using the special search). If top actions then search may include additional refinement inside the collection
if (isset($collection_data['ref']) && !$top_actions) {
$search = "!collection{$collection_data['ref']}";
}
$urlparams = array(
"search" => $search,
"collection" => (isset($collection_data['ref']) ? $collection_data['ref'] : ""),
"ref" => (isset($collection_data['ref']) ? $collection_data['ref'] : ""),
"restypes" => isset($_COOKIE['restypes']) ? $_COOKIE['restypes'] : "",
"order_by" => $order_by,
"col_order_by" => $col_order_by,
"sort" => $sort,
"offset" => $offset,
"find" => $find,
"k" => $k);
$options = array();
$o = 0;
if (empty($collection_data)) {
return $options;
}
if (empty($order_by)) {
$order_by = $default_collection_sort;
}
// Check minimum access if we have all the data (i.e. not padded for search display), if not then render anyway and access will be checked on target page
$lastresource = end($resource_data);
if ($pagename == 'collection_manage') {
$min_access = collection_min_access($collection_data['ref']);
} elseif (isset($lastresource["ref"])) {
$min_access = collection_min_access($resource_data);
} else {
$min_access = 0;
}
// View all resources
if (
!$top_actions // View all resources makes sense only from collection bar context
&& (
($k == "" || $internal_share_access)
&& (isset($collection_data["c"]) && $collection_data["c"] > 0)
|| (is_array($result) && count($result) > 0)
)
) {
$tempurlparams = array(
'sort' => 'ASC',
'search' => (isset($collection_data['ref']) ? "!collection{$collection_data['ref']}" : $search),
);
$data_attribute['url'] = generateURL($baseurl_short . "pages/search.php", $urlparams, $tempurlparams);
$options[$o]['value'] = 'view_all_resources_in_collection';
$options[$o]['label'] = $lang['view_all_resources'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 10;
$o++;
}
// Download option
if ($min_access == 0) {
if ($download_usage && ( isset($zipcommand) || $GLOBALS['use_zip_extension'] || ( isset($archiver_path) && isset($collection_download_settings) ) ) && $collection_download && $count_result > 0) {
$download_url = generateURL($baseurl_short . "pages/download_usage.php", $urlparams);
$data_attribute['url'] = generateURL($baseurl_short . "pages/terms.php", $urlparams, array("url" => $download_url));
$options[$o]['value'] = 'download_collection';
$options[$o]['label'] = $lang['action-download'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 20;
$o++;
} elseif ((isset($zipcommand) || $GLOBALS['use_zip_extension'] || ( isset($archiver_path) && isset($collection_download_settings) ) ) && $collection_download && $count_result > 0) {
$download_url = generateURL($baseurl_short . "pages/collection_download.php", $urlparams);
$data_attribute['url'] = generateURL($baseurl_short . "pages/terms.php", $urlparams, array("url" => $download_url));
$options[$o]['value'] = 'download_collection';
$options[$o]['label'] = $lang['action-download'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 20;
$o++;
}
}
// Upload to collection
if (allow_upload_to_collection($collection_data)) {
if ($upload_then_edit) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/upload_batch.php", array(), array("collection_add" => $collection_data['ref']));
} else {
$data_attribute['url'] = generateURL($baseurl_short . "pages/edit.php", array(), array("uploader" => $top_nav_upload_type,"ref" => -$userref, "collection_add" => $collection_data['ref']));
}
$options[$o]['value'] = 'upload_collection';
$options[$o]['label'] = $lang['action-upload-to-collection'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 30;
$o++;
}
// Remove all resources from collection
if (!checkperm("b") && 0 < $count_result && ($k == "" || $internal_share_access) && isset($emptycollection) && !$system_read_only && collection_writeable($collection_data['ref'])) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collections.php", $urlparams, array("emptycollection" => $collection_data['ref'],"removeall" => "true","ajax" => "true","submitted" => "removeall"));
$options[$o]['value'] = 'empty_collection';
$options[$o]['label'] = $lang['emptycollection'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 50;
$o++;
}
if (!collection_is_research_request($collection_data['ref']) || !checkperm('r')) {
if (
!$top_actions && checkperm('s')
&& $pagename === 'collections'
&& isset($collection_data['request_feedback'])
&& $collection_data['request_feedback']
) {
// Collection feedback
$data_attribute['url'] = sprintf(
'%spages/collection_feedback.php?collection=%s&k=%s',
$baseurl_short,
urlencode($collection_data['ref']),
urlencode($k)
);
$options[$o]['value'] = 'collection_feedback';
$options[$o]['label'] = $lang['sendfeedback'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 70;
$o++;
}
} else {
$research = ps_value('SELECT ref value FROM research_request WHERE collection=?', array("i",$collection_data['ref']), 0);
// Manage research requests
$data_attribute['url'] = generateURL($baseurl_short . "pages/team/team_research.php", $urlparams);
$options[$o]['value'] = 'manage_research_requests';
$options[$o]['label'] = $lang['manageresearchrequests'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESEARCH;
$options[$o]['order_by'] = 80;
$o++;
// Edit research requests
$data_attribute['url'] = generateURL($baseurl_short . "pages/team/team_research_edit.php", $urlparams, array("ref" => $research));
$options[$o]['value'] = 'edit_research_requests';
$options[$o]['label'] = $lang['editresearchrequests'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESEARCH;
$options[$o]['order_by'] = 90;
$o++;
}
// Select collection option - not for collection bar
if (
$pagename != 'collections' && ($k == '' || $internal_share_access) && !checkperm('b')
&& ($pagename == 'load_actions' || $pagename == 'themes' || $pagename === 'collection_manage' || $pagename === 'resource_collection_list' || $top_actions)
&& ((isset($search_collection) && isset($usercollection) && $search_collection != $usercollection) || !isset($search_collection))
&& collection_readable($collection_data['ref'])
) {
$options[$o]['value'] = 'select_collection';
$options[$o]['label'] = $lang['selectcollection'];
$options[$o]['category'] = ACTIONGROUP_COLLECTION;
$options[$o]['order_by'] = 100;
$o++;
}
// Copy resources from another collection. Must be in top actions or have more than one collection available if on collections.php
if (
!checkperm('b')
&& ($k == '' || $internal_share_access)
&& collection_readable($collection_data['ref'])
&& ($top_actions || (is_array($list) && count($list) > 1))
&& $enable_collection_copy
) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_copy_resources.php", array("ref" => $collection_data['ref']));
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['value'] = 'copy_collection';
$options[$o]['label'] = $lang['copyfromcollection'];
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 105;
$o++;
}
// Edit Collection
if ((($userref == $collection_data['user'] && !in_array($collection_data['type'], [COLLECTION_TYPE_REQUEST,COLLECTION_TYPE_SELECTION])) || (checkperm('h'))) && ($k == '' || $internal_share_access) && !$system_read_only) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_edit.php", $urlparams);
$options[$o]['value'] = 'edit_collection';
$options[$o]['label'] = $lang['editcollection'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 110;
$o++;
}
if (isset($lastresource["ref"])) {
// Work this out based on resource data
$allow_multi_edit = allow_multi_edit($resource_data, $collection_data['ref']);
} else {
// Padded result set. It is too expensive to work this out every time for large result sets,
// Show edit actions for logged in users and access will be checked once action has been selected.
$allow_multi_edit = $k == "";
}
// Edit all
# If this collection is (fully) editable, then display an edit all link
if (
( $k == "" || $internal_share_access )
&& $count_result > 0
&& $allow_multi_edit
) {
$extra_params = array(
'editsearchresults' => 'true',
);
$data_attribute['url'] = generateURL($baseurl_short . "pages/edit.php", $urlparams, $extra_params);
$options[$o]['value'] = 'edit_all_in_collection';
$options[$o]['label'] = $lang['edit_all_resources'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 120;
$o++;
}
// Edit Previews
if (($k == "" || $internal_share_access) && $count_result > 0 && !(checkperm('F*')) && ($userref == $collection_data['user'] || $collection_data['allow_changes'] == 1 || checkperm('h')) && $allow_multi_edit) {
$main_pages = array('search', 'collection_manage', 'collection_public', 'themes');
$back_to_page = (in_array($pagename, $main_pages) ? escape($pagename) : '');
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_edit_previews.php", $urlparams, array("backto" => $back_to_page));
$options[$o]['value'] = 'edit_previews';
$options[$o]['label'] = $lang['editcollectionresources'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 130;
$o++;
}
// Share
if (allow_collection_share($collection_data)) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_share.php", $urlparams);
$options[$o]['value'] = 'share_collection';
$options[$o]['label'] = $lang['share'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_SHARE;
$options[$o]['order_by'] = 140;
$o++;
}
// Share external link to upload to collection, not permitted if already externally shared for view access
$eakeys = get_external_shares(array("share_collection" => $collection_data['ref'],"share_type" => 0));
if (can_share_upload_link($collection_data) && count($eakeys) == 0) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/share_upload.php", array(), array("share_collection" => $collection_data['ref']));
$options[$o]['value'] = 'share_upload';
$options[$o]['label'] = $lang['action-share-upload-link'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_SHARE;
$options[$o]['order_by'] = 30;
$o++;
}
// Home_dash is on, AND NOT Anonymous use, AND (Dash tile user (NOT with a managed dash) || Dash Tile Admin)
if (!$top_actions && $home_dash && ($k == '' || $internal_share_access) && checkPermission_dashcreate() && !$system_read_only && !in_array($collection_data['type'], [COLLECTION_TYPE_REQUEST,COLLECTION_TYPE_SELECTION])) {
$is_smart_featured_collection = (isset($collection_data["smart"]) ? (bool) $collection_data["smart"] : false);
$is_featured_collection_category = (is_featured_collection_category($collection_data) || is_featured_collection_category_by_children($collection_data["ref"]));
$is_featured_collection = (!$is_featured_collection_category && !$is_smart_featured_collection);
$tileparams = array(
'create' => 'true',
'tltype' => 'srch',
'tlstyle' => 'thmbs',
'promoted_resource' => 'true',
'freetext' => 'true',
'all_users' => '1',
'title' => $collection_data["name"],
);
if ($is_featured_collection) {
$tileparams['tltype'] = 'srch';
$tileparams['link'] = generateURL($baseurl_short . 'pages/search.php', array('search' => '!collection' . $collection_data['ref']));
} else {
$tileparams['tltype'] = 'fcthm';
$tileparams['link'] = generateURL($baseurl_short . 'pages/collections_featured.php', array('parent' => $collection_data['ref']));
}
$data_attribute['url'] = generateURL($baseurl_short . "pages/dash_tile.php", $urlparams, $tileparams);
$options[$o]['value'] = 'save_collection_to_dash';
$options[$o]['label'] = $lang['createnewdashtile'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_SHARE;
$options[$o]['order_by'] = 150;
$o++;
}
// Add option to publish as featured collection
if ($enable_themes && ($k == '' || $internal_share_access) && checkperm("h") && !in_array($collection_data['type'], [COLLECTION_TYPE_REQUEST,COLLECTION_TYPE_SELECTION])) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_set_category.php", $urlparams);
$options[$o]['value'] = 'collection_set_category';
$options[$o]['label'] = $lang['collection_set_theme_category'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_SHARE;
$options[$o]['order_by'] = 160;
$o++;
}
// Request all
if ($count_result > 0 && checkperm("q")) {
# Ability to request a whole collection
# This option should only be rendered if at least one of the resources is not downloadable
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_request.php", $urlparams);
$options[$o]['value'] = 'request_all';
$options[$o]['label'] = $lang['requestall'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_RESOURCE;
$options[$o]['order_by'] = 170;
$o++;
}
// Contact Sheet
if (0 < $count_result && ($k == "" || $internal_share_access) && $contact_sheet && ($contact_sheet_link_on_collection_bar)) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/contactsheet_settings.php", $urlparams);
$options[$o]['value'] = 'contact_sheet';
$options[$o]['label'] = $lang['contactsheet'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 190;
$o++;
}
// Remove
if (
($k == "" || $internal_share_access)
&& $manage_collections_remove_link
&& $userref != $collection_data['user']
&& !checkperm('b')
&& collection_readable($collection_data['ref'])
) {
$options[$o]['value'] = 'remove_collection';
$options[$o]['label'] = $lang['action-remove'];
$options[$o]['category'] = ACTIONGROUP_COLLECTION;
$options[$o]['order_by'] = 200;
$o++;
}
// Delete
if (($k == "" || $internal_share_access) && (($userref == $collection_data['user']) || checkperm('h')) && ($collection_data['cant_delete'] == 0) && $collection_data['type'] != COLLECTION_TYPE_REQUEST) {
$options[$o]['value'] = 'delete_collection';
$options[$o]['label'] = $lang['action-deletecollection'];
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 210;
$o++;
}
// Collection Purge
if (($k == "" || $internal_share_access) && $collection_purge && isset($collections) && checkperm('e0') && $collection_data['cant_delete'] == 0) {
$options[$o]['value'] = 'purge_collection';
$options[$o]['label'] = $lang['purgeanddelete'];
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 220;
$o++;
}
// Collection log
if (($k == "" || $internal_share_access) && ($userref == $collection_data['user'] || (checkperm('h')))) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/collection_log.php", $urlparams);
$options[$o]['value'] = 'collection_log';
$options[$o]['label'] = $lang['action-log'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 230;
$o++;
}
// Delete all
// Note: functionality moved from edit collection page
if (
($k == "" || $internal_share_access)
&& (!$top_actions || $is_selection_collection)
&& ((is_array($result) && count($result) != 0) || $count_result != 0)
&& collection_writeable($collection_data['ref'])
&& $allow_multi_edit
&& !checkperm('D')
) {
$options[$o]['value'] = 'delete_all_in_collection';
$options[$o]['label'] = $lang['deleteallresourcesfromcollection'];
$options[$o]['category'] = ACTIONGROUP_EDIT;
$options[$o]['order_by'] = 240;
$o++;
}
// Show disk usage
if (($k == "" || $internal_share_access) && (checkperm('a') || checkperm('v')) && !$top_actions && $show_searchitemsdiskusage && 0 < $count_result) {
$data_attribute['url'] = generateURL($baseurl_short . "pages/search_disk_usage.php", $urlparams);
$options[$o]['value'] = 'search_items_disk_usage';
$options[$o]['label'] = $lang['collection_disk_usage'];
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 250;
$o++;
}
// CSV export of collection metadata
if (
0 < $count_result
&& !$top_actions
&& ($k == '' || $internal_share_access)
&& collection_readable($collection_data['ref'])
) {
$options[$o]['value'] = 'csv_export_results_metadata';
$options[$o]['label'] = $lang['csvExportResultsMetadata'];
$data_attribute['url'] = generateURL($baseurl_short . "pages/csv_export_results_metadata.php", $urlparams);
$options[$o]['data_attr'] = $data_attribute;
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 260;
$o++;
if (!checkperm('b') && !$system_read_only) {
// Hide Collection
$user_mycollection = ps_value("select ref value from collection where user=? and name='Default Collection' order by ref limit 1", array("i",$userref), "");
// check that this collection is not hidden. use first in alphabetical order otherwise
if (in_array($user_mycollection, $hidden_collections)) {
$sql = "select ref value from collection where user=?";
$params = array("i",$userref);
if (count($hidden_collections) > 0) {
$sql .= " and ref not in(" . ps_param_insert(count($hidden_collections)) . ")";
$params = array_merge($params, ps_param_fill($hidden_collections, "i"));
}
$user_mycollection = ps_value($sql . " order by ref limit 1", $params, "");
}
$extra_tag_attributes = sprintf(
'
data-mycol="%s"
',
urlencode($user_mycollection)
);
if ($pagename != "load_actions") {
$options[$o]['value'] = 'hide_collection';
$options[$o]['label'] = $lang['hide_collection'];
$options[$o]['extra_tag_attributes'] = $extra_tag_attributes;
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 270;
$o++;
}
}
}
// Relate / Unrelate all resources
if ($enable_related_resources && $allow_multi_edit && 0 < $count_result) {
$options[$o]['value'] = 'relate_all';
$options[$o]['label'] = $lang['relateallresources'];
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 280;
$o++;
$options[$o]['value'] = 'unrelate_all';
$options[$o]['label'] = $lang['unrelateallresources'];
$options[$o]['category'] = ACTIONGROUP_ADVANCED;
$options[$o]['order_by'] = 290;
$o++;
}
// Add extra collection actions and manipulate existing actions through plugins
$modified_options = hook('render_actions_add_collection_option', '', array($top_actions,$options,$collection_data, $urlparams));
if (is_array($modified_options) && !empty($modified_options)) {
$options = $modified_options;
}
return $options;
}
/**
* Make a filename unique by appending a dupe-string.
*
* @param array $base_values
* @param string $filename
* @param string $dupe_string
* @param string $extension
* @param int $dupe_increment
*
* @return string Unique filename
*/
function makeFilenameUnique($base_values, $filename, $dupe_string, $extension, $dupe_increment = null)
{
// Create filename to check if exist in $base_values
$check_filename = $filename . ($dupe_increment ? $dupe_string . $dupe_increment : '') . '.' . $extension;
if (!in_array($check_filename, $base_values)) {
// Confirmed filename does not exist yet
return $check_filename;
}
// Recursive call this function with incremented value
// Doing $dupe_increment = null, ++$dupe_increment results in $dupe_increment = 1
return makeFilenameUnique($base_values, $filename, $dupe_string, $extension, ++$dupe_increment);
}
/**
* Render the new featured collection form
*
* @param int $parent Featured collection parent. Use zero for root featured collection category
*
* @return void
*/
function new_featured_collection_form(int $parent)
{
global $baseurl_short, $lang;
if (!checkperm('h') || !can_create_collections()) {
http_response_code(401);
exit($lang['error-permissiondenied']);
}
$form_action = "{$baseurl_short}pages/collection_manage.php";
?>