diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..fc6b1f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,70 @@ +name: Bug report +description: Create a report to help us improve +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: what-happened + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Tell us what you see! + validations: + required: true + - type: textarea + id: repro-steps + attributes: + label: To Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Fetch a '...' + 2. Update the '....' + 3. See error + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + - type: textarea + id: code-snippets + attributes: + label: Code snippets + description: If applicable, add code snippets to help explain your problem. + render: PHP + validations: + required: false + - type: input + id: os + attributes: + label: OS + placeholder: macOS + validations: + required: true + - type: input + id: language-version + attributes: + label: PHP version + placeholder: PHP 8.1 + validations: + required: true + - type: input + id: lib-version + attributes: + label: Library version + placeholder: v1.0.0 + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context about the problem here. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c500db9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: ConvertKit support + url: https://convertkit.com/support/ + about: | + Please only file issues here that you believe represent actual bugs or feature requests for the ConvertKit PHP SDK. + + If you're having general trouble with your ConvertKit integration, please reach out to support. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..1c5b0f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +name: Feature request +description: Suggest an idea for this PHP SDK +labels: ["feature-request"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + - type: textarea + id: problem + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context about the feature request here. \ No newline at end of file diff --git a/.github/docs/coding-standards.png b/.github/docs/coding-standards.png new file mode 100644 index 0000000..9ad714f Binary files /dev/null and b/.github/docs/coding-standards.png differ diff --git a/.github/docs/new-release.png b/.github/docs/new-release.png new file mode 100644 index 0000000..7b0f916 Binary files /dev/null and b/.github/docs/new-release.png differ diff --git a/.github/docs/phpstan.png b/.github/docs/phpstan.png new file mode 100644 index 0000000..3672607 Binary files /dev/null and b/.github/docs/phpstan.png differ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b9bee92 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing + +Contributions of any kind are welcome! If you've found a bug or have a feature request, please feel free to [open an issue](/issues). + +To make changes yourself, follow these steps: + +1. Follow the [Setup Guide](SETUP.md) to set up your environment and clone this repository +2. Follow the [Development Guide](DEVELOPMENT.md) to create a branch and make changes +3. Refer to the [Testing Guide](TESTING.md) to write applicable unit tests and submit your pull request. + +Thanks! \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..99f0603 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,92 @@ +# Deployment Guide + +This document describes the workflow for deploying a PHP SDK update on GitHub. + +## Merge Pull Requests + +Merge the approved Pull Request(s) to the `main` branch. + +An *approved* Pull Request is when a PR passes all tests **and** has been approved by **one or more** reviewers. + +## Update the PHP SDK's Version Number + +We follow [Semantic Versioning](https://semver.org/). + +- In `src/ConvertKit_API.php`, change the `const VERSION` to the new version number. + +## Run phpDocumentor + +We use [phpDocumentor](https://www.phpdoc.org/) to automatically generate the [PHP SDK documentation](./docs/classes/ConvertKit_API/ConvertKit_API.md). + +In a Terminal window, run the phpDocumentor command to generate documentation in markdown format: + +```bash +phpDocumentor --directory=src --target=docs --template="vendor/saggre/phpdocumentor-markdown/themes/markdown" +``` + +## Commit Changes + +Commit the updated files, which should comprise of: + +- `src/ConvertKit_API.php` +- `docs/classes/ConvertKit_API/ConvertKit_API.md` + +## Create a New Release + +[Create a New Release](https://github.com/ConvertKit/convertkitsdk-php/releases/new), completing the following: + +- Choose a tag: Click this button and enter the new version number (e.g. `1.0`) +- Release title: The version number (e.g. `1.0`) +- Describe this release: Add a changelog detailing the applicable changes this version introduces, with a link to each PR, using the below template + +``` +# Deprecations / Notices + +- PHP: Minimum supported version is now `7.4` +- `add_tag()` will trigger an `E_USER_NOTICE`, as the method name is misleading, and we prefer methods with named arguments; use `tag_subscribe()` (#44) +- `form_subscribe()` will trigger an `E_USER_NOTICE`, as we prefer methods with named arguments; use `add_subscriber_to_form()` (#54) +- `form_unsubscribe()` will trigger an `E_USER_NOTICE`, as the method name is misleading, and we prefer methods with named arguments; use `unsubscribe()` (#45) + +# Features / Additions + +- Added User-Agent on API requests (#34) +- Added `get()`, `post()`, `put()` and `delete()` methods (#36) +- Added `get_forms()` and `get_landing_pages()` methods (#41) +- Added `get_form_subscriptions()` method (#42) +- Added Tag methods `get_tags()`, `create_tag()`, `tag_subscriber()`, `remove_tag_from_subscriber()`, `remove_tag_from_subscriber_by_email()` (#44) +- Added Subscriber methods `update_subscriber()`, `unsubscribe()` (#45) +- Added `add_subscriber_to_sequence()` to support name, custom fields and tags (#43) +- Added Custom Field methods `get_custom_fields()`, `add_custom_field()`, `add_custom_fields()`, `update_custom_field()`, `delete_custom_field()` (#46) +- Added Purchase method `get_purchase()` (#47) +- Added Webhook methods `create_webhook()`, `destroy_webhook() (#48) +- Added Broadcast methods `create_broadcast()`, `get_broadcast()`, `get_broadcast_stats()`, `update_broadcast()`, `destroy_broadcast()` + +# Fixes / Improvements + +- Fixed: Guzzle version set to 6.5 or higher (#20, #26, #27) +- Fixed: `get_subscriber_id()` performance (#21, #22, #29, #39) +- Refactored: fetching legacy forms and landing pages (#32) +- Refactored: using `api_version` property, API calls and logging (#37, #38) +- Removed: Caching of resources and markup in class life cycle (#52) +- Removed: `InvalidArgumentException` where type hints for methods now exist (#43) + +# Testing +- Added PHPStan static analysis (#40) +- Added PSR-12 coding standards with some modifications (#33) +- Updated PHPUnit test coverage (#30, #35) +``` + +Generic changelog items such as `Fix: Various bugfixes` or `Several edge-case bug fixes` should be avoided. They don't tell users (or us, as developers) +what took place in this version. + +Each line in the changelog should start with `Added` or `Fix`. + +![New Release Screen](/.github/docs/new-release.png?raw=true) + +## Publish the Release + +When you're happy with the above, click `Publish Release`. + +This will then make the release available to developers, who can include it manually or using composer. + +The release will also be available to view on the [Releases](https://github.com/ConvertKit/convertkitsdk-php/releases) section of this GitHub repository. \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..4d2c3b2 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,56 @@ +# Development Guide + +This document describes the high level workflow used when working on the ConvertKit PHP SDK. + +You're free to use your preferred IDE and Git client. + +## Prerequisites + +If you haven't yet set up your local development environment with the ConvertKit PHP SDK repository installed, refer to the [Setup Guide](SETUP.md). + +## Create a Branch + +In your Git client / command line, create a new branch: +- If this is for a new feature that does not have a GitHub Issue number, enter a short descriptive name for the branch, relative to what you're working on +- If this is for a feature/bug that has a GitHub Issue number, enter issue-XXX, replacing XXX with the GitHub issue number + +Once done, make sure you've switched to your new branch, and begin making the necessary code additions/changes/deletions. + +## Coding Standards + +Code must follow [PSR-12 Coding standards](https://www.php-fig.org/psr/psr-12/), which is checked when running tests (more on this below). + +## Composer Packages + +We use Composer for package management. A package can be added to one of two sections of the `composer.json` file: `require` or `require-dev`. + +### "require" + +Packages listed in the "require" directive are packages that the PHP SDK needs in order to function for end users. + +These packages are included when the PHP SDK release is published. + +Typically, packages listed in this section would be libraries that the PHP SDK uses, such as: +- [Guzzle](https://docs.guzzlephp.org/en/stable/): PHP HTTP Client +- [Monolog](https://github.com/Seldaek/monolog): PSR-3 compatible logging client + +### "require-dev" + +Packages listed in the "require-dev" directive are packages that the PHP SDK **does not** need in order to function for end users. + +Typically, packages listed in this section would be internal development tools for testing, such as: +- Coding Standards +- PHPStan +- PHPUnit + +## Committing Work + +Remember to commit your changes to your branch relatively frequently, with a meaningful, short summary that explains what the change(s) do. +This helps anyone looking at the commit history in the future to find what they might be looking for. + +If it's a particularly large commit, be sure to include more information in the commit's description. + +## Next Steps + +Once you've finished your feature or issue, you must write/amend tests for it. Refer to the [Testing Guide](TESTING.md) for a detailed walkthrough +on how to write a test. \ No newline at end of file diff --git a/README.md b/README.md index 45b1313..0455258 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,90 @@ # ConvertKit SDK PHP -ConvertKit's official PHP SDK +The ConvertKit PHP SDK provides convinient access to the ConvertKit API from applications written in the PHP language. -### Installation +It includes a pre-defined set of methods for interacting with the API. -#### Standard Installation +## Requirements -1. Download or clone this repository -2. Run `composer install` -3. Add `./vendor/autoload.php` to your project +PHP 7.4 and later. -#### Installation with Package Manager +## Composer -If your project uses [Composer](https://getcomposer.org/), you can install the ConvertKitSDK-PHP package as a composer package. This allows you to have this project as a dependency without the ConvertKitSDK-PHP files being checked into your source code. +You can install this PHP SDK via [Composer](http://getcomposer.org/). Run the following command: -```shell -composer require convertkit/convertkitapi:0.1 +```bash +composer require convertkit/convertkitapi ``` -**If you previously use or rely on `dev-master`, please use the `0.1` release to maintain compatibility with no breaking changes.** - -This package is now versioned, and `1.0` will introduce breaking changes to bring this package up to date with coding standards, PHP versions and third party library versions. - -### Usage - -Get your ConvertKit API Key and API Secret [here](https://app.convertkit.com/account/edit) and set it somewhere in your application. +To use the PHP SDK, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading): ```php -$api = new \ConvertKit_API\ConvertKit_API($api_key, $api_secret); +require_once 'vendor/autoload.php'; ``` -### Examples +## Dependencies -**Subscribe to a form** +The PHP SDK require the following extensions in order to work properly: -Add a subscriber to a form. The `$subscribed` response will be an object. +- [`curl`](https://secure.php.net/manual/en/book.curl.php), although you can use your own non-cURL client if you prefer +- [`json`](https://secure.php.net/manual/en/book.json.php) +- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php) (Multibyte String) -```php -$tag_id = '99999'; // This tag must be valid for your ConvertKit account. - -$options = [ - 'email' => 'test@test.com', - 'name' => 'Full Name', - 'first_name' => 'First Name', - 'tags' => $tag_id, - 'fields' => [ - 'phone' => 134567891243, - 'shirt_size' => 'M', - 'website_url' => 'testurl.com' - ] - ]; - -$subscribed = $api->form_subscribe($this->test_form_id, $options); -``` +If you use Composer, these dependencies should be handled automatically. -**Get Subscriber ID** +## Getting Started -Get the ConvertKit Subscriber ID for a given email address. +Get your ConvertKit API Key and API Secret [here](https://app.convertkit.com/account/edit) and set it somewhere in your application. ```php -$subscriber_id = $api->get_subscriber_id( $email ); +// Require the autoloader (if you're using a PHP framework, this may already be done for you). +require_once 'vendor/autoload.php'; + +// Initialize the API class. +$api = new \ConvertKit_API\ConvertKit_API('', ''); ``` -**Get Subscriber** +## Handling Errors -Get subscriber data for a ConvertKit Subscriber. +The ConvertKit PHP SDK uses Guzzle for all HTTP API requests. Errors will be thrown as Guzzle's `ClientException` (for 4xx errors), +or `ServerException` (for 5xx errors). ```php -$subscriber = $api->get_subscriber( $subscriber_id ); +try { + $forms = $api->add_subscriber_to_form('invalid-form-id'); +} catch (GuzzleHttp\Exception\ClientException $e) { + // Handle 4xx client errors. + die($e->getMessage()); +} catch (GuzzleHttp\Exception\ServerException $e) { + // Handle 5xx server errors. + die($e->getMessage()); +} ``` -**Get Subscriber Tags** - -Get all tags applied to a Subscriber. +For a more detailed error message, it's possible to fetch the API's response when a `ClientException` is thrown: ```php -$subscriber_tags = $api->get_subscriber_tags( $subscriber_id ); +// Errors will be thrown as Guzzle's ClientException or ServerException. +try { + $forms = $api->form_subscribe('invalid-form-id'); +} catch (GuzzleHttp\Exception\ClientException $e) { + // Handle 4xx client errors. + // For ClientException, it's possible to inspect the API's JSON response + // to output an error or handle it accordingly. + $error = json_decode($e->getResponse()->getBody()->getContents()); + die($error->message); // e.g. "Entity not found". +} catch (GuzzleHttp\Exception\ServerException $e) { + // Handle 5xx server errors. + die($e->getMessage()); +} ``` -**Add Tag to a Subscriber** +## Documentation -Apply a tag to a Subscriber. +See the [PHP SDK docs](./docs/classes/ConvertKit_API/ConvertKit_API.md) -```php -$tag_id = '99999'; // This tag must be valid for your ConvertKit account. -$api->add_tag(tag_id, [ - 'email' => 'test@test.com' - ]); -``` +## Contributing +See our [contributor guide](CONTRIBUTING.md) for setting up your development environment, testing and submitting a PR. +For ConvertKit, refer to the [deployment guide](DEPLOYMENT.md) on how to publish a new release. diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..b28399a --- /dev/null +++ b/SETUP.md @@ -0,0 +1,143 @@ +# Setup Guide + +This document describes how to setup your development environment, so that it is ready to run, develop and test the ConvertKit PHP SDK. + +Suggestions are provided for the LAMP/LEMP stack and Git client are for those who prefer the UI over a command line and/or are less familiar with +PHP, MySQL and Git - but you're free to use your preferred software. + +## Setup + +### LAMP/LEMP stack + +Any Apache/nginx, PHP 7.x+ and MySQL 5.8+ stack running. For example, but not limited to: +- Local by Flywheel (recommended) +- Docker +- MAMP +- WAMP +- VVV + +### Composer + +If [Composer](https://getcomposer.org) is not installed on your local environment, enter the following commands at the command line to install it: + +```bash +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" +php composer-setup.php +php -r "unlink('composer-setup.php');" +sudo mv composer.phar /usr/local/bin/composer +``` + +Confirm that installation was successful by entering the `composer` command at the command line + +### Clone Repository + +Using your preferred Git client or command line to clone this repository. + +If you're new to this, use [GitHub Desktop](https://desktop.github.com/) or [Tower](https://www.git-tower.com/mac) + +### Configure Testing Environment + +Copy the `.env.example` file to `.env` in the root of this repository, adding your ConvertKit API keys. + +``` +CONVERTKIT_API_KEY_NO_DATA= +CONVERTKIT_API_SECRET_NO_DATA= +CONVERTKIT_API_KEY= +CONVERTKIT_API_SECRET= +CONVERTKIT_API_BROADCAST_ID="8697158" +CONVERTKIT_API_FORM_ID="2765139" +CONVERTKIT_API_LEGACY_FORM_URL="https://app.convertkit.com/landing_pages/470099" +CONVERTKIT_API_LANDING_PAGE_URL="https://cheerful-architect-3237.ck.page/cc5eb21744" +CONVERTKIT_API_LEGACY_LANDING_PAGE_URL="https://app.convertkit.com/landing_pages/470103" +CONVERTKIT_API_SEQUENCE_ID="1030824" +CONVERTKIT_API_TAG_NAME="wordpress" +CONVERTKIT_API_TAG_ID="2744672" +CONVERTKIT_API_SUBSCRIBER_EMAIL="optin@n7studios.com" +CONVERTKIT_API_SUBSCRIBER_ID="1579118532" +``` + +#### PHPStan + +Copy the `phpstan.neon.example` file to `phpstan.neon` in the root of this repository: +```yaml +# Parameters +parameters: + # Paths to scan + paths: + - src/ + + # Should not need to edit anything below here + # Rule Level: https://phpstan.org/user-guide/rule-levels + level: 8 + + # Ignore the following errors, as PHPStan either does not have registered symbols for them yet, + # or the symbols are inaccurate. + ignoreErrors: + - '#\$headers of class GuzzleHttp\\Psr7\\Request constructor expects#' +``` + +### Install Packages + +In the root directory, at the command line, run `composer install`. + +This will install two types of packages: +- Packages used by the SDK (e.g. Guzzle and Monolog) +- Packages used in the process of development (i.e. testing, coding standards): +-- PHPStan +-- PHPUnit +-- PHP_CodeSniffer + +### Install phpDocumentor + +Download the latest phpDocumentor release. + +On Mac / Linux, mark it as executable and move it to your bin folder, so it can be run globally: +```bash +chmod +x phpDocumentor.phar +mv phpDocumentor.phar /usr/local/bin/phpDocumentor +``` + +### Running PHPUnit Tests + +In a Terminal window, run the tests to make sure there are no errors and that you have +correctly setup your environment: + +```bash +vendor/bin/phpunit +``` + +Don't worry if you don't understand these commands; if your output looks similar to the above screenshot, and no test is prefixed with `E`, +your environment is setup successfully. + +### Running CodeSniffer + +In the root directory, run the following commands to run PHP_CodeSniffer, which will check the code meets PHP Coding Standards: + +```bash +vendor/bin/phpcs -s -v +vendor/bin/phpcs -s -v --standard=phpcs.tests.xml +``` + +![Coding Standards Test Results](/.github/docs/coding-standards.png?raw=true) + +Again, don't worry if you don't understand these commands; if your output looks similar to the above screenshot, with no errors, your environment +is setup successfully. + +### Running PHPStan + +In the Plugin's directory, run the following command to run PHPStan, which will perform static analysis on the code, checking it meets required +standards, that PHP DocBlocks are valid etc: + +```bash +vendor/bin/phpstan --memory-limit=1G +``` + +![PHPStan Test Results](/.github/docs/phpstan.png?raw=true) + +Again, don't worry if you don't understand these commands; if your output looks similar to the above screenshot, with no errors, your environment +is setup successfully. + +### Next Steps + +With your development environment setup, you'll probably want to start development, which is covered in the [Development Guide](DEVELOPMENT.md) \ No newline at end of file diff --git a/TESTING.md b/TESTING.md index 95fc856..59cf0fe 100644 --- a/TESTING.md +++ b/TESTING.md @@ -27,7 +27,6 @@ Tests are written in PHP using [PHPUnit](https://phpunit.de/), and the existing [PHPStan](https://phpstan.org) performs static analysis on the Plugin's PHP code. This ensures: - DocBlocks declarations are valid and uniform -- DocBlocks declarations for WordPress `do_action()` and `apply_filters()` calls are valid - Typehinting variables and return types declared in DocBlocks are correctly cast - Any unused functions are detected - Unnecessary checks / code is highlighted for possible removal