9 Commits

Author SHA1 Message Date
dc437d7044 Fixing a project Test
The test was to make sure it would return a error on No token, but it
was providing a token. More than that, it were a simple and direct check
if it were throwing an error.

Now it is checking for the specific Missing Access Token error message.
2019-06-27 20:21:43 -03:00
51980ebf14 Including git hook to run tests 2019-06-27 20:05:02 -03:00
283e85b834 Refactoring Oauth and Memsource classes 2019-06-27 20:03:41 -03:00
aaabb91039 Including Tests and verifications for errors 2019-06-27 20:03:10 -03:00
e4f2bd04be Fixing README command with wrong function name 2019-06-25 20:35:31 -03:00
97e0a56b65 Updating oauth.php file to match the new base api 2019-06-25 18:08:21 -03:00
13fadfe9ae Refactoring function names as no one is using it yet 2019-06-25 10:49:57 -03:00
6f19522c52 Including more information on README.md 2019-06-25 01:29:13 -03:00
3c6d2f0442 Improving README.md file 2019-06-24 22:06:43 -03:00
14 changed files with 387 additions and 74 deletions

117
README.md
View File

@@ -4,53 +4,128 @@ I am creating this Memsource API as a way to learn how to deal with one and to u
There are other Memsource API repositories on GibHub that appears to be fully functional if you need it.
## Getting an Access Token
## Installing
To be able to use the Memsource API, you need an **access token**, but to get it, you need to:
Install it with [Composer](https://getcomposer.org/):
### Register as a developer on Memsource website
1. Create a `composer.json` file with the following content:
So you will receive your:
- *client id*
- *client secret*
```json
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/brunofontes/Memsource-API"
}
],
"require": {
"brunofontes/memsource-api": "*"
}
}
```
### Get an Authorization Code
2. Run `php composer.phar install`
3. Add the following line on your .php file:
```php
require_once __DIR__ . '/vendor/autoload.php';
```
## Using
This repository returns a JSON string for almost any command.
If you are not sure how to use it, just convert it to an object or an array as follows:
```php
$myObject = json_decode($response);
$myArray = json_decode($response, true);
```
### Create an instance
```php
$memsource = new \BrunoFontes\Memsource();
$url = $memsource->oauth()->getAuthorizationCodeUrl($cliend_id, $callback_uri);
```
Redirect your browser to this returned `$url` so the user can login via *oauth*.
- If you have already an access token, just include it:
The `$callback_uri` will be called by **Memsource** with a `$_GET['code']` that contains your Authorization Code, which you can use to...
```php
$memsource = new \BrunoFontes\Memsource($token);
```
### Get an Access Token
### Getting an Access Token
To be able to use the Memsource API, you need an **access token**. In order to get it, just follow the instructions below.
#### Register as a developer on Memsource website
So you will receive your:
- *client id*
- *client secret*
#### Get an Authorization Code
```php
$memsourceUrl = $memsource->oauth()->getAuthorizationCodeUrl($cliend_id, $callback_uri);
```
Redirect your browser to this returned `$url` so the user can login via *oauth*.
The `$callback_uri` will be called by *Memsource* with a `$_GET['code']` that contains your Authorization Code, which you can use to...
#### Get an Access Token
```php
$authCode = $_GET['code'];
$memsource = new \BrunoFontes\Memsource();
$token = $memsource->oauth()->getAccessToken($authCode, $client_id, $client_secret, $callback_uri);
```
Safely store this `$token` with the related user data and use it on any
Safely store this `$token` with the related user data and use it to instantiate the class whenever it were necessary.
## Project
### Project
### Project list
#### Project list
To list all projects...
To list all projects:
```php
$memsource = new \BrunoFontes\Memsource();
$projectList = $memsource->project()->listProjects;
$projectList = $memsource->project()->list;
```
To use filters, add the API filter as parâmeter:
To use filters, add the API filter as parameter:
```php
$projectList = $memsource->project()->listProjects(['name' => 'Project X']);
$projectList = $memsource->project()->list(['name' => 'Project X']);
```
## Bilingual Files
#### Get Project
```php
$projectList = $memsource->project()->get($projectUid);
```
### Jobs
#### List Jobs
Only projectUid is essencial:
```php
$jobs = $memsource->jobs()->list($projectUid, ['count' => true, 'filename' => 'my_file.html']);
```
### Bilingual Files
#### Download Bilingual File
```php
$memsource->BilingualFile()->download($projectUid, ['JobUid1', 'jobUid2'], 'download.mxliff');
```
#### Upload Bilingual File
```php
$parameters = ['format' => 'MXLF', 'saveToTransMemory' => 'None', 'setCompleted' => 'false'];
$result = $api->bilingualFile()->upload('upload.mxliff', $parameters);
```

1
pre-push.sh Normal file
View File

@@ -0,0 +1 @@
phpunit --bootstrap vendor/autoload.php tests --testdox --color

View File

@@ -15,4 +15,15 @@ class BaseApi
{
$this->fetchApi = $fetchApi;
}
protected function hasError(string $jsonResponse): bool
{
return isset(json_decode($jsonResponse, true)['errorCode']);
}
protected function getError(string $jsonResponse): string
{
return json_decode($jsonResponse, true)['errorDescription'];
}
}

View File

@@ -1,52 +1,58 @@
<?php
/**
* A very compact and simple Memsource API library
*
*
* @author Bruno Fontes <developer@brunofontes.net>
* @link https://github.com/brunofontes
*/
namespace BrunoFontes\Memsource;
class BilingualFile extends \BrunoFontes\Memsource\BaseApi
{
private $_url = '/api2/v1/bilingualFiles';
/**
* Download one or more bilingual files
*
* As Memsource limits downloading files into 100 jobs per time, this script
* will prevent that by making all the necessary fetchs and saving each on
*
* As Memsource limits downloading files into 100 jobs per time, this script
* will prevent that by making all the necessary fetchs and saving each on
* a different file.
*
*
* An array with all the files used will be returned.
*
* @param string $projectUid The project uid which contain the jobs
* @param array $jobUids A simple array of Job uids: ['job_uid1', 'job_uid2']
* @param string $filename File that will be created on server to store the
* @param string $filename File that will be created on server to store the
* downloaded jobs
*
*
* @return array A list of the downloaded files
*/
public function downloadBilingualFile(string $projectUid, array $jobUids, string $filename): array
public function download(string $projectUid, array $jobUids, string $filename): array
{
$url = "/api2/v1/projects/{$projectUid}/jobs/bilingualFile";
$filenames = [];
$groupedJobUids = array_chunk($jobUids, 100);
for ($i = 0; $i < count($groupedJobUids); $i++) {
$apiReadyArray = $this->_convertUidArrayToApiRequest($groupedJobUids[$i]);
$filenames[$i] = count($groupedJobUids) > 1?"{$i}_{$filename}":$filename;
$filenames[$i] = count($groupedJobUids) > 1 ? "{$i}_{$filename}" : $filename;
$filecontent = $this->fetchApi->fetch('jsonPost', $url, $apiReadyArray);
if ($this->hasError($filecontent)) {
$errorMsg = $this->getError($filecontent);
throw new \Exception("Error downloading file: {$errorMsg}", 1);
}
$this->_saveIntoFile($filenames[$i], $filecontent);
}
return $filenames;
}
/**
* Convert a simple Array of uids provided by the user into the array
* Convert a simple Array of uids provided by the user into the array
* format required by Memsource API
*
* @param array $uids A simple array of UIDs
*
*
* @return array The API ready array
*/
private function _convertUidArrayToApiRequest(array $uids): array
@@ -73,16 +79,23 @@ class BilingualFile extends \BrunoFontes\Memsource\BaseApi
*
* @param string $filename The filename to be uploaded
* @param array $params Any API (HTTP GET) parameters as ['key' => 'value'] format
*
*
* @return string The http request responde from API in json format
*/
public function uploadBilingualFile(string $filename, array $params): string
public function upload(string $filename, array $params): string
{
$urlParams = http_build_query($params);
$fileContent = file_get_contents($filename);
if ($fileContent === false) {
throw new \Exception('File for upload inexistent or invalid', 1);
try {
$fileContent = file_get_contents($filename);
} catch (\Exception $e) {
throw new \Exception('File for upload inexistent or invalid: ' . $filename, 1);
}
return $this->fetchApi->fetch('put', $this->_url . "?{$urlParams}", [$fileContent]);
$response = $this->fetchApi->fetch('put', $this->_url . "?{$urlParams}", [$fileContent]);
if ($this->hasError($response)) {
throw new \Exception("Error uploading file {$filename}: " . $this->getError($response), 1);
}
return $response;
}
}

View File

@@ -11,6 +11,11 @@ class FetchApi
{
protected $base_url;
protected $token;
public function getBase_url()
{
return $this->base_url;
}
/**
* BaseAPI needs at least the Memsource Token to use it's API
@@ -18,7 +23,7 @@ class FetchApi
* @param string $token
* @param string $memsourceBaseUrl [Optional] A non-standard Memsource URL base for the API to work
*/
public function __construct(string $token = null, string $memsourceBaseUrl = 'https://cloud.memsource.com/web')
public function __construct(string $token = null, string $memsourceBaseUrl)
{
$this->base_url = $memsourceBaseUrl;
$this->token = $token;
@@ -27,7 +32,7 @@ class FetchApi
/**
* Fetch API data using curl
*
* @param string $method Should be 'get', 'post', 'jsonPost' or 'download'
* @param string $method Should be 'get', 'put', 'post', 'jsonPost', 'download' or 'raw'
* @param string $url The api url
* @param array $parameters Array ['key' => 'value'] of get or post fields or structured array for json requests
* @param string $filename [optional] Specified file in which the download request will be saved
@@ -39,30 +44,47 @@ class FetchApi
$setopt = [];
switch ($method) {
case 'get':
$this->checkAccessToken();
$parameters = http_build_query($parameters);
$url = $url . ($parameters ? '?'.$parameters : '');
break;
case 'put':
$this->checkAccessToken();
$setopt = $this->getPutParam()+$this->getPostParam(implode("", $parameters));
break;
case 'post':
$this->checkAccessToken();
$parameters = http_build_query($parameters);
$setopt = $setopt + $this->getPostParam($parameters);
break;
case 'jsonPost':
$this->checkAccessToken();
$setopt = $this->getJsonPostParam($parameters);
break;
case 'download':
$this->checkAccessToken();
if (empty($filename)) {
throw new Exception('You need to specify a filename to download a file.', 1);
}
$setopt = $this->getDownloadFileParam($filename)
+ $this->getJsonPostParam($parameters);
break;
case 'raw':
$setopt = $parameters;
break;
default:
throw new \Exception("Method {$method} is invalid on Fetch", 1);
}
return $this->curl($url, $setopt);
}
private function checkAccessToken()
{
if (empty($this->token)) {
throw new \Exception("Missing Access Token", 1);
}
}
private function getDownloadFileParam(string $filename)
{
return [
@@ -98,10 +120,10 @@ class FetchApi
protected function curl(string $url, array $curl_extra_setopt = [])
{
if (empty($url)) {
throw new Exception('URL not defined', 1);
throw new \Exception('URL not defined', 1);
}
$header = $this->token ? ["Authorization: Bearer {$this->token}"] : [];
$header = ($this->token ? ["Authorization: Bearer {$this->token}"] : []);
$header = array_merge($header, $curl_extra_setopt[CURLOPT_HTTPHEADER]??[]);
$curl_setopt = [
CURLOPT_URL => $this->base_url . $url,

View File

@@ -10,7 +10,6 @@ namespace BrunoFontes\Memsource;
class Jobs extends \BrunoFontes\Memsource\BaseApi
{
/**
* List jobs of a project
* The API request returns a MAX of 50 Jobs.
@@ -21,9 +20,13 @@ class Jobs extends \BrunoFontes\Memsource\BaseApi
*
* @return string The JSON answer from Memsource
*/
public function listJobs(string $projectUid, array $parameters = []): string
public function list(string $projectUid, array $parameters = []): string
{
$url = "/api2/v2/projects/{$projectUid}/jobs";
return $this->fetchApi->fetch('get', $url, $parameters);
$response = $this->fetchApi->fetch('get', $url, $parameters);
if ($this->hasError($response)) {
throw new \Exception("Error listing projects: " . $this->getError($response), 1);
}
return $response;
}
}

View File

@@ -8,9 +8,15 @@
namespace BrunoFontes;
use \BrunoFontes\Memsource\BilingualFile;
use \BrunoFontes\Memsource\FetchApi;
use \BrunoFontes\Memsource\Jobs;
use \BrunoFontes\Memsource\Oauth;
use \BrunoFontes\Memsource\Project;
/**
* Memsource API class
*
*
* Instructions: https://github.com/brunofontes/Memsource-API/
* Memsource API details: https://cloud.memsource.com/web/docs/api
*/
@@ -24,47 +30,46 @@ class Memsource
public function __construct(string $token = null, string $memsourceBaseUrl = 'https://cloud.memsource.com/web')
{
$this->_fetchApi = new \BrunoFontes\Memsource\FetchApi($token, $memsourceBaseUrl);
$this->_fetchApi = new FetchApi($token, $memsourceBaseUrl);
}
/**
* Memsource Oauth functions
*
* @return \BrunoFontes\Memsource\Oauth
* @return Oauth
*/
public function oauth(): \BrunoFontes\Memsource\Oauth
public function oauth(): Oauth
{
return $this->_oauth ?? $this->_oauth = new \BrunoFontes\Memsource\oauth();
return $this->_oauth ?? $this->_oauth = new oauth($this->_fetchApi);
}
/**
* Memsource API BilingualFile related functions
*
* @return \BrunoFontes\Memsource\BilingualFile
* @return BilingualFile
*/
public function bilingualFile(): \BrunoFontes\Memsource\BilingualFile
public function bilingualFile(): BilingualFile
{
return $this->_bilingualFile ?? $this->_bilingualFile = new \BrunoFontes\Memsource\BilingualFile($this->_fetchApi);
return $this->_bilingualFile ?? $this->_bilingualFile = new BilingualFile($this->_fetchApi);
}
/**
* Memsource API Jobs related functions
*
* @return \BrunoFontes\Memsource\Jobs
* @return Jobs
*/
public function jobs(): \BrunoFontes\Memsource\Jobs
public function jobs(): Jobs
{
return $this->_jobs ?? $this->_jobs = new \BrunoFontes\Memsource\Jobs($this->_fetchApi);
return $this->_jobs ?? $this->_jobs = new Jobs($this->_fetchApi);
}
/**
* Memsource API Project related functions
*
* @return \BrunoFontes\Memsource\Project
* @return Project
*/
public function project(): \BrunoFontes\Memsource\Project
public function project(): Project
{
return $this->_project ?? $this->_project = new \BrunoFontes\Memsource\Project($this->_fetchApi);
return $this->_project ?? $this->_project = new Project($this->_fetchApi);
}
}

View File

@@ -1,29 +1,38 @@
<?php
/**
* A very compact and simple Memsource API library
*
*
* @author Bruno Fontes <developer@brunofontes.net>
* @link https://github.com/brunofontes
*/
namespace BrunoFontes\Memsource;
class Oauth extends \BrunoFontes\Memsource
class Oauth extends \BrunoFontes\Memsource\BaseApi
{
private $_url = '/oauth';
/**
* Get the URL to generate the Authorization Code from Memsource
*
* @param string $client_id Memsource client ID
* @param string $client_secret Memsource client secret
* @param string $callback_uri URL that Memsource will redirect to
*
*
* @return string the authorization code
*/
public function getAuthorizationCodeUrl(string $client_id, string $callback_uri)
{
$authorize_url = $this->base_url . $this->_url . '/authorize';
return $authorize_url . '?response_type=code&client_id=' . $client_id . '&redirect_uri=' . $callback_uri . '&scope=openid';
$authorize_url = $this->fetchApi->getBase_url() . $this->_url . '/authorize';
$parambeters = http_build_query(
[
"response_type" => 'code',
"client_id" => $client_id,
"redirect_uri" => $callback_uri,
"scope" => 'openid'
]
);
return "{$authorize_url}?{$parambeters}";
}
public function getAccessToken(string $authorization_code, string $client_id, string $client_secret, string $callback_uri)
@@ -37,10 +46,12 @@ class Oauth extends \BrunoFontes\Memsource
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $content
];
$response = $this->curl($token_url, $params);
if ($respose['error']) {
throw new Exception("Error getting access token", 1);
$response = json_decode($this->fetchApi->fetch('raw', $token_url, $params), true);
if (isset($response['error'])) {
throw new \Exception("Error getting TOKEN: " . $response['error_description'], 1);
}
return $response['access_token'];
}
}

View File

@@ -19,9 +19,13 @@ class Project extends \BrunoFontes\Memsource\BaseApi
*
* @return string The JSON answer from Memsource
*/
public function listProjects(array $queryParams = []): string
public function list(array $queryParams = []): string
{
return $this->fetchApi->fetch('get', $this->_url, $queryParams);
$response = $this->fetchApi->fetch('get', $this->_url, $queryParams);
if ($this->hasError($response)) {
throw new \Exception("Error listing projects: " . $this->getError($response), 1);
}
return $response;
}
/**
@@ -31,8 +35,11 @@ class Project extends \BrunoFontes\Memsource\BaseApi
*
* @return string A json string with all project info
*/
public function getProject(string $projectUid): string
public function get(string $projectUid): string
{
return $this->fetchApi->fetch('get', "{$this->_url}/{$projectUid}");
$response = $this->fetchApi->fetch('get', "{$this->_url}/{$projectUid}");
if ($this->hasError($response)) {
throw new \Exception("Error getting project {$projectUid}: " . $this->getError($response), 1);
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use BrunoFontes\Memsource;
final class BilingualFileTest extends TestCase
{
public function testEmptyDownloadJobUidListShouldReturnEmptyFilenames()
{
$api = new Memsource('fakeToken');
$this->assertEquals(
[],
$api->bilingualFile()->download('uid', [], 'filename')
);
}
public function testInvalidDownloadUidsShouldThrowError()
{
$api = new Memsource('fakeToken');
$this->expectException(\Exception::class);
$api->bilingualFile()->download('uid', ['a'], 'filename');
}
public function testUploadInexistentFileShouldThrowError()
{
$api = new Memsource('fakeToken');
$this->expectException(\Exception::class);
$api->bilingualFile()->upload('myInvalidFile', []);
}
public function testUploadWithNoTokenShouldThrowError()
{
$api = new Memsource('fakeToken');
$this->expectException(\Exception::class);
$api->bilingualFile()->upload('tests/bilingualFileTest.php', []);
}
}

57
tests/fetchApiTest.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use BrunoFontes\Memsource\FetchApi;
final class FetchApiTest extends TestCase
{
public function testEmptyFetchUrlShouldThrowError()
{
$fetch = new FetchApi('fakeToken', 'https://google.com');
$this->expectExceptionMessage('URL not defined');
$this->assertNotEmpty($fetch->fetch('get', ''));
}
public function testNotEmptyTokenOnFetchShouldNotThrowError()
{
$fetch = new FetchApi('fakeToken', 'https://google.com');
$this->assertNotEmpty($fetch->fetch('get', '/'));
}
public function testEmptyTokenOnFetchRawShouldNotThrowError()
{
$fetch = new FetchApi(null, 'http://google.com');
$this->assertNotEmpty($fetch->fetch('raw', '/'));
}
public function testEmptyTokenOnFetchGetShouldThrowError()
{
$fetch = new FetchApi(null, 'http://testUrl.com');
$this->expectExceptionMessage('Missing Access Token');
$fetch->fetch('get', 'url');
}
public function testEmptyTokenOnFetchPutShouldThrowError()
{
$fetch = new FetchApi(null, 'http://testUrl.com');
$this->expectExceptionMessage('Missing Access Token');
$fetch->fetch('put', 'url');
}
public function testEmptyTokenOnFetchPostShouldThrowError()
{
$fetch = new FetchApi(null, 'http://testUrl.com');
$this->expectExceptionMessage('Missing Access Token');
$fetch->fetch('post', 'url');
}
public function testEmptyTokenOnFetchJsonPostShouldThrowError()
{
$fetch = new FetchApi(null, 'http://testUrl.com');
$this->expectExceptionMessage('Missing Access Token');
$fetch->fetch('jsonPost', 'url');
}
public function testEmptyTokenOnFetchDownloadShouldThrowError()
{
$fetch = new FetchApi(null, 'http://testUrl.com');
$this->expectExceptionMessage('Missing Access Token');
$fetch->fetch('download', 'url');
}
}

15
tests/jobsTest.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use BrunoFontes\Memsource;
final class JobsTest extends TestCase
{
public function testInvalidProjectUidShouldThrowError()
{
$api = new Memsource('fakeToken');
$this->expectException(\Exception::class);
$api->jobs()->list('invalidProjectUid', []);
}
}

34
tests/oauthTest.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use BrunoFontes\Memsource;
final class oauthTest extends TestCase
{
public function testOauthGetAuthorizationCodeUrl()
{
$customServerURL = 'http://myPersonalMemsource.com';
$api = new Memsource(null, $customServerURL);
$expected = $customServerURL . '/oauth/authorize?response_type=code&client_id=id&redirect_uri=http%3A%2F%2Furi&scope=openid';
$memsourceUrl = $api->oauth()->getAuthorizationCodeUrl('id', 'http://uri');
$this->assertEquals(
$expected,
$memsourceUrl
);
}
public function testGetAccessTokenExceptionOnFakeCode()
{
$api = new Memsource();
$this->expectException(\Exception::class);
$token = $api->oauth()->getAccessToken('fakeCode', 'fakeId', 'fakePass', 'http://any');
}
public function testGetAccessTokenExceptionOnEmptyCode()
{
$api = new Memsource();
$this->expectException(\Exception::class);
$token = $api->oauth()->getAccessToken('', '', '', '');
}
}

22
tests/projectTest.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use BrunoFontes\Memsource;
final class ProjectTest extends TestCase
{
public function testNoTokenShouldThrowError()
{
$api = new Memsource();
$this->expectExceptionMessage('Missing Access Token');
$api->project()->list();
}
public function testInvalidProjectUidShouldThrowError()
{
$api = new Memsource('fakeToken');
$this->expectException(\Exception::class);
$api->project()->get('invalidProjectUid');
}
}