From b96dbcd4cb93749d64b4c31f5fb1eec153114e69 Mon Sep 17 00:00:00 2001 From: fabrixxm Date: Tue, 20 Nov 2018 22:33:35 +0100 Subject: [PATCH] Move Photo module, update Photo model --- mod/photo.php | 208 ------------------------------------------- src/Model/Photo.php | 126 +++++++++++++++++++++++++- src/Module/Photo.php | 158 ++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+), 210 deletions(-) delete mode 100644 mod/photo.php create mode 100644 src/Module/Photo.php diff --git a/mod/photo.php b/mod/photo.php deleted file mode 100644 index 54418f730..000000000 --- a/mod/photo.php +++ /dev/null @@ -1,208 +0,0 @@ -argc) { - case 4: - $person = $a->argv[3]; - $customres = intval($a->argv[2]); - $type = $a->argv[1]; - break; - case 3: - $person = $a->argv[2]; - $type = $a->argv[1]; - break; - case 2: - $photo = $a->argv[1]; - $file = $photo; - break; - case 1: - default: - killme(); - // NOTREACHED - } - - if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { - header('HTTP/1.1 304 Not Modified'); - header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . " GMT"); - if (!empty($_SERVER['HTTP_IF_NONE_MATCH'])) { - header('Etag: ' . $_SERVER['HTTP_IF_NONE_MATCH']); - } - header("Expires: " . gmdate("D, d M Y H:i:s", time() + (31536000)) . " GMT"); - header("Cache-Control: max-age=31536000"); - if (function_exists('header_remove')) { - header_remove('Last-Modified'); - header_remove('Expires'); - header_remove('Cache-Control'); - } - exit; - } - - $default = 'images/person-300.jpg'; - $public = true; - - if (isset($type)) { - // Profile photos - switch ($type) { - case 'profile': - case 'custom': - $resolution = 4; - break; - case 'micro': - $resolution = 6; - $default = 'images/person-48.jpg'; - break; - case 'avatar': - default: - $resolution = 5; - $default = 'images/person-80.jpg'; - break; - } - - $uid = str_replace(['.jpg', '.png', '.gif'], ['', '', ''], $person); - - foreach (Image::supportedTypes() AS $m => $e) { - $uid = str_replace('.' . $e, '', $uid); - } - - $r = q("SELECT * FROM `photo` WHERE `scale` = %d AND `uid` = %d AND `profile` = 1 LIMIT 1", - intval($resolution), - intval($uid) - ); - if (DBA::isResult($r)) { - $data = $r[0]['data']; - $mimetype = $r[0]['type']; - } - if (empty($data)) { - $data = file_get_contents($default); - $mimetype = 'image/jpeg'; - } - } else { - // Other photos - $resolution = 0; - $photo = str_replace(['.jpg', '.png', '.gif'], ['', '', ''], $photo); - - foreach (Image::supportedTypes() AS $m => $e) { - $photo = str_replace('.' . $e, '', $photo); - } - - if (substr($photo, -2, 1) == '-') { - $resolution = intval(substr($photo, -1, 1)); - $photo = substr($photo, 0, -2); - } - - // check if the photo exists and get the owner of the photo - $r = q("SELECT `uid` FROM `photo` WHERE `resource-id` = '%s' LIMIT 1", - DBA::escape($photo), - intval($resolution) - ); - if (DBA::isResult($r)) { - $sql_extra = Security::getPermissionsSQLByUserId($r[0]['uid']); - - // Now we'll see if we can access the photo - $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` <= %d $sql_extra ORDER BY scale DESC LIMIT 1", - DBA::escape($photo), - intval($resolution) - ); - if (DBA::isResult($r)) { - $resolution = $r[0]['scale']; - $data = $r[0]['data']; - $mimetype = $r[0]['type']; - $public = $r[0]['allow_cid'] == '' && $r[0]['allow_gid'] == '' && $r[0]['deny_cid'] == '' && $r[0]['deny_gid'] == ''; - } else { - // The picure exists. We already checked with the first query. - // obviously, this is not an authorized viev! - $data = file_get_contents('images/nosign.jpg'); - $mimetype = 'image/jpeg'; - $prvcachecontrol = true; - $public = false; - } - } - } - - if (empty($data)) { - if (isset($resolution)) { - switch ($resolution) { - case 4: - $data = file_get_contents('images/person-300.jpg'); - $mimetype = 'image/jpeg'; - break; - case 5: - $data = file_get_contents('images/person-80.jpg'); - $mimetype = 'image/jpeg'; - break; - case 6: - $data = file_get_contents('images/person-48.jpg'); - $mimetype = 'image/jpeg'; - break; - default: - killme(); - // NOTREACHED - break; - } - } - } - - // Resize only if its not a GIF and it is supported by the library - if ($mimetype != "image/gif" && in_array($mimetype, Image::supportedTypes())) { - $Image = new Image($data, $mimetype); - if ($Image->isValid()) { - if (isset($customres) && $customres > 0 && $customres < 500) { - $Image->scaleToSquare($customres); - } - $data = $Image->asString(); - $mimetype = $Image->getType(); - } - } - - if (function_exists('header_remove')) { - header_remove('Pragma'); - header_remove('pragma'); - } - - header("Content-type: " . $mimetype); - - if ($prvcachecontrol) { - // it is a private photo that they have no permission to view. - // tell the browser not to cache it, in case they authenticate - // and subsequently have permission to see it - header("Cache-Control: no-store, no-cache, must-revalidate"); - } else { - header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . " GMT"); - header('Etag: "' . md5($data) . '"'); - header("Expires: " . gmdate("D, d M Y H:i:s", time() + (31536000)) . " GMT"); - header("Cache-Control: max-age=31536000"); - } - echo $data; - - // If the photo is public and there is an existing photo directory store the photo there - if ($public and $file != '') { - // If the photo path isn't there, try to create it - $basepath = $a->getBasePath(); - if (!is_dir($basepath . "/photo")) { - if (is_writable($basepath)) { - mkdir($basepath . "/photo"); - } - } - - if (is_dir($basepath . "/photo")) { - file_put_contents($basepath . "/photo/" . $file, $data); - } - } - - killme(); - // NOTREACHED -} diff --git a/src/Model/Photo.php b/src/Model/Photo.php index ca909a41c..298ca3920 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -6,11 +6,13 @@ */ namespace Friendica\Model; +use Friendica\BaseObject; use Friendica\Core\Cache; use Friendica\Core\Config; use Friendica\Core\L10n; use Friendica\Core\System; use Friendica\Database\DBA; +use Friendica\Database\DBStructure; use Friendica\Object\Image; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; @@ -19,9 +21,128 @@ use Friendica\Util\Security; /** * Class to handle photo dabatase table */ -class Photo +class Photo extends BaseObject { /** + * @brief Select rows from the photo table + * + * @param array $fields Array of selected fields, empty for all + * @param array $condition Array of fields for condition + * @param array $params Array of several parameters + * + * @return boolean|array + * + * @see \Friendica\Database\DBA::select + */ + public static function select(array $fields = [], array $condition = [], array $params = []) + { + if (empty($fields)) { + $selected = self::getFields(); + } + + $r = DBA::select("photo", $fields, $condition, $params); + return DBA::toArray($r); + } + + /** + * @brief Retrieve a single record from the photo table + * + * @param array $fields Array of selected fields, empty for all + * @param array $condition Array of fields for condition + * @param array $params Array of several parameters + * + * @return bool|array + * + * @see \Friendica\Database\DBA::select + */ + public static function selectFirst(array $fields = [], array $condition = [], array $params = []) + { + if (empty($fields)) { + $selected = self::getFields(); + } + + return DBA::selectFirst("photo", $fields, $condition, $params); + } + + /** + * @brief Get a single photo given resource id and scale + * + * This method checks for permissions. Returns associative array + * on success, a generic "red sign" data if user has no permission, + * false if photo does not exists + * + * @param string $resourceid Rescource ID for the photo + * @param integer $scale Scale of the photo. Defaults to 0 + * + * @return boolean|array + */ + public static function getPhoto($resourceid, $scale = 0) + { + $r = self::selectFirst(["uid"], ["resource-id" => $resourceid]); + if ($r===false) return false; + + $sql_acl = Security::getPermissionsSQLByUserId($r["uid"]); + + $condition = [ + "`resource-id` = ? AND `scale` <= ? " . $sql_acl, + $resourceid, $scale + ]; + + $params = [ "order" => ["scale" => true]]; + + $photo = self::selectFirst([], $condition, $params); + if ($photo === false) { + return false; ///TODO: Return info for red sign image + } + return $photo; + } + + /** + * @brief Check if photo with given resource id exists + * + * @param string $resourceid Resource ID of the photo + * + * @return boolean + */ + public static function exists($resourceid) + { + return DBA::count("photo", ["resource-id" => $resourceid]) > 0; + } + + /** + * @brief Get Image object for given row id. null if row id does not exist + * + * @param integer $id Row id + * + * @return \Friendica\Object\Image + */ + public static function getImageForPhotoId($id) + { + $i = self::selectFirst(["data", "type"],["id"=>$id]); + if ($i===false) { + return null; + } + return new Image($i["data"], $i["type"]); + } + + /** + * @brief Return a list of fields that are associated with the photo table + * + * @return array field list + */ + private static function getFields() + { + $allfields = DBStructure::definition(false); + $fields = array_keys($allfields["photo"]["fields"]); + array_splice($fields, array_search("data", $fields), 1); + return $fields; + } + + + + /** + * @brief store photo metadata in db and binary in default backend + * * @param Image $Image image * @param integer $uid uid * @param integer $cid cid @@ -35,7 +156,8 @@ class Photo * @param string $deny_cid optional, default = '' * @param string $deny_gid optional, default = '' * @param string $desc optional, default = '' - * @return object + * + * @return boolean True on success */ public static function store(Image $Image, $uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '') { diff --git a/src/Module/Photo.php b/src/Module/Photo.php new file mode 100644 index 000000000..0fddb8276 --- /dev/null +++ b/src/Module/Photo.php @@ -0,0 +1,158 @@ +argc <= 1 || $a->argc > 4) { + throw new BadRequestException(); + killme(); + } + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + header('HTTP/1.1 304 Not Modified'); + header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . " GMT"); + if (!empty($_SERVER['HTTP_IF_NONE_MATCH'])) { + header('Etag: ' . $_SERVER['HTTP_IF_NONE_MATCH']); + } + header("Expires: " . gmdate("D, d M Y H:i:s", time() + (31536000)) . " GMT"); + header("Cache-Control: max-age=31536000"); + if (function_exists('header_remove')) { + header_remove('Last-Modified'); + header_remove('Expires'); + header_remove('Cache-Control'); + } + exit; + } + + $customsize = 0; + switch($a->argc) { + case 4: + $customsize = intval($a->argv[2]); + $uid = self::stripExtension($a->argv[3]); + $photo = self::getAvatar($uid, $a->argv[1]); + break; + case 3: + $uid = self::stripExtension($a->argv[2]); + $photo = self::getAvatar($uid, $a->argv[1]); + break; + case 2: + $photoid = self::stripExtension($a->argv[1]); + $scale = 0; + if (substr($photoid, -2, 1) == '-') { + $scale = intval(substr($photoid, -1, 1)); + $photoid = substr($photoid, 0, -2); + } + $photo = MPhoto::getPhoto($photoid, $scale); + break; + } + + if ($photo===false) { + header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found" , true, 404); + killme(); + } + + $cacheable = ($photo["allow_cid"].$photo["allow_gid"].$photo["deny_cid"].$photo["deny_gid"] === "") || defaults($photo, "cacheable", false); + + $img = MPhoto::getImageForPhotoId($photo["id"]); + + if (is_null($img) || !$img->isValid()) { + Logger::log("Invalid photo with id {$photo['id']}."); + throw new InternalServerErrorException(); + } + + + // if customsize is set and image is not a gif, resize it + if ($img->getType() !== "image/gif" && $customsize > 0 && $customsize < 501) { + $img->scaleToSquare($customsize); + } + + + if (function_exists('header_remove')) { + header_remove('Pragma'); + header_remove('pragma'); + } + + header("Content-type: " . $img->getType()); + + if (!$cacheable) { + // it is a private photo that they have no permission to view. + // tell the browser not to cache it, in case they authenticate + // and subsequently have permission to see it + header("Cache-Control: no-store, no-cache, must-revalidate"); + } else { + header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()) . " GMT"); + header('Etag: "' . md5($img->asString()) . '"'); + header("Expires: " . gmdate("D, d M Y H:i:s", time() + (31536000)) . " GMT"); + header("Cache-Control: max-age=31536000"); + } + + + + echo $img->asString(); + + + killme(); + } + + private static function stripExtension($name) + { + $name = str_replace([".jpg", ".png", ".gif"], ["", "", ""], $name); + foreach (Image::supportedTypes() AS $m => $e) { + $name = str_replace('.' . $e, '', $name); + } + return $name; + } + + private static function getAvatar($uid, $type="avatar") + { + + switch($type) { + case "profile": + case "custom": + $scale = 4; + $default = "images/person-300.jpg"; + break; + case "micro": + $scale = 6; + $default = "images/person-48.jpg"; + break; + case "avatar": + default: + $scale = 5; + $default = "images/person-80.jpg"; + } + + $photo = MPhoto::selectFirst([], ["scale" => $scale, "uid" => $uid, "profile" => 1]); + if ($photo===false) { + // todo default image info + } + return $photo; + } + +} \ No newline at end of file