Skip to content

Instantly share code, notes, and snippets.

@shawn-crigger
Created April 12, 2021 13:45
Show Gist options
  • Save shawn-crigger/ea72228b3fff0fba30525154fed387ff to your computer and use it in GitHub Desktop.
Save shawn-crigger/ea72228b3fff0fba30525154fed387ff to your computer and use it in GitHub Desktop.
Code Examples
<?php
// Load requires libraries which should have been loaded via Composer but the Company was against composer because they didn't understand Autoloading and the Power of Using Composer at all. Failure to Continue to Learn is never a good sign for a long life history.
require('/var/www/jb-virts/redbird.aero/v1/lib/Zebra_Image.php');
if ( ! class_exists( 'Aws\\Credentials\\Credentials' ) ) :
require('/var/www/jb-virts/redbird.aero/v1/lib/aws.phar');
endif;
use Aws\Sts\StsClient;
use Aws\S3\S3Client;
use Aws\Credentials\Credentials;
use Aws\S3\Exception\S3Exception;
use Aws\Exception\AwsException;
/**
* This class handles uploading files and images, along with resizing image thumbnails, to multiple AWS S3 buckets configured later on in the code base.
*/
class Image_API {
/**
* // all my private/protected properities are prefixed with a underscore.
* @access private
* @param object Holder for the AWS Class Object
*/
private $_aws;
/**
* @param object Holder for the Uploaded File
*/
public $file;
/**
* @param string Holder for the Filename
*/
public $filename;
/**
* @param string Holder for the File Extension
*/
public $extension;
/**
* @param array Holder for the $_FILES array
*/
public $_files;
/**
* @access private
* @param string Which bucket we are dealing with defined as a CONSTANT later but there is a public and private bucket.
*/
private $_bucket;
/**
* @access private
* @param mixed Handy temp variable for whatever is needed.
*/
private $_temp;
// ACL flags
const ACL_PRIVATE = 'private';
const ACL_PUBLIC_READ = 'public-read';
const ACL_PUBLIC_READ_WRITE = 'public-read-write';
const ACL_AUTHENTICATED_READ = 'authenticated-read';
const STORAGE_CLASS_STANDARD = 'STANDARD';
const STORAGE_CLASS_RRS = 'REDUCED_REDUNDANCY';
const STORAGE_CLASS_STANDARD_IA = 'STANDARD_IA';
const LOG_LEVEL = 'ERROR';
/**
* Cloudfront Key
* @param string
**/
const KEYPAIRID = 'APKAJEU3QMI3O2WDCCIQ';
/**
* Private key location
* @param string
**/
const PRIVATE_KEY_FILE = '/etc/pki/pk-APKAJEU3QMI3O2WDCCIQ.pem';
/**
* Private key location
* @param string
**/
const SECURE_CDN = 'https://cdn-secure.flightcircle.com';
/**
* The length of time a presigned URL is valid for.
* @var string
*/
const REQUEST_TIME_LIMIT = '+2 hours';
/**
* The bucket to store the files on, will be removed infuture version.
* @var string
*/
const AWS_PUBLIC_BUCKET = 'flightcircle-fbo-files';
/**
* The bucket to store the files on, will be removed infuture version.
* @var string
*/
const AWS_PRIVATE_BUCKET = 'flightcircle-cdn-secure';
/**
* Config for different types of files
* @param array
*/
private $_types = array(
'fbo-logo' => array(
//ACL_PUBLIC_READ
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PUBLIC_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'fbo-logo-uploads',
'cache' => 'max-age=2592000, public',
),
'aircraft-photo' => array(
//ACL_PUBLIC_READ
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PUBLIC_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'aircraft-photo-uploads',
'cache' => 'max-age=2592000, public',
),
'dashboard-files' => array(
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PRIVATE_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'flight-circle-dashboard-files',
'cache' => 'max-age=14400, public',
),
'customer-files' => array(
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PRIVATE_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'flight-circle-customer-files',
'cache' => 'max-age=14400, public',
),
'customer-images' => array(
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PRIVATE_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'flight-circle-customer-files',
'cache' => 'max-age=14400, public',
),
'resource-uploads' => array(
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PUBLIC_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'resource-uploads',
'cache' => 'max-age=14400, public',
),
'training-files' => array(
'acl' => self::ACL_PRIVATE,
'bucket' => self::AWS_PRIVATE_BUCKET,
'storage' => self::STORAGE_CLASS_STANDARD,
'base_dir' => 'flight-circle-training-files',
'cache' => 'max-age=14400, public',
),
);
// ------------------------------------------------------------------------
/**
* Array of valid image extensions
* @static
* @var array
*/
public static $valid_img_file_extensions = array( ".jpg", ".jpeg", ".gif", ".png" );
/**
* Array of valid file extensions
* @static
* @var array
*/
public static $valid_file_extensions = array( ".txt", ".jpg", ".jpeg", ".gif", ".png", ".doc", ".docx", ".xls", ".xlsx", ".pdf" );
/**
* Array of valid image mime types
* @static
* @var array
*/
public static $valid_img_mime_types = array( "image/gif", "image/x-png", "image/png", "image/jpeg", "image/pjpeg", "application/octet-stream" );
/**
* Array of valid file mime types
* @static
* @var array
*/
public static $valid_mime_types = array( "text/plain", "image/gif", "image/x-png", "image/png", "image/jpeg", "image/pjpeg", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/pdf" );
// ------------------------------------------------------------------------
/**
* The FBO ID that all files will belong.
* @access private
* @var array
*/
private $fbo_id = null;
// ------------------------------------------------------------------------
/**
* Class constructor, setups up all class properities.
*
* @uses \Aws\Sts\StsClient
* @uses \Aws\S3\S3Client
* @link <https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#getobject> See for more information.
* @param string $key The specific array key of the $_FILES uploaded.
* @param boolean [$private] Dealing with PRIVATE or PUBLIC bucket defaults to PUBLIC
*/
public function __construct( $key = '', $private = FALSE )
{
$this->setup_temp();
$this->set_bucket( 'public' );
if ( TRUE === $private )
{
$this->set_bucket( 'private' );
}
try {
$sdk = new Aws\Sdk([
'version' => 'latest',
'region' => 'us-east-1'
]);
$this->_aws = $sdk->createS3();
} catch (AwsException $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
die( $this->_handle_error( $line, $e->getMessage(), 'ERROR' ) );
}
// @todo: this was removed but I figured they would add it back in later.
if ( isset( $fbo_id ) && $fbo_id != null )
{
$this->fbo_id = $fbo_id . '/';
}
if ( $key == '' OR empty( $_FILES ) ) return;
$this->_files = $_FILES;
$this->file = isset( $this->_files["{$key}"] ) ? $this->_files["{$key}"] : FALSE;
$this->filename = isset( $this->_files["{$key}"]['name'] ) ? $this->_files["{$key}"]['name'] : FALSE;
$this->extension = strrchr( $this->_files["{$key}"]['name'], "." );
}
// ------------------------------------------------------------------------
/**
* Gets the file url.
*
* @param string $filename The filename
* @param boolean $is_thumb Indicates if fetching thumbnail
*
* @return string The file url.
*/
public function get_file_url( $filename = '', $is_thumb = false )
{
$cache_time = 3600;
if ( true === $is_thumb )
{
$filename = self::thumbnail_dir( $filename );
}
if ( true === self::is_valid( $filename, true ) )
{
$cache_time = 3600;
}
$bucket = $this->get_bucket();
$cmd = $this->_aws->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => $filename,
]);
$url = $this->_aws->createPresignedRequest( $cmd, self::REQUEST_TIME_LIMIT )->getUri()->__toString();
return $url;
}
// ------------------------------------------------------------------------
/**
* Sign a private asset url on cloudfront
*
* @param string $resource Full url of the resources
* @param string $file_name. Filename to access
* @return [boolean] $download Whether to Download defaults to false
* @throws Exception
*/
public static function get_cloudfront_url($resource, $file_name, $download = FALSE)
{
$filename = $resource;
$dateTime = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$dt = $dateTime; // clone
$hour = $dateTime->format("H");
// @todo there has got to be a better way to do this but this was the fastest way I could figure it out.
switch ( $hour ) {
case 0:
case 1:
$hour = 2;
break;
case 2:
case 3:
$hour = 4;
break;
case 4:
case 5:
$hour = 6;
break;
case 6:
case 7:
$hour = 8;
break;
case 8:
case 9:
$hour = 11;
break;
case 10:
case 11:
$hour = 13;
break;
case 12:
case 13:
$hour = 15;
break;
case 14:
case 15:
$hour = 17;
break;
case 16:
case 17:
$hour = 19;
break;
case 18:
case 19:
$hour = 21;
break;
case 20:
case 21:
$hour = 23;
break;
case 22:
case 23:
case 24:
$hour = 01;
$dt->modify('+1 day');
break;
}
$time = $dt->setTime( $hour, 59, 59 );
$expires = $dt->getTimestamp();
if ( TRUE == $download) :
$cd = 'attachment';
$file_type = 'application/octet-stream';
$resource .= '?response-content-disposition='.rawurlencode( $cd.';filename='.( utf8_encode( $file_name ) ) ).'&response-content-type='.rawurlencode( $file_type );
endif;
$json = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
// Read Cloudfront Private Key Pair, do not place it in the webroot!
$fp = fopen( self::PRIVATE_KEY_FILE, "r");
$priv_key = fread($fp,8192);
fclose($fp);
// Create the private key
$key = openssl_get_privatekey($priv_key);
if ( ! $key ) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
$errors = 'Loading private key failed';
return $this->_handle_error( $line, $errors, 'ERROR' );
}
// Sign the policy with the private key
if ( ! openssl_sign( $json, $signed_policy, $key, OPENSSL_ALGO_SHA1 ) )
{
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
$errors = 'Signing policy failed, '.openssl_error_string();
return $this->_handle_error( $line, $errors, 'ERROR' );
}
// Create url safe signed policy
$base64_signed_policy = base64_encode($signed_policy);
$signature = str_replace(array('+','=','/'), array('-','_','~'), $base64_signed_policy);
// Construct the URL
$url = $resource . (strpos($resource, '?') === false ? '?' : '&') . 'Expires='.$expires.'&Signature=' . $signature . '&Key-Pair-Id=' . self::KEYPAIRID;
header("Location: {$url}");
return $url;
}
// ------------------------------------------------------------------------
/**
* Gets the file, returns object with body of file and headers of file type/size
* @uses \Aws\S3\S3Client
* @param string $filename The filename
* @param boolean $is_thumb Indicates if thumb
* @return object
*/
public function get_file( $filename = '', $is_thumb = false )
{
if ( true === $is_thumb )
{
$filename = self::thumbnail_dir( $filename );
}
$bucket = $this->get_bucket();
$file = $this->_aws->getObject([
'Bucket' => $bucket,
'Key' => $filename,
]);
$body = $file->body;
$headers = $file->headers;
return $file;
}
// ------------------------------------------------------------------------
/**
* Checks if file exists on bucket, returns boolean
*
* @link <https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-clouddirectory-2017-01-11.html#getobjectinformation>
* @uses \Aws\S3\S3Client
* @param string $filename The filename
* @param boolean $is_thumb Indicates if thumb
*
* @return boolean
*/
public function file_exists( $filename='', $is_thumb = false )
{
if ( true === $is_thumb )
{
$filename = self::thumbnail_dir( $filename );
}
try {
$bucket = $this->get_bucket();
$result = $this->_aws->doesObjectExist( $bucket, $filename );
if ( $result ) return TRUE;
// $this->toSlack( $result, 'Image_API::DEBUG' );
// return $result;
} catch (S3Exception $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, $e->getMessage(), 'ERROR' );
} catch (Exception $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, $e->getMessage(), 'ERROR' );
}
return false;
// return $this->_aws->getObjectInfo( self::AWS_BUCKET, $filename, false );
}
// ------------------------------------------------------------------------
/**
* Handles logging errors
*
* @param string $line The line
* @param string $message The message
* @param string $code The code
*/
private function _handle_error( $line = '', $message = '', $code = '' )
{
if ( self::LOG_LEVEL != 'VERBOSE' ) return;
$error_level = self::_parse_error_level( self::LOG_LEVEL );
$error_code = self::_parse_error_level( $code );
if ( $error_code <= $error_level )
{
return false;
}
if ( is_array( $message ) || is_object( $message ) )
{
$message = print_r( $message, true );
}
$messages = array( $line . PHP_EOL . '[' . $code . ']' . $message );
error_log( $line . PHP_EOL . '[' . $code . ']' . $message );
$this->toSlack( $messages, 'ERROR');
die();
}
// ------------------------------------------------------------------------
/**
* Deletes file from bucket
*
* @link <https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#deleteobject>
* @uses \Aws\S3\S3Client
* @param string $filename The filename
* @param boolean $is_thumb Indicates if thumb
*
* @return boolean
*/
public function delete_file( $filename = '', $is_thumb = false )
{
if ( true === $is_thumb )
{
$filename = self::thumbnail_dir( $filename );
}
$bucket = $this->get_bucket();
$file = $this->_aws->deleteObject([
'Bucket' => $bucket,
'Key' => $filename,
]
);
return $file;
}
// ------------------------------------------------------------------------
/**
* Copies files from web server to S3 bucket.
* @uses \Aws\S3\S3Client
* @param string $filename The filename
* @param string $type The type
* @param [boolean] $is_img Indicates if image defaults to false
* @param [boolean] $thumb The thumb defaults to false
*
* @return boolean|string ( description_of_the_return_value )
*/
public function copy_file( $filename, $type = '', $is_img = FALSE, $thumb = FALSE )
{
$metaHeaders = array();
if ( true === $thumb )
{
$filename = self::thumbnail_dir( $filename );
}
$this->file = isset( $filename ) ? $filename : FALSE;
$this->filename = basename( strrchr( $filename, "/" ) );
$this->extension = strrchr( $filename, "." );
if ( ! $this->file ) return false;
// if ( ! self::is_valid( $this->file["type"], $is_img ) ) self::failure(__LINE__.': Forbidden');
if ( ! self::is_valid_ext( $this->extension, $is_img ) ) return FALSE;
$settings = (object) $this->_types["{$type}"];
$save_dir = $settings->base_dir;
$this->filename = str_replace( $this->extension, '', $this->filename );
$output = $save_dir . '/' . $this->filename . $this->extension;
if ( ! file_exists( $filename ) ) return 'file does not exist on the server ';
if ( self::file_exists( $output ) ) return 'File already exists on S3 Bucket ';
try {
$result = $this->_aws->putObject([
'ACL' => $settings->acl,
'Bucket' => $settings->bucket,
'Key' => $output,
'SourceFile' => $filename,
'CacheControl' => $settings->cache,
'StorageClass' => $settings->storage,
]
);
} catch (S3Exception $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, $e->getMessage(), 'ERROR' );
}
if ( ! is_string( $result ) ) {
return $result;
}
return 'copied';
}
// ------------------------------------------------------------------------
/**
* Handles upload to S3 bucket
* @uses \Aws\S3\S3Client
* @param string $filename The filename
* @param string $type The type
* @param [boolean] $is_img Indicates if image defaults to false
* @param [boolean] $create_thumbnail The create thumbnail defaults to false
*
* @return mixed
*/
public function handle_upload( $filename = '', $type = '', $is_img = FALSE, $create_thumbnail = FALSE )
{
$metaHeaders = array();
if ( ! $this->file ) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, 'Uploaded File does not exist.', 'ERROR' );
self::failure(__LINE__.': Forbidden');
}
if ( ! self::is_valid( $this->file["type"], $is_img ) ) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, 'File is invalid type.' .$this->file["type"], 'ERROR' );
self::failure(__LINE__.': Forbidden');
}
if ( ! self::is_valid_ext( $this->extension, $is_img ) ) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, 'File extension is invalid.' . $this->extension, 'ERROR' );
self::failure(__LINE__.': Forbidden');
}
if ( strlen( $filename ) > 0 )
{
$this->filename = $filename;
$this->filename = str_replace( $this->extension, '', $this->filename );
$filename = $this->filename;
}
$settings = (object) $this->_types["{$type}"];
$save_dir = $settings->base_dir;
$temp = $this->_temp . '/' . $this->filename. $this->extension;
$input = $this->file['tmp_name'];
$output = $save_dir . '/' . $this->fbo_id . $this->filename . $this->extension;
$moved = move_uploaded_file( $input, $temp );
if ( $is_img )
{
$this->resize_image( $temp, $temp, $create_thumbnail );
}
try {
$result = $this->_aws->putObject([
'ACL' => $settings->acl,
'Bucket' => $settings->bucket,
'Key' => $output,
'SourceFile' => $temp,
'CacheControl' => $settings->cache,
'StorageClass' => $settings->storage,
]
);
} catch (S3Exception $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, $e->getMessage(), 'ERROR' );
}
if ( TRUE === $create_thumbnail && TRUE == $result )
{
$this->filename = str_replace( $this->extension, '', $this->filename );
$thumbnail = $this->_temp . '/'. $this->filename . '_thumb' . $this->extension;
$output = $save_dir . '/thumbnails/' . $this->fbo_id . $this->filename . $this->extension;
try {
$result = $this->_aws->putObject([
'ACL' => $settings->acl,
'Bucket' => $settings->bucket,
'Key' => $output,
'SourceFile' => $thumbnail,
'CacheControl' => $settings->cache,
'StorageClass' => $settings->storage,
]
);
} catch (S3Exception $e) {
$line = __CLASS__ . '->' . __METHOD__ . ':' . __LINE__;
return $this->_handle_error( $line, $e->getMessage(), 'ERROR' );
}
}
$this->cleanup();
if ( $result ) {
return $result;
}
return $this->filename.$this->extension;
}
// ------------------------------------------------------------------------
/**
* Creates a thumbnail.
*
* @param string $input The input
* @param string $output The output
* @param. boolean. $create_thumb. Defaults to FALSE
*
* @todo change resize to use a AWS Lamba function and resize without creating file on the server
* @return boolean
*/
public function resize_image( $input, $output = '', $create_thumb = FALSE )
{
if ( '' == $output )
{
$output = $input;
}
$width = 1024;
$cropX = 200;
$cropY = 200;
$image = new Zebra_Image();
$image->auto_handle_exif_orientation = true;
$image->source_path = $input;
$image->target_path = $input;
$image->jpeg_quality = 100;
$image->preserve_aspect_ratio = true;
$image->enlarge_smaller_images = false;
$image->preserve_time = true;
$image->handle_exif_orientation_tag = true;
$image->preserve_aspect_ratio = true;
if ( ! $image->resize( $width, 0 ) )
{
$this->_handle_resize_errors( $input, $image->error );
return FALSE;
}
if ( FALSE === $create_thumb ) return TRUE;
$output = self::thumbnail_name( $input );
$image->target_path = $output;
if ( ! $image->resize( $cropX, 0 ) )
{
$this->_handle_resize_errors( $input, $image->error );
return FALSE;
}
return TRUE;
}
// ------------------------------------------------------------------------
/**
* Handles errors on image resize and sends error message to Slack
* @access private
* @param string $input filename
* @param int $errors The errors
* @return Image_API
*/
private function _handle_resize_errors( $input, $errors )
{
switch ( $errors )
{
case 1:
$error = 'Source file could not be found!';
break;
case 2:
$error = 'Source file is not readable!';
break;
case 3:
$error = 'Could not write target file!';
break;
case 4:
$error = 'Unsupported source file format!';
break;
case 5:
$error = 'Unsupported target file format!';
break;
case 6:
$error = 'GD library version does not support target file format!';
break;
case 7:
$error = 'GD library is not installed!';
break;
case 8:
$error = '"chmod" command is disabled via configuration!';
break;
case 9:
$error = '"exif_read_data" function is not available';
break;
}
$messages = [
'Image resize failed for ' . $input,
'ERROR:[' . $errors . ']' . $error,
];
$this->toSlack( $messages, 'ERROR');
//$this->_handle_error( $messages );
return $this;
}
// ------------------------------------------------------------------------
/**
* Creates temp directory to resize and fix orientaton of uploaded images before being moved to S3 bucket.
* @return boolean
*/
public function setup_temp()
{
$permissions = 0777;
$dir = sys_get_temp_dir();
$is_dir = is_dir( $dir );
$is_writable = is_writable( $dir );
$this->_temp = $dir;
if ( ! file_exists( self::PRIVATE_KEY_FILE ) )
{
$messages = [ 'Could not find CloudFront private key at ', '"'.self::PRIVATE_KEY_FILE.'"' ];
$this->toSlack( $messages, 'ERROR');
} elseif ( ! is_readable( self::PRIVATE_KEY_FILE ) )
{
$messages = [ 'CloudFront Private key is not readable by server ', '"'.self::PRIVATE_KEY_FILE.'"' ];
$this->toSlack( $messages, 'ERROR');
}
if ( $is_dir && $is_writable ) return true;
if ( ! $is_dir ) {
$status = mkdir( $dir );
if ( false === $status )
{
$messages = array(
'COULD NOT MAKE TEMP DIRECTORY',
"'{$this->_temp}'",
);
$this->toSlack( $messages, 'ERROR');
die( 'COULD NOT MAKE TEMP DIRECTORY' );
}
$chmod = chmod( $dir, $permissions );
if ( ! $chmod )
{
$messages = array(
'COULD NOT SET PERMISSIONS TO TEMP DIRECTORY TO ' . $permissions,
"'{$this->_temp}'",
);
$this->toSlack( $messages, 'ERROR');
die( 'COULD NOT SET PERMISSIONS TO TEMP DIRECTORY TO ' . $permissions );
}
}
if ( ! is_writable( $dir ) )
{
$messages = array(
'TEMP DIRECTORY IS NOT WRITABLE',
"'{$this->_temp}'",
);
$this->toSlack( $messages, 'ERROR');
die( 'TEMP DIRECTORY IS NOT WRITABLE' );
}
}
// ------------------------------------------------------------------------
// CLEANUP FUNCTIONS
// ------------------------------------------------------------------------i
/**
* Creates thumbnail name by adding _thumb to the end of the filename.
* @static
* @param string $filename The filename
* @return string
*/
public static function thumbnail_dir($filename='')
{
$file = basename( $filename );
$dir = str_replace( $filename, '', $file );
$output = $dir . '/thumbnails/' . $file;
return $output;
$parts = explode( '.', $filename );
$ext = array_pop( $parts );
$name = '';
foreach ($parts as $part) {
$name .= $part;
}
$name .= '_thumb.'.$ext;
return $name;
}
// ------------------------------------------------------------------------
/**
* Creates thumbnail name by adding _thumb to the end of the filename.
* @static
* @param string $filename The filename
* @return string
*/
public static function thumbnail_name($filename='')
{
$parts = explode( '.', $filename );
$ext = array_pop( $parts );
$name = '';
foreach ($parts as $part) {
$name .= $part;
}
$name .= '_thumb.'.$ext;
return $name;
}
// ------------------------------------------------------------------------
// CLEANUP FUNCTIONS
// ------------------------------------------------------------------------i
/**
* Cleans up temporary files after uploading to S3.
* // @todo Change Image Resizing to use a AWS Lamba Function never had time to do it, since this worked and quick and cheap wins the race vs research and challenging takes time.
*/
public function cleanup()
{
$temp = $this->_temp . '/' . $this->filename.$this->extension;
$temp2 = $this->_temp . $this->filename .'_thumb' . $this->extension;
if ( file_exists( $temp ) ) @unlink( $temp );
if ( file_exists( $temp2 ) ) @unlink( $temp2 );
}
// ------------------------------------------------------------------------
// UTILITIES FUNCTIONS
// ------------------------------------------------------------------------i
/**
* Sets the bucket property.
* @param string $bucket The bucket
* @return Image_API
*/
public function set_bucket( $bucket = 'public' )
{
switch ( $bucket ) {
case 'private':
$this->_bucket = self::AWS_PRIVATE_BUCKET;
break;
default:
$this->_bucket = self::AWS_PUBLIC_BUCKET;
break;
}
return $this;
}
// ------------------------------------------------------------------------i
/**
* Gets the bucket property.
* @return string
*/
public function get_bucket()
{
return $this->_bucket;
}
// ------------------------------------------------------------------------i
/**
* Determines if valid file by mime type.
* @static
* @param string $file The file
* @param boolean $image The image
* @return boolean True if valid, False otherwise.
*/
public static function is_valid( $file = '', $image = false )
{
if ( false === $image ) {
return (bool) ( in_array( strtolower( $file ), self::$valid_mime_types ) );
}
return (bool) ( in_array( strtolower( $file ), self::$valid_img_mime_types ) );
}
// ------------------------------------------------------------------------
/**
* Determines if valid extent.
*
* @param string $ext The extent
* @param boolean $image The image
*
* @return boolean True if valid extent, False otherwise.
*/
public static function is_valid_ext( $ext = '', $image = false )
{
if ( false === $image ) {
return (bool) ( in_array( strtolower( $ext ), self::$valid_file_extensions ) );
}
return (bool) ( in_array( strtolower( $ext ), self::$valid_img_file_extensions ) );
}
// ------------------------------------------------------------------------
/**
* Determines if it has error.
*
* @return boolean True if has error, False otherwise.
*/
public static function has_error( )
{
return (bool) ( isset( $this->_file['error'] ) );
}
// ------------------------------------------------------------------------
/**
* Sends 403 Header on Failure
* @static
* @param string $message The message
* @param integer|string $code The code
*/
public static function failure($message = 'Forbidden', $code = 403)
{
header('HTTP/1.0 '.$code.' '.$message, true, $code);
exit();
}
// ------------------------------------------------------------------------
/**
* Sends 200 Header on Success
* @static
* @param string $message The message
*/
public static function success($message = 'Success')
{
header('HTTP/1.0 200 '.$message, true, 200);
exit();
}
// ------------------------------------------------------------------------
// SLACK LOGGING MESSAGES
// ------------------------------------------------------------------------
/**
* Sends message to slack channel #image_api
*
* @param string $message The message
* @param string $title The title
*
* @return mixed
*/
public function toSlack( $messages='', $title = '' )
{
$slackMessage = '';
if ( is_array( $messages ) ) {
foreach ($messages as $message) {
$slackMessage .= "\n `$message`";
}
} else {
$slackMessage = $messages;
}
return $this->sendSlackNotification(
"https://hooks.slack.com/services/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",
"#image_api",
"Image_API". getHostByName(getHostName()),
":warning:",
$title,
[
[
'text' => $slackMessage,
],
]
);
}
// ------------------------------------------------------------------------
/**
* Parses the error from either a INT or a STRING back to the INT error level
*
* @param int|string $error The error level
* @return int The int error level, it was really just easier to remember ERROR then 5
*/
public static function _parse_error_level( $error='' )
{
switch ( $error ) {
case 5:
case 'ERROR':
return 5;
case 4:
case 'DEBUG':
return 4;
case 3:
case 'INFO':
return 3;
}
return 5;
}
// ------------------------------------------------------------------------
/**
* Outputs the given variables with formatting and location. Huge props
* out to Phil Sturgeon for this one (http://philsturgeon.co.uk/blog/2010/09/power-dump-php-applications).
* To use, pass in any number of variables as arguments.
*
* @return void
*/
public static function dump()
{
list($callee) = debug_backtrace();
$arguments = func_get_args();
$total_arguments = count($arguments);
echo '<fieldset style="background: #fefefe !important; border:2px red solid; padding:5px">';
echo '<legend style="background:lightgrey; padding:5px;">'.$callee['file'].' @ line: '.$callee['line'].'</legend><pre>';
$i = 0;
foreach ($arguments as $argument)
{
echo '<br/><strong>Debug #'.(++$i).' of '.$total_arguments.'</strong>: ';
// below is a cheap hack to prevent $argument not throwing a warning in PHP 7
// since it's a debug function I don't care about the cheap hack it works and not worth
// spending anymore time fixing it.
if ( (is_array($argument) || is_object($argument)) && @count($argument))
{
print_r($argument);
} else {
var_dump($argument);
}
}
echo '</pre>' . PHP_EOL;
echo '</fieldset>' . PHP_EOL;
}
/**
* Sends a slack notification to Slack via CURL the very first version of my SLACK error reporter.
* @access private
* @param string $webhook The webhook
* @param string $channel The channel
* @param string $senderName The sender name
* @param string $icon The icon
* @param string $message The message
* @param array $attachments The attachments
* @param array $actions The actions
* @return mixed
*/
private function sendSlackNotification(
$webhook = "https://hooks.slack.com/services/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",
$channel = "#image_api",
$senderName = "Image_API:image_api.php",
$icon = ":warning:",
$message = "Incoming Webhook",
$attachments = [],
$actions = []
) {
$data = [
'channel' => $channel,
'username' => $senderName,
'text' => $message,
'icon_emoji' => $icon,
'attachments' => $attachments,
'actions' => $actions,
];
$data_string = json_encode($data);
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($data_string),
]
);
$result = curl_exec($ch);
return $result;
}
}
@shawncrigger
Copy link

