Skip to content

Commit

Permalink
Merge pull request #68 from amcintosh/issue-67-file-uploads
Browse files Browse the repository at this point in the history
✨ Add file upload resources
  • Loading branch information
amcintosh authored Apr 21, 2024
2 parents 2ae347b + ed9976c commit fbca2bb
Show file tree
Hide file tree
Showing 14 changed files with 551 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Support for PHP 8.3
- Handle file uploads and invoice, expense attachments
- Handle new API version webhook event errors

## 0.7.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Packagist Version](https://badgen.net/packagist/v/amcintosh/freshbooks)](https://packagist.org/packages/amcintosh/freshbooks)
![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/amcintosh/freshbooks)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/amcintosh/freshbooks-php-sdk/Run%20Tests)](https://github.com/amcintosh/freshbooks-php-sdk/actions?query=workflow%3A%22Run+Tests%22)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/amcintosh/freshbooks-php-sdk/run-tests.yml?branch=main)](https://github.com/amcintosh/freshbooks-php-sdk/actions?query=workflow%3A%22Run+Tests%22)

A FreshBooks PHP SDK to allow you to more easily utilize the [FreshBooks API](https://www.freshbooks.com/api).
This library is not directly maintained by FreshBooks and [community contributions](CONTRIBUTING.md) are welcome.
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"php": ">=8.0 <8.4",
"php-http/client-common": "^2.5",
"php-http/discovery": "^1.14",
"php-http/multipart-stream-builder": "^1.3",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"spatie/data-transfer-object": "^3.8",
Expand Down
4 changes: 2 additions & 2 deletions docs/source/api-calls/errors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Example:
echo $e->getCode(); // 404
echo $e->getErrorCode(); // 1012
echo $e->getRawResponse(); // '{"response": {"errors": [{"errno": 1012,
// "field": "userid", "message": "Client not found.",
// "object": "client", "value": "134"}]}}'
// "field": "userid", "message": "Client not found.",
// "object": "client", "value": "134"}]}}'
}
Not all resources have full CRUD methods available. For example expense categories have ``list`` and ``get``
Expand Down
73 changes: 73 additions & 0 deletions docs/source/file-uploads.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
File Uploads
============

Some FreshBooks resource can include images and attachments. For example, invoices can have a company
logo or banner image as part of the invoice presentation object as well as images or pdfs attachments.
Expenses can also include copies or photos of receipts as attachments.

All images and attachments first need to be uploaded to FreshBooks via the ``images`` or ``attachments``
endpoints.

These will then return a path to your file with a JWT. This path will can then be passed as part of the
data in a subsequent call.

See FreshBooks' `invoice attachment <https://www.freshbooks.com/api/invoice_presentation_attachments>`_
and `expense attachment <https://www.freshbooks.com/api/https://www.freshbooks.com/api/expense-attachments>`_
documentation for more information.

Invoice Images and Attachments
------------------------------

See `FreshBooks' API Documentation <https://www.freshbooks.com/api/invoice_presentation_attachments>`_.

The ``upload()`` function takes a `PHP resource <https://www.php.net/manual/en/language.types.resource.php>`_.
Logo's and banners are added to the invoice presentation object. To include an uploaded attachment on
an invoice, the invoice request must include an attachments object.

.. code-block:: php
$logo = $freshBooksClient->images()->upload($accountId, fopen('./sample_logo.png', 'r'));
$attachment = $freshBooksClient->attachments()->upload($accountId, fopen('./sample_attachment.pdf', 'r'));
$presentation = [
'image_logo_src' => "/uploads/images/{$logo->jwt}",
'theme_primary_color' => '#1fab13',
'theme_layout' => 'simple'
];
$invoiceData = [
'customerid' => $clientId,
'attachments' => [
[
'jwt' => $attachment->jwt,
'media_type' => $attachment->mediaType
]
],
'presentation' => presentation
];
$invoice = $freshBooksClient->invoices()->create($accountId, $invoiceData);
Expense Receipts
----------------

See `FreshBooks' API Documentation <https://www.freshbooks.com/api/expense-attachments>`_.

Expenses have have images or PDFs of the associated receipt attached. The expense request must include
an attachments object.

.. code-block:: php
$attachment = $freshBooksClient->attachments()->upload($accountId, fopen('./sample_receipt.pdf', 'r'));
$expense->amount = new Money("6.49", "CAD");
$expense->date = new DateTime();
$expense->staffId = 1;
$expense->categoryId = 3436009;
$expenseAttachment = new ExpenseAttachment();
$expenseAttachment->jwt = $attachment->jwt;
$expenseAttachment->mediaType = $attachment->mediaType;
$expense->attachment = $expenseAttachment;
$includes = (new IncludesBuilder())->include('attachment');
$expense = $freshBooksClient->expenses()->create($accountId, model: $expense, includes: $includes);
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
configuration
authorization
api-calls/index
file-uploads
webhooks
examples

Expand Down
21 changes: 21 additions & 0 deletions src/FreshBooksClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
use amcintosh\FreshBooks\Resource\EventsResource;
use amcintosh\FreshBooks\Resource\PaymentResource;
use amcintosh\FreshBooks\Resource\ProjectResource;
use amcintosh\FreshBooks\Resource\UploadResource;

/**
* SDK Client.
Expand Down Expand Up @@ -349,4 +350,24 @@ public function invoicePaymentOptions(): PaymentResource
staticPathParams: 'entity_type=invoice',
);
}

/**
* FreshBooks attachment upload resource with call to upload, get
*
* @return UploadResource
*/
public function attachments(): UploadResource
{
return new UploadResource($this->httpClient, 'attachments', 'attachment');
}

/**
* FreshBooks image upload resource with call to upload, get
*
* @return UploadResource
*/
public function images(): UploadResource
{
return new UploadResource($this->httpClient, 'images', 'image');
}
}
7 changes: 1 addition & 6 deletions src/Model/ExpenseAttachment.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,11 @@

