1
1
Fork 0

Merge pull request #3418 from gerhard6380/develop

New API calls for photo management
This commit is contained in:
Tobias Diekershoff 2017-05-08 06:56:24 +02:00 committed by GitHub
commit 1f58bcc114
3 changed files with 839 additions and 32 deletions

View file

@ -465,6 +465,28 @@ Friendica doesn't allow showing followers of other users.
Friendica doesn't allow showing friends of other users. Friendica doesn't allow showing friends of other users.
---
### account/update_profile_image (POST; AUTH)
#### Parameters
* image: image data as base64 (Twitter has a limit of 700kb, Friendica allows more)
* profile_id (optional): id of the profile for which the image should be used, default is changing the default profile
uploads a new profile image (scales 4-6) to database, changes default or specified profile to the new photo
#### Return values
On success:
* JSON return: returns the updated user details (see account/verify_credentials)
On error:
* 403 FORBIDDEN: if not authenticated
* 400 BADREQUEST: "no media data submitted", "profile_id not available"
* 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server",
"image size exceeds Friendica Config setting (uploaded size: x)",
"unable to process image data",
"image upload failed"
## Implemented API calls (not compatible with other APIs) ## Implemented API calls (not compatible with other APIs)
@ -724,6 +746,100 @@ xml
</photos> </photos>
``` ```
---
### friendica/photoalbum/delete (POST,DELETE; AUTH)
#### Parameters
* album: name of the album to be deleted
deletes all images with the specified album name, is not reversible -> ensure that client is asking user for being sure to do this
#### Return values
On success:
* JSON return {"result":"deleted","message":"album 'xyz' with all containing photos has been deleted."}
On error:
* 403 FORBIDDEN: if not authenticated
* 400 BADREQUEST: "no albumname specified", "album not available"
* 500 INTERNALSERVERERROR: "problem with deleting item occured", "unknown error - deleting from database failed"
---
### friendica/photoalbum/update (POST,PUT; AUTH)
#### Parameters
* album: name of the album to be updated
* album_new: new name of the album
changes the album name to album_new for all photos in album
#### Return values
On success:
* JSON return {"result":"updated","message":"album 'abc' with all containing photos has been renamed to 'xyz'."}
On error:
* 403 FORBIDDEN: if not authenticated
* 400 BADREQUEST: "no albumname specified", "no new albumname specified", "album not available"
* 500 INTERNALSERVERERROR: "unknown error - updating in database failed"
---
### friendica/photo/create (POST; AUTH)
### friendica/photo/update (POST; AUTH)
#### Parameters
* photo_id (optional): if specified the photo with this id will be updated
* media (optional): image data as base64, only optional if photo_id is specified (new upload must have media)
* desc (optional): description for the photo, updated when photo_id is specified
* album: name of the album to be deleted (always necessary)
* album_new (optional): can be used to change the album of a single photo if photo_id is specified
* allow_cid/allow_gid/deny_cid/deny_gid (optional): on create: empty string or omitting = public photo, specify in format '```<x><y><z>```' for private photo;
on update: keys need to be present with empty values for setting a private photo now to public
both calls point to one function for creating AND updating photos.
Saves data for the scales 0-2 to database (see above for scale description).
Call adds non-visible entries to items table to enable authenticated contacts to comment/like the photo.
Client should pay attention to the fact that updated access rights are not transferred to the contacts. i.e. public photos remain publicly visible if they have been commented/liked before setting visibility back to a limited group.
Currently it is best way to inform user that updating rights is not the best way, offer a solution to add photo as a new photo with the new rights.
#### Return values
On success:
* new photo uploaded: JSON return with photo data (see friendica/photo)
* photo updated - changed photo data: JSON return with photo data (see friendica/photo)
* photo updated - changed info: JSON return {"result":"updated","message":"Image id 'xyz' has been updated."}
* photo updated - nothing changed: JSON return {"result":"cancelled","message":"Nothing to update for image id 'xyz'."}
On error:
* 403 FORBIDDEN: if not authenticated
* 400 BADREQUEST: "no albumname specified", "no media data submitted", "photo not available", "acl data invalid"
* 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server",
"image size exceeds Friendica Config setting (uploaded size: x)",
"unable to process image data",
"image upload failed",
"unknown error - uploading photo failed, see Friendica log for more information",
"unknown error - update photo entry in database failed",
"unknown error - this error on uploading or updating a photo should never happen"
---
### friendica/photo/delete (DELETE; AUTH)
#### Parameters
* photo_id: id of the photo to be deleted
deletes a single image with the specified id, is not reversible -> ensure that client is asking user for being sure to do this
Sets item table entries for this photo to deleted = 1
#### Return values
On success:
* JSON return {"result":"deleted","message":"photo with id 'xyz' has been deleted from server."}
On error:
* 403 FORBIDDEN: if not authenticated
* 400 BADREQUEST: "no photo_id specified", "photo not available"
* 500 INTERNALSERVERERROR: "unknown error on deleting photo", "problem with deleting items occurred"
--- ---
### friendica/direct_messages_setseen (GET; AUTH) ### friendica/direct_messages_setseen (GET; AUTH)
#### Parameters #### Parameters
@ -794,7 +910,6 @@ The following API calls are implemented in GNU Social but not in Friendica: (inc
* friendships/exists * friendships/exists
* friendships/show * friendships/show
* account/update_profile_background_image * account/update_profile_background_image
* account/update_profile_image
* blocks/create * blocks/create
* blocks/destroy * blocks/destroy
@ -817,7 +932,6 @@ The following API calls from the Twitter API aren't implemented neither in Frien
* account/update_delivery_device * account/update_delivery_device
* account/update_profile * account/update_profile
* account/update_profile_background_image * account/update_profile_background_image
* account/update_profile_image
* blocks/list * blocks/list
* blocks/ids * blocks/ids
* users/lookup * users/lookup

View file

@ -70,9 +70,11 @@ class Photo {
$this->image->destroy(); $this->image->destroy();
return; return;
} }
if (is_resource($this->image)) {
imagedestroy($this->image); imagedestroy($this->image);
} }
} }
}
public function is_imagick() { public function is_imagick() {
return $this->imagick; return $this->imagick;
@ -326,6 +328,7 @@ class Photo {
return; return;
} }
// if script dies at this point check memory_limit setting in php.ini
$this->image = imagerotate($this->image,$degrees,0); $this->image = imagerotate($this->image,$degrees,0);
$this->width = imagesx($this->image); $this->width = imagesx($this->image);
$this->height = imagesy($this->image); $this->height = imagesy($this->image);
@ -622,7 +625,7 @@ class Photo {
public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '') {
$r = q("SELECT `guid` FROM `photo` WHERE `resource-id` = '%s' AND `guid` != '' LIMIT 1", $r = q("SELECT `guid` FROM `photo` WHERE `resource-id` = '%s' AND `guid` != '' LIMIT 1",
dbesc($rid) dbesc($rid)
@ -659,7 +662,8 @@ class Photo {
`allow_cid` = '%s', `allow_cid` = '%s',
`allow_gid` = '%s', `allow_gid` = '%s',
`deny_cid` = '%s', `deny_cid` = '%s',
`deny_gid` = '%s' `deny_gid` = '%s',
`desc` = '%s'
WHERE `id` = %d", WHERE `id` = %d",
intval($uid), intval($uid),
@ -681,12 +685,13 @@ class Photo {
dbesc($allow_gid), dbesc($allow_gid),
dbesc($deny_cid), dbesc($deny_cid),
dbesc($deny_gid), dbesc($deny_gid),
dbesc($desc),
intval($x[0]['id']) intval($x[0]['id'])
); );
} else { } else {
$r = q("INSERT INTO `photo` $r = q("INSERT INTO `photo`
(`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`) (`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`, `desc`)
VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s')",
intval($uid), intval($uid),
intval($cid), intval($cid),
dbesc($guid), dbesc($guid),
@ -705,7 +710,8 @@ class Photo {
dbesc($allow_cid), dbesc($allow_cid),
dbesc($allow_gid), dbesc($allow_gid),
dbesc($deny_cid), dbesc($deny_cid),
dbesc($deny_gid) dbesc($deny_gid),
dbesc($desc)
); );
} }

View file

@ -3287,14 +3287,117 @@ $called_api = null;
api_register_func('api/oauth/request_token', 'api_oauth_request_token', false); api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
api_register_func('api/oauth/access_token', 'api_oauth_access_token', false); api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
/**
* @brief delete a complete photoalbum with all containing photos from database through api
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photoalbum_delete($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
// input params
$album = (x($_REQUEST,'album') ? $_REQUEST['album'] : "");
// we do not allow calls without album string
if ($album == "") {
throw new BadRequestException("no albumname specified");
}
// check if album is existing
$r = q("SELECT DISTINCT `resource-id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
intval(api_user()),
dbesc($album));
if (!dbm::is_result($r))
throw new BadRequestException("album not available");
// function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
// to the user and the contacts of the users (drop_items() performs the federation of the deletion to other networks
foreach ($r as $rr) {
$photo_item = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
intval(local_user()),
dbesc($rr['resource-id'])
);
if (!dbm::is_result($photo_item)) {
throw new InternalServerErrorException("problem with deleting items occured");
}
drop_item($photo_item[0]['id'],false);
}
// now let's delete all photos from the album
$result = q("DELETE FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
intval(api_user()),
dbesc($album));
// return success of deletion or error message
if ($result) {
$answer = array('result' => 'deleted', 'message' => 'album `' . $album . '` with all containing photos has been deleted.');
return api_format_data("photoalbum_delete", $type, array('$result' => $answer));
} else {
throw new InternalServerErrorException("unknown error - deleting from database failed");
}
}
/**
* @brief update the name of the album for all photos of an album
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photoalbum_update($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
// input params
$album = (x($_REQUEST,'album') ? $_REQUEST['album'] : "");
$album_new = (x($_REQUEST,'album_new') ? $_REQUEST['album_new'] : "");
// we do not allow calls without album string
if ($album == "") {
throw new BadRequestException("no albumname specified");
}
if ($album_new == "") {
throw new BadRequestException("no new albumname specified");
}
// check if album is existing
$r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
intval(api_user()),
dbesc($album));
if (!dbm::is_result($r)) {
throw new BadRequestException("album not available");
}
// now let's update all photos to the albumname
$result = q("UPDATE `photo` SET `album` = '%s' WHERE `uid` = %d AND `album` = '%s'",
dbesc($album_new),
intval(api_user()),
dbesc($album));
// return success of updating or error message
if ($result) {
$answer = array('result' => 'updated', 'message' => 'album `' . $album . '` with all containing photos has been renamed to `' . $album_new . '`.');
return api_format_data("photoalbum_update", $type, array('$result' => $answer));
} else {
throw new InternalServerErrorException("unknown error - updating in database failed");
}
}
/**
* @brief list all photos of the authenticated user
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photos_list($type) { function api_fr_photos_list($type) {
if (api_user() === false) { if (api_user() === false) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
$r = q("SELECT `resource-id`, MAX(scale) AS `scale`, `album`, `filename`, `type`, MAX(`created`) AS `created`,
$r = q("SELECT `resource-id`, MAX(`scale`) AS `scale`, `album`, `filename`, `type` MAX(`edited`) AS `edited`, MAX(`desc`) AS `desc` FROM `photo`
FROM `photo` WHERE `uid` = %d AND `album` != 'Contact Photos' GROUP BY `resource-id`",
WHERE `uid` = %d AND `album` != 'Contact Photos' GROUP BY `resource-id`, `album`, `filename`, `type`",
intval(local_user()) intval(local_user())
); );
$typetoext = array( $typetoext = array(
@ -3311,6 +3414,9 @@ $called_api = null;
$photo['filename'] = $rr['filename']; $photo['filename'] = $rr['filename'];
$photo['type'] = $rr['type']; $photo['type'] = $rr['type'];
$thumb = App::get_baseurl() . "/photo/" . $rr['resource-id'] . "-" . $rr['scale'] . "." . $typetoext[$rr['type']]; $thumb = App::get_baseurl() . "/photo/" . $rr['resource-id'] . "-" . $rr['scale'] . "." . $typetoext[$rr['type']];
$photo['created'] = $rr['created'];
$photo['edited'] = $rr['edited'];
$photo['desc'] = $rr['desc'];
if ($type == "xml") { if ($type == "xml") {
$data['photo'][] = array("@attributes" => $photo, "1" => $thumb); $data['photo'][] = array("@attributes" => $photo, "1" => $thumb);
@ -3323,26 +3429,563 @@ $called_api = null;
return api_format_data("photos", $type, $data); return api_format_data("photos", $type, $data);
} }
/**
* @brief upload a new photo or change an existing photo
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photo_create_update($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
// input params
$photo_id = (x($_REQUEST, 'photo_id') ? $_REQUEST['photo_id'] : null);
$desc = (x($_REQUEST, 'desc') ? $_REQUEST['desc'] : (array_key_exists('desc', $_REQUEST) ? "" : null)); // extra check necessary to distinguish between 'not provided' and 'empty string'
$album = (x($_REQUEST,'album') ? $_REQUEST['album'] : null);
$album_new = (x($_REQUEST,'album_new') ? $_REQUEST['album_new'] : null);
$allow_cid = (x($_REQUEST, 'allow_cid') ? $_REQUEST['allow_cid'] : (array_key_exists('allow_cid', $_REQUEST) ? " " : null));
$deny_cid = (x($_REQUEST, 'deny_cid') ? $_REQUEST['deny_cid'] : (array_key_exists('deny_cid', $_REQUEST) ? " " : null));
$allow_gid = (x($_REQUEST, 'allow_gid') ? $_REQUEST['allow_gid'] : (array_key_exists('allow_gid', $_REQUEST) ? " " : null));
$deny_gid = (x($_REQUEST, 'deny_gid') ? $_REQUEST['deny_gid'] : (array_key_exists('deny_gid', $_REQUEST) ? " " : null));
$visibility = (x($_REQUEST, 'visibility') ? (($_REQUEST['visibility'] == "true" || $_REQUEST['visibility'] == 1) ? true : false) : false);
// do several checks on input parameters
// we do not allow calls without album string
if ($album == null) {
throw new BadRequestException("no albumname specified");
}
// if photo_id == null --> we are uploading a new photo
if ($photo_id == null) {
$mode = "create";
// error if no media posted in create-mode
if (!x($_FILES,'media')) {
// Output error
throw new BadRequestException("no media data submitted");
}
// album_new will be ignored in create-mode
$album_new = "";
} else {
$mode = "update";
// check if photo is existing in database
$r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'",
intval(api_user()),
dbesc($photo_id),
dbesc($album));
if (!dbm::is_result($r)) {
throw new BadRequestException("photo not available");
}
}
// checks on acl strings provided by clients
$acl_input_error = false;
$acl_input_error |= check_acl_input($allow_cid);
$acl_input_error |= check_acl_input($deny_cid);
$acl_input_error |= check_acl_input($allow_gid);
$acl_input_error |= check_acl_input($deny_gid);
if ($acl_input_error) {
throw new BadRequestException("acl data invalid");
}
// now let's upload the new media in create-mode
if ($mode == "create") {
$media = $_FILES['media'];
$data = save_media_to_database("photo", $media, $type, $album, trim($allow_cid), trim($deny_cid), trim($allow_gid), trim($deny_gid), $desc, $visibility);
// return success of updating or error message
if (!is_null($data)) {
return api_format_data("photo_create", $type, $data);
} else {
throw new InternalServerErrorException("unknown error - uploading photo failed, see Friendica log for more information");
}
}
// now let's do the changes in update-mode
if ($mode == "update") {
$sql_extra = "";
if (!is_null($desc)) {
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`desc` = '$desc'";
}
if (!is_null($album_new)) {
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`album` = '$album_new'";
}
if (!is_null($allow_cid)) {
$allow_cid = trim($allow_cid);
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_cid` = '$allow_cid'";
}
if (!is_null($deny_cid)) {
$deny_cid = trim($deny_cid);
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_cid` = '$deny_cid'";
}
if (!is_null($allow_gid)) {
$allow_gid = trim($allow_gid);
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_gid` = '$allow_gid'";
}
if (!is_null($deny_gid)) {
$deny_gid = trim($deny_gid);
$sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_gid` = '$deny_gid'";
}
$result = false;
if ($sql_extra != "") {
$nothingtodo = false;
$result = q("UPDATE `photo` SET %s, `edited`='%s' WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'",
$sql_extra,
datetime_convert(), // update edited timestamp
intval(api_user()),
dbesc($photo_id),
dbesc($album));
} else {
$nothingtodo = true;
}
if (x($_FILES,'media')) {
$nothingtodo = false;
$media = $_FILES['media'];
$data = save_media_to_database("photo", $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, 0, $visibility, $photo_id);
if (!is_null($data)) {
return api_format_data("photo_update", $type, $data);
}
}
// return success of updating or error message
if ($result) {
$answer = array('result' => 'updated', 'message' => 'Image id `' . $photo_id . '` has been updated.');
return api_format_data("photo_update", $type, array('$result' => $answer));
} else {
if ($nothingtodo) {
$answer = array('result' => 'cancelled', 'message' => 'Nothing to update for image id `' . $photo_id . '`.');
return api_format_data("photo_update", $type, array('$result' => $answer));
}
throw new InternalServerErrorException("unknown error - update photo entry in database failed");
}
}
throw new InternalServerErrorException("unknown error - this error on uploading or updating a photo should never happen");
}
/**
* @brief delete a single photo from the database through api
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photo_delete($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
// input params
$photo_id = (x($_REQUEST, 'photo_id') ? $_REQUEST['photo_id'] : null);
// do several checks on input parameters
// we do not allow calls without photo id
if ($photo_id == null) {
throw new BadRequestException("no photo_id specified");
}
// check if photo is existing in database
$r = q("SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'",
intval(api_user()),
dbesc($photo_id)
);
if (!dbm::is_result($r)) {
throw new BadRequestException("photo not available");
}
// now we can perform on the deletion of the photo
$result = q("DELETE FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'",
intval(api_user()),
dbesc($photo_id));
// return success of deletion or error message
if ($result) {
// retrieve the id of the parent element (the photo element)
$photo_item = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
intval(local_user()),
dbesc($photo_id)
);
if (!dbm::is_result($photo_item)) {
throw new InternalServerErrorException("problem with deleting items occured");
}
// function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
// to the user and the contacts of the users (drop_items() do all the necessary magic to avoid orphans in database and federate deletion)
drop_item($photo_item[0]['id'], false);
$answer = array('result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.');
return api_format_data("photo_delete", $type, array('$result' => $answer));
} else {
throw new InternalServerErrorException("unknown error on deleting photo from database table");
}
}
/**
* @brief returns the details of a specified photo id, if scale is given, returns the photo data in base 64
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_fr_photo_detail($type) { function api_fr_photo_detail($type) {
if (api_user() === false) { if (api_user() === false) {
throw new ForbiddenException(); throw new ForbiddenException();
} elseif (!x($_REQUEST, 'photo_id')) { }
if (!x($_REQUEST, 'photo_id')) {
throw new BadRequestException("No photo id."); throw new BadRequestException("No photo id.");
} }
$scale = (x($_REQUEST, 'scale') ? intval($_REQUEST['scale']) : false); $scale = (x($_REQUEST, 'scale') ? intval($_REQUEST['scale']) : false);
$scale_sql = ($scale === false ? "" : sprintf("AND `scale`=%d",intval($scale))); $photo_id = $_REQUEST['photo_id'];
$data_sql = ($scale === false ? "" : "ANY_VALUE(`data`) AS data`,");
$r = q("SELECT %s ANY_VALUE(`resource-id`) AS `resource-id`, ANY_VALUE(`created`) AS `created`, // prepare json/xml output with data from database for the requested photo
ANY_VALUE(`edited`) AS `edited`, ANY_VALUE(`title`) AS `title`, ANY_VALUE(`desc`) AS `desc`, $data = prepare_photo_data($type, $scale, $photo_id);
ANY_VALUE(`album`) AS `album`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
ANY_VALUE(`height`) AS `height`, ANY_VALUE(`width`) AS `width`, ANY_VALUE(`datasize`) AS `datasize`, return api_format_data("photo_detail", $type, $data);
ANY_VALUE(`profile`) AS `profile`, min(`scale`) as minscale, max(`scale`) as maxscale }
FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' %s",
/**
* @brief updates the profile image for the user (either a specified profile or the default profile)
*
* @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
* @return string
*/
function api_account_update_profile_image($type) {
if (api_user() === false) {
throw new ForbiddenException();
}
// input params
$profileid = (x($_REQUEST, 'profile_id') ? $_REQUEST['profile_id'] : 0);
// error if image data is missing
if (!x($_FILES, 'image')) {
throw new BadRequestException("no media data submitted");
}
// check if specified profile id is valid
if ($profileid != 0) {
$r = q("SELECT `id` FROM `profile` WHERE `uid` = %d AND `id` = %d",
intval(api_user()),
intval($profileid));
// error message if specified profile id is not in database
if (!dbm::is_result($r)) {
throw new BadRequestException("profile_id not available");
}
$is_default_profile = $r['profile'];
} else {
$is_default_profile = 1;
}
// get mediadata from image or media (Twitter call api/account/update_profile_image provides image)
$media = null;
if (x($_FILES, 'image')) {
$media = $_FILES['image'];
} elseif (x($_FILES, 'media')) {
$media = $_FILES['media'];
}
// save new profile image
$data = save_media_to_database("profileimage", $media, $type, t('Profile Photos'), "", "", "", "", "", $is_default_profile);
// get filetype
if (is_array($media['type'])) {
$filetype = $media['type'][0];
} else {
$filetype = $media['type'];
}
if ($filetype == "image/jpeg") {
$fileext = "jpg";
} elseif ($filetype == "image/png") {
$fileext = "png";
}
// change specified profile or all profiles to the new resource-id
if ($is_default_profile) {
$r = q("UPDATE `photo` SET `profile` = 0 WHERE `profile` = 1 AND `resource-id` != '%s' AND `uid` = %d",
dbesc($data['photo']['id']),
intval(local_user())
);
$r = q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s' WHERE `self` AND `uid` = %d",
dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-4.' . $fileext),
dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-5.' . $fileext),
dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-6.' . $fileext),
intval(local_user())
);
} else {
$r = q("UPDATE `profile` SET `photo` = '%s', `thumb` = '%s' WHERE `id` = %d AND `uid` = %d",
dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-4.' . $filetype),
dbesc(App::get_baseurl() . '/photo/' . $data['photo']['id'] . '-5.' . $filetype),
intval($_REQUEST['profile']),
intval(local_user())
);
}
// we'll set the updated profile-photo timestamp even if it isn't the default profile,
// so that browsers will do a cache update unconditionally
$r = q("UPDATE `contact` SET `avatar-date` = '%s' WHERE `self` = 1 AND `uid` = %d",
dbesc(datetime_convert()),
intval(local_user())
);
// Update global directory in background
//$user = api_get_user(get_app());
$url = App::get_baseurl() . '/profile/' . get_app()->user['nickname'];
if ($url && strlen(get_config('system', 'directory'))) {
proc_run(PRIORITY_LOW, "include/directory.php", $url);
}
require_once 'include/profile_update.php';
profile_change();
// output for client
if ($data) {
return api_account_verify_credentials($type);
} else {
// SaveMediaToDatabase failed for some reason
throw new InternalServerErrorException("image upload failed");
}
}
// place api-register for photoalbum calls before 'api/friendica/photo', otherwise this function is never reached
api_register_func('api/friendica/photoalbum/delete', 'api_fr_photoalbum_delete', true, API_METHOD_DELETE);
api_register_func('api/friendica/photoalbum/update', 'api_fr_photoalbum_update', true, API_METHOD_POST);
api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
api_register_func('api/friendica/photo/create', 'api_fr_photo_create_update', true, API_METHOD_POST);
api_register_func('api/friendica/photo/update', 'api_fr_photo_create_update', true, API_METHOD_POST);
api_register_func('api/friendica/photo/delete', 'api_fr_photo_delete', true, API_METHOD_DELETE);
api_register_func('api/friendica/photo', 'api_fr_photo_detail', true);
api_register_func('api/account/update_profile_image', 'api_account_update_profile_image', true, API_METHOD_POST);
function check_acl_input($acl_string) {
if ($acl_string == null || $acl_string == " ") {
return false;
}
$contact_not_found = false;
// split <x><y><z> into array of cid's
preg_match_all("/<[A-Za-z0-9]+>/", $acl_string, $array);
// check for each cid if it is available on server
$cid_array = $array[0];
foreach ($cid_array as $cid) {
$cid = str_replace("<", "", $cid);
$cid = str_replace(">", "", $cid);
$contact = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d",
intval($cid),
intval(api_user()));
$contact_not_found |= !dbm::is_result($contact);
}
return $contact_not_found;
}
function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, $profile = 0, $visibility = false, $photo_id = null) {
$visitor = 0;
$src = "";
$filetype = "";
$filename = "";
$filesize = 0;
if (is_array($media)) {
if (is_array($media['tmp_name'])) {
$src = $media['tmp_name'][0];
} else {
$src = $media['tmp_name'];
}
if (is_array($media['name'])) {
$filename = basename($media['name'][0]);
} else {
$filename = basename($media['name']);
}
if (is_array($media['size'])) {
$filesize = intval($media['size'][0]);
} else {
$filesize = intval($media['size']);
}
if (is_array($media['type'])) {
$filetype = $media['type'][0];
} else {
$filetype = $media['type'];
}
}
if ($filetype == "") {
$filetype=guess_image_type($filename);
}
$imagedata = getimagesize($src);
if ($imagedata) {
$filetype = $imagedata['mime'];
}
logger("File upload src: " . $src . " - filename: " . $filename .
" - size: " . $filesize . " - type: " . $filetype, LOGGER_DEBUG);
// check if there was a php upload error
if ($filesize == 0 && $media['error'] == 1) {
throw new InternalServerErrorException("image size exceeds PHP config settings, file was rejected by server");
}
// check against max upload size within Friendica instance
$maximagesize = get_config('system', 'maximagesize');
if (($maximagesize) && ($filesize > $maximagesize)) {
$formattedBytes = formatBytes($maximagesize);
throw new InternalServerErrorException("image size exceeds Friendica config setting (uploaded size: $formattedBytes)");
}
// create Photo instance with the data of the image
$imagedata = @file_get_contents($src);
$ph = new Photo($imagedata, $filetype);
if (! $ph->is_valid()) {
throw new InternalServerErrorException("unable to process image data");
}
// check orientation of image
$ph->orient($src);
@unlink($src);
// check max length of images on server
$max_length = get_config('system', 'max_image_length');
if (! $max_length) {
$max_length = MAX_IMAGE_LENGTH;
}
if ($max_length > 0) {
$ph->scaleImage($max_length);
logger("File upload: Scaling picture to new size " . $max_length, LOGGER_DEBUG);
}
$width = $ph->getWidth();
$height = $ph->getHeight();
// create a new resource-id if not already provided
$hash = ($photo_id == null) ? photo_new_resource() : $photo_id;
if ($mediatype == "photo") {
// upload normal image (scales 0, 1, 2)
logger("photo upload: starting new photo upload", LOGGER_DEBUG);
$r =$ph->store(local_user(), $visitor, $hash, $filename, $album, 0, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: image upload with scale 0 (original size) failed");
}
if($width > 640 || $height > 640) {
$ph->scaleImage(640);
$r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 1, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: image upload with scale 1 (640x640) failed");
}
}
if ($width > 320 || $height > 320) {
$ph->scaleImage(320);
$r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 2, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: image upload with scale 2 (320x320) failed");
}
}
logger("photo upload: new photo upload ended", LOGGER_DEBUG);
} elseif ($mediatype == "profileimage") {
// upload profile image (scales 4, 5, 6)
logger("photo upload: starting new profile image upload", LOGGER_DEBUG);
if ($width > 175 || $height > 175) {
$ph->scaleImage(175);
$r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 4, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: profile image upload with scale 4 (175x175) failed");
}
}
if ($width > 80 || $height > 80) {
$ph->scaleImage(80);
$r = $ph->store(local_user(),$visitor, $hash, $filename, $album, 5, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: profile image upload with scale 5 (80x80) failed");
}
}
if ($width > 48 || $height > 48) {
$ph->scaleImage(48);
$r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 6, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
if (! $r) {
logger("photo upload: profile image upload with scale 6 (48x48) failed");
}
}
$ph->__destruct();
logger("photo upload: new profile image upload ended", LOGGER_DEBUG);
}
if ($r) {
// create entry in 'item'-table on new uploads to enable users to comment/like/dislike the photo
if ($photo_id == null && $mediatype == "photo") {
post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility);
}
// on success return image data in json/xml format (like /api/friendica/photo does when no scale is given)
return prepare_photo_data($type, false, $hash);
} else {
throw new InternalServerErrorException("image upload failed");
}
}
function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility = false) {
// get data about the api authenticated user
$uri = item_new_uri(get_app()->get_hostname(), intval(api_user()));
$owner_record = q("SELECT * FROM `contact` WHERE `uid`= %d AND `self` LIMIT 1", intval(api_user()));
$arr = array();
$arr['guid'] = get_guid(32);
$arr['uid'] = intval(api_user());
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
$arr['type'] = 'photo';
$arr['wall'] = 1;
$arr['resource-id'] = $hash;
$arr['contact-id'] = $owner_record[0]['id'];
$arr['owner-name'] = $owner_record[0]['name'];
$arr['owner-link'] = $owner_record[0]['url'];
$arr['owner-avatar'] = $owner_record[0]['thumb'];
$arr['author-name'] = $owner_record[0]['name'];
$arr['author-link'] = $owner_record[0]['url'];
$arr['author-avatar'] = $owner_record[0]['thumb'];
$arr['title'] = "";
$arr['allow_cid'] = $allow_cid;
$arr['allow_gid'] = $allow_gid;
$arr['deny_cid'] = $deny_cid;
$arr['deny_gid'] = $deny_gid;
$arr['last-child'] = 1;
$arr['visible'] = $visibility;
$arr['origin'] = 1;
$typetoext = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif'
);
// adds link to the thumbnail scale photo
$arr['body'] = '[url=' . App::get_baseurl() . '/photos/' . $owner_record[0]['name'] . '/image/' . $hash . ']'
. '[img]' . App::get_baseurl() . '/photo/' . $hash . '-' . "2" . '.'. $typetoext[$filetype] . '[/img]'
. '[/url]';
// do the magic for storing the item in the database and trigger the federation to other contacts
item_store($arr);
}
function prepare_photo_data($type, $scale, $photo_id) {
$scale_sql = ($scale === false ? "" : sprintf("and scale=%d", intval($scale)));
$data_sql = ($scale === false ? "" : "data, ");
// added allow_cid, allow_gid, deny_cid, deny_gid to output as string like stored in database
// clients needs to convert this in their way for further processing
$r = q("SELECT %s `resource-id`, `created`, `edited`, `title`, `desc`, `album`, `filename`,
`type`, `height`, `width`, `datasize`, `profile`, `allow_cid`, `deny_cid`, `allow_gid`, `deny_gid`,
MIN(`scale`) AS `minscale`, MAX(`scale`) AS `maxscale`
FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' %s GROUP BY `resource-id`",
$data_sql, $data_sql,
intval(local_user()), intval(local_user()),
dbesc($_REQUEST['photo_id']), dbesc($photo_id),
$scale_sql $scale_sql
); );
@ -3352,6 +3995,7 @@ $called_api = null;
'image/gif' => 'gif' 'image/gif' => 'gif'
); );
// prepare output data for photo
if (dbm::is_result($r)) { if (dbm::is_result($r)) {
$data = array('photo' => $r[0]); $data = array('photo' => $r[0]);
$data['photo']['id'] = $data['photo']['resource-id']; $data['photo']['id'] = $data['photo']['resource-id'];
@ -3369,8 +4013,11 @@ $called_api = null;
} }
} else { } else {
$data['photo']['link'] = array(); $data['photo']['link'] = array();
// when we have profile images we could have only scales from 4 to 6, but index of array always needs to start with 0
$i = 0;
for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) { for ($k = intval($data['photo']['minscale']); $k <= intval($data['photo']['maxscale']); $k++) {
$data['photo']['link'][$k] = App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']]; $data['photo']['link'][$i] = App::get_baseurl() . "/photo/" . $data['photo']['resource-id'] . "-" . $k . "." . $typetoext[$data['photo']['type']];
$i++;
} }
} }
unset($data['photo']['resource-id']); unset($data['photo']['resource-id']);
@ -3381,12 +4028,53 @@ $called_api = null;
throw new NotFoundException(); throw new NotFoundException();
} }
return api_format_data("photo_detail", $type, $data); // retrieve item element for getting activities (like, dislike etc.) related to photo
$item = q("SELECT * FROM `item` WHERE `uid` = %d AND `resource-id` = '%s' AND `type` = 'photo'",
intval(local_user()),
dbesc($photo_id)
);
$data['photo']['friendica_activities'] = api_format_items_activities($item[0], $type);
// retrieve comments on photo
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
`contact`.`id` AS `cid`
FROM `item`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
AND (NOT `contact`.`blocked` OR `contact`.`pending`)
WHERE `item`.`parent` = %d AND `item`.`visible`
AND NOT `item`.`moderated` AND NOT `item`.`deleted`
AND `item`.`uid` = %d AND (`item`.`verb`='%s' OR `type`='photo')",
intval($item[0]['parent']),
intval(api_user()),
dbesc(ACTIVITY_POST)
);
// prepare output of comments
$commentData = api_format_items($r, api_get_user(get_app()), false, $type);
$comments = array();
if ($type == "xml") {
$k = 0;
foreach ($commentData as $comment) {
$comments[$k++ . ":comment"] = $comment;
} }
} else {
foreach ($commentData as $comment) {
$comments[] = $comment;
}
}
$data['photo']['friendica_comments'] = $comments;
api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true); // include info if rights on photo and rights on item are mismatching
api_register_func('api/friendica/photo', 'api_fr_photo_detail', true); $rights_mismatch = $data['photo']['allow_cid'] != $item[0]['allow_cid'] ||
$data['photo']['deny_cid'] != $item[0]['deny_cid'] ||
$data['photo']['allow_gid'] != $item[0]['allow_gid'] ||
$data['photo']['deny_cid'] != $item[0]['deny_cid'];
$data['photo']['rights_mismatch'] = $rights_mismatch;
return $data;
}
/** /**
@ -4310,7 +4998,6 @@ friendships/exists
friendships/show friendships/show
account/update_location account/update_location
account/update_profile_background_image account/update_profile_background_image
account/update_profile_image
blocks/create blocks/create
blocks/destroy blocks/destroy
friendica/profile/update friendica/profile/update