filestack/filestack-php

View on GitHub
filestack/mixins/CommonMixin.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
namespace Filestack\Mixins;

use Filestack\FilestackConfig;
use Filestack\Filelink;
use Filestack\FileSecurity;
use Filestack\FilestackException;

use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;


/**
 * Mixin for common functionalities used by most Filestack objects
 *
 */
trait CommonMixin
{
    protected $http_client;
    protected $http_promises;

    protected $user_agent_header;
    protected $source_header;

    public $cname;

    /**
     * If CNAME is set, return custom CNAME URL, else, noop.
     *
     * @param string $url
     *
     * @return string
     */
    protected function getCustomUrl($url) {
      if (!is_null($this->cname)) {
        $url = str_replace(
          FilestackConfig::CNAME_NEEDLE,
          $this->cname,
          FilestackConfig::CNAME_TEMPLATE[$url]
        );
      }
      return $url;
    }

    /**
     * Check if a string is a valid url.
     *
     * @param   string  $url    url string to check
     *
     * @return bool
     */
    public function isUrl($url)
    {
        $path = parse_url($url, PHP_URL_PATH);
        $encoded_path = array_map('urlencode', explode('/', $path));
        $url = str_replace($path, implode('/', $encoded_path), $url);

        return filter_var($url, FILTER_VALIDATE_URL);
    }

    /**
     * Get the miliseconds of exponential backoff retry strategy
     *
     * @param   int  $retry_num    the retry number
     *
     * @return int
     */
    public function get_retry_miliseconds($retry_num)
    {
        // (2^retries * 100) milliseconds
        return pow(2, $retry_num) * 100;
    }

    /**
     * Delete a file from cloud storage
     *
     * @param string            $handle         Filestack file handle to delete
     * @param string            $api_key        Filestack API Key
     * @param FilestackSecurity $security       Filestack security object is
     *                                          required for this call
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return bool (true = delete success, false = failed)
     */
    public function sendDelete($handle, $api_key, $security)
    {
        $url = sprintf('%s/file/%s?key=%s',
            $this->getCustomUrl(FilestackConfig::API_URL), $handle, $api_key);

        if ($security) {
            $url = $security->signUrl($url);
        }

        $response = $this->sendRequest('DELETE', $url);
        $status_code = $response->getStatusCode();

        // handle response
        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        return true;
    }

    /**
     * Download a file to specified destination given a url
     *
     * @param string            $url            Filestack file url
     * @param string            $destination    destination filepath to save to,
     *                                          can be a directory name
     * @param FilestackSecurity $security       Filestack security object if
     *                                          security settings is turned on
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return bool (true = download success, false = failed)
     */
    protected function sendDownload($url, $destination, $security = null)
    {
        if (is_dir($destination)) {
            // destination is a folder
            $json = $this->sendGetMetaData($url, ["filename"], $security);
            $remote_filename = $json['filename'];
            $destination .= $remote_filename;
        }

        // sign url if security is passed in
        if ($security) {
            $url = $security->signUrl($url);
        }

        # send request
        $options = ['sink' => $destination];

        // add download flag to url
        $url = $this->addDownloadFlagToUrl($url);

        $response = $this->sendRequest('GET', $url, $options);
        $status_code = $response->getStatusCode();

        // handle response
        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        return true;
    }

    /**
     * Append the download flag to a url that may or may not have existing query string flags
     */
    public function addDownloadFlagToUrl($url)
    {
        $main_url = (string) explode('?', $url)[0];
        $query_string = parse_url($url, PHP_URL_QUERY);
        return $main_url.'?dl=true'.($query_string ? '&'.$query_string : '');
    }

    /**
     * Get the content of a file.
     *
     * @param string            $url        Filestack file url
     * @param FilestackSecurity $security   Filestack security object if
     *                                      security settings is turned on
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return string (file content)
     */
    protected function sendGetContent($url, $security = null)
    {
        // sign url if security is passed in
        if ($security) {
            $url = $security->signUrl($url);
        }

        $response = $this->sendRequest('GET', $url);
        $status_code = $response->getStatusCode();

        // handle response
        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $content = $response->getBody()->getContents();

        return $content;
    }

    /**
     * Get the metadata of a remote file.  Will only retrieve specific fields
     * if optional fields are passed in
     *
     * @param                   $url        url of file
     * @param                   $fields     optional, specific fields to retrieve.
     *                                      values are: mimetype, filename, size,
     *                                      width, height,location, path,
     *                                      container, exif, uploaded (timestamp),
     *                                      writable, cloud, source_url
     * @param FilestackSecurity $security   Filestack security object if
     *                                      security settings is turned on
     *
     * @throws FilestackException   if API call fails, e.g 400 bad request
     *
     * @return array
     */
    protected function sendGetMetaData($url, $fields = [], $security = null)
    {
        $url .= "/metadata?";
        foreach ($fields as $field_name) {
            $url .= "&$field_name=true";
        }

        // sign url if security is passed in
        if ($security) {
            $url = $security->signUrl($url);
        }

        $response = $this->sendRequest('GET', $url);
        $status_code = $response->getStatusCode();

        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $json_response = json_decode($response->getBody(), true);

        return $json_response;
    }

    /**
     * Get the safe for work (sfw) flag of a filelink.
     *
     * @param string            $handle     Filestack file handle
     * @param FilestackSecurity $security   Filestack security object if
     *                                      security settings is turned on
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return json
     */
    protected function sendGetSafeForWork($handle, $security)
    {
        $url = sprintf('%s/sfw/security=policy:%s,signature:%s/%s',
            $this->getCustomUrl(FilestackConfig::CDN_URL),
            $security->policy,
            $security->signature,
            $handle);

        $response = $this->sendRequest('GET', $url);
        $status_code = $response->getStatusCode();

        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $json_response = json_decode($response->getBody(), true);

        return $json_response;
    }

    /**
     * Get the tags of a filelink.
     *
     * @param string            $handle     Filestack file handle
     * @param FilestackSecurity $security   Filestack security object if
     *                                      security settings is turned on
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return json
     */
    protected function sendGetTags($handle, $security)
    {
        $url = sprintf('%s/tags/security=policy:%s,signature:%s/%s',
            $this->getCustomUrl(FilestackConfig::CDN_URL),
            $security->policy,
            $security->signature,
            $handle);

        $response = $this->sendRequest('GET', $url);
        $status_code = $response->getStatusCode();

        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $json_response = json_decode($response->getBody(), true);

        return $json_response;
    }

    /**
     * Overwrite a file in cloud storage
     *
     * @param string            $resource   url or filepath
     * @param string            $handle     Filestack file handle to overwrite
     * @param string            $api_key    Filestack API Key
     * @param FilestackSecurity $security   Filestack security object is
     *                                      required for this call
     *
     * @throws FilestackException   if API call fails, e.g 404 file not found
     *
     * @return Filestack\Filelink
     */
    public function sendOverwrite($resource, $handle, $api_key, $security)
    {
        $url = sprintf('%s/file/%s?key=%s',
            $this->getCustomURL(FilestackConfig::API_URL), $handle, $api_key);

        if ($security) {
            $url = $security->signUrl($url);
        }

        if ($this->isUrl($resource)) {
            // external source (passing url instead of filepath)
            $data['form_params'] = ['url' => $resource];
        }
        else {
            // local file
            $data['body'] = fopen($resource, 'r');
        }

        $response = $this->sendRequest('POST', $url, $data);
        $filelink = $this->handleResponseCreateFilelink($response);

        return $filelink;
    }

    /**
     * Handle a Filestack response and create a filelink object
     *
     * @param   Http\Message\Response    $response    response object
     *
     * @throws FilestackException   if statuscode is not OK
     *
     * @return Filestack\Filelink
     */
    protected function handleResponseCreateFilelink($response)
    {
        $status_code = $response->getStatusCode();

        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $json_response = json_decode($response->getBody(), true);
        $url = $json_response['url'];
        $file_handle = substr($url, strrpos($url, '/') + 1);

        $filelink = new Filelink(
            $file_handle,
            $this->api_key,
            $this->security,
            $this->cname
        );
        $filelink->metadata['filename'] = $json_response['filename'];
        $filelink->metadata['size'] = $json_response['size'];
        $filelink->metadata['mimetype'] = 'unknown';

        if (isset($json_response['type'])) {
            $filelink->metadata['mimetype'] = $json_response['type'];
        } elseif (isset($json_response['mimetype'])) {
            $filelink->metadata['mimetype'] = $json_response['mimetype'];
        }

        return $filelink;
    }

    /**
     * Handles a response.  decode and return json if 200,
     * throws exception otherwise.
     *
     * @param Response  $response   the response object
     *
     * @throws FilestackException   if statuscode is not OK
     *
     * @return array (decoded json)
     */
    protected function handleResponseDecodeJson($response)
    {
        $status_code = $response->getStatusCode();
        if ($status_code !== 200) {
            throw new FilestackException($response->getBody(), $status_code);
        }

        $json_response = json_decode($response->getBody(), true);
        return $json_response;
    }

    /**
     * Send request
     *
     * @param string    $url        url to post to
     * @param array     $params     optional params to send
     * @param array     $headers    optional headers to send
     */
    protected function sendRequest($method, $url, $data = [], $headers = [])
    {
        $this->addRequestSourceHeader($headers);
        $data['http_errors'] = false;
        $data['headers'] = $headers;
        $response = $this->http_client->request($method, $url, $data);

        return $response;
    }

    /**
     * Get source header
     */
    protected function getSourceHeaders()
    {
        $headers = [];

        if (!$this->user_agent_header || !$this->source_header) {
            $version = trim(file_get_contents(__DIR__ . '/../../VERSION'));

            if (!$this->user_agent_header) {
                $this->user_agent_header = sprintf('filestack-php-%s',
                    $version);
            }

            if (!$this->source_header) {
                $this->source_header = sprintf('PHP-%s',
                    $version);
            }
        }

        $headers['user-agent'] = $this->user_agent_header;
        $headers['filestack-source'] = $this->source_header;

        //user_agent_header
        return $headers;
    }

    /**
     * Append source header to request headers array
     */
    protected function addRequestSourceHeader(&$headers)
    {
        $source_headers = $this->getSourceHeaders();
        $headers['User-Agent'] = $source_headers['user-agent'];
        $headers['Filestack-Source'] = $source_headers['filestack-source'];
    }

    /**
     * Append a data item
     */
    protected function appendData(&$data, $name, $value)
    {
        array_push($data, ['name' => $name, 'contents' => $value]);
    }

    /**
     *
     */
    protected function appendPromise(&$promises, $method, $url, $to_send)
    {
        $promises[] = $this->http_client->requestAsync($method,
                $url, $to_send);
    }

    protected function settlePromises($promises)
    {
        $api_results = \GuzzleHttp\Promise\settle($promises)->wait();
        return $api_results;
    }
}