From ee66b85aedce983857bbfde0c2d481995c0ca7c2 Mon Sep 17 00:00:00 2001 From: Linty Date: Tue, 20 Oct 2026 11:56:45 +0200 Subject: [PATCH] issue #2560 people backend and API --- include/common.inc.php | 1 + include/functions.inc.php | 1 + include/functions_people.inc.php | 330 ++++++++++++++++++++++++++++ include/ws_functions/pwg.people.php | 148 +++++++++++++ ws.php | 111 ++++++++++ 5 files changed, 591 insertions(+) create mode 100644 include/functions_people.inc.php create mode 100644 include/ws_functions/pwg.people.php diff --git a/include/common.inc.php b/include/common.inc.php index 70d465d08..61e1f20d8 100644 --- a/include/common.inc.php +++ b/include/common.inc.php @@ -360,6 +360,7 @@ if ( !$conf['allow_html_descriptions'] ) add_event_handler('render_comment_content', 'render_comment_content'); add_event_handler('render_comment_author', 'strip_tags'); add_event_handler('render_tag_url', 'str2url'); +add_event_handler('render_person_url', 'str2url'); add_event_handler('blockmanager_register_blocks', 'register_default_menubar_blocks', EVENT_HANDLER_PRIORITY_NEUTRAL-1); if ( !empty($conf['original_url_protection']) ) { diff --git a/include/functions.inc.php b/include/functions.inc.php index 4a9c840ee..aa030e8a9 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -21,6 +21,7 @@ include_once( PHPWG_ROOT_PATH .'include/functions_url.inc.php' ); include_once( PHPWG_ROOT_PATH .'include/derivative_params.inc.php'); include_once( PHPWG_ROOT_PATH .'include/derivative_std_params.inc.php'); include_once( PHPWG_ROOT_PATH .'include/derivative.inc.php'); +include_once( PHPWG_ROOT_PATH .'include/functions_people.inc.php' ); /** diff --git a/include/functions_people.inc.php b/include/functions_people.inc.php new file mode 100644 index 000000000..fc37db4dd --- /dev/null +++ b/include/functions_people.inc.php @@ -0,0 +1,330 @@ + 0 + ? 'WHERE ' . implode(' AND ', $where_clauses) + : ''; + + // build pagination + $per_page = intval($params['per_page']); + $page = intval($params['page']); + $offset = $page * $per_page; + + // queries + $query = ' +SELECT + p.*, + COUNT(ip.image_id) AS photos + FROM '.PERSONS_TABLE.' AS p + LEFT JOIN '.IMAGE_PERSON_TABLE.' AS ip ON ip.person_id = p.id + '. $where_sql .' + GROUP BY p.id + ORDER BY p.id DESC + LIMIT ' . $per_page . ' + OFFSET ' . $offset . ' +;'; + + $people = query2array($query); + + $query = ' +SELECT COUNT(*) AS nb + FROM ' . PERSONS_TABLE .' AS p + '. $where_sql .' +;'; + $count_result = pwg_db_fetch_assoc(pwg_query($query)); + $total_count = intval($count_result['nb']); + + return array( + 'paging' => array( + 'page' => $page, + 'per_page' => $per_page, + 'total' => $total_count, + 'total_pages' => ceil($total_count / $per_page), + ), + 'people' => $people, + ); +} + +function get_person_by_id(int $id) +{ + $query = ' +SELECT * + FROM '.PERSONS_TABLE.' + WHERE id = '.$id.' +;'; + + $result = pwg_query($query); + + if (pwg_db_num_rows($result) == 0) + { + return null; + } + + return pwg_db_fetch_assoc($result); +} + +function get_person_by_user_id(int $user_id) +{ + $query = ' +SELECT * + FROM '.PERSONS_TABLE.' + WHERE user_id = '.$user_id.' +;'; + + $result = pwg_query($query); + + if (pwg_db_num_rows($result) == 0) + { + return null; + } + + return pwg_db_fetch_assoc($result); +} + +function get_person_in_image_by_id(int $id) +{ + $query = ' +SELECT * + FROM '.IMAGE_PERSON_TABLE.' + WHERE id = '.$id.' +;'; + + $result = pwg_query($query); + + if (pwg_db_num_rows($result) == 0) + { + return null; + } + + return pwg_db_fetch_assoc($result); +} + +function get_total_people() +{ + $query = ' +SELECT COUNT(*) as nb + FROM '.PERSONS_TABLE.' +;'; + $count_result = pwg_db_fetch_assoc(pwg_query($query)); + + return intval($count_result['nb']); +} + +/** + * Create a person. + */ +function create_person(string $name, ?int $user_id = null, bool $is_visible = true, string $source = 'manual') +{ + $user_id_sql = $user_id === null ? 'NULL' : $user_id; + $name = strip_tags($name); + $query = ' +INSERT INTO '.PERSONS_TABLE.' + (name, url_name, user_id, is_visible, source) + VALUES ( + \''.pwg_db_real_escape_string($name).'\', + \''.pwg_db_real_escape_string(trigger_change('render_person_url', $name)).'\', + '.$user_id_sql.', + \''.boolean_to_string($is_visible).'\', + \''.pwg_db_real_escape_string($source).'\' + ) +;'; + + pwg_query($query); + + $new_person_id = pwg_db_insert_id(); + pwg_activity('people', $new_person_id, 'add'); + return array( + 'id' => $new_person_id, + ); +} + +function edit_person(int $id, array $data) +{ + $validated_data = []; + $person = get_person_by_id($id); + + if (!$person) + { + return array('code' => 404, 'error' => 'Person not found.'); + } + + // name + if (!empty($data['name'])) + { + $name = strip_tags($data['name']); + $validated_data['name'] = pwg_db_real_escape_string($name); + $validated_data['url_name'] = pwg_db_real_escape_string(trigger_change('render_person_url', $name)); + } + + // is_visible + if (isset($data['is_visible'])) + { + + if (!is_bool($data['is_visible'])) + { + return array('code' => 403, 'error' => 'is_visible must be: true or false.'); + } + + $validated_data['is_visible'] = boolean_to_string($data['is_visible']); + } + + // source + if (!empty($data['source'])) + { + if (!validate_people_source($data['source'])) + { + return array('code' => 403, 'error' => 'source must be: manual, auto or imported.'); + } + $validated_data['source'] = $data['source']; + } + + // user_id, 0 dissociates the user + if (isset($data['user_id'])) + { + if (!preg_match('/^\d+$/', $data['user_id'])) + { + return array('code' => 403, 'error' => 'user_id must be a digit.'); + } + + if (0 == $data['user_id']) + { + $validated_data['user_id'] = null; + } + else if ($person['user_id'] != $data['user_id']) + { + $is_user_already_associated = get_person_by_user_id($data['user_id']); + + if ($is_user_already_associated) + { + return array('code' => 401, 'error' => 'This user is already associated with a person.'); + } + + $validated_data['user_id'] = $data['user_id']; + } + } + + // cover_image_person_id + if (!empty($data['cover_image_person_id'])) + { + if (!preg_match('/^\d+$/', $data['cover_image_person_id'])) + { + return array('code' => 403, 'error' => 'cover_image_person_id must be a digit.'); + } + + $cover_exist = get_person_in_image_by_id($data['cover_image_person_id']); + if (!$cover_exist) + { + return array('code' => 404, 'error' => 'Person in image not found.'); + } + + $validated_data['cover_image_person_id'] = $data['cover_image_person_id']; + } + + // save data + if (count($validated_data) === 0) + { + return true; + } + + single_update( + PERSONS_TABLE, + $validated_data, + array('id' => $id) + ); + + pwg_activity('people', $person['id'], 'edit'); + return true; +} + +/** + * @param int[]|int $people_ids + * @return bool + */ +function delete_people($people_ids) +{ + if (is_numeric($people_ids)) + { + $people_ids = array($people_ids); + } + + if (!is_array($people_ids)) + { + return false; + } + + // we need the list of impacted images, to update their lastmodified like tags + $query = ' +SELECT + image_id + FROM '.IMAGE_PERSON_TABLE.' + WHERE person_id IN ('.implode(',', $people_ids).') +;'; + $image_ids = query2array($query, null, 'image_id'); + + $query = ' +DELETE + FROM '.IMAGE_PERSON_TABLE.' + WHERE person_id IN ('.implode(',', $people_ids).') +;'; + pwg_query($query); + + $query = ' +DELETE + FROM '.PERSONS_TABLE.' + WHERE id IN ('.implode(',', $people_ids).') +;'; + pwg_query($query); + + pwg_activity('people', $people_ids, 'delete'); + update_images_lastmodified($image_ids); + + return true; +} + +function merge_people() {} + +// utils +function validate_people_source(string $source) +{ + if (!in_array($source, ['manual', 'imported', 'auto'])) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/include/ws_functions/pwg.people.php b/include/ws_functions/pwg.people.php new file mode 100644 index 000000000..d352a9430 --- /dev/null +++ b/include/ws_functions/pwg.people.php @@ -0,0 +1,148 @@ + $new_person['id'], + ); +} + +/** + * API method + * Edit a person + * @param mixed[] $params + * @option int id + * @option string name + */ +function ws_people_edit($params, &$service) +{ + if (get_pwg_token() != $params['pwg_token']) + { + return new PwgError(403, 'Invalid security token'); + } + + $is_edited = edit_person($params['person_id'], $params); + + if (isset($is_edited['error'])) + { + return new PwgError($is_edited['code'], $is_edited['error']); + } + + return array( + 'id' => $params['person_id'], + ); +} + +/** + * API method + * Delete a person + * @param mixed[] $params + * @option int id + */ +function ws_people_delete($params, &$service) +{ + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + + if (get_pwg_token() != $params['pwg_token']) + { + return new PwgError(403, 'Invalid security token'); + } + + $people_ids = $params['people_ids']; + + $query = ' +SELECT COUNT(*) + FROM `'. PERSONS_TABLE .'` + WHERE id in ('.implode(',', $people_ids) .') +;'; + list($count) = pwg_db_fetch_row(pwg_query($query)); + if ($count != count($people_ids)) + { + return new PwgError(401, 'All people does not exist.'); + } + + + + if (count($people_ids) > 0) + { + delete_people($people_ids); + return array( 'id' => $people_ids ); + } else { + return array( 'id' => array() ); + } +} \ No newline at end of file diff --git a/ws.php b/ws.php index 017d5e4eb..56ef55da7 100644 --- a/ws.php +++ b/ws.php @@ -1709,6 +1709,117 @@ enabled_high, registration_date, registration_date_string, registration_date_sin 'post_only'=>true ) ); + + $service->addMethod( + 'pwg.people.getAdminList', + 'ws_people_getAdminList', + array( + 'search' => array( + 'default' => null, + 'flags' => WS_PARAM_OPTIONAL, + ), + 'is_visible' => array( + 'type' => WS_TYPE_BOOL, + 'flags' => WS_PARAM_OPTIONAL, + ), + 'per_page' => array( + 'default' => 50, + 'maxValue' => 1000, + 'type' => WS_TYPE_INT|WS_TYPE_POSITIVE, + ), + 'page' => array( + 'default' => 0, + 'type' => WS_TYPE_INT|WS_TYPE_POSITIVE, + ), + ), + 'Admin only.', + $ws_functions_root . 'pwg.people.php', + array( + 'hidden' => false, + 'post_only' => false, + 'admin_only' => true, + ) + ); + + $service->addMethod( + 'pwg.people.add', + 'ws_people_add', + array( + 'name' => array(), + 'is_visible' => array( + 'type' => WS_TYPE_BOOL, + ), + 'source' => array( + 'default' => 'manual', + 'info' => 'manual,auto,imported' + ), + 'pwg_token' => array(), + ), + 'Add a new person.', + $ws_functions_root . 'pwg.people.php', + array( + 'hidden' => false, + 'post_only' => true, + 'admin_only' => true, + ) + ); + + $service->addMethod( + 'pwg.people.edit', + 'ws_people_edit', + array( + 'person_id' => array( + 'type' => WS_TYPE_ID, + ), + 'name' => array( + 'flags' => WS_PARAM_OPTIONAL, + ), + 'is_visible' => array( + 'type' => WS_TYPE_BOOL, + 'flags' => WS_PARAM_OPTIONAL, + ), + 'source' => array( + 'flags' => WS_PARAM_OPTIONAL, + 'info' => 'manual,auto,imported' + ), + 'user_id' => array( + 'type' => WS_TYPE_INT|WS_TYPE_POSITIVE, + 'flags' => WS_PARAM_OPTIONAL, + 'info' => '0 to dissociate the user', + ), + 'cover_image_person_id' => array( + 'type' => WS_TYPE_ID, + 'flags' => WS_PARAM_OPTIONAL, + ), + 'pwg_token' => array(), + ), + 'Edit a person.', + $ws_functions_root . 'pwg.people.php', + array( + 'hidden' => false, + 'post_only' => true, + 'admin_only' => true, + ) + ); + + $service->addMethod( + 'pwg.people.delete', + 'ws_people_delete', + array( + 'people_ids' => array( + 'type' => WS_TYPE_ID, + 'flags' => WS_PARAM_FORCE_ARRAY, + ), + 'pwg_token' => array(), + ), + 'Delete people by ID.', + $ws_functions_root . 'pwg.people.php', + array( + 'hidden' => false, + 'post_only' => true, + 'admin_only' => true, + ) + ); } ?>