├── tests ├── .gitignore ├── test.pdf └── GoogleCloudPrintTest.php ├── .gitignore ├── src ├── config │ └── config.php ├── Exceptions │ ├── InvalidSourceException.php │ ├── PrintTaskFailedException.php │ └── InvalidCredentialsException.php ├── Facades │ └── GoogleCloudPrint.php ├── PrintJob.php ├── LaravelServiceProvider.php ├── GoogleCloudPrint.php ├── PrintApi.php └── PrintTask.php ├── phpunit.xml ├── composer.json ├── LICENSE └── README.md /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | env('GCP_CREDENTIALS_PATH'), 5 | ]; -------------------------------------------------------------------------------- /tests/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnbwebexpertise/laravel-google-cloud-print/HEAD/tests/test.pdf -------------------------------------------------------------------------------- /src/Exceptions/InvalidSourceException.php: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bnbwebexpertise/laravel-google-cloud-print", 3 | "description": "Google Cloud Print Service Provider for Laravel 5", 4 | "type": "Library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Jérémy Gaulin", 9 | "email": "jeremy@bnb.re" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Bnb\\GoogleCloudPrint\\": "src/" 15 | } 16 | }, 17 | "require": { 18 | "php": ">=5.6.0", 19 | "illuminate/config": "5.x", 20 | "illuminate/session": "5.x", 21 | "illuminate/support": "5.x", 22 | "google/apiclient": "^2.1" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "~4.6", 26 | "mockery/mockery": "0.9.*" 27 | }, 28 | "minimum-stability": "stable" 29 | } 30 | -------------------------------------------------------------------------------- /src/PrintJob.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2016 - B&B Web Expertise 7 | */ 8 | 9 | namespace Bnb\GoogleCloudPrint; 10 | 11 | /** 12 | * Class PrintJob 13 | * 14 | * @property string id 15 | * @property string printerid 16 | * @property string status 17 | * @property string title 18 | * 19 | * @package Bnb\GoogleCloudPrint 20 | */ 21 | class PrintJob 22 | { 23 | 24 | public function __construct($job) 25 | { 26 | $this->data = $job; 27 | } 28 | 29 | 30 | public function __get($attribute) 31 | { 32 | if (isset($this->data->{$attribute})) { 33 | return $this->data->{$attribute}; 34 | } 35 | 36 | return null; 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 B&B Web Expertise 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LaravelServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__ . '/config/config.php', 'gcp'); 25 | 26 | $this->app->singleton('google.print', function ($app) { 27 | return new GoogleCloudPrint($app['config']); 28 | }); 29 | } 30 | 31 | 32 | /** 33 | * Boot the service provider. 34 | * 35 | * @return void 36 | */ 37 | public function boot() 38 | { 39 | if (function_exists('config_path')) { 40 | $publishPath = config_path('gcp.php'); 41 | } else { 42 | $publishPath = base_path('config/gcp.php'); 43 | } 44 | 45 | $this->publishes([ 46 | __DIR__ . '/config/config.php' => $publishPath 47 | ], 'config'); 48 | } 49 | 50 | 51 | /** 52 | * Get the services provided by the provider. 53 | * 54 | * @return array 55 | */ 56 | public function provides() 57 | { 58 | return ['google.print']; 59 | } 60 | } -------------------------------------------------------------------------------- /src/GoogleCloudPrint.php: -------------------------------------------------------------------------------- 1 | config = $config; 30 | } 31 | 32 | 33 | protected function setUpClient() 34 | { 35 | if ($this->client === null) { 36 | $credentialsPath = $this->credentialsPath ?: $this->config->get('gcp.credentials'); 37 | 38 | if ( ! preg_match('/^\//', $credentialsPath)) { 39 | $credentialsPath = base_path($credentialsPath); 40 | } 41 | 42 | putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentialsPath); 43 | 44 | $this->client = new Google_Client(); 45 | $this->client->useApplicationDefaultCredentials(); 46 | $this->client->addScope(['https://www.googleapis.com/auth/cloudprint']); 47 | } 48 | } 49 | 50 | 51 | protected function requireToken() 52 | { 53 | $this->setUpClient(); 54 | 55 | if ($this->client->isAccessTokenExpired()) { 56 | $this->client->refreshTokenWithAssertion(); 57 | } 58 | 59 | if ( ! ($accessToken = $this->client->getAccessToken())) { 60 | throw new InvalidCredentialsException(); 61 | } 62 | 63 | return $accessToken; 64 | } 65 | 66 | 67 | public function setCredentialsPath($credentialsPath) 68 | { 69 | $this->client = null; 70 | $this->credentialsPath = $credentialsPath; 71 | } 72 | 73 | 74 | public function getAccessToken() 75 | { 76 | $accessToken = $this->requireToken(); 77 | 78 | return $accessToken['access_token']; 79 | } 80 | 81 | 82 | public function asText() 83 | { 84 | return new PrintTask($this->getAccessToken(), 'text/plain'); 85 | } 86 | 87 | 88 | public function asHtml() 89 | { 90 | return new PrintTask($this->getAccessToken(), 'text/html'); 91 | } 92 | 93 | 94 | public function asPdf() 95 | { 96 | return new PrintTask($this->getAccessToken(), 'application/pdf'); 97 | } 98 | } -------------------------------------------------------------------------------- /src/PrintApi.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2016 - B&B Web Expertise 7 | */ 8 | 9 | namespace Bnb\GoogleCloudPrint; 10 | 11 | class PrintApi 12 | { 13 | 14 | const URL_SEARCH = 'https://www.google.com/cloudprint/search'; 15 | const URL_SUBMIT = 'https://www.google.com/cloudprint/submit'; 16 | const URL_DELETE_JOB = 'https://www.google.com/cloudprint/deletejob'; 17 | const URL_JOBS = 'https://www.google.com/cloudprint/jobs'; 18 | const URL_PRINTER = 'https://www.google.com/cloudprint/printer'; 19 | const URL_PROCESS_INVITE = 'https://www.google.com/cloudprint/processinvite'; 20 | 21 | protected $accessToken; 22 | 23 | 24 | private function __construct() 25 | { 26 | 27 | } 28 | 29 | 30 | /** 31 | * @param string $accessToken OAuth2 offline access token 32 | * @param string $printer Printer ID 33 | * @param array $options Print request post fields 34 | * @param array $headers Print request headers 35 | * 36 | * @return array 37 | */ 38 | public static function processInvite($accessToken, $printer) 39 | { 40 | $api = new self; 41 | $api->accessToken = $accessToken; 42 | 43 | $options = [ 44 | 'printerid' => $printer, 45 | 'accept' => 'true', 46 | ]; 47 | 48 | return $api->makeHttpCall(self::URL_PROCESS_INVITE, $options); 49 | } 50 | 51 | 52 | /** 53 | * @param string $accessToken OAuth2 offline access token 54 | * @param string $printer Printer ID 55 | * @param array $options Print request post fields 56 | * @param array $headers Print request headers 57 | * 58 | * @return array 59 | */ 60 | public static function submit($accessToken, $printer, $options, $headers = []) 61 | { 62 | $api = new self; 63 | $api->accessToken = $accessToken; 64 | 65 | $options['printerid'] = $printer; 66 | 67 | if (empty($options['title'])) { 68 | $options['title'] = 'job-' . date('YmdHis') . '-' . rand(1000, 9999); 69 | } 70 | 71 | return $api->makeHttpCall(self::URL_SUBMIT, $options, $headers); 72 | } 73 | 74 | 75 | /** 76 | * Makes http calls to Google Cloud Print using curl 77 | * 78 | * @param string $url Http url to hit 79 | * @param array $postFields Array of post fields to be posted 80 | * @param array $headers Array of http headers 81 | * 82 | * @return mixed 83 | */ 84 | private function makeHttpCall($url, $postFields = [], $headers = []) 85 | { 86 | $headers = array_merge($headers, [ 87 | "Authorization: Bearer " . $this->accessToken 88 | ]); 89 | 90 | $curl = curl_init($url); 91 | 92 | if ( ! empty($postFields)) { 93 | curl_setopt($curl, CURLOPT_POST, true); 94 | curl_setopt($curl, CURLOPT_POSTFIELDS, $postFields); 95 | } 96 | 97 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 98 | curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 99 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 100 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 101 | 102 | $response = curl_exec($curl); 103 | 104 | curl_close($curl); 105 | 106 | return $response; 107 | } 108 | } -------------------------------------------------------------------------------- /tests/GoogleCloudPrintTest.php: -------------------------------------------------------------------------------- 1 | configMock = Mockery::mock('Illuminate\Contracts\Config\Repository')->shouldDeferMissing(); 13 | } 14 | 15 | 16 | /** 17 | * @return GoogleCloudPrint 18 | */ 19 | private function getService() 20 | { 21 | $this->configMock->shouldReceive('get') 22 | ->with('credentials') 23 | ->once() 24 | ->andReturn(__DIR__ . '/' . getenv('GCP_TEST_CREDENTIALS')); 25 | 26 | return new GoogleCloudPrint($this->configMock); 27 | } 28 | 29 | 30 | private function getPrinterId() 31 | { 32 | return $printerId = getenv('GCP_TEST_PRINTER_ID'); 33 | } 34 | 35 | 36 | /** @test */ 37 | public function it_authenticates() 38 | { 39 | $gcp = $this->getService(); 40 | 41 | $this->assertNotEmpty($gcp->getAccessToken()); 42 | } 43 | 44 | 45 | /** @test */ 46 | public function it_prints_text() 47 | { 48 | $gcp = $this->getService(); 49 | $printer = $this->getPrinterId(); 50 | 51 | $printJob = $gcp 52 | ->asText() 53 | ->content(<<printer($printer) 83 | ->send(); 84 | 85 | $this->assertNotNull($printJob); 86 | $this->assertEquals('IN_PROGRESS', $printJob->status); 87 | } 88 | 89 | 90 | /** @test */ 91 | public function it_prints_pdf_from_file() 92 | { 93 | $gcp = $this->getService(); 94 | $printer = $this->getPrinterId(); 95 | 96 | $printJob = $gcp 97 | ->asPdf() 98 | ->file(__DIR__ . '/test.pdf') 99 | ->printer($printer) 100 | ->send(); 101 | 102 | $this->assertNotNull($printJob); 103 | $this->assertEquals('IN_PROGRESS', $printJob->status); 104 | } 105 | 106 | 107 | /** @test */ 108 | public function it_prints_html_from_url_with_range() 109 | { 110 | $gcp = $this->getService(); 111 | $printer = $this->getPrinterId(); 112 | 113 | $printJob = $gcp 114 | ->asHtml() 115 | ->url('https://opensource.org/licenses/MIT') 116 | ->range(1, 1) 117 | ->marginsInMillimeters(5, 5, 5, 5) 118 | ->printer($printer) 119 | ->send(); 120 | 121 | $this->assertNotNull($printJob); 122 | $this->assertEquals('IN_PROGRESS', $printJob->status); 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] Google Cloud Print service for Laravel 5 2 | 3 | ## Install 4 | 5 | Via composer : 6 | 7 | composer require bnbwebexpertise/laravel-google-cloud-print 8 | 9 | Then add the service provider class to your Laravel `config/app.php` : 10 | 11 | 'providers' => [ 12 | // ... 13 | Bnb\GoogleCloudPrint\LaravelServiceProvider::class, 14 | // ... 15 | ], 16 | 17 | Also add the Facade alias if you intend to use it : 18 | 19 | 'aliases' => [ 20 | // ... 21 | 'GoogleCloudPrint' => Bnb\GoogleCloudPrint\Facades\GoogleCloudPrint::class, 22 | // ... 23 | ], 24 | 25 | ## Configuration 26 | 27 | Set the env parameter `GCP_CREDENTIALS_PATH` to the absolute path 28 | (or relative to the laravel application root) of the servie account 29 | JSON file downloaded from Google Console. 30 | 31 | ### Google service setup 32 | 33 | Create a service account key (IAM) with a `***@re-speedy-diagnostic.iam.gserviceaccount.com` 34 | and download the JSON key file at [https://console.developers.google.com/apis/credentials](https://console.developers.google.com/apis/credentials). 35 | Copy the file into the project at the configured env path. 36 | 37 | You also need to allow print access to the generated email address on 38 | all the desired printers via the Google Cloud Print console at 39 | [https://www.google.com/cloudprint/#printers](https://www.google.com/cloudprint/#printers). 40 | 41 | This library will attempt to accept the invite if the Google API rejects 42 | the credentials. Indeed Google service accounts do not get the invitation 43 | email with the accept link and therefore need to use the API to complete 44 | the process. 45 | 46 | ## Usage 47 | 48 | ### Create a print task 49 | 50 | Either use the Facade or the shortcut with one of the three provided 51 | content type to get a print task object : 52 | 53 | ``` 54 | $task = GoogleCloudPrint::asText() 55 | $task = GoogleCloudPrint::asHtml() 56 | $task = GoogleCloudPrint::asPdf() 57 | 58 | // or 59 | 60 | $task = app('google.print')->asText() 61 | $task = app('google.print')->asHtml() 62 | $task = app('google.print')->asPdf() 63 | 64 | ``` 65 | 66 | #### Configure and send the print task 67 | 68 | Calling `->printer($printerId)` is required. The `$printerId` is the 69 | printer's UUID you get on the printer details page at Google Cloud Print 70 | console (or in the printer URL). 71 | 72 | The content can be provided in three way : 73 | - raw via `->content('A raw content')`. 74 | - local file via `->file('/path/to/my/file')`. An exception is thrown if the file is not accessible 75 | - url via `->url('http://acme.foo/bar')`. The content is downloaded locally before sending the print job. An exception is thrown if the URL does not begin with `http(s)://` 76 | 77 | You can set any other Cloud Job Ticket option via the `->ticket($key, $value)` method. 78 | Some helpers are provided : 79 | - range helper via `->range($start, $end)` (start and end pages are included). 80 | - margins helpers via the `->marginsInMillimeters($top, $right, $bottom, $left)` and `->marginsInCentimeters($top, $right, $bottom, $left)`. 81 | 82 | 83 | If the job is rejected an exception is thrown. 84 | 85 | #### Examples 86 | 87 | ``` 88 | $printerId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; 89 | 90 | // Printing HTML from an URL 91 | GoogleCloudPrint::asHtml() 92 | ->url('https://opensource.org/licenses/MIT') 93 | ->printer($printerId) 94 | ->send(); 95 | 96 | // Printing page 3 to 10 of a PDF from a local file 97 | GoogleCloudPrint::asPdf() 98 | ->file('storage/document.pdf') 99 | ->range(3, 10) 100 | ->printer($printerId) 101 | ->send(); 102 | 103 | // Printing plain text with a 1cm margin on each sides using 104 | GoogleCloudPrint::asText() 105 | ->content('This is a test') 106 | ->printer($printerId) 107 | ->marginsInCentimeters(1, 1, 1, 1) 108 | ->send(); 109 | ``` 110 | -------------------------------------------------------------------------------- /src/PrintTask.php: -------------------------------------------------------------------------------- 1 | accessToken = $accessToken; 36 | $this->contentType = $contentType; 37 | } 38 | 39 | 40 | /** 41 | * @param string $raw The raw content to print 42 | * 43 | * @return self 44 | */ 45 | public function content($raw) 46 | { 47 | $this->source = $raw; 48 | 49 | return $this; 50 | } 51 | 52 | 53 | /** 54 | * @param string $file An accessible file path 55 | * 56 | * @return PrintTask 57 | * @throws InvalidSourceException 58 | */ 59 | public function file($file) 60 | { 61 | if ( ! file_exists($file)) { 62 | throw new InvalidSourceException(); 63 | } 64 | 65 | $this->source = file_get_contents($file); 66 | 67 | return $this; 68 | } 69 | 70 | 71 | /** 72 | * @param string $url An absolute public URL (prefixed by http or https) 73 | * 74 | * @return self 75 | * @throws InvalidSourceException 76 | */ 77 | public function url($url) 78 | { 79 | if ( ! preg_match('/^https?:\/\//', $url)) { 80 | throw new InvalidSourceException(); 81 | } 82 | 83 | $this->source = file_get_contents($url); 84 | 85 | return $this; 86 | } 87 | 88 | 89 | /** 90 | * @param string $title The task title 91 | * 92 | * @return self 93 | */ 94 | public function title($title) 95 | { 96 | $this->title = $title; 97 | 98 | return $this; 99 | } 100 | 101 | 102 | /** 103 | * @param string $printer The printer ID 104 | * 105 | * @return self 106 | */ 107 | public function printer($printer) 108 | { 109 | $this->printer = $printer; 110 | 111 | return $this; 112 | } 113 | 114 | 115 | /** 116 | * @param array|string|string... $tags 117 | * 118 | * @return self 119 | */ 120 | public function tags($tags) 121 | { 122 | if (is_array($tags)) { 123 | $this->tags = $tags; 124 | } elseif (func_num_args() > 1) { 125 | $this->tags = func_get_args(); 126 | } else { 127 | $this->tags[] = $tags; 128 | } 129 | 130 | $this->tags = array_map('str_slug', $this->tags); 131 | 132 | return $this; 133 | } 134 | 135 | 136 | /** 137 | * @param int $start 138 | * @param int $end 139 | * 140 | * @return self 141 | */ 142 | public function range($start, $end) 143 | { 144 | if ($start > $end) { 145 | $tmp = $start; 146 | $start = $end; 147 | $end = $tmp; 148 | } 149 | 150 | $this->ticket('page_range', [ 151 | 'interval' => [ 152 | [ 153 | 'start' => $start, 154 | 'end' => $end 155 | ] 156 | ] 157 | ]); 158 | 159 | return $this; 160 | } 161 | 162 | 163 | /** 164 | * Sets the margins in millimeters 165 | * 166 | * @param int $top 167 | * @param int $right 168 | * @param int $bottom 169 | * @param int $left 170 | * 171 | * @return PrintTask 172 | */ 173 | public function marginsInMillimeters($top, $right, $bottom, $left) 174 | { 175 | $this->ticket('margins', [ 176 | 'top_microns' => $top * 1000, 177 | 'right_microns' => $right * 1000, 178 | 'bottom_microns' => $bottom * 1000, 179 | 'left_microns' => $left * 1000, 180 | ]); 181 | 182 | return $this; 183 | } 184 | 185 | 186 | /** 187 | * Sets the margins in centimeters 188 | * 189 | * @param int $top 190 | * @param int $right 191 | * @param int $bottom 192 | * @param int $left 193 | * 194 | * @return PrintTask 195 | */ 196 | public function marginsInCentimeters($top, $right, $bottom, $left) 197 | { 198 | return $this->marginsInMillimeters($top * 10, $right * 10, $bottom * 10, $left * 10); 199 | } 200 | 201 | 202 | /** 203 | * @param string $key 204 | * @param mixed $value 205 | * 206 | * @return self 207 | */ 208 | public function ticket($key, $value) 209 | { 210 | $this->printOptions[$key] = $value; 211 | 212 | return $this; 213 | } 214 | 215 | 216 | /** 217 | * @return PrintJob 218 | * 219 | * @throws PrintTaskFailedException 220 | */ 221 | public function send() 222 | { 223 | $ticket = [ 224 | 'version' => '1.0' 225 | ]; 226 | 227 | if ( ! empty($this->printOptions)) { 228 | $ticket['print'] = $this->printOptions; 229 | } 230 | 231 | $job = PrintApi::submit($this->accessToken, $this->printer, [ 232 | 'title' => $this->title, 233 | 'contentTransferEncoding' => 'base64', 234 | 'content' => base64_encode($this->source), 235 | 'contentType' => $this->contentType, 236 | 'tag' => join(',', $this->tags), 237 | 'ticket' => json_encode($ticket) 238 | ]); 239 | 240 | if ($job && ($job = json_decode($job))) { 241 | 242 | if ($job->success) { 243 | return new PrintJob($job->job); 244 | } 245 | 246 | if ($job->errorCode === 8 && $this->tryProcessInvite) { 247 | $this->tryProcessInvite = false; 248 | 249 | $invite = PrintApi::processInvite($this->accessToken, $this->printer); 250 | 251 | if ($invite) { 252 | $invite = json_decode($invite); 253 | 254 | if ($invite->success) { 255 | return $this->send(); 256 | } 257 | } 258 | } 259 | } 260 | 261 | throw new PrintTaskFailedException(sprintf('The print job submission has failed : %s', json_encode($job ?: 'Unknown error'))); 262 | } 263 | } 264 | --------------------------------------------------------------------------------