Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add file upload resources #68

Merged
merged 2 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading