1<?php
2/*
3 * Copyright (C) 2014-2019  Frédéric France      <frederic.france@netlogic.fr>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 * or see https://www.gnu.org/
18 */
19
20/**
21 *      \file       htdocs/core/modules/printing/printgcp.modules.php
22 *      \ingroup    printing
23 *      \brief      File to provide printing with Google Cloud Print
24 */
25
26include_once DOL_DOCUMENT_ROOT.'/core/modules/printing/modules_printing.php';
27require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
28
29use OAuth\Common\Storage\DoliStorage;
30use OAuth\Common\Consumer\Credentials;
31use OAuth\OAuth2\Service\Google;
32
33/**
34 *     Class to provide printing with Google Cloud Print
35 */
36class printing_printgcp extends PrintingDriver
37{
38	/**
39	 * @var string module name
40	 */
41	public $name = 'printgcp';
42
43	/**
44	 * @var string module description
45	 */
46	public $desc = 'PrintGCPDesc';
47
48	/**
49	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
50	 */
51	public $picto = 'printer';
52
53	/**
54	 * @var string module description
55	 */
56	public $active = 'PRINTING_PRINTGCP';
57
58	/**
59	 * @var array module parameters
60	 */
61	public $conf = array();
62
63	/**
64	 * @var string google id
65	 */
66	public $google_id = '';
67
68	/**
69	 * @var string google secret
70	 */
71	public $google_secret = '';
72
73	/**
74	 * @var string Error code (or message)
75	 */
76	public $error = '';
77
78	/**
79	 * @var string[] Error codes (or messages)
80	 */
81	public $errors = array();
82
83	/**
84	 * @var DoliDB Database handler.
85	 */
86	public $db;
87
88	private $OAUTH_SERVICENAME_GOOGLE = 'Google';
89
90	const LOGIN_URL = 'https://accounts.google.com/o/oauth2/token';
91	const PRINTERS_SEARCH_URL = 'https://www.google.com/cloudprint/search';
92	const PRINTERS_GET_JOBS = 'https://www.google.com/cloudprint/jobs';
93	const PRINT_URL = 'https://www.google.com/cloudprint/submit';
94	const LANGFILE = 'printgcp';
95
96	/**
97	 *  Constructor
98	 *
99	 *  @param      DoliDB      $db      Database handler
100	 */
101	public function __construct($db)
102	{
103		global $conf, $langs, $dolibarr_main_url_root;
104
105		// Define $urlwithroot
106		$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
107		$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
108		//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
109
110		$this->db = $db;
111
112		if (!$conf->oauth->enabled) {
113			$this->conf[] = array(
114				'varname'=>'PRINTGCP_INFO',
115				'info'=>$langs->transnoentitiesnoconv("WarningModuleNotActive", "OAuth"),
116				'type'=>'info',
117			);
118		} else {
119			$this->google_id = $conf->global->OAUTH_GOOGLE_ID;
120			$this->google_secret = $conf->global->OAUTH_GOOGLE_SECRET;
121			// Token storage
122			$storage = new DoliStorage($this->db, $this->conf);
123			//$storage->clearToken($this->OAUTH_SERVICENAME_GOOGLE);
124			// Setup the credentials for the requests
125			$credentials = new Credentials(
126				$this->google_id,
127				$this->google_secret,
128				$urlwithroot.'/core/modules/oauth/google_oauthcallback.php'
129			);
130			$access = ($storage->hasAccessToken($this->OAUTH_SERVICENAME_GOOGLE) ? 'HasAccessToken' : 'NoAccessToken');
131			$serviceFactory = new \OAuth\ServiceFactory();
132			$apiService = $serviceFactory->createService($this->OAUTH_SERVICENAME_GOOGLE, $credentials, $storage, array());
133			$token_ok = true;
134			try {
135				$token = $storage->retrieveAccessToken($this->OAUTH_SERVICENAME_GOOGLE);
136			} catch (Exception $e) {
137				$this->errors[] = $e->getMessage();
138				$token_ok = false;
139			}
140			//var_dump($this->errors);exit;
141
142			$expire = false;
143			// Is token expired or will token expire in the next 30 seconds
144			if ($token_ok) {
145				$expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30));
146			}
147
148			// Token expired so we refresh it
149			if ($token_ok && $expire) {
150				try {
151					// il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
152					$refreshtoken = $token->getRefreshToken();
153					$token = $apiService->refreshAccessToken($token);
154					$token->setRefreshToken($refreshtoken);
155					$storage->storeAccessToken($this->OAUTH_SERVICENAME_GOOGLE, $token);
156				} catch (Exception $e) {
157					$this->errors[] = $e->getMessage();
158				}
159			}
160			if ($this->google_id != '' && $this->google_secret != '') {
161				$this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthConfigured', 'type'=>'info');
162				$this->conf[] = array(
163					'varname'=>'PRINTGCP_TOKEN_ACCESS',
164					'info'=>$access,
165					'type'=>'info',
166					'renew'=>$urlwithroot.'/core/modules/oauth/google_oauthcallback.php?state=userinfo_email,userinfo_profile,cloud_print&backtourl='.urlencode(DOL_URL_ROOT.'/printing/admin/printing.php?mode=setup&driver=printgcp'),
167					'delete'=>($storage->hasAccessToken($this->OAUTH_SERVICENAME_GOOGLE) ? $urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/printing/admin/printing.php?mode=setup&driver=printgcp') : '')
168				);
169				if ($token_ok) {
170					$expiredat = '';
171
172					$refreshtoken = $token->getRefreshToken();
173
174					$endoflife = $token->getEndOfLife();
175
176					if ($endoflife == $token::EOL_NEVER_EXPIRES) {
177						$expiredat = $langs->trans("Never");
178					} elseif ($endoflife == $token::EOL_UNKNOWN) {
179						$expiredat = $langs->trans("Unknown");
180					} else {
181						$expiredat = dol_print_date($endoflife, "dayhour");
182					}
183
184					$this->conf[] = array('varname'=>'TOKEN_REFRESH', 'info'=>((!empty($refreshtoken)) ? 'Yes' : 'No'), 'type'=>'info');
185					$this->conf[] = array('varname'=>'TOKEN_EXPIRED', 'info'=>($expire ? 'Yes' : 'No'), 'type'=>'info');
186					$this->conf[] = array('varname'=>'TOKEN_EXPIRE_AT', 'info'=>($expiredat), 'type'=>'info');
187				}
188				/*
189				if ($storage->hasAccessToken($this->OAUTH_SERVICENAME_GOOGLE)) {
190					$this->conf[] = array('varname'=>'PRINTGCP_AUTHLINK', 'link'=>$urlwithroot.'/core/modules/oauth/google_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/printing/admin/printing.php?mode=setup&driver=printgcp'), 'type'=>'authlink');
191					$this->conf[] = array('varname'=>'DELETE_TOKEN', 'link'=>$urlwithroot.'/core/modules/oauth/google_oauthcallback.php?action=delete&token='.newToken().'&backtourl='.urlencode(DOL_URL_ROOT.'/printing/admin/printing.php?mode=setup&driver=printgcp'), 'type'=>'delete');
192				} else {
193					$this->conf[] = array('varname'=>'PRINTGCP_AUTHLINK', 'link'=>$urlwithroot.'/core/modules/oauth/google_oauthcallback.php?backtourl='.urlencode(DOL_URL_ROOT.'/printing/admin/printing.php?mode=setup&driver=printgcp'), 'type'=>'authlink');
194				}*/
195			} else {
196				$this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthNotConfigured', 'type'=>'info');
197			}
198		}
199		// do not display submit button
200		$this->conf[] = array('enabled'=>0, 'type'=>'submit');
201	}
202
203	/**
204	 *  Return list of available printers
205	 *
206	 *  @return  int                     0 if OK, >0 if KO
207	 */
208	public function listAvailablePrinters()
209	{
210		global $conf, $langs;
211		$error = 0;
212		$langs->load('printing');
213
214		$html = '<tr class="liste_titre">';
215		$html .= '<td>'.$langs->trans('GCP_Name').'</td>';
216		$html .= '<td>'.$langs->trans('GCP_displayName').'</td>';
217		$html .= '<td>'.$langs->trans('GCP_Id').'</td>';
218		$html .= '<td>'.$langs->trans('GCP_OwnerName').'</td>';
219		$html .= '<td>'.$langs->trans('GCP_State').'</td>';
220		$html .= '<td>'.$langs->trans('GCP_connectionStatus').'</td>';
221		$html .= '<td>'.$langs->trans('GCP_Type').'</td>';
222		$html .= '<td class="center">'.$langs->trans("Select").'</td>';
223		$html .= '</tr>'."\n";
224		$list = $this->getlistAvailablePrinters();
225		//$html.= '<td><pre>'.print_r($list,true).'</pre></td>';
226		foreach ($list['available'] as $printer_det) {
227			$html .= '<tr class="oddeven">';
228			$html .= '<td>'.$printer_det['name'].'</td>';
229			$html .= '<td>'.$printer_det['displayName'].'</td>';
230			$html .= '<td>'.$printer_det['id'].'</td>'; // id to identify printer to use
231			$html .= '<td>'.$printer_det['ownerName'].'</td>';
232			$html .= '<td>'.$printer_det['status'].'</td>';
233			$html .= '<td>'.$langs->trans('STATE_'.$printer_det['connectionStatus']).'</td>';
234			$html .= '<td>'.$langs->trans('TYPE_'.$printer_det['type']).'</td>';
235			// Defaut
236			$html .= '<td class="center">';
237			if ($conf->global->PRINTING_GCP_DEFAULT == $printer_det['id']) {
238				$html .= img_picto($langs->trans("Default"), 'on');
239			} else {
240				$html .= '<a href="'.$_SERVER["PHP_SELF"].'?action=setvalue&amp;token='.newToken().'&amp;mode=test&amp;varname=PRINTING_GCP_DEFAULT&amp;driver=printgcp&amp;value='.urlencode($printer_det['id']).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'</a>';
241			}
242			$html .= '</td>';
243			$html .= '</tr>'."\n";
244		}
245		$this->resprint = $html;
246		return $error;
247	}
248
249
250	/**
251	 *  Return list of available printers
252	 *
253	 *  @return array      list of printers
254	 */
255	public function getlistAvailablePrinters()
256	{
257		$ret = array();
258		// Token storage
259		$storage = new DoliStorage($this->db, $this->conf);
260		// Setup the credentials for the requests
261		$credentials = new Credentials(
262			$this->google_id,
263			$this->google_secret,
264			DOL_MAIN_URL_ROOT.'/core/modules/oauth/google_oauthcallback.php'
265		);
266		$serviceFactory = new \OAuth\ServiceFactory();
267		$apiService = $serviceFactory->createService($this->OAUTH_SERVICENAME_GOOGLE, $credentials, $storage, array());
268		// Check if we have auth token
269		$token_ok = true;
270		try {
271			$token = $storage->retrieveAccessToken($this->OAUTH_SERVICENAME_GOOGLE);
272		} catch (Exception $e) {
273			$this->errors[] = $e->getMessage();
274			$token_ok = false;
275		}
276		$expire = false;
277		// Is token expired or will token expire in the next 30 seconds
278		if ($token_ok) {
279			$expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30));
280		}
281
282		// Token expired so we refresh it
283		if ($token_ok && $expire) {
284			try {
285				// il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
286				$refreshtoken = $token->getRefreshToken();
287				$token = $apiService->refreshAccessToken($token);
288				$token->setRefreshToken($refreshtoken);
289				$storage->storeAccessToken($this->OAUTH_SERVICENAME_GOOGLE, $token);
290			} catch (Exception $e) {
291				$this->errors[] = $e->getMessage();
292			}
293		}
294		// Send a request with api
295		try {
296			$response = $apiService->request(self::PRINTERS_SEARCH_URL);
297		} catch (Exception $e) {
298			$this->errors[] = $e->getMessage();
299			print '<pre>'.print_r($e->getMessage(), true).'</pre>';
300		}
301		//print '<tr><td><pre>'.print_r($response, true).'</pre></td></tr>';
302		$responsedata = json_decode($response, true);
303		$printers = $responsedata['printers'];
304		// Check if we have printers?
305		if (is_array($printers) && count($printers) == 0) {
306			// We dont have printers so return blank array
307			$ret['available'] = array();
308		} else {
309			// We have printers so returns printers as array
310			$ret['available'] = $printers;
311		}
312		return $ret;
313	}
314
315	/**
316	 *  Print selected file
317	 *
318	 * @param   string      $file       file
319	 * @param   string      $module     module
320	 * @param   string      $subdir     subdir for file
321	 * @return  int                     0 if OK, >0 if KO
322	 */
323	public function printFile($file, $module, $subdir = '')
324	{
325		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
326
327		global $conf, $user;
328		$error = 0;
329
330		$fileprint = $conf->{$module}->dir_output;
331		if ($subdir != '') {
332			$fileprint .= '/'.$subdir;
333		}
334		$fileprint .= '/'.$file;
335		$mimetype = dol_mimetype($fileprint);
336		// select printer uri for module order, propal,...
337		$sql = "SELECT rowid, printer_id, copy FROM ".MAIN_DB_PREFIX."printing WHERE module='".$this->db->escape($module)."' AND driver='printgcp' AND userid=".((int) $user->id);
338		$result = $this->db->query($sql);
339		if ($result) {
340			$obj = $this->db->fetch_object($result);
341			if ($obj) {
342				$printer_id = $obj->printer_id;
343			} else {
344				if (!empty($conf->global->PRINTING_GCP_DEFAULT)) {
345					$printer_id = $conf->global->PRINTING_GCP_DEFAULT;
346				} else {
347					$this->errors[] = 'NoDefaultPrinterDefined';
348					$error++;
349					return $error;
350				}
351			}
352		} else {
353			dol_print_error($this->db);
354		}
355
356		$ret = $this->sendPrintToPrinter($printer_id, $file, $fileprint, $mimetype);
357		$this->error = 'PRINTGCP: '.$ret['errormessage'];
358		if ($ret['status'] != 1) {
359			$error++;
360		}
361		return $error;
362	}
363
364	/**
365	 *  Sends document to the printer
366	 *
367	 *  @param  string      $printerid      Printer id returned by Google Cloud Print
368	 *  @param  string      $printjobtitle  Job Title
369	 *  @param  string      $filepath       File Path to be send to Google Cloud Print
370	 *  @param  string      $contenttype    File content type by example application/pdf, image/png
371	 *  @return array                       status array
372	 */
373	public function sendPrintToPrinter($printerid, $printjobtitle, $filepath, $contenttype)
374	{
375		// Check if printer id
376		if (empty($printerid)) {
377			return array('status' =>0, 'errorcode' =>'', 'errormessage'=>'No provided printer ID');
378		}
379		// Open the file which needs to be print
380		$handle = fopen($filepath, "rb");
381		if (!$handle) {
382			return array('status' =>0, 'errorcode' =>'', 'errormessage'=>'Could not read the file.');
383		}
384		// Read file content
385		$contents = fread($handle, filesize($filepath));
386		fclose($handle);
387		// Prepare post fields for sending print
388		$post_fields = array(
389			'printerid' => $printerid,
390			'title' => $printjobtitle,
391			'contentTransferEncoding' => 'base64',
392			'content' => base64_encode($contents), // encode file content as base64
393			'contentType' => $contenttype,
394		);
395		// Dolibarr Token storage
396		$storage = new DoliStorage($this->db, $this->conf);
397		// Setup the credentials for the requests
398		$credentials = new Credentials(
399			$this->google_id,
400			$this->google_secret,
401			DOL_MAIN_URL_ROOT.'/core/modules/oauth/google_oauthcallback.php?service=google'
402		);
403		$serviceFactory = new \OAuth\ServiceFactory();
404		$apiService = $serviceFactory->createService($this->OAUTH_SERVICENAME_GOOGLE, $credentials, $storage, array());
405
406		// Check if we have auth token and refresh it
407		$token_ok = true;
408		try {
409			$token = $storage->retrieveAccessToken($this->OAUTH_SERVICENAME_GOOGLE);
410		} catch (Exception $e) {
411			$this->errors[] = $e->getMessage();
412			$token_ok = false;
413		}
414		if ($token_ok) {
415			try {
416				// il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
417				$refreshtoken = $token->getRefreshToken();
418				$token = $apiService->refreshAccessToken($token);
419				$token->setRefreshToken($refreshtoken);
420				$storage->storeAccessToken($this->OAUTH_SERVICENAME_GOOGLE, $token);
421			} catch (Exception $e) {
422				$this->errors[] = $e->getMessage();
423			}
424		}
425
426		// Send a request with api
427		$response = json_decode($apiService->request(self::PRINT_URL, 'POST', $post_fields), true);
428		//print '<tr><td><pre>'.print_r($response, true).'</pre></td></tr>';
429		return array('status' => $response['success'], 'errorcode' => $response['errorCode'], 'errormessage' => $response['message']);
430	}
431
432
433	/**
434	 *  List jobs print
435	 *
436	 *  @return  int                     0 if OK, >0 if KO
437	 */
438	public function listJobs()
439	{
440		global $conf, $langs;
441
442		$error = 0;
443		$html = '';
444		// Token storage
445		$storage = new DoliStorage($this->db, $this->conf);
446		// Setup the credentials for the requests
447		$credentials = new Credentials(
448			$this->google_id,
449			$this->google_secret,
450			DOL_MAIN_URL_ROOT.'/core/modules/oauth/google_oauthcallback.php'
451		);
452		$serviceFactory = new \OAuth\ServiceFactory();
453		$apiService = $serviceFactory->createService($this->OAUTH_SERVICENAME_GOOGLE, $credentials, $storage, array());
454		// Check if we have auth token
455		$token_ok = true;
456		try {
457			$token = $storage->retrieveAccessToken($this->OAUTH_SERVICENAME_GOOGLE);
458		} catch (Exception $e) {
459			$this->errors[] = $e->getMessage();
460			$token_ok = false;
461			$error++;
462		}
463		$expire = false;
464		// Is token expired or will token expire in the next 30 seconds
465		if ($token_ok) {
466			$expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30));
467		}
468
469		// Token expired so we refresh it
470		if ($token_ok && $expire) {
471			try {
472				// il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
473				$refreshtoken = $token->getRefreshToken();
474				$token = $apiService->refreshAccessToken($token);
475				$token->setRefreshToken($refreshtoken);
476				$storage->storeAccessToken($this->OAUTH_SERVICENAME_GOOGLE, $token);
477			} catch (Exception $e) {
478				$this->errors[] = $e->getMessage();
479				$error++;
480			}
481		}
482		// Getting Jobs
483		// Send a request with api
484		try {
485			$response = $apiService->request(self::PRINTERS_GET_JOBS);
486		} catch (Exception $e) {
487			$this->errors[] = $e->getMessage();
488			$error++;
489		}
490		$responsedata = json_decode($response, true);
491		//$html .= '<pre>'.print_r($responsedata,true).'</pre>';
492		$html .= '<div class="div-table-responsive">';
493		$html .= '<table width="100%" class="noborder">';
494		$html .= '<tr class="liste_titre">';
495		$html .= '<td>'.$langs->trans("Id").'</td>';
496		$html .= '<td>'.$langs->trans("Date").'</td>';
497		$html .= '<td>'.$langs->trans("Owner").'</td>';
498		$html .= '<td>'.$langs->trans("Printer").'</td>';
499		$html .= '<td>'.$langs->trans("Filename").'</td>';
500		$html .= '<td>'.$langs->trans("Status").'</td>';
501		$html .= '<td>'.$langs->trans("Cancel").'</td>';
502		$html .= '</tr>'."\n";
503
504		$jobs = $responsedata['jobs'];
505		//$html .= '<pre>'.print_r($jobs['0'],true).'</pre>';
506		if (is_array($jobs)) {
507			foreach ($jobs as $value) {
508				$html .= '<tr class="oddeven">';
509				$html .= '<td>'.$value['id'].'</td>';
510				$dates = dol_print_date((int) substr($value['createTime'], 0, 10), 'dayhour');
511				$html .= '<td>'.$dates.'</td>';
512				$html .= '<td>'.$value['ownerId'].'</td>';
513				$html .= '<td>'.$value['printerName'].'</td>';
514				$html .= '<td>'.$value['title'].'</td>';
515				$html .= '<td>'.$value['status'].'</td>';
516				$html .= '<td>&nbsp;</td>';
517				$html .= '</tr>';
518			}
519		} else {
520				$html .= '<tr class="oddeven">';
521				$html .= '<td colspan="7" class="opacitymedium">'.$langs->trans("None").'</td>';
522				$html .= '</tr>';
523		}
524		$html .= '</table>';
525		$html .= '</div>';
526
527		$this->resprint = $html;
528
529		return $error;
530	}
531}
532