namespace amcintosh\FreshBooks\Model;

use DateTime;
use DateTimeImmutable;
use Spatie\DataTransferObject\Attributes\CastWith;
use Spatie\DataTransferObject\Attributes\MapFrom;
use Spatie\DataTransferObject\Attributes\MapTo;
use Spatie\DataTransferObject\Caster;
use Spatie\DataTransferObject\DataTransferObject;
use amcintosh\FreshBooks\Model\DataModel;
use amcintosh\FreshBooks\Model\Caster\AccountingDateTimeImmutableCaster;
use amcintosh\FreshBooks\Model\Caster\MoneyCaster;

/**
* Attached receipt image details for an expense.
Expand All @@ -22,7 +17,7 @@
* present with the use of a corresponding "includes" filter.
*
* @package amcintosh\FreshBooks\Model
* @link https://www.freshbooks.com/api/expenses
* @link https://www.freshbooks.com/api/expense-attachments
*/
class ExpenseAttachment extends DataTransferObject implements DataModel
{
Expand Down
49 changes: 49 additions & 0 deletions src/Model/FileUpload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace amcintosh\FreshBooks\Model;

use Psr\Http\Message\StreamInterface;

/**
* A file that has been uploaded to FreshBooks.
*
* @package amcintosh\FreshBooks\Model
*/
class FileUpload
{
/**
* @var string The JWT used to fetch the file from FreshBooks.
*/
public ?string $jwt;

/**
* @var string The name of the file uploaded to FreshBooks.
*
* This is returned from the API in the `X-filename` header.
*/
public ?string $fileName;

/**
* @var string The media type (eg. `image/png`) of the file uploaded to FreshBooks.
*/
public ?string $mediaType;

/**
* @var string The PSR StreamInterface steam of data from the request body.
*/
public ?StreamInterface $responseBody;

/**
* @var string A fully qualified path the the file from FreshBooks.
*/
public ?string $link;

public function __construct(?string $fileName, ?string $mediaType, ?StreamInterface $responseBody)
{
$this->fileName = $fileName;
$this->mediaType = $mediaType;
$this->responseBody = $responseBody;
}
}
74 changes: 74 additions & 0 deletions src/Model/InvoiceAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace amcintosh\FreshBooks\Model;

use Spatie\DataTransferObject\Attributes\MapFrom;
use Spatie\DataTransferObject\Attributes\MapTo;
use Spatie\DataTransferObject\Caster;
use Spatie\DataTransferObject\DataTransferObject;
use amcintosh\FreshBooks\Model\DataModel;

/**
* Attached files and images to include with an invoice.
*
* _Note:_ This data is not in the default response and will only be
* present with the use of a corresponding "includes" filter.
*
* @package amcintosh\FreshBooks\Model
* @link https://www.freshbooks.com/api/invoice_presentation_attachments
*/
class InvoiceAttachment extends DataTransferObject implements DataModel
{
public const RESPONSE_FIELD = 'expense';

/**
* @var int The unique identifier of this expense attachment within this business.
*/
public ?int $id;

/**
* @var int Duplicate of id
*/
#[MapFrom('attachmentid')]
public ?int $attachmentId;

/**
* @var int Id of the expense this attachment is associated with, if applicable.
*/
#[MapFrom('expenseid')]
#[MapTo('expenseid')]
public ?int $expenseId;

/**
* @var string JWT link to the attachment.
*/
public ?string $jwt;

/**
* @var string Type of the attachment.
*/
#[MapFrom('media_type')]
#[MapTo('media_type')]
public ?string $mediaType;

/**
* Get the data as an array to POST or PUT to FreshBooks, removing any read-only fields.
*
* @return array
*/
public function getContent(): array
{
$data = $this
->except('id')
->except('attachmentId')
->toArray();
foreach ($data as $key => $value) {
if (is_null($value)) {
unset($data[$key]);
}
}
return $data;
}
}
Loading

0 comments on commit fbca2bb

Please sign in to comment.