shawncrigger commented Apr 12, 2021

The purpose of this library was the existing application was storing user uploads on the EC-2 Server and not an S3 bucket.

Now there are 2 types of files uploaded ones that every customer of the FBO can see(things like pictures of Aircrafts, FBO Logos, etc) and ones that only the Admin or User who Uploaded the File can See(things like licenses, certifications, etc)

I abstracted all the AWS S3 handling into this library which removed hundreds of lines of duplicate code in the main API file(It was about 160k lines of PHP with duplicate code being a major problem)

All the configuration is changable via the class variables.

To show or download the files, you would access a API endpoint that would do permissions checks, etc then it would just use the following code to get the file

   $image_path = 'https://cdn-secure.flightcircle.com/flight-circle-customer-files/' . (isset($_GET['thumbnail']) ? 'thumbnails/' : '') . $result['files'];
   if (isset($_GET['download'])) {
      $name = generateFileName($result);
      $url = Image_API::get_cloudfront_url($image_path, $name, true);
      header("Location: {$url}");
   } else {
      $url = Image_API::get_cloudfront_url($image_path, 60);
      header("Location: {$url}");
   }

To upload the files, in the API's ADD/PUT methods instead of handling all the file processing inside the main API it was abstracted to this class which looked basically like this.

if (isset($file['files']) && ! empty($file['files'])) {
 $aws = new Image_API( 'file', TRUE );
 $file_exists = $aws->file_exists( 'flight-circle-customer-files/' . $file['files']);
 $thumb_exists = $aws->file_exists( 'flight-circle-customer-files/thumbnails/' . $file['files']);
 if (true == $file_exists) {
    $del = $aws->delete_file( 'flight-circle-customer-files/' . $file['files']);
 }

 if (true == $thumb_exists) {
    $del = $aws->delete_file('flight-circle-customer-files/thumbnails/' . $file['files']);
 }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment