UpdraftPlus WordPress Backup Plugin - Version 1.9.4

Version Description

  • 2014/04/23 =

  • FEATURE: New remote storage back-end for OpenStack Swift

  • FEATURE: New remote storage back-end for Bitcasa (Premium - http://updraftplus.com/shop/updraftplus-premium/)

  • FEATURE: New Google Drive back-end now uses new SDK; resulting new capabilities include ability to rescan remote storage, and chunked downloading for huge files; also requires a shorter list of permissions

  • FEATURE: Restore backups that were created by the plugin BackWPup (Premium - http://updraftplus.com/shop/updraftplus-premium/)

  • FIX: WebDAV storage: remove requirement for PEAR to be pre-installed on server

  • FIX: Fix restoration on sites where WP did not have direct filesystem access

  • FIX: Fix regex which prevented download progress of mu-plugins zip displaying correctly

  • FIX: Fix issue preventing some useful information about URL changes being included in the migration log file

  • FIX: Restore compatibility with WordPress 3.2 (if you're using that, you're overdue an upgrade by some years!)

  • TWEAK: Enable new locations for plupload Flash/Silverlight widgets (for non-HTML5 browsers) in WP3.9+ (later reverted by core devs, but is harmless in case they re-introduce)

  • TWEAK: Take advantage of WP 3.9+'s new method (if available) for maintaining DB connectivity on very long runs

  • TWEAK: Add filter so that programmers can allow the options page to be shown to non-admins

  • TWEAK: Add filter allowing programmers to forbid a backup

  • TWEAK: Detect and adapt to cases where the site is moved to a system with different case-sensitivity and the database record of the theme is now wrong

  • TWEAK: Prevent erroneous warning about a missing table in the database backup on some WPMU installs that began life as a very old WP version

  • TWEAK: Introduce constant allowing users of pre-release WP installs to disable notices about using a version of WP that UpdraftPlus has not been tested on.

  • TWEAK: Make Dropbox uploads at least 25% faster (in our testing) by increasing the chunk size

  • TWEAK: Reduce number of rows fetched from MySQL if no activity took place on the previous resumption

  • TWEAK: AWS image in settings page will now use https if dashboard access is https - prevents non-https warnings in recent browsers

  • TWEAK: Hook into Better WP Security so that it doesn't tell the user that they have no backup plugin

  • TWEAK: New debugging tool to test remote HTTP connectivity

  • TWEAK: Tweak the MySQL version detection in the 'debug' section of the admin page to prevent a PHP message being thrown on PHP 5.5+/WP3.9+

  • TRANSLATION: New Czech (cs_CZ) translation; thanks to Martin Kek

  • TRANSLATION: Updated Russian, Swedish, Dutch and Portuguese translations

Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 UpdraftPlus WordPress Backup Plugin
Version 1.9.4
Comparing to
See all releases

Code changes from version 1.9.0 to 1.9.4

Files changed (53) hide show
  1. admin.php +249 -64
  2. backup.php +55 -16
  3. class-zip.php +5 -1
  4. example-decrypt.php +0 -2
  5. images/bitcasa.png +0 -0
  6. images/copy.png +0 -0
  7. includes/Dropbox/OAuth/Consumer/ConsumerAbstract.php +1 -1
  8. includes/Google/Auth/Abstract.php +41 -0
  9. includes/Google/Auth/AssertionCredentials.php +133 -0
  10. includes/Google/Auth/Exception.php +22 -0
  11. includes/Google/Auth/LoginTicket.php +69 -0
  12. includes/Google/Auth/OAuth2.php +580 -0
  13. includes/Google/Auth/Simple.php +92 -0
  14. includes/Google/Cache/Abstract.php +53 -0
  15. includes/Google/Cache/Apc.php +73 -0
  16. includes/Google/Cache/Exception.php +21 -0
  17. includes/Google/Cache/File.php +145 -0
  18. includes/Google/Cache/Memcache.php +137 -0
  19. includes/Google/Cache/Null.php +56 -0
  20. includes/Google/Client.php +608 -0
  21. includes/Google/Collection.php +94 -0
  22. includes/Google/Config.php +315 -0
  23. includes/Google/Exception.php +20 -0
  24. includes/Google/Http/Batch.php +143 -0
  25. includes/Google/Http/CacheParser.php +184 -0
  26. includes/Google/Http/MediaFileUpload.php +292 -0
  27. includes/Google/Http/REST.php +139 -0
  28. includes/Google/Http/Request.php +476 -0
  29. includes/Google/IO/Abstract.php +312 -0
  30. includes/Google/IO/Curl.php +136 -0
  31. includes/Google/IO/Exception.php +22 -0
  32. includes/Google/IO/Stream.php +189 -0
  33. includes/Google/Model.php +247 -0
  34. includes/Google/Service.php +39 -0
  35. includes/Google/Service/Drive.php +5732 -0
  36. includes/Google/Service/Exception.php +53 -0
  37. includes/Google/Service/Resource.php +210 -0
  38. includes/Google/Signer/Abstract.php +29 -0
  39. includes/Google/Signer/P12.php +90 -0
  40. includes/Google/Utils.php +135 -0
  41. includes/Google/Utils/URITemplate.php +333 -0
  42. includes/Google/Verifier/Abstract.php +30 -0
  43. includes/Google/Verifier/Pem.php +73 -0
  44. includes/class-gdocs.php +0 -641
  45. includes/updraft-admin-ui.js +219 -147
  46. languages/updraftplus-ar.mo +0 -0
  47. languages/updraftplus-ar.po +914 -758
  48. languages/updraftplus-cs_CZ.mo +0 -0
  49. languages/updraftplus-cs_CZ.po +906 -750
  50. languages/updraftplus-el.mo +0 -0
  51. languages/updraftplus-el.po +1843 -924
  52. languages/updraftplus-es_ES.mo +0 -0
  53. languages/updraftplus-es_ES.po +439 -250
admin.php CHANGED
@@ -36,14 +36,27 @@ class UpdraftPlus_Admin {
36
 
37
  $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
38
 
39
- if (UpdraftPlus_Options::user_can_manage() && ('googledrive' === $service || is_array($service) && in_array('googledrive', $service)) && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid','') != '' && UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token','') == '') {
40
- add_action('all_admin_notices', array($this,'show_admin_warning_googledrive') );
 
 
 
 
 
 
 
 
41
  }
42
 
43
  if (UpdraftPlus_Options::user_can_manage() && ('dropbox' === $service || is_array($service) && in_array('dropbox', $service)) && UpdraftPlus_Options::get_updraft_option('updraft_dropboxtk_request_token','') == '') {
44
  add_action('all_admin_notices', array($this,'show_admin_warning_dropbox') );
45
  }
46
 
 
 
 
 
 
47
  if (UpdraftPlus_Options::user_can_manage() && $this->disk_space_check(1048576*35) === false) add_action('all_admin_notices', array($this, 'show_admin_warning_diskspace'));
48
 
49
  // Next, the actions that only come on the UpdraftPlus page
@@ -75,7 +88,26 @@ class UpdraftPlus_Admin {
75
 
76
  if (version_compare($wp_version, '3.2', '<')) add_action('all_admin_notices', array($this, 'show_admin_warning_wordpressversion'));
77
 
78
- wp_enqueue_script('updraftplus-admin-ui', UPDRAFTPLUS_URL.'/includes/updraft-admin-ui.js', array('jquery', 'jquery-ui-dialog', 'plupload-all'), '32');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  wp_localize_script( 'updraftplus-admin-ui', 'updraftlion', array(
81
  'sendonlyonwarnings' => __('Send a report only when there are warnings/errors', 'updraftplus'),
@@ -127,10 +159,9 @@ class UpdraftPlus_Admin {
127
  'close' => __('Close', 'updraftplus'),
128
  'restore' => __('Restore', 'updraftplus'),
129
  ) );
130
-
131
  }
132
 
133
- function core_upgrade_preamble() {
134
  if (!class_exists('UpdraftPlus_Addon_Autobackup')) {
135
  if (defined('UPDRAFTPLUS_NOADS_A')) return;
136
  # TODO: Remove legacy/wrong use of transient any time from 1 Jun 2014
@@ -160,8 +191,11 @@ class UpdraftPlus_Admin {
160
 
161
  $chunk_size = min(wp_max_upload_size()-1024, 1024*1024*2);
162
 
 
 
 
163
  $plupload_init = array(
164
- 'runtimes' => 'html5,silverlight,flash,html4',
165
  'browse_button' => 'plupload-browse-button',
166
  'container' => 'plupload-upload-ui',
167
  'drop_element' => 'drag-drop-area',
@@ -170,7 +204,7 @@ class UpdraftPlus_Admin {
170
  'max_file_size' => '100Gb',
171
  'chunk_size' => $chunk_size.'b',
172
  'url' => admin_url('admin-ajax.php'),
173
- 'filters' => array(array('title' => __('Allowed Files'), 'extensions' => 'zip,gz,crypt,sql,txt')),
174
  'multipart' => true,
175
  'multi_selection' => true,
176
  'urlstream_upload' => true,
@@ -180,6 +214,8 @@ class UpdraftPlus_Admin {
180
  'action' => 'plupload_action'
181
  )
182
  );
 
 
183
 
184
  # WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
185
  if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.swf')) {
@@ -294,11 +330,7 @@ class UpdraftPlus_Admin {
294
 
295
  }
296
 
297
- function googledrive_remove_folderurlprefix($input) {
298
- return preg_replace('/https:\/\/drive.google.com\/(.*)#folders\//', '', $input);
299
- }
300
-
301
- function disk_space_check($space) {
302
  global $updraftplus;
303
  $updraft_dir = $updraftplus->backups_dir_location();
304
  $disk_free_space = @disk_free_space($updraft_dir);
@@ -319,7 +351,7 @@ class UpdraftPlus_Admin {
319
  return $links;
320
  }
321
 
322
- function admin_action_upgrade_pluginortheme() {
323
 
324
  if (isset($_GET['action']) && ($_GET['action'] == 'upgrade-plugin' || $_GET['action'] == 'upgrade-theme') && !class_exists('UpdraftPlus_Addon_Autobackup') && !defined('UPDRAFTPLUS_NOADS_A')) {
325
 
@@ -390,6 +422,10 @@ class UpdraftPlus_Admin {
390
  $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=doit">'.sprintf(__('Click here to authenticate your %s account (you will not be able to back up to %s without it).','updraftplus'),'Dropbox','Dropbox').'</a>');
391
  }
392
 
 
 
 
 
393
  public function show_admin_warning_googledrive() {
394
  $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-googledrive-auth&updraftplus_googleauth=doit">'.sprintf(__('Click here to authenticate your %s account (you will not be able to back up to %s without it).','updraftplus'),'Google Drive','Google Drive').'</a>');
395
  }
@@ -503,12 +539,7 @@ class UpdraftPlus_Admin {
503
  $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath");
504
 
505
  if ($needs_downloading) {
506
- // Close browser connection so that it can resume AJAX polling
507
- header('Content-Length: 0');
508
- header('Connection: close');
509
- header('Content-Encoding: none');
510
- if (session_id()) session_write_close();
511
- echo "\r\n\r\n";
512
  $is_downloaded = false;
513
  foreach ($services as $service) {
514
  if ($is_downloaded) continue;
@@ -546,6 +577,16 @@ class UpdraftPlus_Admin {
546
 
547
  }
548
 
 
 
 
 
 
 
 
 
 
 
549
  # Pass only a single service, as a string, into this function
550
  private function download_file($file, $service) {
551
 
@@ -577,7 +618,7 @@ class UpdraftPlus_Admin {
577
 
578
  // Test the nonce
579
  $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
580
- if (! wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
581
  if (isset($_REQUEST['subaction']) && 'lastlog' == $_REQUEST['subaction']) {
582
  echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '('.__('Nothing yet logged', 'updraftplus').')'));
583
  } elseif (isset($_GET['subaction']) && 'activejobs_list' == $_GET['subaction']) {
@@ -609,6 +650,70 @@ class UpdraftPlus_Admin {
609
  'j' => $active_jobs,
610
  'ds' => $download_status
611
  ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  } elseif (isset($_REQUEST['subaction']) && 'dismissautobackup' == $_REQUEST['subaction']) {
613
  UpdraftPlus_Options::update_updraft_option('updraftplus_dismissedautobackup', time() + 84*86400);
614
  } elseif (isset($_REQUEST['subaction']) && 'dismissexpiry' == $_REQUEST['subaction']) {
@@ -1170,7 +1275,7 @@ class UpdraftPlus_Admin {
1170
  //$mess[] = sprintf(__('%s version: %s', 'updraftplus'), 'WordPress', $old_wp_version);
1171
  $warn[] = sprintf(__('You are importing from a newer version of WordPress (%s) into an older one (%s). There are no guarantees that WordPress can handle this.', 'updraftplus'), $old_wp_version, $wp_version);
1172
  }
1173
- } elseif ('' == $old_table_prefix && preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches)) {
1174
  $old_table_prefix = $matches[1];
1175
  // echo '<strong>'.__('Old table prefix:', 'updraftplus').'</strong> '.htmlspecialchars($old_table_prefix).'<br>';
1176
  } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) {
@@ -1348,7 +1453,7 @@ CREATE TABLE $wpdb->signups (
1348
  add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
1349
  // handle file upload
1350
 
1351
- $farray = array( 'test_form' => true, 'action' => 'plupload_action' );
1352
 
1353
  $farray['test_type'] = false;
1354
  $farray['ext'] = 'x-gzip';
@@ -1391,6 +1496,16 @@ CREATE TABLE $wpdb->signups (
1391
  }
1392
  fclose($wh);
1393
  $status['file'] = $updraft_dir.'/'.$final_file;
 
 
 
 
 
 
 
 
 
 
1394
  }
1395
  }
1396
 
@@ -1430,6 +1545,7 @@ CREATE TABLE $wpdb->signups (
1430
  exit;
1431
  }
1432
 
 
1433
  public function plupload_action2() {
1434
 
1435
  @set_time_limit(900);
@@ -1516,8 +1632,6 @@ CREATE TABLE $wpdb->signups (
1516
 
1517
  global $updraftplus;
1518
 
1519
- wp_enqueue_style('jquery-ui', UPDRAFTPLUS_URL.'/includes/jquery-ui-1.8.22.custom.css');
1520
-
1521
  /*
1522
  we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1523
  for the WP_Filesystem. to do this WP outputs a form, but we don't pass our parameters via that. So the values are
@@ -1738,7 +1852,7 @@ CREATE TABLE $wpdb->signups (
1738
 
1739
  <tr>
1740
  <th><?php echo htmlspecialchars(__('Backups, logs & restoring','updraftplus')); ?>:</th>
1741
- <td><a id="updraft_showbackups" href="#" title="<?php _e('Press to see available backups','updraftplus');?>" onclick="jQuery('.download-backups').fadeToggle(); updraft_historytimertoggle(0);"><?php echo sprintf(__('%d set(s) available', 'updraftplus'), count($backup_history)); ?></a></td>
1742
  </tr>
1743
  <?php
1744
  if (defined('UPDRAFTPLUS_EXPERIMENTAL_MISC') && UPDRAFTPLUS_EXPERIMENTAL_MISC == true) {
@@ -1783,17 +1897,26 @@ CREATE TABLE $wpdb->signups (
1783
 
1784
  <div id="updraft-plupload-modal" title="<?php _e('UpdraftPlus - Upload backup files','updraftplus'); ?>" style="width: 75%; margin: 16px; display:none; margin-left: 100px;">
1785
  <p style="max-width: 610px;"><em><?php _e("Upload files into UpdraftPlus. Use this to import backups made on a different WordPress installation." ,'updraftplus');?> <?php echo htmlspecialchars(__('Or, you can place them manually into your UpdraftPlus directory (usually wp-content/updraft), e.g. via FTP, and then use the "rescan" link above.', 'updraftplus'));?></em></p>
1786
- <div id="plupload-upload-ui" style="width: 70%;">
1787
- <div id="drag-drop-area">
1788
- <div class="drag-drop-inside">
1789
- <p class="drag-drop-info"><?php _e('Drop backup files here', 'updraftplus'); ?></p>
1790
- <p><?php _ex('or', 'Uploader: Drop backup files here - or - Select Files'); ?></p>
1791
- <p class="drag-drop-buttons"><input id="plupload-browse-button" type="button" value="<?php esc_attr_e('Select Files'); ?>" class="button" /></p>
 
 
 
 
 
 
 
 
 
1792
  </div>
1793
  </div>
1794
- <div id="filelist">
1795
- </div>
1796
- </div>
1797
 
1798
  </div>
1799
 
@@ -1959,7 +2082,17 @@ CREATE TABLE $wpdb->signups (
1959
  echo __('PHP memory limit','updraftplus').': '.ini_get('memory_limit').' <br/>';
1960
  echo sprintf(__('%s version:','updraftplus'), 'PHP').' '.phpversion().' - ';
1961
  echo '<a href="admin-ajax.php?page=updraftplus&action=updraft_ajax&subaction=phpinfo&nonce='.wp_create_nonce('updraftplus-credentialtest-nonce').'" id="updraftplus-phpinfo">'.__('show PHP information (phpinfo)', 'updraftplus').'</a><br/>';
1962
- echo sprintf(__('%s version:','updraftplus'), 'MySQL').' '.((function_exists('mysql_get_server_info')) ? mysql_get_server_info() : '?').'<br>';
 
 
 
 
 
 
 
 
 
 
1963
 
1964
  if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
1965
  $ziparchive_exists = __('Yes', 'updraftplus');
@@ -1984,6 +2117,17 @@ CREATE TABLE $wpdb->signups (
1984
 
1985
  echo __('Install plugins for debugging:', 'updraftplus').' <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=wp-crontrol'), 'install-plugin_wp-crontrol').'">WP Crontrol</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=sql-executioner'), 'install-plugin_sql-executioner').'">SQL Executioner</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=advanced-code-editor'), 'install-plugin_advanced-code-editor').'">Advanced Code Editor</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=wp-filemanager'), 'install-plugin_wp-filemanager').'">WP Filemanager</a><br>';
1986
 
 
 
 
 
 
 
 
 
 
 
 
1987
  echo '<h3>'.__('Total (uncompressed) on-disk data:','updraftplus').'</h3>';
1988
  echo '<p style="clear: left; max-width: 600px;"><em>'.__('N.B. This count is based upon what was, or was not, excluded the last time you saved the options.', 'updraftplus').'</em></p>';
1989
 
@@ -2033,9 +2177,9 @@ CREATE TABLE $wpdb->signups (
2033
  <?php if ($include_blurb) {
2034
  ?>
2035
  <div id="updraft_delete_old_dirs_pagediv" class="updated" style="padding:8px;"><p> <?php _e('Your WordPress install has old directories from its state before you restored/migrated (technical information: these are suffixed with -old). You should press this button to delete them as soon as you have verified that the restoration worked.','updraftplus');?></p><?php } ?>
2036
- <form method="post" onsubmit="return updraft_delete_old_dirs();" action="<?php echo remove_query_arg(array('updraft_restore_success','action')) ?>">
2037
  <?php wp_nonce_field('updraftplus-credentialtest-nonce'); ?>
2038
- <input type="hidden" name="action" value="updraft_delete_old_dirs" />
2039
  <input type="submit" class="button-primary" value="<?php echo esc_attr(__('Delete Old Directories', 'updraftplus'));?>" />
2040
  </form>
2041
  <?php
@@ -2043,7 +2187,7 @@ CREATE TABLE $wpdb->signups (
2043
  }
2044
 
2045
 
2046
- function print_active_jobs() {
2047
  $cron = get_option('cron');
2048
  if (!is_array($cron)) $cron = array();
2049
  // $found_jobs = 0;
@@ -2068,7 +2212,7 @@ CREATE TABLE $wpdb->signups (
2068
  return $ret;
2069
  }
2070
 
2071
- function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) {
2072
 
2073
  $ret = '';
2074
 
@@ -2212,7 +2356,7 @@ CREATE TABLE $wpdb->signups (
2212
 
2213
  }
2214
 
2215
- function delete_old_dirs_go($show_return = true) {
2216
  echo ($show_return) ? '<h1>UpdraftPlus - '.__('Remove old directories', 'updraftplus').'</h1>' : '<h2>'.__('Remove old directories', 'updraftplus').'</h2>';
2217
 
2218
  if($this->delete_old_dirs()) {
@@ -2224,7 +2368,7 @@ CREATE TABLE $wpdb->signups (
2224
  }
2225
 
2226
  //deletes the -old directories that are created when a backup is restored.
2227
- function delete_old_dirs() {
2228
  global $wp_filesystem, $updraftplus;
2229
  $credentials = request_filesystem_credentials(wp_nonce_url(UpdraftPlus_Options::admin_page_url()."?page=updraftplus&action=updraft_delete_old_dirs", 'updraftplus-credentialtest-nonce'));
2230
  WP_Filesystem($credentials);
@@ -2261,7 +2405,7 @@ CREATE TABLE $wpdb->signups (
2261
  return $ret && $ret3 && $ret4;
2262
  }
2263
 
2264
- function delete_old_dirs_dir($dir, $wpfs = true) {
2265
 
2266
  $dir = trailingslashit($dir);
2267
 
@@ -2302,7 +2446,7 @@ CREATE TABLE $wpdb->signups (
2302
  }
2303
 
2304
  // The aim is to get a directory that is writable by the webserver, because that's the only way we can create zip files
2305
- function create_backup_dir() {
2306
 
2307
  global $wp_filesystem, $updraftplus;
2308
 
@@ -2356,7 +2500,7 @@ CREATE TABLE $wpdb->signups (
2356
  }
2357
 
2358
  //scans the content dir to see if any -old dirs are present
2359
- function scan_old_dirs() {
2360
  global $updraftplus;
2361
  $dirs = scandir(untrailingslashit(WP_CONTENT_DIR));
2362
  if (!is_array($dirs)) $dirs = array();
@@ -2368,6 +2512,15 @@ CREATE TABLE $wpdb->signups (
2368
  return false;
2369
  }
2370
 
 
 
 
 
 
 
 
 
 
2371
  private function last_backup_html() {
2372
 
2373
  global $updraftplus;
@@ -2536,6 +2689,14 @@ CREATE TABLE $wpdb->signups (
2536
 
2537
  <div id="updraft-manualdecrypt-modal" style="width: 85%; margin: 16px; display:none; margin-left: 100px;">
2538
  <p><h3><?php _e("Manually decrypt a database backup file" ,'updraftplus');?></h3></p>
 
 
 
 
 
 
 
 
2539
  <div id="plupload-upload-ui2" style="width: 80%;">
2540
  <div id="drag-drop-area2">
2541
  <div class="drag-drop-inside">
@@ -2549,6 +2710,8 @@ CREATE TABLE $wpdb->signups (
2549
  </div>
2550
  </div>
2551
 
 
 
2552
  </div>
2553
 
2554
 
@@ -2766,41 +2929,49 @@ CREATE TABLE $wpdb->signups (
2766
  <?php
2767
  }
2768
 
2769
- function show_double_warning($text, $extraclass = '') {
2770
-
2771
- ?><div class="error updraftplusmethod <?php echo $extraclass; ?>"><p><?php echo $text; ?></p></div>
2772
 
2773
- <p style="border:1px solid; padding: 6px;"><?php echo $text; ?></p>
 
2774
 
2775
- <?php
 
2776
 
2777
  }
2778
 
2779
- function optionfilter_split_every($value) {
2780
- $value=absint($value);
2781
  if (!$value >= UPDRAFTPLUS_SPLIT_MIN) $value = UPDRAFTPLUS_SPLIT_MIN;
2782
  return $value;
2783
  }
2784
 
2785
- function curl_check($service, $has_fallback = false, $extraclass = '') {
 
 
 
2786
  // Check requirements
2787
  if (!function_exists("curl_init") || !function_exists('curl_exec')) {
2788
 
2789
- $this->show_double_warning('<strong>'.__('Warning','updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not included a <strong>required</strong> (for %s) module (%s). Please contact your web hosting provider\'s support and ask for them to enable it.', 'updraftplus'), $service, 'Curl').' '.sprintf(__("Your options are 1) Install/enable %s or 2) Change web hosting companies - %s is a standard PHP component, and required by all cloud backup plugins that we know of.",'updraftplus'), 'Curl', 'Curl'), $extraclass);
2790
 
2791
  } else {
2792
  $curl_version = curl_version();
2793
  $curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL);
2794
  if (!$curl_ssl_supported) {
2795
  if ($has_fallback) {
2796
- ?><p><strong><?php _e('Warning','updraftplus'); ?>:</strong> <?php echo sprintf(__("Your web server's PHP/Curl installation does not support https access. Communications with %s will be unencrypted. ask your web host to install Curl/SSL in order to gain the ability for encryption (via an add-on).",'updraftplus'),$service);?></p><?php
2797
  } else {
2798
- $this->show_double_warning('<p><strong>'.__('Warning','updraftplus').':</strong> '.sprintf(__("Your web server's PHP/Curl installation does not support https access. We cannot access %s without this support. Please contact your web hosting provider's support. %s <strong>requires</strong> Curl+https. Please do not file any support requests; there is no alternative.",'updraftplus'),$service).'</p>', $extraclass);
2799
  }
2800
  } else {
2801
- ?><p><em><?php echo sprintf(__("Good news: Your site's communications with %s can be encrypted. If you see any errors to do with encryption, then look in the 'Expert Settings' for more help.", 'updraftplus'),$service);?></em></p><?php
2802
  }
2803
  }
 
 
 
 
 
2804
  }
2805
 
2806
  # If $basedirs is passed as an array, then $directorieses must be too
@@ -3168,7 +3339,7 @@ ENDHERE;
3168
  }
3169
  } elseif (is_wp_error($files)) {
3170
  foreach ($files->get_error_codes() as $code) {
3171
- if ('no_settings' == $code || 'no_addon' == $code) continue;
3172
  $messages[] = array(
3173
  'method' => $method,
3174
  'desc' => $desc,
@@ -3390,7 +3561,7 @@ ENDHERE;
3390
  }
3391
 
3392
  foreach ($_POST as $key => $value) {
3393
- if (strpos($key, 'updraft_restore_') === 0 ) {
3394
  $nkey = substr($key, 16);
3395
  if (!isset($entities_to_restore[$nkey])) {
3396
  $_POST['updraft_restore'][] = $nkey;
@@ -3407,14 +3578,14 @@ ENDHERE;
3407
  }
3408
  }
3409
 
3410
- $updraftplus->log("Restore job started. Entities to restore: ".implode(', ', array_flip($entities_to_restore)));
3411
-
3412
  if (0 == count($_POST['updraft_restore'])) {
3413
  echo '<p>'.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'</p>';
3414
  echo '<p>'.__('If making a request for support, please include this information:','updraftplus').' '.count($_POST).' : '.htmlspecialchars(serialize($_POST)).'</p>';
3415
  return new WP_Error('missing_info', 'Backup information not found');
3416
  }
3417
 
 
 
3418
  set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
3419
 
3420
  /*
@@ -3603,6 +3774,22 @@ ENDHERE;
3603
  add_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt));
3604
  }
3605
  if (!function_exists('validate_current_theme')) require_once(ABSPATH.'wp-includes/themes');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3606
  if (!validate_current_theme()) {
3607
  global $updraftplus;
3608
  echo '<strong>';
@@ -3645,11 +3832,9 @@ ENDHERE;
3645
  }
3646
 
3647
  private function get_settings_keys() {
3648
- return array('updraft_autobackup_default', 'updraftplus_tmp_googledrive_access_token', 'updraftplus_dismissedautobackup', 'updraftplus_dismissedexpiry', 'updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_wpcore', 'updraft_include_wpcore_exclude', 'updraft_include_more', 'updraft_include_blogs', 'updraft_include_mu-plugins', 'updraft_include_others_exclude', 'updraft_include_uploads_exclude', 'updraft_lastmessage', 'updraft_googledrive_token',
3649
  'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder',
3650
- 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_db', 'updraft_startday_files', 'updraft_sftp_settings', 'updraft_s3', 'updraft_s3generic', 'updraft_dreamhost', 'updraft_s3generic_login', 'updraft_s3generic_pass', 'updraft_s3generic_remote_path', 'updraft_s3generic_endpoint', 'updraft_webdav_settings', 'updraft_disable_ping', 'updraft_cloudfiles', 'updraft_cloudfiles_user', 'updraft_cloudfiles_apikey', 'updraft_cloudfiles_path', 'updraft_cloudfiles_authurl', 'updraft_ssl_useservercerts', 'updraft_ssl_disableverify', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dreamobjects_login', 'updraft_dreamobjects_pass', 'updraft_dreamobjects_remote_path', 'updraft_report_warningsonly', 'updraft_report_wholebackup', 'updraft_log_syslog');
3651
  }
3652
 
3653
  }
3654
-
3655
- ?>
36
 
37
  $service = UpdraftPlus_Options::get_updraft_option('updraft_service');
38
 
39
+ if (UpdraftPlus_Options::user_can_manage() && ('googledrive' === $service || is_array($service) && in_array('googledrive', $service))) {
40
+ $opts = UpdraftPlus_Options::get_updraft_option('updraft_googledrive');
41
+ if (empty($opts)) {
42
+ $clientid = UpdraftPlus_Options::get_updraft_option('updraft_googledrive_clientid', '');
43
+ $token = UpdraftPlus_Options::get_updraft_option('updraft_googledrive_token', '');
44
+ } else {
45
+ $clientid = $opts['clientid'];
46
+ $token = $opts['token'];
47
+ }
48
+ if (!empty($clientid) && empty($token)) add_action('all_admin_notices', array($this,'show_admin_warning_googledrive'));
49
  }
50
 
51
  if (UpdraftPlus_Options::user_can_manage() && ('dropbox' === $service || is_array($service) && in_array('dropbox', $service)) && UpdraftPlus_Options::get_updraft_option('updraft_dropboxtk_request_token','') == '') {
52
  add_action('all_admin_notices', array($this,'show_admin_warning_dropbox') );
53
  }
54
 
55
+ if (UpdraftPlus_Options::user_can_manage() && ('bitcasa' === $service || is_array($service) && in_array('bitcasa', $service))) {
56
+ $opts = UpdraftPlus_Options::get_updraft_option('updraft_bitcasa');
57
+ if (!empty($opts['clientid']) && !empty($opts['secret']) && empty($opts['token'])) add_action('all_admin_notices', array($this,'show_admin_warning_bitcasa') );
58
+ }
59
+
60
  if (UpdraftPlus_Options::user_can_manage() && $this->disk_space_check(1048576*35) === false) add_action('all_admin_notices', array($this, 'show_admin_warning_diskspace'));
61
 
62
  // Next, the actions that only come on the UpdraftPlus page
88
 
89
  if (version_compare($wp_version, '3.2', '<')) add_action('all_admin_notices', array($this, 'show_admin_warning_wordpressversion'));
90
 
91
+ add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
92
+
93
+ }
94
+
95
+ public function admin_enqueue_scripts() {
96
+
97
+ wp_enqueue_style('jquery-ui', UPDRAFTPLUS_URL.'/includes/jquery-ui-1.8.22.custom.css');
98
+
99
+ global $wp_version;
100
+ if (version_compare($wp_version, '3.3', '<')) {
101
+ # Require a newer jQuery (3.2.1 has 1.6.1, so we go for something not too much newer). We use .on() in a way that is incompatible with < 1.7
102
+ wp_deregister_script('jquery');
103
+ wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', false, '1.7.2', false);
104
+ wp_enqueue_script('jquery');
105
+ # No plupload until 3.3
106
+ # Put in footer, to make sure that jQuery loads first
107
+ wp_enqueue_script('updraftplus-admin-ui', UPDRAFTPLUS_URL.'/includes/updraft-admin-ui.js', array('jquery', 'jquery-ui-dialog'), '35', true);
108
+ } else {
109
+ wp_enqueue_script('updraftplus-admin-ui', UPDRAFTPLUS_URL.'/includes/updraft-admin-ui.js', array('jquery', 'jquery-ui-dialog', 'plupload-all'), '35');
110
+ }
111
 
112
  wp_localize_script( 'updraftplus-admin-ui', 'updraftlion', array(
113
  'sendonlyonwarnings' => __('Send a report only when there are warnings/errors', 'updraftplus'),
159
  'close' => __('Close', 'updraftplus'),
160
  'restore' => __('Restore', 'updraftplus'),
161
  ) );
 
162
  }
163
 
164
+ public function core_upgrade_preamble() {
165
  if (!class_exists('UpdraftPlus_Addon_Autobackup')) {
166
  if (defined('UPDRAFTPLUS_NOADS_A')) return;
167
  # TODO: Remove legacy/wrong use of transient any time from 1 Jun 2014
191
 
192
  $chunk_size = min(wp_max_upload_size()-1024, 1024*1024*2);
193
 
194
+ # The multiple_queues argument is ignored in plupload 2.x (WP3.9+) - http://make.wordpress.org/core/2014/04/11/plupload-2-x-in-wordpress-3-9/
195
+ # max_file_size is also in filters as of plupload 2.x, but in its default position is still supported for backwards-compatibility. Likewise, our use of filters.extensions below is supported by a backwards-compatibility option (the current way is filters.mime-types.extensions
196
+
197
  $plupload_init = array(
198
+ 'runtimes' => 'html5,flash,silverlight,html4',
199
  'browse_button' => 'plupload-browse-button',
200
  'container' => 'plupload-upload-ui',
201
  'drop_element' => 'drag-drop-area',
204
  'max_file_size' => '100Gb',
205
  'chunk_size' => $chunk_size.'b',
206
  'url' => admin_url('admin-ajax.php'),
207
+ 'filters' => array(array('title' => __('Allowed Files'), 'extensions' => 'zip,tar,gz,bz2,crypt,sql,txt')),
208
  'multipart' => true,
209
  'multi_selection' => true,
210
  'urlstream_upload' => true,
214
  'action' => 'plupload_action'
215
  )
216
  );
217
+ // 'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'),
218
+ // 'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'),
219
 
220
  # WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
221
  if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.swf')) {
330
 
331
  }
332
 
333
+ private function disk_space_check($space) {
 
 
 
 
334
  global $updraftplus;
335
  $updraft_dir = $updraftplus->backups_dir_location();
336
  $disk_free_space = @disk_free_space($updraft_dir);
351
  return $links;
352
  }
353
 
354
+ public function admin_action_upgrade_pluginortheme() {
355
 
356
  if (isset($_GET['action']) && ($_GET['action'] == 'upgrade-plugin' || $_GET['action'] == 'upgrade-theme') && !class_exists('UpdraftPlus_Addon_Autobackup') && !defined('UPDRAFTPLUS_NOADS_A')) {
357
 
422
  $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth&updraftplus_dropboxauth=doit">'.sprintf(__('Click here to authenticate your %s account (you will not be able to back up to %s without it).','updraftplus'),'Dropbox','Dropbox').'</a>');
423
  }
424
 
425
+ public function show_admin_warning_bitcasa() {
426
+ $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-bitcasa-auth&updraftplus_bitcasaauth=doit">'.sprintf(__('Click here to authenticate your %s account (you will not be able to back up to %s without it).','updraftplus'),'Bitcasa','Bitcasa').'</a>');
427
+ }
428
+
429
  public function show_admin_warning_googledrive() {
430
  $this->show_admin_warning('<strong>'.__('UpdraftPlus notice:','updraftplus').'</strong> <a href="'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-googledrive-auth&updraftplus_googleauth=doit">'.sprintf(__('Click here to authenticate your %s account (you will not be able to back up to %s without it).','updraftplus'),'Google Drive','Google Drive').'</a>');
431
  }
539
  $updraftplus->jobdata_set('dlfile_'.$timestamp.'_'.$type.'_'.$findex, "downloading:$known_size:$fullpath");
540
 
541
  if ($needs_downloading) {
542
+ $this->close_browser_connection();
 
 
 
 
 
543
  $is_downloaded = false;
544
  foreach ($services as $service) {
545
  if ($is_downloaded) continue;
577
 
578
  }
579
 
580
+ private function close_browser_connection($txt = '') {
581
+ // Close browser connection so that it can resume AJAX polling
582
+ header('Content-Length: '.((!empty($txt)) ? 4+strlen($txt) : '0'));
583
+ header('Connection: close');
584
+ header('Content-Encoding: none');
585
+ if (session_id()) session_write_close();
586
+ echo "\r\n\r\n";
587
+ echo $txt;
588
+ }
589
+
590
  # Pass only a single service, as a string, into this function
591
  private function download_file($file, $service) {
592
 
618
 
619
  // Test the nonce
620
  $nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
621
+ if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
622
  if (isset($_REQUEST['subaction']) && 'lastlog' == $_REQUEST['subaction']) {
623
  echo htmlspecialchars(UpdraftPlus_Options::get_updraft_option('updraft_lastmessage', '('.__('Nothing yet logged', 'updraftplus').')'));
624
  } elseif (isset($_GET['subaction']) && 'activejobs_list' == $_GET['subaction']) {
650
  'j' => $active_jobs,
651
  'ds' => $download_status
652
  ));
653
+ } elseif (isset($_REQUEST['subaction']) && 'callwpaction' == $_REQUEST['subaction'] && !empty($_REQUEST['wpaction'])) {
654
+ ob_start();
655
+
656
+ $res = '<em>Request received: </em>';
657
+
658
+ if (preg_match('/^([^:]+)+:(.*)$/', stripslashes($_REQUEST['wpaction']), $matches)) {
659
+ $action = $matches[1];
660
+ if (null === ($args = json_decode($matches[2], true))) {
661
+ $res .= "The parameters (should be JSON) could not be decoded";
662
+ $action = false;
663
+ } else {
664
+ $res .= "Will despatch action: ".htmlspecialchars($action).", parameters: ".htmlspecialchars(implode(',', $args));
665
+ }
666
+ } else {
667
+ $action = $_REQUEST['wpaction'];
668
+ $res .= "Will despatch action: ".htmlspecialchars($action).", no parameters";
669
+ }
670
+
671
+ echo json_encode(array('r' => $res));
672
+ $ret = ob_get_clean();
673
+ ob_end_clean();
674
+ $this->close_browser_connection($ret);
675
+ if (!empty($action)) {
676
+ if (!empty($args)) {
677
+ do_action_ref_array($action, $args);
678
+ } else {
679
+ do_action($action);
680
+ }
681
+ }
682
+ die;
683
+ } elseif (isset($_REQUEST['subaction']) && 'httpget' == $_REQUEST['subaction']) {
684
+ if (empty($_REQUEST['uri'])) {
685
+ echo json_encode(array('r' => ''));
686
+ die;
687
+ }
688
+ $uri = $_REQUEST['uri'];
689
+ if (!empty($_REQUEST['curl'])) {
690
+ if (!function_exists('curl_exec')) {
691
+ echo json_encode(array('e' => 'No Curl installed'));
692
+ die;
693
+ }
694
+ $ch = curl_init();
695
+ curl_setopt($ch, CURLOPT_URL, $uri);
696
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
697
+ curl_setopt($ch, CURLOPT_FAILONERROR, true);
698
+ $output = curl_exec($ch);
699
+ $response = curl_exec($ch);
700
+ $error = curl_error($ch);
701
+ $getinfo = curl_getinfo($ch);
702
+ curl_close($ch);
703
+ if (false === $response) {
704
+ echo json_encode(array('e' => htmlspecialchars($error)));
705
+ die;
706
+ }
707
+ echo json_encode(array('r' => $getinfo['http_code'].': '.htmlspecialchars(substr($response, 0, 200))));
708
+ } else {
709
+ $response = wp_remote_get($uri, array('timeout' => 10));
710
+ if (is_wp_error($response)) {
711
+ echo json_encode(array('e' => htmlspecialchars($response->get_error_message())));
712
+ die;
713
+ }
714
+ echo json_encode(array('r' => $response['response']['code'].': '.htmlspecialchars(substr($response['body'], 0, 200))));
715
+ }
716
+ die;
717
  } elseif (isset($_REQUEST['subaction']) && 'dismissautobackup' == $_REQUEST['subaction']) {
718
  UpdraftPlus_Options::update_updraft_option('updraftplus_dismissedautobackup', time() + 84*86400);
719
  } elseif (isset($_REQUEST['subaction']) && 'dismissexpiry' == $_REQUEST['subaction']) {
1275
  //$mess[] = sprintf(__('%s version: %s', 'updraftplus'), 'WordPress', $old_wp_version);
1276
  $warn[] = sprintf(__('You are importing from a newer version of WordPress (%s) into an older one (%s). There are no guarantees that WordPress can handle this.', 'updraftplus'), $old_wp_version, $wp_version);
1277
  }
1278
+ } elseif ('' == $old_table_prefix && (preg_match('/^\# Table prefix: (\S+)$/', $buffer, $matches) || preg_match('/^-- Table prefix: (\S+)$/i', $buffer, $matches))) {
1279
  $old_table_prefix = $matches[1];
1280
  // echo '<strong>'.__('Old table prefix:', 'updraftplus').'</strong> '.htmlspecialchars($old_table_prefix).'<br>';
1281
  } elseif ($gathering_siteinfo && preg_match('/^\# Site info: (\S+)$/', $buffer, $matches)) {
1453
  add_filter('sanitize_file_name', array($this, 'sanitize_file_name'));
1454
  // handle file upload
1455
 
1456
+ $farray = array('test_form' => true, 'action' => 'plupload_action');
1457
 
1458
  $farray['test_type'] = false;
1459
  $farray['ext'] = 'x-gzip';
1496
  }
1497
  fclose($wh);
1498
  $status['file'] = $updraft_dir.'/'.$final_file;
1499
+ if ('.tar' == substr($final_file, -4, 4)) {
1500
+ if (file_exists($status['file'].'.gz')) unlink($status['file'].'.gz');
1501
+ if (file_exists($status['file'].'.bz2')) unlink($status['file'].'.bz2');
1502
+ } elseif ('.tar.gz' == substr($final_file, -7, 7)) {
1503
+ if (file_exists(substr($status['file'], 0, strlen($status['file'])-3))) unlink(substr($status['file'], 0, strlen($status['file'])-3));
1504
+ if (file_exists(substr($status['file'], 0, strlen($status['file'])-3).'.bz2')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.bz2');
1505
+ } elseif ('.tar.bz2' == substr($final_file, -8, 8)) {
1506
+ if (file_exists(substr($status['file'], 0, strlen($status['file'])-4))) unlink(substr($status['file'], 0, strlen($status['file'])-4));
1507
+ if (file_exists(substr($status['file'], 0, strlen($status['file'])-4).'.gz')) unlink(substr($status['file'], 0, strlen($status['file'])-3).'.gz');
1508
+ }
1509
  }
1510
  }
1511
 
1545
  exit;
1546
  }
1547
 
1548
+ # Database decrypter
1549
  public function plupload_action2() {
1550
 
1551
  @set_time_limit(900);
1632
 
1633
  global $updraftplus;
1634
 
 
 
1635
  /*
1636
  we use request here because the initial restore is triggered by a POSTed form. we then may need to obtain credentials
1637
  for the WP_Filesystem. to do this WP outputs a form, but we don't pass our parameters via that. So the values are
1852
 
1853
  <tr>
1854
  <th><?php echo htmlspecialchars(__('Backups, logs & restoring','updraftplus')); ?>:</th>
1855
+ <td><a id="updraft_showbackups" href="#" title="<?php _e('Press to see available backups','updraftplus');?>" onclick="jQuery('.download-backups').fadeToggle(); updraft_historytimertoggle(0); return false;"><?php echo sprintf(__('%d set(s) available', 'updraftplus'), count($backup_history)); ?></a></td>
1856
  </tr>
1857
  <?php
1858
  if (defined('UPDRAFTPLUS_EXPERIMENTAL_MISC') && UPDRAFTPLUS_EXPERIMENTAL_MISC == true) {
1897
 
1898
  <div id="updraft-plupload-modal" title="<?php _e('UpdraftPlus - Upload backup files','updraftplus'); ?>" style="width: 75%; margin: 16px; display:none; margin-left: 100px;">
1899
  <p style="max-width: 610px;"><em><?php _e("Upload files into UpdraftPlus. Use this to import backups made on a different WordPress installation." ,'updraftplus');?> <?php echo htmlspecialchars(__('Or, you can place them manually into your UpdraftPlus directory (usually wp-content/updraft), e.g. via FTP, and then use the "rescan" link above.', 'updraftplus'));?></em></p>
1900
+ <?php
1901
+ global $wp_version;
1902
+ if (version_compare($wp_version, '3.3', '<')) {
1903
+ echo '<em>'.sprintf(__('This feature requires %s version %s or later', 'updraftplus'), 'WordPress', '3.3').'</em>';
1904
+ } else {
1905
+ ?>
1906
+ <div id="plupload-upload-ui" style="width: 70%;">
1907
+ <div id="drag-drop-area">
1908
+ <div class="drag-drop-inside">
1909
+ <p class="drag-drop-info"><?php _e('Drop backup files here', 'updraftplus'); ?></p>
1910
+ <p><?php _ex('or', 'Uploader: Drop backup files here - or - Select Files'); ?></p>
1911
+ <p class="drag-drop-buttons"><input id="plupload-browse-button" type="button" value="<?php esc_attr_e('Select Files'); ?>" class="button" /></p>
1912
+ </div>
1913
+ </div>
1914
+ <div id="filelist">
1915
  </div>
1916
  </div>
1917
+ <?php
1918
+ }
1919
+ ?>
1920
 
1921
  </div>
1922
 
2082
  echo __('PHP memory limit','updraftplus').': '.ini_get('memory_limit').' <br/>';
2083
  echo sprintf(__('%s version:','updraftplus'), 'PHP').' '.phpversion().' - ';
2084
  echo '<a href="admin-ajax.php?page=updraftplus&action=updraft_ajax&subaction=phpinfo&nonce='.wp_create_nonce('updraftplus-credentialtest-nonce').'" id="updraftplus-phpinfo">'.__('show PHP information (phpinfo)', 'updraftplus').'</a><br/>';
2085
+ global $wpdb;
2086
+ echo sprintf(__('%s version:','updraftplus'), 'MySQL').' '.htmlspecialchars($wpdb->db_version()).'<br>';
2087
+ echo sprintf(__('%s version:','updraftplus'), 'Curl').' ';
2088
+ if (function_exists('curl_version') && function_exists('curl_exec')) {
2089
+ $cv = curl_version();
2090
+ echo $cv['version'].' / SSL: '.$cv['ssl_version'].' / libz: '.$cv['libz_version'];
2091
+ } else {
2092
+ echo '-';
2093
+ }
2094
+
2095
+ echo '<br>';
2096
 
2097
  if (version_compare(phpversion(), '5.2.0', '>=') && extension_loaded('zip')) {
2098
  $ziparchive_exists = __('Yes', 'updraftplus');
2117
 
2118
  echo __('Install plugins for debugging:', 'updraftplus').' <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=wp-crontrol'), 'install-plugin_wp-crontrol').'">WP Crontrol</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=sql-executioner'), 'install-plugin_sql-executioner').'">SQL Executioner</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=advanced-code-editor'), 'install-plugin_advanced-code-editor').'">Advanced Code Editor</a> | <a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&updraftplus_noautobackup=1&plugin=wp-filemanager'), 'install-plugin_wp-filemanager').'">WP Filemanager</a><br>';
2119
 
2120
+ echo "HTTP Get: ";
2121
+ echo '<input id="updraftplus_httpget_uri" type="text" style="width: 300px; height: 22px;"> ';
2122
+ echo '<a href="#" id="updraftplus_httpget_go">'.__('Fetch', 'updraftplus').'</a> ';
2123
+ echo '<a href="#" id="updraftplus_httpget_gocurl">'.__('Fetch', 'updraftplus').' (Curl)</a>';
2124
+ echo '<p id="updraftplus_httpget_results"></p>';
2125
+
2126
+ echo "Call WordPress action: ";
2127
+ echo '<input id="updraftplus_callwpaction" type="text" style="width: 300px; height: 22px;"> ';
2128
+ echo '<a href="#" id="updraftplus_callwpaction_go">'.__('Call', 'updraftplus').'</a> ';
2129
+ echo '<p id="updraftplus_callwpaction_results"></p>';
2130
+
2131
  echo '<h3>'.__('Total (uncompressed) on-disk data:','updraftplus').'</h3>';
2132
  echo '<p style="clear: left; max-width: 600px;"><em>'.__('N.B. This count is based upon what was, or was not, excluded the last time you saved the options.', 'updraftplus').'</em></p>';
2133
 
2177
  <?php if ($include_blurb) {
2178
  ?>
2179
  <div id="updraft_delete_old_dirs_pagediv" class="updated" style="padding:8px;"><p> <?php _e('Your WordPress install has old directories from its state before you restored/migrated (technical information: these are suffixed with -old). You should press this button to delete them as soon as you have verified that the restoration worked.','updraftplus');?></p><?php } ?>
2180
+ <form method="post" onsubmit="return updraft_delete_old_dirs();" action="<?php echo add_query_arg(array('updraft_restore_success' => false, 'action' => false, 'page' => 'updraftplus')); ?>">
2181
  <?php wp_nonce_field('updraftplus-credentialtest-nonce'); ?>
2182
+ <input type="hidden" name="action" value="updraft_delete_old_dirs">
2183
  <input type="submit" class="button-primary" value="<?php echo esc_attr(__('Delete Old Directories', 'updraftplus'));?>" />
2184
  </form>
2185
  <?php
2187
  }
2188
 
2189
 
2190
+ private function print_active_jobs() {
2191
  $cron = get_option('cron');
2192
  if (!is_array($cron)) $cron = array();
2193
  // $found_jobs = 0;
2212
  return $ret;
2213
  }
2214
 
2215
+ private function print_active_job($job_id, $is_oneshot = false, $time = false, $next_resumption = false) {
2216
 
2217
  $ret = '';
2218
 
2356
 
2357
  }
2358
 
2359
+ private function delete_old_dirs_go($show_return = true) {
2360
  echo ($show_return) ? '<h1>UpdraftPlus - '.__('Remove old directories', 'updraftplus').'</h1>' : '<h2>'.__('Remove old directories', 'updraftplus').'</h2>';
2361
 
2362
  if($this->delete_old_dirs()) {
2368
  }
2369
 
2370
  //deletes the -old directories that are created when a backup is restored.
2371
+ private function delete_old_dirs() {
2372
  global $wp_filesystem, $updraftplus;
2373
  $credentials = request_filesystem_credentials(wp_nonce_url(UpdraftPlus_Options::admin_page_url()."?page=updraftplus&action=updraft_delete_old_dirs", 'updraftplus-credentialtest-nonce'));
2374
  WP_Filesystem($credentials);
2405
  return $ret && $ret3 && $ret4;
2406
  }
2407
 
2408
+ private function delete_old_dirs_dir($dir, $wpfs = true) {
2409
 
2410
  $dir = trailingslashit($dir);
2411
 
2446
  }
2447
 
2448
  // The aim is to get a directory that is writable by the webserver, because that's the only way we can create zip files
2449
+ private function create_backup_dir() {
2450
 
2451
  global $wp_filesystem, $updraftplus;
2452
 
2500
  }
2501
 
2502
  //scans the content dir to see if any -old dirs are present
2503
+ private function scan_old_dirs() {
2504
  global $updraftplus;
2505
  $dirs = scandir(untrailingslashit(WP_CONTENT_DIR));
2506
  if (!is_array($dirs)) $dirs = array();
2512
  return false;
2513
  }
2514
 
2515
+ public function storagemethod_row($method, $header, $contents) {
2516
+ ?>
2517
+ <tr class="updraftplusmethod <?php echo $method;?>">
2518
+ <th><?php echo $header;?></th>
2519
+ <td><?php echo $contents;?></td>
2520
+ </tr>
2521
+ <?php
2522
+ }
2523
+
2524
  private function last_backup_html() {
2525
 
2526
  global $updraftplus;
2689
 
2690
  <div id="updraft-manualdecrypt-modal" style="width: 85%; margin: 16px; display:none; margin-left: 100px;">
2691
  <p><h3><?php _e("Manually decrypt a database backup file" ,'updraftplus');?></h3></p>
2692
+
2693
+ <?php
2694
+ global $wp_version;
2695
+ if (version_compare($wp_version, '3.3', '<')) {
2696
+ echo '<em>'.sprintf(__('This feature requires %s version %s or later', 'updraftplus'), 'WordPress', '3.3').'</em>';
2697
+ } else {
2698
+ ?>
2699
+
2700
  <div id="plupload-upload-ui2" style="width: 80%;">
2701
  <div id="drag-drop-area2">
2702
  <div class="drag-drop-inside">
2710
  </div>
2711
  </div>
2712
 
2713
+ <?php } ?>
2714
+
2715
  </div>
2716
 
2717
 
2929
  <?php
2930
  }
2931
 
2932
+ public function show_double_warning($text, $extraclass = '', $echo = true) {
 
 
2933
 
2934
+ $ret = "<div class=\"error updraftplusmethod $extraclass\"><p>$text</p></div>";
2935
+ $ret .= "<p style=\"border:1px solid; padding: 6px;\">$text</p>";
2936
 
2937
+ if ($echo) echo $ret;
2938
+ return $ret;
2939
 
2940
  }
2941
 
2942
+ public function optionfilter_split_every($value) {
2943
+ $value = absint($value);
2944
  if (!$value >= UPDRAFTPLUS_SPLIT_MIN) $value = UPDRAFTPLUS_SPLIT_MIN;
2945
  return $value;
2946
  }
2947
 
2948
+ public function curl_check($service, $has_fallback = false, $extraclass = '', $echo = true) {
2949
+
2950
+ $ret = '';
2951
+
2952
  // Check requirements
2953
  if (!function_exists("curl_init") || !function_exists('curl_exec')) {
2954
 
2955
+ $ret .= $this->show_double_warning('<strong>'.__('Warning','updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not included a <strong>required</strong> (for %s) module (%s). Please contact your web hosting provider\'s support and ask for them to enable it.', 'updraftplus'), $service, 'Curl').' '.sprintf(__("Your options are 1) Install/enable %s or 2) Change web hosting companies - %s is a standard PHP component, and required by all cloud backup plugins that we know of.",'updraftplus'), 'Curl', 'Curl'), $extraclass, false);
2956
 
2957
  } else {
2958
  $curl_version = curl_version();
2959
  $curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL);
2960
  if (!$curl_ssl_supported) {
2961
  if ($has_fallback) {
2962
+ $ret .= '<p><strong>'.__('Warning','updraftplus').':</strong> '.sprintf(__("Your web server's PHP/Curl installation does not support https access. Communications with %s will be unencrypted. ask your web host to install Curl/SSL in order to gain the ability for encryption (via an add-on).",'updraftplus'),$service).'</p>';
2963
  } else {
2964
+ $ret .= $this->show_double_warning('<p><strong>'.__('Warning','updraftplus').':</strong> '.sprintf(__("Your web server's PHP/Curl installation does not support https access. We cannot access %s without this support. Please contact your web hosting provider's support. %s <strong>requires</strong> Curl+https. Please do not file any support requests; there is no alternative.",'updraftplus'),$service).'</p>', $extraclass, false);
2965
  }
2966
  } else {
2967
+ $ret .= '<p><em>'.sprintf(__("Good news: Your site's communications with %s can be encrypted. If you see any errors to do with encryption, then look in the 'Expert Settings' for more help.", 'updraftplus'),$service).'</em></p>';
2968
  }
2969
  }
2970
+ if ($echo) {
2971
+ echo $ret;
2972
+ } else {
2973
+ return $ret;
2974
+ }
2975
  }
2976
 
2977
  # If $basedirs is passed as an array, then $directorieses must be too
3339
  }
3340
  } elseif (is_wp_error($files)) {
3341
  foreach ($files->get_error_codes() as $code) {
3342
+ if ('no_settings' == $code || 'no_addon' == $code || 'insufficient_php' == $code) continue;
3343
  $messages[] = array(
3344
  'method' => $method,
3345
  'desc' => $desc,
3561
  }
3562
 
3563
  foreach ($_POST as $key => $value) {
3564
+ if (0 === strpos($key, 'updraft_restore_')) {
3565
  $nkey = substr($key, 16);
3566
  if (!isset($entities_to_restore[$nkey])) {
3567
  $_POST['updraft_restore'][] = $nkey;
3578
  }
3579
  }
3580
 
 
 
3581
  if (0 == count($_POST['updraft_restore'])) {
3582
  echo '<p>'.__('ABORT: Could not find the information on which entities to restore.', 'updraftplus').'</p>';
3583
  echo '<p>'.__('If making a request for support, please include this information:','updraftplus').' '.count($_POST).' : '.htmlspecialchars(serialize($_POST)).'</p>';
3584
  return new WP_Error('missing_info', 'Backup information not found');
3585
  }
3586
 
3587
+ $updraftplus->log("Restore job started. Entities to restore: ".implode(', ', array_flip($entities_to_restore)));
3588
+
3589
  set_error_handler(array($updraftplus, 'php_error'), E_ALL & ~E_STRICT);
3590
 
3591
  /*
3774
  add_filter('pre_option_'.$opt, array($this, 'option_filter_'.$opt));
3775
  }
3776
  if (!function_exists('validate_current_theme')) require_once(ABSPATH.'wp-includes/themes');
3777
+
3778
+ # Have seen a case where the current theme in the DB began with a capital, but not on disk - and this breaks migrating from Windows to a case-sensitive system
3779
+ $template = get_option('template');
3780
+ if (!empty($template) && $template != WP_DEFAULT_THEME && $template != strtolower($template)) {
3781
+
3782
+ $theme_root = get_theme_root($template);
3783
+ $theme_root2 = get_theme_root(strtolower($template));
3784
+
3785
+ if (!file_exists("$theme_root/$template/style.css") && file_exists("$theme_root/".strtolower($template)."/style.css")) {
3786
+ $updraftplus->log_e("Theme directory (%s) not found, but lower-case version exists; updating database option accordingly", $template);
3787
+ update_option('template', strtolower($template));
3788
+ }
3789
+
3790
+ }
3791
+
3792
+
3793
  if (!validate_current_theme()) {
3794
  global $updraftplus;
3795
  echo '<strong>';
3832
  }
3833
 
3834
  private function get_settings_keys() {
3835
+ return array('updraft_autobackup_default', 'updraft_googledrive', 'updraftplus_tmp_googledrive_access_token', 'updraftplus_dismissedautobackup', 'updraftplus_dismissedexpiry', 'updraft_interval', 'updraft_interval_database', 'updraft_retain', 'updraft_retain_db', 'updraft_encryptionphrase', 'updraft_service', 'updraft_dropbox_appkey', 'updraft_dropbox_secret', 'updraft_googledrive_clientid', 'updraft_googledrive_secret', 'updraft_googledrive_remotepath', 'updraft_ftp_login', 'updraft_ftp_pass', 'updraft_ftp_remote_path', 'updraft_server_address', 'updraft_dir', 'updraft_email', 'updraft_delete_local', 'updraft_debug_mode', 'updraft_include_plugins', 'updraft_include_themes', 'updraft_include_uploads', 'updraft_include_others', 'updraft_include_wpcore', 'updraft_include_wpcore_exclude', 'updraft_include_more', 'updraft_include_blogs', 'updraft_include_mu-plugins', 'updraft_include_others_exclude', 'updraft_include_uploads_exclude', 'updraft_lastmessage', 'updraft_googledrive_token',
3836
  'updraft_dropboxtk_request_token', 'updraft_dropboxtk_access_token', 'updraft_dropbox_folder',
3837
+ 'updraft_last_backup', 'updraft_starttime_files', 'updraft_starttime_db', 'updraft_startday_db', 'updraft_startday_files', 'updraft_sftp_settings', 'updraft_s3', 'updraft_s3generic', 'updraft_dreamhost', 'updraft_s3generic_login', 'updraft_s3generic_pass', 'updraft_s3generic_remote_path', 'updraft_s3generic_endpoint', 'updraft_webdav_settings', 'updraft_disable_ping', 'updraft_openstack', 'updraft_bitcasa', 'updraft_cloudfiles', 'updraft_cloudfiles_user', 'updraft_cloudfiles_apikey', 'updraft_cloudfiles_path', 'updraft_cloudfiles_authurl', 'updraft_ssl_useservercerts', 'updraft_ssl_disableverify', 'updraft_s3_login', 'updraft_s3_pass', 'updraft_s3_remote_path', 'updraft_dreamobjects_login', 'updraft_dreamobjects_pass', 'updraft_dreamobjects_remote_path', 'updraft_report_warningsonly', 'updraft_report_wholebackup', 'updraft_log_syslog');
3838
  }
3839
 
3840
  }
 
 
backup.php CHANGED
@@ -141,7 +141,8 @@ class UpdraftPlus_Backup {
141
  $time_mod = (int)@filemtime($zip_name);
142
  if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
143
  $updraftplus->terminate_due_to_activity($zip_name, $time_now, $time_mod);
144
- } elseif (file_exists($zip_name)) {
 
145
  $updraftplus->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")");
146
  }
147
 
@@ -259,7 +260,7 @@ class UpdraftPlus_Backup {
259
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
260
  if (file_exists($method_include)) require_once($method_include);
261
 
262
- if ($service == "none" || $service == "") {
263
  $updraftplus->log("No remote despatch: user chose no remote backup service");
264
  $this->prune_retained_backups(array("none" => array(null, null)));
265
  } else {
@@ -841,6 +842,16 @@ class UpdraftPlus_Backup {
841
 
842
  $total_tables = 0;
843
 
 
 
 
 
 
 
 
 
 
 
844
  $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
845
  $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
846
 
@@ -860,12 +871,23 @@ class UpdraftPlus_Backup {
860
  # Why not just fail now? We saw a bizarre case when the results of really_is_writable() changed during the run.
861
  }
862
 
 
 
 
 
 
 
 
 
 
863
  $stitch_files = array();
864
 
865
  $how_many_tables = count($all_tables);
866
 
867
  $found_options_table = false;
868
 
 
 
869
  foreach ($all_tables as $table) {
870
 
871
  $manyrows_warning = false;
@@ -876,14 +898,14 @@ class UpdraftPlus_Backup {
876
  // The table file may already exist if we have produced it on a previous run
877
  $table_file_prefix = $file_base.'-db-table-'.$table.'.table';
878
 
879
- if ($this->table_prefix_raw.'options' == $table) $found_options_table = true;
880
 
881
  if (file_exists($this->updraft_dir.'/'.$table_file_prefix.'.gz')) {
882
  $updraftplus->log("Table $table: corresponding file already exists; moving on");
883
  $stitch_files[] = $table_file_prefix;
884
  } else {
885
  # === is needed, otherwise 'false' matches (i.e. prefix does not match)
886
- if (empty($this->table_prefix) || strpos($table, $this->table_prefix) === 0 ) {
887
 
888
  // Open file, store the handle
889
  $opened = $this->backup_db_open($this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true);
@@ -905,9 +927,9 @@ class UpdraftPlus_Backup {
905
  }
906
 
907
  # Don't include the job data for any backups - so that when the database is restored, it doesn't continue an apparently incomplete backup
908
- if (!empty($this->table_prefix) && $this->table_prefix.'sitemeta' == $table) {
909
  $where = 'meta_key NOT LIKE "updraft_jobdata_%"';
910
- } elseif (!empty($this->table_prefix) && $this->table_prefix.'options' == $table) {
911
  $where = 'option_name NOT LIKE "updraft_jobdata_%"';
912
  } else {
913
  $where = '';
@@ -943,7 +965,7 @@ class UpdraftPlus_Backup {
943
  # Have seen this happen; not sure how, but it was apparently deterministic; if the current process had been running for a long time, then apparently all database commands silently failed.
944
  # If we have been running that long, then the resumption may be far off; bring it closer
945
  $updraftplus->reschedule(60);
946
- $updraftplus->log("Have been running very long, and it seems the database went away; terminating");
947
  $updraftplus->record_still_alive();
948
  die;
949
  }
@@ -957,11 +979,17 @@ class UpdraftPlus_Backup {
957
  $time_mod = (int)@filemtime($backup_final_file_name);
958
  if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
959
  $updraftplus->terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod);
960
- } elseif (file_exists($backup_final_file_name)) {
 
961
  $updraftplus->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another UpdraftPlus terminated; thus we will continue.");
962
  }
963
 
964
  // Finally, stitch the files together
 
 
 
 
 
965
  $opendb = $this->backup_db_open($backup_final_file_name, true);
966
  if (false === $opendb) return false;
967
  $this->backup_db_header();
@@ -1140,12 +1168,18 @@ class UpdraftPlus_Backup {
1140
  }
1141
 
1142
  // Experimentation here shows that on large tables (we tested with 180,000 rows) on MyISAM, 1000 makes the table dump out 3x faster than the previous value of 100. After that, the benefit diminishes (increasing to 4000 only saved another 12%)
 
 
 
 
 
 
1143
  if($segment == 'none') {
1144
  $row_start = 0;
1145
- $row_inc = 1000;
1146
  } else {
1147
- $row_start = $segment * 1000;
1148
- $row_inc = 1000;
1149
  }
1150
 
1151
  $search = array("\x00", "\x0a", "\x0d", "\x1a");
@@ -1311,7 +1345,11 @@ class UpdraftPlus_Backup {
1311
  if (empty($this->attachments) || !is_array($this->attachments)) return;
1312
  foreach ($this->attachments as $attach) {
1313
  $mime_type = (preg_match('/\.gz$/', $attach)) ? 'application/x-gzip' : 'text/plain';
1314
- $phpmailer->AddAttachment($attach, '', 'base64', $mime_type);
 
 
 
 
1315
  }
1316
  }
1317
 
@@ -1691,15 +1729,16 @@ class UpdraftPlus_Backup {
1691
  # Since we don't test before the file has been created (so that zip_last_ratio has meaningful data), we rely on max_zip_batch being less than zip_split_every - which should always be the case
1692
  $reaching_split_limit = ( $this->zip_last_ratio > 0 && $original_size>0 && ($original_size + 1.1*$data_added_since_reopen*$this->zip_last_ratio) > $this->zip_split_every) ? true : false;
1693
 
1694
- if ($zipfiles_added_thisbatch > 500 || $reaching_split_limit || $data_added_since_reopen > $maxzipbatch || (time() - $this->zipfiles_lastwritetime) > 1.5) {
1695
 
 
1696
  $something_useful_sizetest = false;
1697
 
1698
  if ($data_added_since_reopen > $maxzipbatch) {
1699
  $something_useful_sizetest = true;
1700
  $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over ".round($maxzipbatch/1048576,1)." Mb added on this batch (".round($data_added_since_reopen/1048576,1)." Mb, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1701
- } elseif ($zipfiles_added_thisbatch >500) {
1702
- $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over 500 files added on this batch (".round($data_added_since_reopen/1048576,1)." Mb, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1703
  } elseif (!$reaching_split_limit) {
1704
  $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over 1.5 seconds have passed since the last write (".round($data_added_since_reopen/1048576,1)." Mb, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1705
  } else {
@@ -1713,7 +1752,7 @@ class UpdraftPlus_Backup {
1713
  }
1714
  }
1715
  $zipfiles_added_thisbatch = 0;
1716
-
1717
  # This triggers a re-open, later
1718
  unset($zip);
1719
  $files_zipadded_since_open = array();
141
  $time_mod = (int)@filemtime($zip_name);
142
  if (file_exists($zip_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
143
  $updraftplus->terminate_due_to_activity($zip_name, $time_now, $time_mod);
144
+ }
145
+ if (file_exists($zip_name)) {
146
  $updraftplus->log("File exists ($zip_name), but was apparently not modified within the last 30 seconds, so we assume that any previous run has now terminated (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod).")");
147
  }
148
 
260
  $method_include = UPDRAFTPLUS_DIR.'/methods/'.$service.'.php';
261
  if (file_exists($method_include)) require_once($method_include);
262
 
263
+ if ($service == "none" || '' == $service) {
264
  $updraftplus->log("No remote despatch: user chose no remote backup service");
265
  $this->prune_retained_backups(array("none" => array(null, null)));
266
  } else {
842
 
843
  $total_tables = 0;
844
 
845
+ # WP 3.9 onwards - https://core.trac.wordpress.org/browser/trunk/src/wp-includes/wp-db.php?rev=27925 - check_connection() allows us to get the database connection back if it had dropped
846
+ if (method_exists($wpdb, 'check_connection')) {
847
+ if (!$wpdb->check_connection(false)) {
848
+ $updraftplus->reschedule(60);
849
+ $updraftplus->log("It seems the database went away; scheduling a resumption and terminating for now");
850
+ $updraftplus->record_still_alive();
851
+ die;
852
+ }
853
+ }
854
+
855
  $all_tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
856
  $all_tables = array_map(create_function('$a', 'return $a[0];'), $all_tables);
857
 
871
  # Why not just fail now? We saw a bizarre case when the results of really_is_writable() changed during the run.
872
  }
873
 
874
+ # This check doesn't strictly get all possible duplicates; it's only designed for the case that can happen when moving between deprecated Windows setups and Linux
875
+ $duplicate_tables_exist = false;
876
+ foreach ($all_tables as $table) {
877
+ if (strtolower($table) != $table && in_array(strtolower($table), $all_tables)) {
878
+ $duplicate_tables_exist = true;
879
+ $updraftplus->log("Tables with names differing only based on case-sensitivity exist in the MySQL database: $table / ".strtolower($table));
880
+ }
881
+ }
882
+
883
  $stitch_files = array();
884
 
885
  $how_many_tables = count($all_tables);
886
 
887
  $found_options_table = false;
888
 
889
+ $is_multisite = is_multisite();
890
+
891
  foreach ($all_tables as $table) {
892
 
893
  $manyrows_warning = false;
898
  // The table file may already exist if we have produced it on a previous run
899
  $table_file_prefix = $file_base.'-db-table-'.$table.'.table';
900
 
901
+ if (strtolower($this->table_prefix_raw.'options') == strtolower($table) || ($is_multisite && strtolower($this->table_prefix_raw.'1_options') == strtolower($table))) $found_options_table = true;
902
 
903
  if (file_exists($this->updraft_dir.'/'.$table_file_prefix.'.gz')) {
904
  $updraftplus->log("Table $table: corresponding file already exists; moving on");
905
  $stitch_files[] = $table_file_prefix;
906
  } else {
907
  # === is needed, otherwise 'false' matches (i.e. prefix does not match)
908
+ if (empty($this->table_prefix) || ($duplicate_tables_exist == false && stripos($table, $this->table_prefix) === 0 ) || ($duplicate_tables_exist == true && strpos($table, $this->table_prefix) === 0 )) {
909
 
910
  // Open file, store the handle
911
  $opened = $this->backup_db_open($this->updraft_dir.'/'.$table_file_prefix.'.tmp.gz', true);
927
  }
928
 
929
  # Don't include the job data for any backups - so that when the database is restored, it doesn't continue an apparently incomplete backup
930
+ if (!empty($this->table_prefix) && strtolower($this->table_prefix.'sitemeta') == strtolower($table)) {
931
  $where = 'meta_key NOT LIKE "updraft_jobdata_%"';
932
+ } elseif (!empty($this->table_prefix) && strtolower($this->table_prefix.'options') == strtolower($table)) {
933
  $where = 'option_name NOT LIKE "updraft_jobdata_%"';
934
  } else {
935
  $where = '';
965
  # Have seen this happen; not sure how, but it was apparently deterministic; if the current process had been running for a long time, then apparently all database commands silently failed.
966
  # If we have been running that long, then the resumption may be far off; bring it closer
967
  $updraftplus->reschedule(60);
968
+ $updraftplus->log("Have been running very long, and it seems the database went away; scheduling a resumption and terminating for now");
969
  $updraftplus->record_still_alive();
970
  die;
971
  }
979
  $time_mod = (int)@filemtime($backup_final_file_name);
980
  if (file_exists($backup_final_file_name) && $time_mod>100 && ($time_now-$time_mod)<30) {
981
  $updraftplus->terminate_due_to_activity($backup_final_file_name, $time_now, $time_mod);
982
+ }
983
+ if (file_exists($backup_final_file_name)) {
984
  $updraftplus->log("The final database file ($backup_final_file_name) exists, but was apparently not modified within the last 30 seconds (time_mod=$time_mod, time_now=$time_now, diff=".($time_now-$time_mod)."). Thus we assume that another UpdraftPlus terminated; thus we will continue.");
985
  }
986
 
987
  // Finally, stitch the files together
988
+
989
+ if (!function_exists('gzopen')) {
990
+ $updraftplus->log("PHP function is disabled; abort expected: gzopen");
991
+ }
992
+
993
  $opendb = $this->backup_db_open($backup_final_file_name, true);
994
  if (false === $opendb) return false;
995
  $this->backup_db_header();
1168
  }
1169
 
1170
  // Experimentation here shows that on large tables (we tested with 180,000 rows) on MyISAM, 1000 makes the table dump out 3x faster than the previous value of 100. After that, the benefit diminishes (increasing to 4000 only saved another 12%)
1171
+
1172
+ $increment = 1000;
1173
+ if (!$updraftplus->something_useful_happened && !empty($updraftplus->current_resumption) && ($updraftplus->current_resumption - $updraftplus->last_successful_resumption > 1)) {
1174
+ $increment = 500;
1175
+ }
1176
+
1177
  if($segment == 'none') {
1178
  $row_start = 0;
1179
+ $row_inc = $increment;
1180
  } else {
1181
+ $row_start = $segment * $increment;
1182
+ $row_inc = $increment;
1183
  }
1184
 
1185
  $search = array("\x00", "\x0a", "\x0d", "\x1a");
1345
  if (empty($this->attachments) || !is_array($this->attachments)) return;
1346
  foreach ($this->attachments as $attach) {
1347
  $mime_type = (preg_match('/\.gz$/', $attach)) ? 'application/x-gzip' : 'text/plain';
1348
+ try {
1349
+ $phpmailer->AddAttachment($attach, '', 'base64', $mime_type);
1350
+ } catch (Exception $e) {
1351
+ $updraftplus->log("Exception occurred when adding attachment (".get_class($e)."): ".$e->getMessage());
1352
+ }
1353
  }
1354
  }
1355
 
1729
  # Since we don't test before the file has been created (so that zip_last_ratio has meaningful data), we rely on max_zip_batch being less than zip_split_every - which should always be the case
1730
  $reaching_split_limit = ( $this->zip_last_ratio > 0 && $original_size>0 && ($original_size + 1.1*$data_added_since_reopen*$this->zip_last_ratio) > $this->zip_split_every) ? true : false;
1731
 
1732
+ if ($zipfiles_added_thisbatch > UPDRAFTPLUS_MAXBATCHFILES || $reaching_split_limit || $data_added_since_reopen > $maxzipbatch || (time() - $this->zipfiles_lastwritetime) > 1.5) {
1733
 
1734
+ @set_time_limit(900);
1735
  $something_useful_sizetest = false;
1736
 
1737
  if ($data_added_since_reopen > $maxzipbatch) {
1738
  $something_useful_sizetest = true;
1739
  $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over ".round($maxzipbatch/1048576,1)." Mb added on this batch (".round($data_added_since_reopen/1048576,1)." Mb, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1740
+ } elseif ($zipfiles_added_thisbatch > UPDRAFTPLUS_MAXBATCHFILES) {
1741
+ $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over ".UPDRAFTPLUS_MAXBATCHFILES." files added on this batch (".round($data_added_since_reopen/1048576,1)." Mb, ".count($this->zipfiles_batched)." files batched, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1742
  } elseif (!$reaching_split_limit) {
1743
  $updraftplus->log("Adding batch to zip file (".$this->use_zip_object."): over 1.5 seconds have passed since the last write (".round($data_added_since_reopen/1048576,1)." Mb, $zipfiles_added_thisbatch (".$this->zipfiles_added_thisrun.") files added so far); re-opening (prior size: ".round($original_size/1024,1).' Kb)');
1744
  } else {
1752
  }
1753
  }
1754
  $zipfiles_added_thisbatch = 0;
1755
+
1756
  # This triggers a re-open, later
1757
  unset($zip);
1758
  $files_zipadded_since_open = array();
class-zip.php CHANGED
@@ -155,7 +155,11 @@ class UpdraftPlus_BinZip extends UpdraftPlus_PclZip {
155
  $ret = proc_close($process);
156
 
157
  if ($ret != 0 && $ret != 12) {
158
- $updraftplus->log("Binary zip: error (code: $ret - look it up in the Diagnostics section at http://www.info-zip.org/mans/zip.html for interpretation... and also check that your hosting account quota is not full)");
 
 
 
 
159
  if (!empty($w) && !$updraftplus_backup->debug) $updraftplus->log("Last output from zip: ".trim($w), 'debug');
160
  return false;
161
  }
155
  $ret = proc_close($process);
156
 
157
  if ($ret != 0 && $ret != 12) {
158
+ if ($ret < 128) {
159
+ $updraftplus->log("Binary zip: error (code: $ret - look it up in the Diagnostics section of the zip manual at http://www.info-zip.org/mans/zip.html for interpretation... and also check that your hosting account quota is not full)");
160
+ } else {
161
+ $updraftplus->log("Binary zip: error (code: $ret - a code above 127 normally means that the zip process was deliberately killed ... and also check that your hosting account quota is not full)");
162
+ }
163
  if (!empty($w) && !$updraftplus_backup->debug) $updraftplus->log("Last output from zip: ".trim($w), 'debug');
164
  return false;
165
  }
example-decrypt.php CHANGED
@@ -35,5 +35,3 @@ function rijndael_decrypt_file($file, $key) {
35
  print $rijndael->decrypt($ciphertext);
36
 
37
  }
38
-
39
- ?>
35
  print $rijndael->decrypt($ciphertext);
36
 
37
  }
 
 
images/bitcasa.png ADDED
Binary file
images/copy.png ADDED
Binary file
includes/Dropbox/OAuth/Consumer/ConsumerAbstract.php CHANGED
@@ -86,7 +86,7 @@ abstract class Dropbox_ConsumerAbstract
86
  header('Location: ' . $url);
87
  exit;
88
  } else {
89
- throw new Dropbox_Exception(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).', ''), 'Dropbox'));
90
  }
91
  ?><?php
92
  return false;
86
  header('Location: ' . $url);
87
  exit;
88
  } else {
89
+ throw new Dropbox_Exception(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).', 'updraftplus'), 'Dropbox'));
90
  }
91
  ?><?php
92
  return false;
includes/Google/Auth/Abstract.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ require_once "Google/Http/Request.php";
18
+
19
+ /**
20
+ * Abstract class for the Authentication in the API client
21
+ * @author Chris Chabot <chabotc@google.com>
22
+ *
23
+ */
24
+ abstract class Google_Auth_Abstract
25
+ {
26
+ /**
27
+ * An utility function that first calls $this->auth->sign($request) and then
28
+ * executes makeRequest() on that signed request. Used for when a request
29
+ * should be authenticated
30
+ * @param Google_Http_Request $request
31
+ * @return Google_Http_Request $request
32
+ */
33
+ abstract public function authenticatedRequest(Google_Http_Request $request);
34
+
35
+ abstract public function authenticate($code);
36
+ abstract public function sign(Google_Http_Request $request);
37
+ abstract public function createAuthUrl($scope);
38
+
39
+ abstract public function refreshToken($refreshToken);
40
+ abstract public function revokeToken();
41
+ }
includes/Google/Auth/AssertionCredentials.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Auth/OAuth2.php";
19
+ require_once "Google/Signer/P12.php";
20
+ require_once "Google/Utils.php";
21
+
22
+ /**
23
+ * Credentials object used for OAuth 2.0 Signed JWT assertion grants.
24
+ *
25
+ * @author Chirag Shah <chirags@google.com>
26
+ */
27
+ class Google_Auth_AssertionCredentials
28
+ {
29
+ const MAX_TOKEN_LIFETIME_SECS = 3600;
30
+
31
+ public $serviceAccountName;
32
+ public $scopes;
33
+ public $privateKey;
34
+ public $privateKeyPassword;
35
+ public $assertionType;
36
+ public $sub;
37
+ /**
38
+ * @deprecated
39
+ * @link http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
40
+ */
41
+ public $prn;
42
+ private $useCache;
43
+
44
+ /**
45
+ * @param $serviceAccountName
46
+ * @param $scopes array List of scopes
47
+ * @param $privateKey
48
+ * @param string $privateKeyPassword
49
+ * @param string $assertionType
50
+ * @param bool|string $sub The email address of the user for which the
51
+ * application is requesting delegated access.
52
+ * @param bool useCache Whether to generate a cache key and allow
53
+ * automatic caching of the generated token.
54
+ */
55
+ public function __construct(
56
+ $serviceAccountName,
57
+ $scopes,
58
+ $privateKey,
59
+ $privateKeyPassword = 'notasecret',
60
+ $assertionType = 'http://oauth.net/grant_type/jwt/1.0/bearer',
61
+ $sub = false,
62
+ $useCache = true
63
+ ) {
64
+ $this->serviceAccountName = $serviceAccountName;
65
+ $this->scopes = is_string($scopes) ? $scopes : implode(' ', $scopes);
66
+ $this->privateKey = $privateKey;
67
+ $this->privateKeyPassword = $privateKeyPassword;
68
+ $this->assertionType = $assertionType;
69
+ $this->sub = $sub;
70
+ $this->prn = $sub;
71
+ $this->useCache = $useCache;
72
+ }
73
+
74
+ /**
75
+ * Generate a unique key to represent this credential.
76
+ * @return string
77
+ */
78
+ public function getCacheKey()
79
+ {
80
+ if (!$this->useCache) {
81
+ return false;
82
+ }
83
+ $h = $this->sub;
84
+ $h .= $this->assertionType;
85
+ $h .= $this->privateKey;
86
+ $h .= $this->scopes;
87
+ $h .= $this->serviceAccountName;
88
+ return md5($h);
89
+ }
90
+
91
+ public function generateAssertion()
92
+ {
93
+ $now = time();
94
+
95
+ $jwtParams = array(
96
+ 'aud' => Google_Auth_OAuth2::OAUTH2_TOKEN_URI,
97
+ 'scope' => $this->scopes,
98
+ 'iat' => $now,
99
+ 'exp' => $now + self::MAX_TOKEN_LIFETIME_SECS,
100
+ 'iss' => $this->serviceAccountName,
101
+ );
102
+
103
+ if ($this->sub !== false) {
104
+ $jwtParams['sub'] = $this->sub;
105
+ } else if ($this->prn !== false) {
106
+ $jwtParams['prn'] = $this->prn;
107
+ }
108
+
109
+ return $this->makeSignedJwt($jwtParams);
110
+ }
111
+
112
+ /**
113
+ * Creates a signed JWT.
114
+ * @param array $payload
115
+ * @return string The signed JWT.
116
+ */
117
+ private function makeSignedJwt($payload)
118
+ {
119
+ $header = array('typ' => 'JWT', 'alg' => 'RS256');
120
+
121
+ $segments = array(
122
+ Google_Utils::urlSafeB64Encode(json_encode($header)),
123
+ Google_Utils::urlSafeB64Encode(json_encode($payload))
124
+ );
125
+
126
+ $signingInput = implode('.', $segments);
127
+ $signer = new Google_Signer_P12($this->privateKey, $this->privateKeyPassword);
128
+ $signature = $signer->sign($signingInput);
129
+ $segments[] = Google_Utils::urlSafeB64Encode($signature);
130
+
131
+ return implode(".", $segments);
132
+ }
133
+ }
includes/Google/Auth/Exception.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Exception.php";
19
+
20
+ class Google_Auth_Exception extends Google_Exception
21
+ {
22
+ }
includes/Google/Auth/LoginTicket.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2011 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Auth/Exception.php";
19
+
20
+ /**
21
+ * Class to hold information about an authenticated login.
22
+ *
23
+ * @author Brian Eaton <beaton@google.com>
24
+ */
25
+ class Google_Auth_LoginTicket
26
+ {
27
+ const USER_ATTR = "sub";
28
+
29
+ // Information from id token envelope.
30
+ private $envelope;
31
+
32
+ // Information from id token payload.
33
+ private $payload;
34
+
35
+ /**
36
+ * Creates a user based on the supplied token.
37
+ *
38
+ * @param string $envelope Header from a verified authentication token.
39
+ * @param string $payload Information from a verified authentication token.
40
+ */
41
+ public function __construct($envelope, $payload)
42
+ {
43
+ $this->envelope = $envelope;
44
+ $this->payload = $payload;
45
+ }
46
+
47
+ /**
48
+ * Returns the numeric identifier for the user.
49
+ * @throws Google_Auth_Exception
50
+ * @return
51
+ */
52
+ public function getUserId()
53
+ {
54
+ if (array_key_exists(self::USER_ATTR, $this->payload)) {
55
+ return $this->payload[self::USER_ATTR];
56
+ }
57
+ throw new Google_Auth_Exception("No user_id in token");
58
+ }
59
+
60
+ /**
61
+ * Returns attributes from the login ticket. This can contain
62
+ * various information about the user session.
63
+ * @return array
64
+ */
65
+ public function getAttributes()
66
+ {
67
+ return array("envelope" => $this->envelope, "payload" => $this->payload);
68
+ }
69
+ }
includes/Google/Auth/OAuth2.php ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Auth/Abstract.php";
19
+ require_once "Google/Auth/AssertionCredentials.php";
20
+ require_once "Google/Auth/Exception.php";
21
+ require_once "Google/Auth/LoginTicket.php";
22
+ require_once "Google/Client.php";
23
+ require_once "Google/Http/Request.php";
24
+ require_once "Google/Utils.php";
25
+ require_once "Google/Verifier/Pem.php";
26
+
27
+ /**
28
+ * Authentication class that deals with the OAuth 2 web-server authentication flow
29
+ *
30
+ * @author Chris Chabot <chabotc@google.com>
31
+ * @author Chirag Shah <chirags@google.com>
32
+ *
33
+ */
34
+ class Google_Auth_OAuth2 extends Google_Auth_Abstract
35
+ {
36
+ const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
37
+ const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
38
+ const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
39
+ const CLOCK_SKEW_SECS = 300; // five minutes in seconds
40
+ const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
41
+ const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
42
+ const OAUTH2_ISSUER = 'accounts.google.com';
43
+
44
+ /** @var Google_Auth_AssertionCredentials $assertionCredentials */
45
+ private $assertionCredentials;
46
+
47
+ /**
48
+ * @var string The state parameters for CSRF and other forgery protection.
49
+ */
50
+ private $state;
51
+
52
+ /**
53
+ * @var string The token bundle.
54
+ */
55
+ private $token;
56
+
57
+ /**
58
+ * @var Google_Client the base client
59
+ */
60
+ private $client;
61
+
62
+ /**
63
+ * Instantiates the class, but does not initiate the login flow, leaving it
64
+ * to the discretion of the caller.
65
+ */
66
+ public function __construct(Google_Client $client)
67
+ {
68
+ $this->client = $client;
69
+ }
70
+
71
+ /**
72
+ * Perform an authenticated / signed apiHttpRequest.
73
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
74
+ * (which can modify the request in what ever way fits the auth mechanism)
75
+ * and then calls apiCurlIO::makeRequest on the signed request
76
+ *
77
+ * @param Google_Http_Request $request
78
+ * @return Google_Http_Request The resulting HTTP response including the
79
+ * responseHttpCode, responseHeaders and responseBody.
80
+ */
81
+ public function authenticatedRequest(Google_Http_Request $request)
82
+ {
83
+ $request = $this->sign($request);
84
+ return $this->client->getIo()->makeRequest($request);
85
+ }
86
+
87
+ /**
88
+ * @param string $code
89
+ * @throws Google_Auth_Exception
90
+ * @return string
91
+ */
92
+ public function authenticate($code)
93
+ {
94
+ if (strlen($code) == 0) {
95
+ throw new Google_Auth_Exception("Invalid code");
96
+ }
97
+
98
+ // We got here from the redirect from a successful authorization grant,
99
+ // fetch the access token
100
+ $request = new Google_Http_Request(
101
+ self::OAUTH2_TOKEN_URI,
102
+ 'POST',
103
+ array(),
104
+ array(
105
+ 'code' => $code,
106
+ 'grant_type' => 'authorization_code',
107
+ 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
108
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
109
+ 'client_secret' => $this->client->getClassConfig($this, 'client_secret')
110
+ )
111
+ );
112
+ $request->disableGzip();
113
+ $response = $this->client->getIo()->makeRequest($request);
114
+
115
+ if ($response->getResponseHttpCode() == 200) {
116
+ $this->setAccessToken($response->getResponseBody());
117
+ $this->token['created'] = time();
118
+ return $this->getAccessToken();
119
+ } else {
120
+ $decodedResponse = json_decode($response->getResponseBody(), true);
121
+ if ($decodedResponse != null && $decodedResponse['error']) {
122
+ $decodedResponse = $decodedResponse['error'];
123
+ }
124
+ throw new Google_Auth_Exception(
125
+ sprintf(
126
+ "Error fetching OAuth2 access token, message: '%s'",
127
+ $decodedResponse
128
+ ),
129
+ $response->getResponseHttpCode()
130
+ );
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Create a URL to obtain user authorization.
136
+ * The authorization endpoint allows the user to first
137
+ * authenticate, and then grant/deny the access request.
138
+ * @param string $scope The scope is expressed as a list of space-delimited strings.
139
+ * @return string
140
+ */
141
+ public function createAuthUrl($scope)
142
+ {
143
+ $params = array(
144
+ 'response_type' => 'code',
145
+ 'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
146
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
147
+ 'scope' => $scope,
148
+ 'access_type' => $this->client->getClassConfig($this, 'access_type'),
149
+ 'approval_prompt' => $this->client->getClassConfig($this, 'approval_prompt'),
150
+ );
151
+
152
+ // If the list of scopes contains plus.login, add request_visible_actions
153
+ // to auth URL.
154
+ $rva = $this->client->getClassConfig($this, 'request_visible_actions');
155
+ if (strpos($scope, 'plus.login') && strlen($rva) > 0) {
156
+ $params['request_visible_actions'] = $rva;
157
+ }
158
+
159
+ if (isset($this->state)) {
160
+ $params['state'] = $this->state;
161
+ }
162
+
163
+ return self::OAUTH2_AUTH_URL . "?" . http_build_query($params, '', '&');
164
+ }
165
+
166
+ /**
167
+ * @param string $token
168
+ * @throws Google_Auth_Exception
169
+ */
170
+ public function setAccessToken($token)
171
+ {
172
+ $token = json_decode($token, true);
173
+ if ($token == null) {
174
+ throw new Google_Auth_Exception('Could not json decode the token');
175
+ }
176
+ if (! isset($token['access_token'])) {
177
+ throw new Google_Auth_Exception("Invalid token format");
178
+ }
179
+ $this->token = $token;
180
+ }
181
+
182
+ public function getAccessToken()
183
+ {
184
+ return json_encode($this->token);
185
+ }
186
+
187
+ public function setState($state)
188
+ {
189
+ $this->state = $state;
190
+ }
191
+
192
+ public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
193
+ {
194
+ $this->assertionCredentials = $creds;
195
+ }
196
+
197
+ /**
198
+ * Include an accessToken in a given apiHttpRequest.
199
+ * @param Google_Http_Request $request
200
+ * @return Google_Http_Request
201
+ * @throws Google_Auth_Exception
202
+ */
203
+ public function sign(Google_Http_Request $request)
204
+ {
205
+ // add the developer key to the request before signing it
206
+ if ($this->client->getClassConfig($this, 'developer_key')) {
207
+ $request->setQueryParam('key', $this->client->getClassConfig($this, 'developer_key'));
208
+ }
209
+
210
+ // Cannot sign the request without an OAuth access token.
211
+ if (null == $this->token && null == $this->assertionCredentials) {
212
+ return $request;
213
+ }
214
+
215
+ // Check if the token is set to expire in the next 30 seconds
216
+ // (or has already expired).
217
+ if ($this->isAccessTokenExpired()) {
218
+ if ($this->assertionCredentials) {
219
+ $this->refreshTokenWithAssertion();
220
+ } else {
221
+ if (! array_key_exists('refresh_token', $this->token)) {
222
+ throw new Google_Auth_Exception(
223
+ "The OAuth 2.0 access token has expired,"
224
+ ." and a refresh token is not available. Refresh tokens"
225
+ ." are not returned for responses that were auto-approved."
226
+ );
227
+ }
228
+ $this->refreshToken($this->token['refresh_token']);
229
+ }
230
+ }
231
+
232
+ // Add the OAuth2 header to the request
233
+ $request->setRequestHeaders(
234
+ array('Authorization' => 'Bearer ' . $this->token['access_token'])
235
+ );
236
+
237
+ return $request;
238
+ }
239
+
240
+ /**
241
+ * Fetches a fresh access token with the given refresh token.
242
+ * @param string $refreshToken
243
+ * @return void
244
+ */
245
+ public function refreshToken($refreshToken)
246
+ {
247
+ $this->refreshTokenRequest(
248
+ array(
249
+ 'client_id' => $this->client->getClassConfig($this, 'client_id'),
250
+ 'client_secret' => $this->client->getClassConfig($this, 'client_secret'),
251
+ 'refresh_token' => $refreshToken,
252
+ 'grant_type' => 'refresh_token'
253
+ )
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Fetches a fresh access token with a given assertion token.
259
+ * @param Google_Auth_AssertionCredentials $assertionCredentials optional.
260
+ * @return void
261
+ */
262
+ public function refreshTokenWithAssertion($assertionCredentials = null)
263
+ {
264
+ if (!$assertionCredentials) {
265
+ $assertionCredentials = $this->assertionCredentials;
266
+ }
267
+
268
+ $cacheKey = $assertionCredentials->getCacheKey();
269
+
270
+ if ($cacheKey) {
271
+ // We can check whether we have a token available in the
272
+ // cache. If it is expired, we can retrieve a new one from
273
+ // the assertion.
274
+ $token = $this->client->getCache()->get($cacheKey);
275
+ if ($token) {
276
+ $this->setAccessToken($token);
277
+ }
278
+ if (!$this->isAccessTokenExpired()) {
279
+ return;
280
+ }
281
+ }
282
+
283
+ $this->refreshTokenRequest(
284
+ array(
285
+ 'grant_type' => 'assertion',
286
+ 'assertion_type' => $assertionCredentials->assertionType,
287
+ 'assertion' => $assertionCredentials->generateAssertion(),
288
+ )
289
+ );
290
+
291
+ if ($cacheKey) {
292
+ // Attempt to cache the token.
293
+ $this->client->getCache()->set(
294
+ $cacheKey,
295
+ $this->getAccessToken()
296
+ );
297
+ }
298
+ }
299
+
300
+ private function refreshTokenRequest($params)
301
+ {
302
+ $http = new Google_Http_Request(
303
+ self::OAUTH2_TOKEN_URI,
304
+ 'POST',
305
+ array(),
306
+ $params
307
+ );
308
+ $http->disableGzip();
309
+ $request = $this->client->getIo()->makeRequest($http);
310
+
311
+ $code = $request->getResponseHttpCode();
312
+ $body = $request->getResponseBody();
313
+ if (200 == $code) {
314
+ $token = json_decode($body, true);
315
+ if ($token == null) {
316
+ throw new Google_Auth_Exception("Could not json decode the access token");
317
+ }
318
+
319
+ if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
320
+ throw new Google_Auth_Exception("Invalid token format");
321
+ }
322
+
323
+ $this->token['access_token'] = $token['access_token'];
324
+ $this->token['expires_in'] = $token['expires_in'];
325
+ $this->token['created'] = time();
326
+ } else {
327
+ throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
333
+ * token, if a token isn't provided.
334
+ * @throws Google_Auth_Exception
335
+ * @param string|null $token The token (access token or a refresh token) that should be revoked.
336
+ * @return boolean Returns True if the revocation was successful, otherwise False.
337
+ */
338
+ public function revokeToken($token = null)
339
+ {
340
+ if (!$token) {
341
+ $token = $this->token['access_token'];
342
+ }
343
+ $request = new Google_Http_Request(
344
+ self::OAUTH2_REVOKE_URI,
345
+ 'POST',
346
+ array(),
347
+ "token=$token"
348
+ );
349
+ $request->disableGzip();
350
+ $response = $this->client->getIo()->makeRequest($request);
351
+ $code = $response->getResponseHttpCode();
352
+ if ($code == 200) {
353
+ $this->token = null;
354
+ return true;
355
+ }
356
+
357
+ return false;
358
+ }
359
+
360
+ /**
361
+ * Returns if the access_token is expired.
362
+ * @return bool Returns True if the access_token is expired.
363
+ */
364
+ public function isAccessTokenExpired()
365
+ {
366
+ if (!$this->token || !isset($this->token['created'])) {
367
+ return true;
368
+ }
369
+
370
+ // If the token is set to expire in the next 30 seconds.
371
+ $expired = ($this->token['created']
372
+ + ($this->token['expires_in'] - 30)) < time();
373
+
374
+ return $expired;
375
+ }
376
+
377
+ // Gets federated sign-on certificates to use for verifying identity tokens.
378
+ // Returns certs as array structure, where keys are key ids, and values
379
+ // are PEM encoded certificates.
380
+ private function getFederatedSignOnCerts()
381
+ {
382
+ return $this->retrieveCertsFromLocation(
383
+ $this->client->getClassConfig($this, 'federated_signon_certs_url')
384
+ );
385
+ }
386
+
387
+ /**
388
+ * Retrieve and cache a certificates file.
389
+ * @param $url location
390
+ * @return array certificates
391
+ */
392
+ public function retrieveCertsFromLocation($url)
393
+ {
394
+ // If we're retrieving a local file, just grab it.
395
+ if ("http" != substr($url, 0, 4)) {
396
+ $file = file_get_contents($url);
397
+ if ($file) {
398
+ return json_decode($file, true);
399
+ } else {
400
+ throw new Google_Auth_Exception(
401
+ "Failed to retrieve verification certificates: '" .
402
+ $url . "'."
403
+ );
404
+ }
405
+ }
406
+
407
+ // This relies on makeRequest caching certificate responses.
408
+ $request = $this->client->getIo()->makeRequest(
409
+ new Google_Http_Request(
410
+ $url
411
+ )
412
+ );
413
+ if ($request->getResponseHttpCode() == 200) {
414
+ $certs = json_decode($request->getResponseBody(), true);
415
+ if ($certs) {
416
+ return $certs;
417
+ }
418
+ }
419
+ throw new Google_Auth_Exception(
420
+ "Failed to retrieve verification certificates: '" .
421
+ $request->getResponseBody() . "'.",
422
+ $request->getResponseHttpCode()
423
+ );
424
+ }
425
+
426
+ /**
427
+ * Verifies an id token and returns the authenticated apiLoginTicket.
428
+ * Throws an exception if the id token is not valid.
429
+ * The audience parameter can be used to control which id tokens are
430
+ * accepted. By default, the id token must have been issued to this OAuth2 client.
431
+ *
432
+ * @param $id_token
433
+ * @param $audience
434
+ * @return Google_Auth_LoginTicket
435
+ */
436
+ public function verifyIdToken($id_token = null, $audience = null)
437
+ {
438
+ if (!$id_token) {
439
+ $id_token = $this->token['id_token'];
440
+ }
441
+ $certs = $this->getFederatedSignonCerts();
442
+ if (!$audience) {
443
+ $audience = $this->client->getClassConfig($this, 'client_id');
444
+ }
445
+
446
+ return $this->verifySignedJwtWithCerts($id_token, $certs, $audience, self::OAUTH2_ISSUER);
447
+ }
448
+
449
+ /**
450
+ * Verifies the id token, returns the verified token contents.
451
+ *
452
+ * @param $jwt the token
453
+ * @param $certs array of certificates
454
+ * @param $required_audience the expected consumer of the token
455
+ * @param [$issuer] the expected issues, defaults to Google
456
+ * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
457
+ * @return token information if valid, false if not
458
+ */
459
+ public function verifySignedJwtWithCerts(
460
+ $jwt,
461
+ $certs,
462
+ $required_audience,
463
+ $issuer = null,
464
+ $max_expiry = null
465
+ ) {
466
+ if (!$max_expiry) {
467
+ // Set the maximum time we will accept a token for.
468
+ $max_expiry = self::MAX_TOKEN_LIFETIME_SECS;
469
+ }
470
+
471
+ $segments = explode(".", $jwt);
472
+ if (count($segments) != 3) {
473
+ throw new Google_Auth_Exception("Wrong number of segments in token: $jwt");
474
+ }
475
+ $signed = $segments[0] . "." . $segments[1];
476
+ $signature = Google_Utils::urlSafeB64Decode($segments[2]);
477
+
478
+ // Parse envelope.
479
+ $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
480
+ if (!$envelope) {
481
+ throw new Google_Auth_Exception("Can't parse token envelope: " . $segments[0]);
482
+ }
483
+
484
+ // Parse token
485
+ $json_body = Google_Utils::urlSafeB64Decode($segments[1]);
486
+ $payload = json_decode($json_body, true);
487
+ if (!$payload) {
488
+ throw new Google_Auth_Exception("Can't parse token payload: " . $segments[1]);
489
+ }
490
+
491
+ // Check signature
492
+ $verified = false;
493
+ foreach ($certs as $keyName => $pem) {
494
+ $public_key = new Google_Verifier_Pem($pem);
495
+ if ($public_key->verify($signed, $signature)) {
496
+ $verified = true;
497
+ break;
498
+ }
499
+ }
500
+
501
+ if (!$verified) {
502
+ throw new Google_Auth_Exception("Invalid token signature: $jwt");
503
+ }
504
+
505
+ // Check issued-at timestamp
506
+ $iat = 0;
507
+ if (array_key_exists("iat", $payload)) {
508
+ $iat = $payload["iat"];
509
+ }
510
+ if (!$iat) {
511
+ throw new Google_Auth_Exception("No issue time in token: $json_body");
512
+ }
513
+ $earliest = $iat - self::CLOCK_SKEW_SECS;
514
+
515
+ // Check expiration timestamp
516
+ $now = time();
517
+ $exp = 0;
518
+ if (array_key_exists("exp", $payload)) {
519
+ $exp = $payload["exp"];
520
+ }
521
+ if (!$exp) {
522
+ throw new Google_Auth_Exception("No expiration time in token: $json_body");
523
+ }
524
+ if ($exp >= $now + $max_expiry) {
525
+ throw new Google_Auth_Exception(
526
+ sprintf("Expiration time too far in future: %s", $json_body)
527
+ );
528
+ }
529
+
530
+ $latest = $exp + self::CLOCK_SKEW_SECS;
531
+ if ($now < $earliest) {
532
+ throw new Google_Auth_Exception(
533
+ sprintf(
534
+ "Token used too early, %s < %s: %s",
535
+ $now,
536
+ $earliest,
537
+ $json_body
538
+ )
539
+ );
540
+ }
541
+ if ($now > $latest) {
542
+ throw new Google_Auth_Exception(
543
+ sprintf(
544
+ "Token used too late, %s > %s: %s",
545
+ $now,
546
+ $latest,
547
+ $json_body
548
+ )
549
+ );
550
+ }
551
+
552
+ $iss = $payload['iss'];
553
+ if ($issuer && $iss != $issuer) {
554
+ throw new Google_Auth_Exception(
555
+ sprintf(
556
+ "Invalid issuer, %s != %s: %s",
557
+ $iss,
558
+ $issuer,
559
+ $json_body
560
+ )
561
+ );
562
+ }
563
+
564
+ // Check audience
565
+ $aud = $payload["aud"];
566
+ if ($aud != $required_audience) {
567
+ throw new Google_Auth_Exception(
568
+ sprintf(
569
+ "Wrong recipient, %s != %s:",
570
+ $aud,
571
+ $required_audience,
572
+ $json_body
573
+ )
574
+ );
575
+ }
576
+
577
+ // All good.
578
+ return new Google_Auth_LoginTicket($envelope, $payload);
579
+ }
580
+ }
includes/Google/Auth/Simple.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Auth/Abstract.php";
19
+ require_once "Google/Http/Request.php";
20
+
21
+ /**
22
+ * Simple API access implementation. Can either be used to make requests
23
+ * completely unauthenticated, or by using a Simple API Access developer
24
+ * key.
25
+ * @author Chris Chabot <chabotc@google.com>
26
+ * @author Chirag Shah <chirags@google.com>
27
+ */
28
+ class Google_Auth_Simple extends Google_Auth_Abstract
29
+ {
30
+ private $key = null;
31
+ private $client;
32
+
33
+ public function __construct(Google_Client $client, $config = null)
34
+ {
35
+ $this->client = $client;
36
+ }
37
+
38
+ /**
39
+ * Perform an authenticated / signed apiHttpRequest.
40
+ * This function takes the apiHttpRequest, calls apiAuth->sign on it
41
+ * (which can modify the request in what ever way fits the auth mechanism)
42
+ * and then calls apiCurlIO::makeRequest on the signed request
43
+ *
44
+ * @param Google_Http_Request $request
45
+ * @return Google_Http_Request The resulting HTTP response including the
46
+ * responseHttpCode, responseHeaders and responseBody.
47
+ */
48
+ public function authenticatedRequest(Google_Http_Request $request)
49
+ {
50
+ $request = $this->sign($request);
51
+ return $this->io->makeRequest($request);
52
+ }
53
+
54
+ public function authenticate($code)
55
+ {
56
+ throw new Google_Auth_Exception("Simple auth does not exchange tokens.");
57
+ }
58
+
59
+ public function setAccessToken($accessToken)
60
+ {
61
+ /* noop*/
62
+ }
63
+
64
+ public function getAccessToken()
65
+ {
66
+ return null;
67
+ }
68
+
69
+ public function createAuthUrl($scope)
70
+ {
71
+ return null;
72
+ }
73
+
74
+ public function refreshToken($refreshToken)
75
+ {
76
+ /* noop*/
77
+ }
78
+
79
+ public function revokeToken()
80
+ {
81
+ /* noop*/
82
+ }
83
+
84
+ public function sign(Google_Http_Request $request)
85
+ {
86
+ $key = $this->client->getClassConfig($this, 'developer_key');
87
+ if ($key) {
88
+ $request->setQueryParam('key', $key);
89
+ }
90
+ return $request;
91
+ }
92
+ }
includes/Google/Cache/Abstract.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Abstract storage class
20
+ *
21
+ * @author Chris Chabot <chabotc@google.com>
22
+ */
23
+ abstract class Google_Cache_Abstract
24
+ {
25
+
26
+ abstract public function __construct(Google_Client $client);
27
+
28
+ /**
29
+ * Retrieves the data for the given key, or false if they
30
+ * key is unknown or expired
31
+ *
32
+ * @param String $key The key who's data to retrieve
33
+ * @param boolean|int $expiration Expiration time in seconds
34
+ *
35
+ */
36
+ abstract public function get($key, $expiration = false);
37
+
38
+ /**
39
+ * Store the key => $value set. The $value is serialized
40
+ * by this function so can be of any type
41
+ *
42
+ * @param string $key Key of the data
43
+ * @param string $value data
44
+ */
45
+ abstract public function set($key, $value);
46
+
47
+ /**
48
+ * Removes the key/data pair for the given $key
49
+ *
50
+ * @param String $key
51
+ */
52
+ abstract public function delete($key);
53
+ }
includes/Google/Cache/Apc.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Cache/Abstract.php";
19
+ require_once "Google/Cache/Exception.php";
20
+
21
+ /**
22
+ * A persistent storage class based on the APC cache, which is not
23
+ * really very persistent, as soon as you restart your web server
24
+ * the storage will be wiped, however for debugging and/or speed
25
+ * it can be useful, and cache is a lot cheaper then storage.
26
+ *
27
+ * @author Chris Chabot <chabotc@google.com>
28
+ */
29
+ class Google_Cache_Apc extends Google_Cache_Abstract
30
+ {
31
+ public function __construct(Google_Client $client)
32
+ {
33
+ if (! function_exists('apc_add') ) {
34
+ throw new Google_Cache_Exception("Apc functions not available");
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @inheritDoc
40
+ */
41
+ public function get($key, $expiration = false)
42
+ {
43
+ $ret = apc_fetch($key);
44
+ if ($ret === false) {
45
+ return false;
46
+ }
47
+ if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
48
+ $this->delete($key);
49
+ return false;
50
+ }
51
+ return $ret['data'];
52
+ }
53
+
54
+ /**
55
+ * @inheritDoc
56
+ */
57
+ public function set($key, $value)
58
+ {
59
+ $rc = apc_store($key, array('time' => time(), 'data' => $value));
60
+ if ($rc == false) {
61
+ throw new Google_Cache_Exception("Couldn't store data");
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @inheritDoc
67
+ * @param String $key
68
+ */
69
+ public function delete($key)
70
+ {
71
+ apc_delete($key);
72
+ }
73
+ }
includes/Google/Cache/Exception.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ require_once "Google/Exception.php";
18
+
19
+ class Google_Cache_Exception extends Google_Exception
20
+ {
21
+ }
includes/Google/Cache/File.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Cache/Abstract.php";
19
+ require_once "Google/Cache/Exception.php";
20
+
21
+ /*
22
+ * This class implements a basic on disk storage. While that does
23
+ * work quite well it's not the most elegant and scalable solution.
24
+ * It will also get you into a heap of trouble when you try to run
25
+ * this in a clustered environment.
26
+ *
27
+ * @author Chris Chabot <chabotc@google.com>
28
+ */
29
+ class Google_Cache_File extends Google_Cache_Abstract
30
+ {
31
+ const MAX_LOCK_RETRIES = 10;
32
+ private $path;
33
+ private $fh;
34
+
35
+ public function __construct(Google_Client $client)
36
+ {
37
+ $this->path = $client->getClassConfig($this, 'directory');
38
+ }
39
+
40
+ public function get($key, $expiration = false)
41
+ {
42
+ $storageFile = $this->getCacheFile($key);
43
+ $data = false;
44
+
45
+ if (!file_exists($storageFile)) {
46
+ return false;
47
+ }
48
+
49
+ if ($expiration) {
50
+ $mtime = filemtime($storageFile);
51
+ if (($now - $mtime) >= $expiration) {
52
+ $this->delete($key);
53
+ return false;
54
+ }
55
+ }
56
+
57
+ if ($this->acquireReadLock($storageFile)) {
58
+ $data = fread($this->fh, filesize($storageFile));
59
+ $data = unserialize($data);
60
+ $this->unlock($storageFile);
61
+ }
62
+
63
+ return $data;
64
+ }
65
+
66
+ public function set($key, $value)
67
+ {
68
+ $storageFile = $this->getWriteableCacheFile($key);
69
+ if ($this->acquireWriteLock($storageFile)) {
70
+ // We serialize the whole request object, since we don't only want the
71
+ // responseContent but also the postBody used, headers, size, etc.
72
+ $data = serialize($value);
73
+ $result = fwrite($this->fh, $data);
74
+ $this->unlock($storageFile);
75
+ }
76
+ }
77
+
78
+ public function delete($key)
79
+ {
80
+ $file = $this->getCacheFile($key);
81
+ if (file_exists($file) && !unlink($file)) {
82
+ throw new Google_Cache_Exception("Cache file could not be deleted");
83
+ }
84
+ }
85
+
86
+ private function getWriteableCacheFile($file)
87
+ {
88
+ return $this->getCacheFile($file, true);
89
+ }
90
+
91
+ private function getCacheFile($file, $forWrite = false)
92
+ {
93
+ return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
94
+ }
95
+
96
+ private function getCacheDir($file, $forWrite)
97
+ {
98
+ // use the first 2 characters of the hash as a directory prefix
99
+ // this should prevent slowdowns due to huge directory listings
100
+ // and thus give some basic amount of scalability
101
+ $storageDir = $this->path . '/' . substr(md5($file), 0, 2);
102
+ if ($forWrite && ! is_dir($storageDir)) {
103
+ if (! mkdir($storageDir, 0755, true)) {
104
+ throw new Google_Cache_Exception("Could not create storage directory: $storageDir");
105
+ }
106
+ }
107
+ return $storageDir;
108
+ }
109
+
110
+ private function acquireReadLock($storageFile)
111
+ {
112
+ return $this->acquireLock(LOCK_SH, $storageFile);
113
+ }
114
+
115
+ private function acquireWriteLock($storageFile)
116
+ {
117
+ $rc = $this->acquireLock(LOCK_EX, $storageFile);
118
+ if (!$rc) {
119
+ $this->delete($storageFile);
120
+ }
121
+ return $rc;
122
+ }
123
+
124
+ private function acquireLock($type, $storageFile)
125
+ {
126
+ $mode = $type == LOCK_EX ? "w" : "r";
127
+ $this->fh = fopen($storageFile, $mode);
128
+ $count = 0;
129
+ while (!flock($this->fh, $type | LOCK_NB)) {
130
+ // Sleep for 10ms.
131
+ usleep(10000);
132
+ if (++$count < self::MAX_LOCK_RETRIES) {
133
+ return false;
134
+ }
135
+ }
136
+ return true;
137
+ }
138
+
139
+ public function unlock($storageFile)
140
+ {
141
+ if ($this->fh) {
142
+ flock($this->fh, LOCK_UN);
143
+ }
144
+ }
145
+ }
includes/Google/Cache/Memcache.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2008 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Cache/Abstract.php";
19
+ require_once "Google/Cache/Exception.php";
20
+
21
+ /**
22
+ * A persistent storage class based on the memcache, which is not
23
+ * really very persistent, as soon as you restart your memcache daemon
24
+ * the storage will be wiped.
25
+ *
26
+ * Will use either the memcache or memcached extensions, preferring
27
+ * memcached.
28
+ *
29
+ * @author Chris Chabot <chabotc@google.com>
30
+ */
31
+ class Google_Cache_Memcache extends Google_Cache_Abstract
32
+ {
33
+ private $connection = false;
34
+ private $mc = false;
35
+ private $host;
36
+ private $port;
37
+
38
+ public function __construct(Google_Client $client)
39
+ {
40
+ if (!function_exists('memcache_connect') && !class_exists("Memcached")) {
41
+ throw new Google_Cache_Exception("Memcache functions not available");
42
+ }
43
+ if ($client->isAppEngine()) {
44
+ // No credentials needed for GAE.
45
+ $this->mc = new Memcached();
46
+ $this->connection = true;
47
+ } else {
48
+ $this->host = $client->getClassConfig($this, 'host');
49
+ $this->port = $client->getClassConfig($this, 'port');
50
+ if (empty($this->host) || empty($this->port)) {
51
+ throw new Google_Cache_Exception("You need to supply a valid memcache host and port");
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @inheritDoc
58
+ */
59
+ public function get($key, $expiration = false)
60
+ {
61
+ $this->connect();
62
+ $ret = false;
63
+ if ($this->mc) {
64
+ $ret = $this->mc->get($key);
65
+ } else {
66
+ $ret = memcache_get($this->connection, $key);
67
+ }
68
+ if ($ret === false) {
69
+ return false;
70
+ }
71
+ if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
72
+ $this->delete($key);
73
+ return false;
74
+ }
75
+ return $ret['data'];
76
+ }
77
+
78
+ /**
79
+ * @inheritDoc
80
+ * @param string $key
81
+ * @param string $value
82
+ * @throws Google_Cache_Exception
83
+ */
84
+ public function set($key, $value)
85
+ {
86
+ $this->connect();
87
+ // we store it with the cache_time default expiration so objects will at
88
+ // least get cleaned eventually.
89
+ $data = array('time' => time(), 'data' => $value);
90
+ $rc = false;
91
+ if ($this->mc) {
92
+ $rc = $this->mc->set($key, $data);
93
+ } else {
94
+ $rc = memcache_set($this->connection, $key, $data, false);
95
+ }
96
+ if ($rc == false) {
97
+ throw new Google_Cache_Exception("Couldn't store data in cache");
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @inheritDoc
103
+ * @param String $key
104
+ */
105
+ public function delete($key)
106
+ {
107
+ $this->connect();
108
+ if ($this->mc) {
109
+ $this->mc->delete($key, 0);
110
+ } else {
111
+ memcache_delete($this->connection, $key, 0);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Lazy initialiser for memcache connection. Uses pconnect for to take
117
+ * advantage of the persistence pool where possible.
118
+ */
119
+ private function connect()
120
+ {
121
+ if ($this->connection) {
122
+ return;
123
+ }
124
+
125
+ if (class_exists("Memcached")) {
126
+ $this->mc = new Memcached();
127
+ $this->mc->addServer($this->host, $this->port);
128
+ $this->connection = true;
129
+ } else {
130
+ $this->connection = memcache_pconnect($this->host, $this->port);
131
+ }
132
+
133
+ if (! $this->connection) {
134
+ throw new Google_Cache_Exception("Couldn't connect to memcache server");
135
+ }
136
+ }
137
+ }
includes/Google/Cache/Null.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once "Google/Cache/Abstract.php";
19
+ require_once "Google/Cache/Exception.php";
20
+
21
+ /**
22
+ * A blank storage class, for cases where caching is not
23
+ * required.
24
+ */
25
+ class Google_Cache_Null extends Google_Cache_Abstract
26
+ {
27
+ public function __construct(Google_Client $client)
28
+ {
29
+
30
+ }
31
+
32
+ /**
33
+ * @inheritDoc
34
+ */
35
+ public function get($key, $expiration = false)
36
+ {
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * @inheritDoc
42
+ */
43
+ public function set($key, $value)
44
+ {
45
+ // Nop.
46
+ }
47
+
48
+ /**
49
+ * @inheritDoc
50
+ * @param String $key
51
+ */
52
+ public function delete($key)
53
+ {
54
+ // Nop.
55
+ }
56
+ }
includes/Google/Client.php ADDED
@@ -0,0 +1,608 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Auth/AssertionCredentials.php';
19
+ require_once 'Google/Cache/File.php';
20
+ require_once 'Google/Cache/Memcache.php';
21
+ require_once 'Google/Config.php';
22
+ require_once 'Google/Collection.php';
23
+ require_once 'Google/Exception.php';
24
+ require_once 'Google/IO/Curl.php';
25
+ require_once 'Google/IO/Stream.php';
26
+ require_once 'Google/Model.php';
27
+ require_once 'Google/Service.php';
28
+ require_once 'Google/Service/Resource.php';
29
+
30
+ /**
31
+ * The Google API Client
32
+ * http://code.google.com/p/google-api-php-client/
33
+ *
34
+ * @author Chris Chabot <chabotc@google.com>
35
+ * @author Chirag Shah <chirags@google.com>
36
+ */
37
+ class Google_Client
38
+ {
39
+ const LIBVER = "1.0.5-beta";
40
+ const USER_AGENT_SUFFIX = "google-api-php-client/";
41
+ /**
42
+ * @var Google_Auth_Abstract $auth
43
+ */
44
+ private $auth;
45
+
46
+ /**
47
+ * @var Google_IO_Abstract $io
48
+ */
49
+ private $io;
50
+
51
+ /**
52
+ * @var Google_Cache_Abstract $cache
53
+ */
54
+ private $cache;
55
+
56
+ /**
57
+ * @var Google_Config $config
58
+ */
59
+ private $config;
60
+
61
+ /**
62
+ * @var boolean $deferExecution
63
+ */
64
+ private $deferExecution = false;
65
+
66
+ /** @var array $scopes */
67
+ // Scopes requested by the client
68
+ protected $requestedScopes = array();
69
+
70
+ // definitions of services that are discovered.
71
+ protected $services = array();
72
+
73
+ // Used to track authenticated state, can't discover services after doing authenticate()
74
+ private $authenticated = false;
75
+
76
+ /**
77
+ * Construct the Google Client.
78
+ *
79
+ * @param $config Google_Config or string for the ini file to load
80
+ */
81
+ public function __construct($config = null)
82
+ {
83
+ if (! ini_get('date.timezone') &&
84
+ function_exists('date_default_timezone_set')) {
85
+ date_default_timezone_set('UTC');
86
+ }
87
+
88
+ if (is_string($config) && strlen($config)) {
89
+ $config = new Google_Config($config);
90
+ } else if ( !($config instanceof Google_Config)) {
91
+ $config = new Google_Config();
92
+
93
+ if ($this->isAppEngine()) {
94
+ // Automatically use Memcache if we're in AppEngine.
95
+ $config->setCacheClass('Google_Cache_Memcache');
96
+ }
97
+
98
+ if (version_compare(phpversion(), "5.3.4", "<=") || $this->isAppEngine()) {
99
+ // Automatically disable compress.zlib, as currently unsupported.
100
+ $config->setClassConfig('Google_Http_Request', 'disable_gzip', true);
101
+ }
102
+ }
103
+
104
+ if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) {
105
+ if (function_exists('curl_version') && function_exists('curl_exec')) {
106
+ $config->setIoClass("Google_Io_Curl");
107
+ } else {
108
+ $config->setIoClass("Google_Io_Stream");
109
+ }
110
+ }
111
+
112
+ $this->config = $config;
113
+ }
114
+
115
+ /**
116
+ * Get a string containing the version of the library.
117
+ *
118
+ * @return string
119
+ */
120
+ public function getLibraryVersion()
121
+ {
122
+ return self::LIBVER;
123
+ }
124
+
125
+ /**
126
+ * Attempt to exchange a code for an valid authentication token.
127
+ * Helper wrapped around the OAuth 2.0 implementation.
128
+ *
129
+ * @param $code string code from accounts.google.com
130
+ * @return string token
131
+ */
132
+ public function authenticate($code)
133
+ {
134
+ $this->authenticated = true;
135
+ return $this->getAuth()->authenticate($code);
136
+ }
137
+
138
+ /**
139
+ * Set the auth config from the JSON string provided.
140
+ * This structure should match the file downloaded from
141
+ * the "Download JSON" button on in the Google Developer
142
+ * Console.
143
+ * @param string $json the configuration json
144
+ */
145
+ public function setAuthConfig($json)
146
+ {
147
+ $data = json_decode($json);
148
+ $key = isset($data->installed) ? 'installed' : 'web';
149
+ if (!isset($data->$key)) {
150
+ throw new Google_Exception("Invalid client secret JSON file.");
151
+ }
152
+ $this->setClientId($data->$key->client_id);
153
+ $this->setClientSecret($data->$key->client_secret);
154
+ if (isset($data->$key->redirect_uris)) {
155
+ $this->setRedirectUri($data->$key->redirect_uris[0]);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Set the auth config from the JSON file in the path
161
+ * provided. This should match the file downloaded from
162
+ * the "Download JSON" button on in the Google Developer
163
+ * Console.
164
+ * @param string $file the file location of the client json
165
+ */
166
+ public function setAuthConfigFile($file)
167
+ {
168
+ $this->setAuthConfig(file_get_contents($file));
169
+ }
170
+
171
+ /**
172
+ * @return array
173
+ * @visible For Testing
174
+ */
175
+ public function prepareScopes()
176
+ {
177
+ if (empty($this->requestedScopes)) {
178
+ throw new Google_Auth_Exception("No scopes specified");
179
+ }
180
+ $scopes = implode(' ', $this->requestedScopes);
181
+ return $scopes;
182
+ }
183
+
184
+ /**
185
+ * Set the OAuth 2.0 access token using the string that resulted from calling createAuthUrl()
186
+ * or Google_Client#getAccessToken().
187
+ * @param string $accessToken JSON encoded string containing in the following format:
188
+ * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
189
+ * "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
190
+ */
191
+ public function setAccessToken($accessToken)
192
+ {
193
+ if ($accessToken == 'null') {
194
+ $accessToken = null;
195
+ }
196
+ $this->getAuth()->setAccessToken($accessToken);
197
+ }
198
+
199
+
200
+
201
+ /**
202
+ * Set the authenticator object
203
+ * @param Google_Auth_Abstract $auth
204
+ */
205
+ public function setAuth(Google_Auth_Abstract $auth)
206
+ {
207
+ $this->config->setAuthClass(get_class($auth));
208
+ $this->auth = $auth;
209
+ }
210
+
211
+ /**
212
+ * Set the IO object
213
+ * @param Google_Io_Abstract $auth
214
+ */
215
+ public function setIo(Google_Io_Abstract $io)
216
+ {
217
+ $this->config->setIoClass(get_class($io));
218
+ $this->io = $io;
219
+ }
220
+
221
+ /**
222
+ * Set the Cache object
223
+ * @param Google_Cache_Abstract $auth
224
+ */
225
+ public function setCache(Google_Cache_Abstract $cache)
226
+ {
227
+ $this->config->setCacheClass(get_class($cache));
228
+ $this->cache = $cache;
229
+ }
230
+
231
+ /**
232
+ * Construct the OAuth 2.0 authorization request URI.
233
+ * @return string
234
+ */
235
+ public function createAuthUrl()
236
+ {
237
+ $scopes = $this->prepareScopes();
238
+ return $this->getAuth()->createAuthUrl($scopes);
239
+ }
240
+
241
+ /**
242
+ * Get the OAuth 2.0 access token.
243
+ * @return string $accessToken JSON encoded string in the following format:
244
+ * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
245
+ * "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
246
+ */
247
+ public function getAccessToken()
248
+ {
249
+ $token = $this->getAuth()->getAccessToken();
250
+ // The response is json encoded, so could be the string null.
251
+ // It is arguable whether this check should be here or lower
252
+ // in the library.
253
+ return (null == $token || 'null' == $token) ? null : $token;
254
+ }
255
+
256
+ /**
257
+ * Returns if the access_token is expired.
258
+ * @return bool Returns True if the access_token is expired.
259
+ */
260
+ public function isAccessTokenExpired()
261
+ {
262
+ return $this->getAuth()->isAccessTokenExpired();
263
+ }
264
+
265
+ /**
266
+ * Set OAuth 2.0 "state" parameter to achieve per-request customization.
267
+ * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
268
+ * @param string $state
269
+ */
270
+ public function setState($state)
271
+ {
272
+ $this->getAuth()->setState($state);
273
+ }
274
+
275
+ /**
276
+ * @param string $accessType Possible values for access_type include:
277
+ * {@code "offline"} to request offline access from the user.
278
+ * {@code "online"} to request online access from the user.
279
+ */
280
+ public function setAccessType($accessType)
281
+ {
282
+ $this->config->setAccessType($accessType);
283
+ }
284
+
285
+ /**
286
+ * @param string $approvalPrompt Possible values for approval_prompt include:
287
+ * {@code "force"} to force the approval UI to appear. (This is the default value)
288
+ * {@code "auto"} to request auto-approval when possible.
289
+ */
290
+ public function setApprovalPrompt($approvalPrompt)
291
+ {
292
+ $this->config->setApprovalPrompt($approvalPrompt);
293
+ }
294
+
295
+ /**
296
+ * Set the application name, this is included in the User-Agent HTTP header.
297
+ * @param string $applicationName
298
+ */
299
+ public function setApplicationName($applicationName)
300
+ {
301
+ $this->config->setApplicationName($applicationName);
302
+ }
303
+
304
+ /**
305
+ * Set the OAuth 2.0 Client ID.
306
+ * @param string $clientId
307
+ */
308
+ public function setClientId($clientId)
309
+ {
310
+ $this->config->setClientId($clientId);
311
+ }
312
+
313
+ /**
314
+ * Set the OAuth 2.0 Client Secret.
315
+ * @param string $clientSecret
316
+ */
317
+ public function setClientSecret($clientSecret)
318
+ {
319
+ $this->config->setClientSecret($clientSecret);
320
+ }
321
+
322
+ /**
323
+ * Set the OAuth 2.0 Redirect URI.
324
+ * @param string $redirectUri
325
+ */
326
+ public function setRedirectUri($redirectUri)
327
+ {
328
+ $this->config->setRedirectUri($redirectUri);
329
+ }
330
+
331
+ /**
332
+ * If 'plus.login' is included in the list of requested scopes, you can use
333
+ * this method to define types of app activities that your app will write.
334
+ * You can find a list of available types here:
335
+ * @link https://developers.google.com/+/api/moment-types
336
+ *
337
+ * @param array $requestVisibleActions Array of app activity types
338
+ */
339
+ public function setRequestVisibleActions($requestVisibleActions)
340
+ {
341
+ if (is_array($requestVisibleActions)) {
342
+ $requestVisibleActions = join(" ", $requestVisibleActions);
343
+ }
344
+ $this->config->setRequestVisibleActions($requestVisibleActions);
345
+ }
346
+
347
+ /**
348
+ * Set the developer key to use, these are obtained through the API Console.
349
+ * @see http://code.google.com/apis/console-help/#generatingdevkeys
350
+ * @param string $developerKey
351
+ */
352
+ public function setDeveloperKey($developerKey)
353
+ {
354
+ $this->config->setDeveloperKey($developerKey);
355
+ }
356
+
357
+ /**
358
+ * Fetches a fresh OAuth 2.0 access token with the given refresh token.
359
+ * @param string $refreshToken
360
+ * @return void
361
+ */
362
+ public function refreshToken($refreshToken)
363
+ {
364
+ return $this->getAuth()->refreshToken($refreshToken);
365
+ }
366
+
367
+ /**
368
+ * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
369
+ * token, if a token isn't provided.
370
+ * @throws Google_Auth_Exception
371
+ * @param string|null $token The token (access token or a refresh token) that should be revoked.
372
+ * @return boolean Returns True if the revocation was successful, otherwise False.
373
+ */
374
+ public function revokeToken($token = null)
375
+ {
376
+ return $this->getAuth()->revokeToken($token);
377
+ }
378
+
379
+ /**
380
+ * Verify an id_token. This method will verify the current id_token, if one
381
+ * isn't provided.
382
+ * @throws Google_Auth_Exception
383
+ * @param string|null $token The token (id_token) that should be verified.
384
+ * @return Google_Auth_LoginTicket Returns an apiLoginTicket if the verification was
385
+ * successful.
386
+ */
387
+ public function verifyIdToken($token = null)
388
+ {
389
+ return $this->getAuth()->verifyIdToken($token);
390
+ }
391
+
392
+ /**
393
+ * Verify a JWT that was signed with your own certificates.
394
+ *
395
+ * @param $jwt the token
396
+ * @param $certs array of certificates
397
+ * @param $required_audience the expected consumer of the token
398
+ * @param [$issuer] the expected issues, defaults to Google
399
+ * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
400
+ * @return token information if valid, false if not
401
+ */
402
+ public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
403
+ {
404
+ $auth = new Google_Auth_OAuth2($this);
405
+ $certs = $auth->retrieveCertsFromLocation($cert_location);
406
+ return $auth->verifySignedJwtWithCerts($id_token, $certs, $audience, $issuer, $max_expiry);
407
+ }
408
+
409
+ /**
410
+ * @param Google_Auth_AssertionCredentials $creds
411
+ * @return void
412
+ */
413
+ public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
414
+ {
415
+ $this->getAuth()->setAssertionCredentials($creds);
416
+ }
417
+
418
+ /**
419
+ * Set the scopes to be requested. Must be called before createAuthUrl().
420
+ * Will remove any previously configured scopes.
421
+ * @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.login',
422
+ * 'https://www.googleapis.com/auth/moderator')
423
+ */
424
+ public function setScopes($scopes)
425
+ {
426
+ $this->requestedScopes = array();
427
+ $this->addScope($scopes);
428
+ }
429
+
430
+ /**
431
+ * This functions adds a scope to be requested as part of the OAuth2.0 flow.
432
+ * Will append any scopes not previously requested to the scope parameter.
433
+ * A single string will be treated as a scope to request. An array of strings
434
+ * will each be appended.
435
+ * @param $scope_or_scopes string|array e.g. "profile"
436
+ */
437
+ public function addScope($scope_or_scopes)
438
+ {
439
+ if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
440
+ $this->requestedScopes[] = $scope_or_scopes;
441
+ } else if (is_array($scope_or_scopes)) {
442
+ foreach ($scope_or_scopes as $scope) {
443
+ $this->addScope($scope);
444
+ }
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Returns the list of scopes requested by the client
450
+ * @return array the list of scopes
451
+ *
452
+ */
453
+ public function getScopes()
454
+ {
455
+ return $this->requestedScopes;
456
+ }
457
+
458
+ /**
459
+ * Declare whether batch calls should be used. This may increase throughput
460
+ * by making multiple requests in one connection.
461
+ *
462
+ * @param boolean $useBatch True if the batch support should
463
+ * be enabled. Defaults to False.
464
+ */
465
+ public function setUseBatch($useBatch)
466
+ {
467
+ // This is actually an alias for setDefer.
468
+ $this->setDefer($useBatch);
469
+ }
470
+
471
+ /**
472
+ * Declare whether making API calls should make the call immediately, or
473
+ * return a request which can be called with ->execute();
474
+ *
475
+ * @param boolean $defer True if calls should not be executed right away.
476
+ */
477
+ public function setDefer($defer)
478
+ {
479
+ $this->deferExecution = $defer;
480
+ }
481
+
482
+ /**
483
+ * Helper method to execute deferred HTTP requests.
484
+ *
485
+ * @returns object of the type of the expected class or array.
486
+ */
487
+ public function execute($request)
488
+ {
489
+ if ($request instanceof Google_Http_Request) {
490
+ $request->setUserAgent(
491
+ $this->getApplicationName()
492
+ . " " . self::USER_AGENT_SUFFIX
493
+ . $this->getLibraryVersion()
494
+ );
495
+ if (!$this->getClassConfig("Google_Http_Request", "disable_gzip")) {
496
+ $request->enableGzip();
497
+ }
498
+ $request->maybeMoveParametersToBody();
499
+ return Google_Http_REST::execute($this, $request);
500
+ } else if ($request instanceof Google_Http_Batch) {
501
+ return $request->execute();
502
+ } else {
503
+ throw new Google_Exception("Do not know how to execute this type of object.");
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Whether or not to return raw requests
509
+ * @return boolean
510
+ */
511
+ public function shouldDefer()
512
+ {
513
+ return $this->deferExecution;
514
+ }
515
+
516
+ /**
517
+ * @return Google_Auth_Abstract Authentication implementation
518
+ */
519
+ public function getAuth()
520
+ {
521
+ if (!isset($this->auth)) {
522
+ $class = $this->config->getAuthClass();
523
+ $this->auth = new $class($this);
524
+ }
525
+ return $this->auth;
526
+ }
527
+
528
+ /**
529
+ * @return Google_IO_Abstract IO implementation
530
+ */
531
+ public function getIo()
532
+ {
533
+ if (!isset($this->io)) {
534
+ $class = $this->config->getIoClass();
535
+ $this->io = new $class($this);
536
+ }
537
+ return $this->io;
538
+ }
539
+
540
+ /**
541
+ * @return Google_Cache_Abstract Cache implementation
542
+ */
543
+ public function getCache()
544
+ {
545
+ if (!isset($this->cache)) {
546
+ $class = $this->config->getCacheClass();
547
+ $this->cache = new $class($this);
548
+ }
549
+ return $this->cache;
550
+ }
551
+
552
+ /**
553
+ * Retrieve custom configuration for a specific class.
554
+ * @param $class string|object - class or instance of class to retrieve
555
+ * @param $key string optional - key to retrieve
556
+ */
557
+ public function getClassConfig($class, $key = null)
558
+ {
559
+ if (!is_string($class)) {
560
+ $class = get_class($class);
561
+ }
562
+ return $this->config->getClassConfig($class, $key);
563
+ }
564
+
565
+ /**
566
+ * Set configuration specific to a given class.
567
+ * $config->setClassConfig('Google_Cache_File',
568
+ * array('directory' => '/tmp/cache'));
569
+ * @param $class The class name for the configuration
570
+ * @param $config string key or an array of configuration values
571
+ * @param $value optional - if $config is a key, the value
572
+ *
573
+ */
574
+ public function setClassConfig($class, $config, $value = null)
575
+ {
576
+ if (!is_string($class)) {
577
+ $class = get_class($class);
578
+ }
579
+ return $this->config->setClassConfig($class, $config, $value);
580
+
581
+ }
582
+
583
+ /**
584
+ * @return string the base URL to use for calls to the APIs
585
+ */
586
+ public function getBasePath()
587
+ {
588
+ return $this->config->getBasePath();
589
+ }
590
+
591
+ /**
592
+ * @return string the name of the application
593
+ */
594
+ public function getApplicationName()
595
+ {
596
+ return $this->config->getApplicationName();
597
+ }
598
+
599
+ /**
600
+ * Are we running in Google AppEngine?
601
+ * return bool
602
+ */
603
+ public function isAppEngine()
604
+ {
605
+ return (isset($_SERVER['SERVER_SOFTWARE']) &&
606
+ strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
607
+ }
608
+ }
includes/Google/Collection.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once "Google/Model.php";
4
+
5
+ /**
6
+ * Extension to the regular Google_Model that automatically
7
+ * exposes the items array for iteration, so you can just
8
+ * iterate over the object rather than a reference inside.
9
+ */
10
+ class Google_Collection extends Google_Model implements Iterator, Countable
11
+ {
12
+ protected $collection_key = 'items';
13
+
14
+ public function rewind()
15
+ {
16
+ if (is_array($this->data[$this->collection_key])) {
17
+ reset($this->data[$this->collection_key]);
18
+ }
19
+ }
20
+
21
+ public function current()
22
+ {
23
+ $this->coerceType($this->key());
24
+ if (is_array($this->data[$this->collection_key])) {
25
+ return current($this->data[$this->collection_key]);
26
+ }
27
+ }
28
+
29
+ public function key()
30
+ {
31
+ if (is_array($this->data[$this->collection_key])) {
32
+ return key($this->data[$this->collection_key]);
33
+ }
34
+ }
35
+
36
+ public function next()
37
+ {
38
+ return next($this->data[$this->collection_key]);
39
+ }
40
+
41
+ public function valid()
42
+ {
43
+ $key = $this->key();
44
+ return $key !== null && $key !== false;
45
+ }
46
+
47
+ public function count()
48
+ {
49
+ return count($this->data[$this->collection_key]);
50
+ }
51
+
52
+ public function offsetExists ($offset)
53
+ {
54
+ if (!is_numeric($offset)) {
55
+ return parent::offsetExists($offset);
56
+ }
57
+ return isset($this->data[$this->collection_key][$offset]);
58
+ }
59
+
60
+ public function offsetGet($offset)
61
+ {
62
+ if (!is_numeric($offset)) {
63
+ return parent::offsetGet($offset);
64
+ }
65
+ $this->coerceType($offset);
66
+ return $this->data[$this->collection_key][$offset];
67
+ }
68
+
69
+ public function offsetSet($offset, $value)
70
+ {
71
+ if (!is_numeric($offset)) {
72
+ return parent::offsetSet($offset, $value);
73
+ }
74
+ $this->data[$this->collection_key][$offset] = $value;
75
+ }
76
+
77
+ public function offsetUnset($offset)
78
+ {
79
+ if (!is_numeric($offset)) {
80
+ return parent::offsetUnset($offset);
81
+ }
82
+ unset($this->data[$this->collection_key][$offset]);
83
+ }
84
+
85
+ private function coerceType($offset)
86
+ {
87
+ $typeKey = $this->keyType($this->collection_key);
88
+ if (isset($this->$typeKey) && !is_object($this->data[$this->collection_key][$offset])) {
89
+ $type = $this->$typeKey;
90
+ $this->data[$this->collection_key][$offset] =
91
+ new $type($this->data[$this->collection_key][$offset]);
92
+ }
93
+ }
94
+ }
includes/Google/Config.php ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * A class to contain the library configuration for the Google API client.
20
+ */
21
+ class Google_Config
22
+ {
23
+ const GZIP_DISABLED = true;
24
+ const GZIP_ENABLED = false;
25
+ const GZIP_UPLOADS_ENABLED = true;
26
+ const GZIP_UPLOADS_DISABLED = false;
27
+ const USE_AUTO_IO_SELECTION = "auto";
28
+ private $configuration;
29
+
30
+ /**
31
+ * Create a new Google_Config. Can accept an ini file location with the
32
+ * local configuration. For example:
33
+ * application_name: "My App";
34
+ *
35
+ * @param [$ini_file_location] - optional - The location of the ini file to load
36
+ */
37
+ public function __construct($ini_file_location = null)
38
+ {
39
+ $this->configuration = array(
40
+ // The application_name is included in the User-Agent HTTP header.
41
+ 'application_name' => '',
42
+
43
+ // Which Authentication, Storage and HTTP IO classes to use.
44
+ 'auth_class' => 'Google_Auth_OAuth2',
45
+ 'io_class' => self::USE_AUTO_IO_SELECTION,
46
+ 'cache_class' => 'Google_Cache_File',
47
+
48
+ // Don't change these unless you're working against a special development
49
+ // or testing environment.
50
+ 'base_path' => 'https://www.googleapis.com',
51
+
52
+ // Definition of class specific values, like file paths and so on.
53
+ 'classes' => array(
54
+ 'Google_IO_Abstract' => array(
55
+ 'request_timeout_seconds' => 100,
56
+ ),
57
+ 'Google_Http_Request' => array(
58
+ // Disable the use of gzip on calls if set to true. Defaults to false.
59
+ 'disable_gzip' => self::GZIP_ENABLED,
60
+
61
+ // We default gzip to disabled on uploads even if gzip is otherwise
62
+ // enabled, due to some issues seen with small packet sizes for uploads.
63
+ // Please test with this option before enabling gzip for uploads in
64
+ // a production environment.
65
+ 'enable_gzip_for_uploads' => self::GZIP_UPLOADS_DISABLED,
66
+ ),
67
+ // If you want to pass in OAuth 2.0 settings, they will need to be
68
+ // structured like this.
69
+ 'Google_Auth_OAuth2' => array(
70
+ // Keys for OAuth 2.0 access, see the API console at
71
+ // https://developers.google.com/console
72
+ 'client_id' => '',
73
+ 'client_secret' => '',
74
+ 'redirect_uri' => '',
75
+
76
+ // Simple API access key, also from the API console. Ensure you get
77
+ // a Server key, and not a Browser key.
78
+ 'developer_key' => '',
79
+
80
+ // Other parameters.
81
+ 'access_type' => 'online',
82
+ 'approval_prompt' => 'auto',
83
+ 'request_visible_actions' => '',
84
+ 'federated_signon_certs_url' =>
85
+ 'https://www.googleapis.com/oauth2/v1/certs',
86
+ ),
87
+ // Set a default directory for the file cache.
88
+ 'Google_Cache_File' => array(
89
+ 'directory' => sys_get_temp_dir() . '/Google_Client'
90
+ )
91
+ ),
92
+
93
+ // Definition of service specific values like scopes, oauth token URLs,
94
+ // etc. Example:
95
+ 'services' => array(
96
+ ),
97
+ );
98
+ if ($ini_file_location) {
99
+ $ini = parse_ini_file($ini_file_location, true);
100
+ if (is_array($ini) && count($ini)) {
101
+ $this->configuration = array_merge($this->configuration, $ini);
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Set configuration specific to a given class.
108
+ * $config->setClassConfig('Google_Cache_File',
109
+ * array('directory' => '/tmp/cache'));
110
+ * @param $class The class name for the configuration
111
+ * @param $config string key or an array of configuration values
112
+ * @param $value optional - if $config is a key, the value
113
+ */
114
+ public function setClassConfig($class, $config, $value = null)
115
+ {
116
+ if (!is_array($config)) {
117
+ if (!isset($this->configuration['classes'][$class])) {
118
+ $this->configuration['classes'][$class] = array();
119
+ }
120
+ $this->configuration['classes'][$class][$config] = $value;
121
+ } else {
122
+ $this->configuration['classes'][$class] = $config;
123
+ }
124
+ }
125
+
126
+ public function getClassConfig($class, $key = null)
127
+ {
128
+ if (!isset($this->configuration['classes'][$class])) {
129
+ return null;
130
+ }
131
+ if ($key === null) {
132
+ return $this->configuration['classes'][$class];
133
+ } else {
134
+ return $this->configuration['classes'][$class][$key];
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Return the configured cache class.
140
+ * @return string
141
+ */
142
+ public function getCacheClass()
143
+ {
144
+ return $this->configuration['cache_class'];
145
+ }
146
+
147
+ /**
148
+ * Return the configured Auth class.
149
+ * @return string
150
+ */
151
+ public function getAuthClass()
152
+ {
153
+ return $this->configuration['auth_class'];
154
+ }
155
+
156
+ /**
157
+ * Set the auth class.
158
+ *
159
+ * @param $class the class name to set
160
+ */
161
+ public function setAuthClass($class)
162
+ {
163
+ $prev = $this->configuration['auth_class'];
164
+ if (!isset($this->configuration['classes'][$class]) &&
165
+ isset($this->configuration['classes'][$prev])) {
166
+ $this->configuration['classes'][$class] =
167
+ $this->configuration['classes'][$prev];
168
+ }
169
+ $this->configuration['auth_class'] = $class;
170
+ }
171
+
172
+ /**
173
+ * Set the IO class.
174
+ *
175
+ * @param $class the class name to set
176
+ */
177
+ public function setIoClass($class)
178
+ {
179
+ $prev = $this->configuration['io_class'];
180
+ if (!isset($this->configuration['classes'][$class]) &&
181
+ isset($this->configuration['classes'][$prev])) {
182
+ $this->configuration['classes'][$class] =
183
+ $this->configuration['classes'][$prev];
184
+ }
185
+ $this->configuration['io_class'] = $class;
186
+ }
187
+
188
+ /**
189
+ * Set the cache class.
190
+ *
191
+ * @param $class the class name to set
192
+ */
193
+ public function setCacheClass($class)
194
+ {
195
+ $prev = $this->configuration['cache_class'];
196
+ if (!isset($this->configuration['classes'][$class]) &&
197
+ isset($this->configuration['classes'][$prev])) {
198
+ $this->configuration['classes'][$class] =
199
+ $this->configuration['classes'][$prev];
200
+ }
201
+ $this->configuration['cache_class'] = $class;
202
+ }
203
+
204
+ /**
205
+ * Return the configured IO class.
206
+ * @return string
207
+ */
208
+ public function getIoClass()
209
+ {
210
+ return $this->configuration['io_class'];
211
+ }
212
+
213
+ /**
214
+ * Set the application name, this is included in the User-Agent HTTP header.
215
+ * @param string $name
216
+ */
217
+ public function setApplicationName($name)
218
+ {
219
+ $this->configuration['application_name'] = $name;
220
+ }
221
+
222
+ /**
223
+ * @return string the name of the application
224
+ */
225
+ public function getApplicationName()
226
+ {
227
+ return $this->configuration['application_name'];
228
+ }
229
+
230
+ /**
231
+ * Set the client ID for the auth class.
232
+ * @param $key string - the API console client ID
233
+ */
234
+ public function setClientId($clientId)
235
+ {
236
+ $this->setAuthConfig('client_id', $clientId);
237
+ }
238
+
239
+ /**
240
+ * Set the client secret for the auth class.
241
+ * @param $key string - the API console client secret
242
+ */
243
+ public function setClientSecret($secret)
244
+ {
245
+ $this->setAuthConfig('client_secret', $secret);
246
+ }
247
+
248
+ /**
249
+ * Set the redirect uri for the auth class. Note that if using the
250
+ * Javascript based sign in flow, this should be the string 'postmessage'.
251
+ * @param $key string - the URI that users should be redirected to
252
+ */
253
+ public function setRedirectUri($uri)
254
+ {
255
+ $this->setAuthConfig('redirect_uri', $uri);
256
+ }
257
+
258
+ /**
259
+ * Set the app activities for the auth class.
260
+ * @param $rva string a space separated list of app activity types
261
+ */
262
+ public function setRequestVisibleActions($rva)
263
+ {
264
+ $this->setAuthConfig('request_visible_actions', $rva);
265
+ }
266
+
267
+ /**
268
+ * Set the the access type requested (offline or online.)
269
+ * @param $access string - the access type
270
+ */
271
+ public function setAccessType($access)
272
+ {
273
+ $this->setAuthConfig('access_type', $access);
274
+ }
275
+
276
+ /**
277
+ * Set when to show the approval prompt (auto or force)
278
+ * @param $approval string - the approval request
279
+ */
280
+ public function setApprovalPrompt($approval)
281
+ {
282
+ $this->setAuthConfig('approval_prompt', $approval);
283
+ }
284
+
285
+ /**
286
+ * Set the developer key for the auth class. Note that this is separate value
287
+ * from the client ID - if it looks like a URL, its a client ID!
288
+ * @param $key string - the API console developer key
289
+ */
290
+ public function setDeveloperKey($key)
291
+ {
292
+ $this->setAuthConfig('developer_key', $key);
293
+ }
294
+
295
+ /**
296
+ * @return string the base URL to use for API calls
297
+ */
298
+ public function getBasePath()
299
+ {
300
+ return $this->configuration['base_path'];
301
+ }
302
+
303
+ /**
304
+ * Set the auth configuration for the current auth class.
305
+ * @param $key - the key to set
306
+ * @param $value - the parameter value
307
+ */
308
+ private function setAuthConfig($key, $value)
309
+ {
310
+ if (!isset($this->configuration['classes'][$this->getAuthClass()])) {
311
+ $this->configuration['classes'][$this->getAuthClass()] = array();
312
+ }
313
+ $this->configuration['classes'][$this->getAuthClass()][$key] = $value;
314
+ }
315
+ }
includes/Google/Exception.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ class Google_Exception extends Exception
19
+ {
20
+ }
includes/Google/Http/Batch.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Client.php';
19
+ require_once 'Google/Http/Request.php';
20
+ require_once 'Google/Http/REST.php';
21
+
22
+ /**
23
+ * @author Chirag Shah <chirags@google.com>
24
+ */
25
+ class Google_Http_Batch
26
+ {
27
+ /** @var string Multipart Boundary. */
28
+ private $boundary;
29
+
30
+ /** @var array service requests to be executed. */
31
+ private $requests = array();
32
+
33
+ /** @var Google_Client */
34
+ private $client;
35
+
36
+ private $expected_classes = array();
37
+
38
+ private $base_path;
39
+
40
+ public function __construct(Google_Client $client, $boundary = false)
41
+ {
42
+ $this->client = $client;
43
+ $this->base_path = $this->client->getBasePath();
44
+ $this->expected_classes = array();
45
+ $boundary = (false == $boundary) ? mt_rand() : $boundary;
46
+ $this->boundary = str_replace('"', '', $boundary);
47
+ }
48
+
49
+ public function add(Google_Http_Request $request, $key = false)
50
+ {
51
+ if (false == $key) {
52
+ $key = mt_rand();
53
+ }
54
+
55
+ $this->requests[$key] = $request;
56
+ }
57
+
58
+ public function execute()
59
+ {
60
+ $body = '';
61
+
62
+ /** @var Google_Http_Request $req */
63
+ foreach ($this->requests as $key => $req) {
64
+ $body .= "--{$this->boundary}\n";
65
+ $body .= $req->toBatchString($key) . "\n";
66
+ $this->expected_classes["response-" . $key] = $req->getExpectedClass();
67
+ }
68
+
69
+ $body = rtrim($body);
70
+ $body .= "\n--{$this->boundary}--";
71
+
72
+ $url = $this->base_path . '/batch';
73
+ $httpRequest = new Google_Http_Request($url, 'POST');
74
+ $httpRequest->setRequestHeaders(
75
+ array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary)
76
+ );
77
+
78
+ $httpRequest->setPostBody($body);
79
+ $response = $this->client->getIo()->makeRequest($httpRequest);
80
+
81
+ return $this->parseResponse($response);
82
+ }
83
+
84
+ public function parseResponse(Google_Http_Request $response)
85
+ {
86
+ $contentType = $response->getResponseHeader('content-type');
87
+ $contentType = explode(';', $contentType);
88
+ $boundary = false;
89
+ foreach ($contentType as $part) {
90
+ $part = (explode('=', $part, 2));
91
+ if (isset($part[0]) && 'boundary' == trim($part[0])) {
92
+ $boundary = $part[1];
93
+ }
94
+ }
95
+
96
+ $body = $response->getResponseBody();
97
+ if ($body) {
98
+ $body = str_replace("--$boundary--", "--$boundary", $body);
99
+ $parts = explode("--$boundary", $body);
100
+ $responses = array();
101
+
102
+ foreach ($parts as $part) {
103
+ $part = trim($part);
104
+ if (!empty($part)) {
105
+ list($metaHeaders, $part) = explode("\r\n\r\n", $part, 2);
106
+ $metaHeaders = $this->client->getIo()->getHttpResponseHeaders($metaHeaders);
107
+
108
+ $status = substr($part, 0, strpos($part, "\n"));
109
+ $status = explode(" ", $status);
110
+ $status = $status[1];
111
+
112
+ list($partHeaders, $partBody) = $this->client->getIo()->ParseHttpResponse($part, false);
113
+ $response = new Google_Http_Request("");
114
+ $response->setResponseHttpCode($status);
115
+ $response->setResponseHeaders($partHeaders);
116
+ $response->setResponseBody($partBody);
117
+
118
+ // Need content id.
119
+ $key = $metaHeaders['content-id'];
120
+
121
+ if (isset($this->expected_classes[$key]) &&
122
+ strlen($this->expected_classes[$key]) > 0) {
123
+ $class = $this->expected_classes[$key];
124
+ $response->setExpectedClass($class);
125
+ }
126
+
127
+ try {
128
+ $response = Google_Http_REST::decodeHttpResponse($response);
129
+ $responses[$key] = $response;
130
+ } catch (Google_Service_Exception $e) {
131
+ // Store the exception as the response, so succesful responses
132
+ // can be processed.
133
+ $responses[$key] = $e;
134
+ }
135
+ }
136
+ }
137
+
138
+ return $responses;
139
+ }
140
+
141
+ return null;
142
+ }
143
+ }
includes/Google/Http/CacheParser.php ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Http/Request.php';
19
+
20
+ /**
21
+ * Implement the caching directives specified in rfc2616. This
22
+ * implementation is guided by the guidance offered in rfc2616-sec13.
23
+ * @author Chirag Shah <chirags@google.com>
24
+ */
25
+ class Google_Http_CacheParser
26
+ {
27
+ public static $CACHEABLE_HTTP_METHODS = array('GET', 'HEAD');
28
+ public static $CACHEABLE_STATUS_CODES = array('200', '203', '300', '301');
29
+
30
+ /**
31
+ * Check if an HTTP request can be cached by a private local cache.
32
+ *
33
+ * @static
34
+ * @param Google_Http_Request $resp
35
+ * @return bool True if the request is cacheable.
36
+ * False if the request is uncacheable.
37
+ */
38
+ public static function isRequestCacheable(Google_Http_Request $resp)
39
+ {
40
+ $method = $resp->getRequestMethod();
41
+ if (! in_array($method, self::$CACHEABLE_HTTP_METHODS)) {
42
+ return false;
43
+ }
44
+
45
+ // Don't cache authorized requests/responses.
46
+ // [rfc2616-14.8] When a shared cache receives a request containing an
47
+ // Authorization field, it MUST NOT return the corresponding response
48
+ // as a reply to any other request...
49
+ if ($resp->getRequestHeader("authorization")) {
50
+ return false;
51
+ }
52
+
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * Check if an HTTP response can be cached by a private local cache.
58
+ *
59
+ * @static
60
+ * @param Google_Http_Request $resp
61
+ * @return bool True if the response is cacheable.
62
+ * False if the response is un-cacheable.
63
+ */
64
+ public static function isResponseCacheable(Google_Http_Request $resp)
65
+ {
66
+ // First, check if the HTTP request was cacheable before inspecting the
67
+ // HTTP response.
68
+ if (false == self::isRequestCacheable($resp)) {
69
+ return false;
70
+ }
71
+
72
+ $code = $resp->getResponseHttpCode();
73
+ if (! in_array($code, self::$CACHEABLE_STATUS_CODES)) {
74
+ return false;
75
+ }
76
+
77
+ // The resource is uncacheable if the resource is already expired and
78
+ // the resource doesn't have an ETag for revalidation.
79
+ $etag = $resp->getResponseHeader("etag");
80
+ if (self::isExpired($resp) && $etag == false) {
81
+ return false;
82
+ }
83
+
84
+ // [rfc2616-14.9.2] If [no-store is] sent in a response, a cache MUST NOT
85
+ // store any part of either this response or the request that elicited it.
86
+ $cacheControl = $resp->getParsedCacheControl();
87
+ if (isset($cacheControl['no-store'])) {
88
+ return false;
89
+ }
90
+
91
+ // Pragma: no-cache is an http request directive, but is occasionally
92
+ // used as a response header incorrectly.
93
+ $pragma = $resp->getResponseHeader('pragma');
94
+ if ($pragma == 'no-cache' || strpos($pragma, 'no-cache') !== false) {
95
+ return false;
96
+ }
97
+
98
+ // [rfc2616-14.44] Vary: * is extremely difficult to cache. "It implies that
99
+ // a cache cannot determine from the request headers of a subsequent request
100
+ // whether this response is the appropriate representation."
101
+ // Given this, we deem responses with the Vary header as uncacheable.
102
+ $vary = $resp->getResponseHeader('vary');
103
+ if ($vary) {
104
+ return false;
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ /**
111
+ * @static
112
+ * @param Google_Http_Request $resp
113
+ * @return bool True if the HTTP response is considered to be expired.
114
+ * False if it is considered to be fresh.
115
+ */
116
+ public static function isExpired(Google_Http_Request $resp)
117
+ {
118
+ // HTTP/1.1 clients and caches MUST treat other invalid date formats,
119
+ // especially including the value “0”, as in the past.
120
+ $parsedExpires = false;
121
+ $responseHeaders = $resp->getResponseHeaders();
122
+
123
+ if (isset($responseHeaders['expires'])) {
124
+ $rawExpires = $responseHeaders['expires'];
125
+ // Check for a malformed expires header first.
126
+ if (empty($rawExpires) || (is_numeric($rawExpires) && $rawExpires <= 0)) {
127
+ return true;
128
+ }
129
+
130
+ // See if we can parse the expires header.
131
+ $parsedExpires = strtotime($rawExpires);
132
+ if (false == $parsedExpires || $parsedExpires <= 0) {
133
+ return true;
134
+ }
135
+ }
136
+
137
+ // Calculate the freshness of an http response.
138
+ $freshnessLifetime = false;
139
+ $cacheControl = $resp->getParsedCacheControl();
140
+ if (isset($cacheControl['max-age'])) {
141
+ $freshnessLifetime = $cacheControl['max-age'];
142
+ }
143
+
144
+ $rawDate = $resp->getResponseHeader('date');
145
+ $parsedDate = strtotime($rawDate);
146
+
147
+ if (empty($rawDate) || false == $parsedDate) {
148
+ // We can't default this to now, as that means future cache reads
149
+ // will always pass with the logic below, so we will require a
150
+ // date be injected if not supplied.
151
+ throw new Google_Exception("All cacheable requests must have creation dates.");
152
+ }
153
+
154
+ if (false == $freshnessLifetime && isset($responseHeaders['expires'])) {
155
+ $freshnessLifetime = $parsedExpires - $parsedDate;
156
+ }
157
+
158
+ if (false == $freshnessLifetime) {
159
+ return true;
160
+ }
161
+
162
+ // Calculate the age of an http response.
163
+ $age = max(0, time() - $parsedDate);
164
+ if (isset($responseHeaders['age'])) {
165
+ $age = max($age, strtotime($responseHeaders['age']));
166
+ }
167
+
168
+ return $freshnessLifetime <= $age;
169
+ }
170
+
171
+ /**
172
+ * Determine if a cache entry should be revalidated with by the origin.
173
+ *
174
+ * @param Google_Http_Request $response
175
+ * @return bool True if the entry is expired, else return false.
176
+ */
177
+ public static function mustRevalidate(Google_Http_Request $response)
178
+ {
179
+ // [13.3] When a cache has a stale entry that it would like to use as a
180
+ // response to a client's request, it first has to check with the origin
181
+ // server to see if its cached entry is still usable.
182
+ return self::isExpired($response);
183
+ }
184
+ }
includes/Google/Http/MediaFileUpload.php ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright 2012 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Client.php';
19
+ require_once 'Google/Exception.php';
20
+ require_once 'Google/Http/Request.php';
21
+ require_once 'Google/Http/REST.php';
22
+ require_once 'Google/Utils.php';
23
+
24
+ /**
25
+ * @author Chirag Shah <chirags@google.com>
26
+ *
27
+ */
28
+ class Google_Http_MediaFileUpload
29
+ {
30
+ const UPLOAD_MEDIA_TYPE = 'media';
31
+ const UPLOAD_MULTIPART_TYPE = 'multipart';
32
+ const UPLOAD_RESUMABLE_TYPE = 'resumable';
33
+
34
+ /** @var string $mimeType */
35
+ private $mimeType;
36
+
37
+ /** @var string $data */
38
+ private $data;
39
+
40
+ /** @var bool $resumable */
41
+ private $resumable;
42
+
43
+ /** @var int $chunkSize */
44
+ private $chunkSize;
45
+
46
+ /** @var int $size */
47
+ private $size;
48
+
49
+ /** @var string $resumeUri */
50
+ public $resumeUri;
51
+
52
+ /** @var int $progress */
53
+ public $progress;
54
+
55
+ /** @var Google_Client */
56
+ private $client;
57
+
58
+ /** @var Google_Http_Request */
59
+ private $request;
60
+
61
+ /** @var string */
62
+ private $boundary;
63
+
64
+ /**
65
+ * Result code from last HTTP call
66
+ * @var int
67
+ */
68
+ private $httpResultCode;
69
+
70
+ /**
71
+ * @param $mimeType string
72
+ * @param $data string The bytes you want to upload.
73
+ * @param $resumable bool
74
+ * @param bool $chunkSize File will be uploaded in chunks of this many bytes.
75
+ * only used if resumable=True
76
+ */
77
+ public function __construct(
78
+ Google_Client $client,
79
+ Google_Http_Request $request,
80
+ $mimeType,
81
+ $data,
82
+ $resumable = false,
83
+ $chunkSize = false,
84
+ $boundary = false
85
+ ) {
86
+ $this->client = $client;
87
+ $this->request = $request;
88
+ $this->mimeType = $mimeType;
89
+ $this->data = $data;
90
+ $this->size = strlen($this->data);
91
+ $this->resumable = $resumable;
92
+ if (!$chunkSize) {
93
+ $chunkSize = 256 * 1024;
94
+ }
95
+ $this->chunkSize = $chunkSize;
96
+ $this->progress = 0;
97
+ $this->boundary = $boundary;
98
+
99
+ // Process Media Request
100
+ $this->process();
101
+ }
102
+
103
+ /**
104
+ * Set the size of the file that is being uploaded.
105
+ * @param $size - int file size in bytes
106
+ */
107
+ public function setFileSize($size)
108
+ {
109
+ $this->size = $size;
110
+ }
111
+
112
+ /**
113
+ * Return the progress on the upload
114
+ * @return int progress in bytes uploaded.
115
+ */
116
+ public function getProgress()
117
+ {
118
+ return $this->progress;
119
+ }
120
+
121
+ /**
122
+ * Return the HTTP result code from the last call made.
123
+ * @return int code
124
+ */
125
+ public function getHttpResultCode()
126
+ {
127
+ return $this->httpResultCode;
128
+ }
129
+
130
+ /**
131
+ * Send the next part of the file to upload.
132
+ * @param [$chunk] the next set of bytes to send. If false will used $data passed
133
+ * at construct time.
134
+ */
135
+ public function nextChunk($chunk = false)
136
+ {
137
+ if (false == $this->resumeUri) {
138
+ $this->resumeUri = $this->getResumeUri();
139
+ }
140
+
141
+ if (false == $chunk) {
142
+ $chunk = substr($this->data, $this->progress, $this->chunkSize);
143
+ }
144
+
145
+ $lastBytePos = $this->progress + strlen($chunk) - 1;
146
+ $headers = array(
147
+ 'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
148
+ 'content-type' => $this->request->getRequestHeader('content-type'),
149
+ 'content-length' => $this->chunkSize,
150
+ 'expect' => '',
151
+ );
152
+
153
+ $httpRequest = new Google_Http_Request(
154
+ $this->resumeUri,
155
+ 'PUT',
156
+ $headers,
157
+ $chunk
158
+ );
159
+
160
+ if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) {
161
+ $httpRequest->enableGzip();
162
+ } else {
163
+ $httpRequest->disableGzip();
164
+ }
165
+
166
+ $response = $this->client->getIo()->makeRequest($httpRequest);
167
+ $response->setExpectedClass($this->request->getExpectedClass());
168
+ $code = $response->getResponseHttpCode();
169
+ $this->httpResultCode = $code;
170
+
171
+ if (308 == $code) {
172
+ // Track the amount uploaded.
173
+ $range = explode('-', $response->getResponseHeader('range'));
174
+ $this->progress = $range[1] + 1;
175
+
176
+ // Allow for changing upload URLs.
177
+ $location = $response->getResponseHeader('location');
178
+ if ($location) {
179
+ $this->resumeUri = $location;
180
+ }
181
+
182
+ // No problems, but upload not complete.
183
+ return false;
184
+ } else {
185
+ return Google_Http_REST::decodeHttpResponse($response);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @param $meta
191
+ * @param $params
192
+ * @return array|bool
193
+ * @visible for testing
194
+ */
195
+ private function process()
196
+ {
197
+ $postBody = false;
198
+ $contentType = false;
199
+
200
+ $meta = $this->request->getPostBody();
201
+ $meta = is_string($meta) ? json_decode($meta, true) : $meta;
202
+
203
+ $uploadType = $this->getUploadType($meta);
204
+ $this->request->setQueryParam('uploadType', $uploadType);
205
+ $this->transformToUploadUrl();
206
+ $mimeType = $this->mimeType ?
207
+ $this->mimeType :
208
+ $this->request->getRequestHeader('content-type');
209
+
210
+ if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
211
+ $contentType = $mimeType;
212
+ $postBody = is_string($meta) ? $meta : json_encode($meta);
213
+ } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
214
+ $contentType = $mimeType;
215
+ $postBody = $this->data;
216
+ } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
217
+ // This is a multipart/related upload.
218
+ $boundary = $this->boundary ? $this->boundary : mt_rand();
219
+ $boundary = str_replace('"', '', $boundary);
220
+ $contentType = 'multipart/related; boundary=' . $boundary;
221
+ $related = "--$boundary\r\n";
222
+ $related .= "Content-Type: application/json; charset=UTF-8\r\n";
223
+ $related .= "\r\n" . json_encode($meta) . "\r\n";
224
+ $related .= "--$boundary\r\n";
225
+ $related .= "Content-Type: $mimeType\r\n";
226
+ $related .= "Content-Transfer-Encoding: base64\r\n";
227
+ $related .= "\r\n" . base64_encode($this->data) . "\r\n";
228
+ $related .= "--$boundary--";
229
+ $postBody = $related;
230
+ }
231
+
232
+ $this->request->setPostBody($postBody);
233
+
234
+ if (isset($contentType) && $contentType) {
235
+ $contentTypeHeader['content-type'] = $contentType;
236
+ $this->request->setRequestHeaders($contentTypeHeader);
237
+ }
238
+ }
239
+
240
+ private function transformToUploadUrl()
241
+ {
242
+ $base = $this->request->getBaseComponent();
243
+ $this->request->setBaseComponent($base . '/upload');
244
+ }
245
+
246
+ /**
247
+ * Valid upload types:
248
+ * - resumable (UPLOAD_RESUMABLE_TYPE)
249
+ * - media (UPLOAD_MEDIA_TYPE)
250
+ * - multipart (UPLOAD_MULTIPART_TYPE)
251
+ * @param $meta
252
+ * @return string
253
+ * @visible for testing
254
+ */
255
+ public function getUploadType($meta)
256
+ {
257
+ if ($this->resumable) {
258
+ return self::UPLOAD_RESUMABLE_TYPE;
259
+ }
260
+
261
+ if (false == $meta && $this->data) {
262
+ return self::UPLOAD_MEDIA_TYPE;
263
+ }
264
+
265
+ return self::UPLOAD_MULTIPART_TYPE;
266
+ }
267
+
268
+ private function getResumeUri()
269
+ {
270
+ $result = null;
271
+ $body = $this->request->getPostBody();
272
+ if ($body) {
273
+ $headers = array(
274
+ 'content-type' => 'application/json; charset=UTF-8',
275
+ 'content-length' => Google_Utils::getStrLen($body),
276
+ 'x-upload-content-type' => $this->mimeType,
277
+ 'x-upload-content-length' => $this->size,
278
+ 'expect' => '',
279
+ );
280
+ $this->request->setRequestHeaders($headers);
281
+ }
282
+
283
+ $response = $this->client->getIo()->makeRequest($this->request);
284
+ $location = $response->getResponseHeader('location');
285
+ $code = $response->getResponseHttpCode();
286
+
287
+ if (200 == $code && true == $location) {
288
+ return $location;
289
+ }
290
+ throw new Google_Exception("Failed to start the resumable upload");
291
+ }
292
+ }
includes/Google/Http/REST.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Client.php';
19
+ require_once 'Google/Http/Request.php';
20
+ require_once 'Google/Service/Exception.php';
21
+ require_once 'Google/Utils/URITemplate.php';
22
+
23
+ /**
24
+ * This class implements the RESTful transport of apiServiceRequest()'s
25
+ *
26
+ * @author Chris Chabot <chabotc@google.com>
27
+ * @author Chirag Shah <chirags@google.com>
28
+ */
29
+ class Google_Http_REST
30
+ {
31
+ /**
32
+ * Executes a Google_Http_Request
33
+ *
34
+ * @param Google_Client $client
35
+ * @param Google_Http_Request $req
36
+ * @return array decoded result
37
+ * @throws Google_Service_Exception on server side error (ie: not authenticated,
38
+ * invalid or malformed post body, invalid url)
39
+ */
40
+ public static function execute(Google_Client $client, Google_Http_Request $req)
41
+ {
42
+ $httpRequest = $client->getIo()->makeRequest($req);
43
+ $httpRequest->setExpectedClass($req->getExpectedClass());
44
+ return self::decodeHttpResponse($httpRequest);
45
+ }
46
+
47
+ /**
48
+ * Decode an HTTP Response.
49
+ * @static
50
+ * @throws Google_Service_Exception
51
+ * @param Google_Http_Request $response The http response to be decoded.
52
+ * @return mixed|null
53
+ */
54
+ public static function decodeHttpResponse($response)
55
+ {
56
+ $code = $response->getResponseHttpCode();
57
+ $body = $response->getResponseBody();
58
+ $decoded = null;
59
+
60
+ if ((intVal($code)) >= 300) {
61
+ $decoded = json_decode($body, true);
62
+ $err = 'Error calling ' . $response->getRequestMethod() . ' ' . $response->getUrl();
63
+ if (isset($decoded['error']) &&
64
+ isset($decoded['error']['message']) &&
65
+ isset($decoded['error']['code'])) {
66
+ // if we're getting a json encoded error definition, use that instead of the raw response
67
+ // body for improved readability
68
+ $err .= ": ({$decoded['error']['code']}) {$decoded['error']['message']}";
69
+ } else {
70
+ $err .= ": ($code) $body";
71
+ }
72
+
73
+ $errors = null;
74
+ // Specific check for APIs which don't return error details, such as Blogger.
75
+ if (isset($decoded['error']) && isset($decoded['error']['errors'])) {
76
+ $errors = $decoded['error']['errors'];
77
+ }
78
+
79
+ throw new Google_Service_Exception($err, $code, null, $errors);
80
+ }
81
+
82
+ // Only attempt to decode the response, if the response code wasn't (204) 'no content'
83
+ if ($code != '204') {
84
+ $decoded = json_decode($body, true);
85
+ if ($decoded === null || $decoded === "") {
86
+ throw new Google_Service_Exception("Invalid json in service response: $body");
87
+ }
88
+
89
+ if ($response->getExpectedClass()) {
90
+ $class = $response->getExpectedClass();
91
+ $decoded = new $class($decoded);
92
+ }
93
+ }
94
+ return $decoded;
95
+ }
96
+
97
+ /**
98
+ * Parse/expand request parameters and create a fully qualified
99
+ * request uri.
100
+ * @static
101
+ * @param string $servicePath
102
+ * @param string $restPath
103
+ * @param array $params
104
+ * @return string $requestUrl
105
+ */
106
+ public static function createRequestUri($servicePath, $restPath, $params)
107
+ {
108
+ $requestUrl = $servicePath . $restPath;
109
+ $uriTemplateVars = array();
110
+ $queryVars = array();
111
+ foreach ($params as $paramName => $paramSpec) {
112
+ if ($paramSpec['type'] == 'boolean') {
113
+ $paramSpec['value'] = ($paramSpec['value']) ? 'true' : 'false';
114
+ }
115
+ if ($paramSpec['location'] == 'path') {
116
+ $uriTemplateVars[$paramName] = $paramSpec['value'];
117
+ } else if ($paramSpec['location'] == 'query') {
118
+ if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) {
119
+ foreach ($paramSpec['value'] as $value) {
120
+ $queryVars[] = $paramName . '=' . rawurlencode($value);
121
+ }
122
+ } else {
123
+ $queryVars[] = $paramName . '=' . rawurlencode($paramSpec['value']);
124
+ }
125
+ }
126
+ }
127
+
128
+ if (count($uriTemplateVars)) {
129
+ $uriTemplateParser = new Google_Utils_URITemplate();
130
+ $requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars);
131
+ }
132
+
133
+ if (count($queryVars)) {
134
+ $requestUrl .= '?' . implode($queryVars, '&');
135
+ }
136
+
137
+ return $requestUrl;
138
+ }
139
+ }
includes/Google/Http/Request.php ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Utils.php';
19
+
20
+ /**
21
+ * HTTP Request to be executed by IO classes. Upon execution, the
22
+ * responseHttpCode, responseHeaders and responseBody will be filled in.
23
+ *
24
+ * @author Chris Chabot <chabotc@google.com>
25
+ * @author Chirag Shah <chirags@google.com>
26
+ *
27
+ */
28
+ class Google_Http_Request
29
+ {
30
+ const GZIP_UA = " (gzip)";
31
+
32
+ private $batchHeaders = array(
33
+ 'Content-Type' => 'application/http',
34
+ 'Content-Transfer-Encoding' => 'binary',
35
+ 'MIME-Version' => '1.0',
36
+ );
37
+
38
+ protected $queryParams;
39
+ protected $requestMethod;
40
+ protected $requestHeaders;
41
+ protected $baseComponent = null;
42
+ protected $path;
43
+ protected $postBody;
44
+ protected $userAgent;
45
+ protected $canGzip = null;
46
+
47
+ protected $responseHttpCode;
48
+ protected $responseHeaders;
49
+ protected $responseBody;
50
+
51
+ protected $expectedClass;
52
+
53
+ public $accessKey;
54
+
55
+ public function __construct(
56
+ $url,
57
+ $method = 'GET',
58
+ $headers = array(),
59
+ $postBody = null
60
+ ) {
61
+ $this->setUrl($url);
62
+ $this->setRequestMethod($method);
63
+ $this->setRequestHeaders($headers);
64
+ $this->setPostBody($postBody);
65
+ }
66
+
67
+ /**
68
+ * Misc function that returns the base url component of the $url
69
+ * used by the OAuth signing class to calculate the base string
70
+ * @return string The base url component of the $url.
71
+ */
72
+ public function getBaseComponent()
73
+ {
74
+ return $this->baseComponent;
75
+ }
76
+
77
+ /**
78
+ * Set the base URL that path and query parameters will be added to.
79
+ * @param $baseComponent string
80
+ */
81
+ public function setBaseComponent($baseComponent)
82
+ {
83
+ $this->baseComponent = $baseComponent;
84
+ }
85
+
86
+ /**
87
+ * Enable support for gzipped responses with this request.
88
+ */
89
+ public function enableGzip()
90
+ {
91
+ $this->setRequestHeaders(array("Accept-Encoding" => "gzip"));
92
+ $this->canGzip = true;
93
+ $this->setUserAgent($this->userAgent);
94
+ }
95
+
96
+ /**
97
+ * Disable support for gzip responses with this request.
98
+ */
99
+ public function disableGzip()
100
+ {
101
+ if (
102
+ isset($this->requestHeaders['accept-encoding']) &&
103
+ $this->requestHeaders['accept-encoding'] == "gzip"
104
+ ) {
105
+ unset($this->requestHeaders['accept-encoding']);
106
+ }
107
+ $this->canGzip = false;
108
+ $this->userAgent = str_replace(self::GZIP_UA, "", $this->userAgent);
109
+ }
110
+
111
+ /**
112
+ * Can this request accept a gzip response?
113
+ * @return bool
114
+ */
115
+ public function canGzip()
116
+ {
117
+ return $this->canGzip;
118
+ }
119
+
120
+ /**
121
+ * Misc function that returns an array of the query parameters of the current
122
+ * url used by the OAuth signing class to calculate the signature
123
+ * @return array Query parameters in the query string.
124
+ */
125
+ public function getQueryParams()
126
+ {
127
+ return $this->queryParams;
128
+ }
129
+
130
+ /**
131
+ * Set a new query parameter.
132
+ * @param $key - string to set, does not need to be URL encoded
133
+ * @param $value - string to set, does not need to be URL encoded
134
+ */
135
+ public function setQueryParam($key, $value)
136
+ {
137
+ $this->queryParams[$key] = $value;
138
+ }
139
+
140
+ /**
141
+ * @return string HTTP Response Code.
142
+ */
143
+ public function getResponseHttpCode()
144
+ {
145
+ return (int) $this->responseHttpCode;
146
+ }
147
+
148
+ /**
149
+ * @param int $responseHttpCode HTTP Response Code.
150
+ */
151
+ public function setResponseHttpCode($responseHttpCode)
152
+ {
153
+ $this->responseHttpCode = $responseHttpCode;
154
+ }
155
+
156
+ /**
157
+ * @return $responseHeaders (array) HTTP Response Headers.
158
+ */
159
+ public function getResponseHeaders()
160
+ {
161
+ return $this->responseHeaders;
162
+ }
163
+
164
+ /**
165
+ * @return string HTTP Response Body
166
+ */
167
+ public function getResponseBody()
168
+ {
169
+ return $this->responseBody;
170
+ }
171
+
172
+ /**
173
+ * Set the class the response to this request should expect.
174
+ *
175
+ * @param $class string the class name
176
+ */
177
+ public function setExpectedClass($class)
178
+ {
179
+ $this->expectedClass = $class;
180
+ }
181
+
182
+ /**
183
+ * Retrieve the expected class the response should expect.
184
+ * @return string class name
185
+ */
186
+ public function getExpectedClass()
187
+ {
188
+ return $this->expectedClass;
189
+ }
190
+
191
+ /**
192
+ * @param array $headers The HTTP response headers
193
+ * to be normalized.
194
+ */
195
+ public function setResponseHeaders($headers)
196
+ {
197
+ $headers = Google_Utils::normalize($headers);
198
+ if ($this->responseHeaders) {
199
+ $headers = array_merge($this->responseHeaders, $headers);
200
+ }
201
+
202
+ $this->responseHeaders = $headers;
203
+ }
204
+
205
+ /**
206
+ * @param string $key
207
+ * @return array|boolean Returns the requested HTTP header or
208
+ * false if unavailable.
209
+ */
210
+ public function getResponseHeader($key)
211
+ {
212
+ return isset($this->responseHeaders[$key])
213
+ ? $this->responseHeaders[$key]
214
+ : false;
215
+ }
216
+
217
+ /**
218
+ * @param string $responseBody The HTTP response body.
219
+ */
220
+ public function setResponseBody($responseBody)
221
+ {
222
+ $this->responseBody = $responseBody;
223
+ }
224
+
225
+ /**
226
+ * @return string $url The request URL.
227
+ */
228
+ public function getUrl()
229
+ {
230
+ return $this->baseComponent . $this->path .
231
+ (count($this->queryParams) ?
232
+ "?" . $this->buildQuery($this->queryParams) :
233
+ '');
234
+ }
235
+
236
+ /**
237
+ * @return string $method HTTP Request Method.
238
+ */
239
+ public function getRequestMethod()
240
+ {
241
+ return $this->requestMethod;
242
+ }
243
+
244
+ /**
245
+ * @return array $headers HTTP Request Headers.
246
+ */
247
+ public function getRequestHeaders()
248
+ {
249
+ return $this->requestHeaders;
250
+ }
251
+
252
+ /**
253
+ * @param string $key
254
+ * @return array|boolean Returns the requested HTTP header or
255
+ * false if unavailable.
256
+ */
257
+ public function getRequestHeader($key)
258
+ {
259
+ return isset($this->requestHeaders[$key])
260
+ ? $this->requestHeaders[$key]
261
+ : false;
262
+ }
263
+
264
+ /**
265
+ * @return string $postBody HTTP Request Body.
266
+ */
267
+ public function getPostBody()
268
+ {
269
+ return $this->postBody;
270
+ }
271
+
272
+ /**
273
+ * @param string $url the url to set
274
+ */
275
+ public function setUrl($url)
276
+ {
277
+ if (substr($url, 0, 4) != 'http') {
278
+ // Force the path become relative.
279
+ if (substr($url, 0, 1) !== '/') {
280
+ $url = '/' . $url;
281
+ }
282
+ }
283
+ $parts = parse_url($url);
284
+ if (isset($parts['host'])) {
285
+ $this->baseComponent = sprintf(
286
+ "%s%s%s",
287
+ isset($parts['scheme']) ? $parts['scheme'] . "://" : '',
288
+ isset($parts['host']) ? $parts['host'] : '',
289
+ isset($parts['port']) ? ":" . $parts['port'] : ''
290
+ );
291
+ }
292
+ $this->path = isset($parts['path']) ? $parts['path'] : '';
293
+ $this->queryParams = array();
294
+ if (isset($parts['query'])) {
295
+ $this->queryParams = $this->parseQuery($parts['query']);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * @param string $method Set he HTTP Method and normalize
301
+ * it to upper-case, as required by HTTP.
302
+ *
303
+ */
304
+ public function setRequestMethod($method)
305
+ {
306
+ $this->requestMethod = strtoupper($method);
307
+ }
308
+
309
+ /**
310
+ * @param array $headers The HTTP request headers
311
+ * to be set and normalized.
312
+ */
313
+ public function setRequestHeaders($headers)
314
+ {
315
+ $headers = Google_Utils::normalize($headers);
316
+ if ($this->requestHeaders) {
317
+ $headers = array_merge($this->requestHeaders, $headers);
318
+ }
319
+ $this->requestHeaders = $headers;
320
+ }
321
+
322
+ /**
323
+ * @param string $postBody the postBody to set
324
+ */
325
+ public function setPostBody($postBody)
326
+ {
327
+ $this->postBody = $postBody;
328
+ }
329
+
330
+ /**
331
+ * Set the User-Agent Header.
332
+ * @param string $userAgent The User-Agent.
333
+ */
334
+ public function setUserAgent($userAgent)
335
+ {
336
+ $this->userAgent = $userAgent;
337
+ if ($this->canGzip) {
338
+ $this->userAgent = $userAgent . self::GZIP_UA;
339
+ }
340
+ }
341
+
342
+ /**
343
+ * @return string The User-Agent.
344
+ */
345
+ public function getUserAgent()
346
+ {
347
+ return $this->userAgent;
348
+ }
349
+
350
+ /**
351
+ * Returns a cache key depending on if this was an OAuth signed request
352
+ * in which case it will use the non-signed url and access key to make this
353
+ * cache key unique per authenticated user, else use the plain request url
354
+ * @return string The md5 hash of the request cache key.
355
+ */
356
+ public function getCacheKey()
357
+ {
358
+ $key = $this->getUrl();
359
+
360
+ if (isset($this->accessKey)) {
361
+ $key .= $this->accessKey;
362
+ }
363
+
364
+ if (isset($this->requestHeaders['authorization'])) {
365
+ $key .= $this->requestHeaders['authorization'];
366
+ }
367
+
368
+ return md5($key);
369
+ }
370
+
371
+ public function getParsedCacheControl()
372
+ {
373
+ $parsed = array();
374
+ $rawCacheControl = $this->getResponseHeader('cache-control');
375
+ if ($rawCacheControl) {
376
+ $rawCacheControl = str_replace(', ', '&', $rawCacheControl);
377
+ parse_str($rawCacheControl, $parsed);
378
+ }
379
+
380
+ return $parsed;
381
+ }
382
+
383
+ /**
384
+ * @param string $id
385
+ * @return string A string representation of the HTTP Request.
386
+ */
387
+ public function toBatchString($id)
388
+ {
389
+ $str = '';
390
+ $path = parse_url($this->getUrl(), PHP_URL_PATH) . "?" .
391
+ http_build_query($this->queryParams);
392
+ $str .= $this->getRequestMethod() . ' ' . $path . " HTTP/1.1\n";
393
+
394
+ foreach ($this->getRequestHeaders() as $key => $val) {
395
+ $str .= $key . ': ' . $val . "\n";
396
+ }
397
+
398
+ if ($this->getPostBody()) {
399
+ $str .= "\n";
400
+ $str .= $this->getPostBody();
401
+ }
402
+
403
+ $headers = '';
404
+ foreach ($this->batchHeaders as $key => $val) {
405
+ $headers .= $key . ': ' . $val . "\n";
406
+ }
407
+
408
+ $headers .= "Content-ID: $id\n";
409
+ $str = $headers . "\n" . $str;
410
+
411
+ return $str;
412
+ }
413
+
414
+ /**
415
+ * Our own version of parse_str that allows for multiple variables
416
+ * with the same name.
417
+ * @param $string - the query string to parse
418
+ */
419
+ private function parseQuery($string)
420
+ {
421
+ $return = array();
422
+ $parts = explode("&", $string);
423
+ foreach ($parts as $part) {
424
+ list($key, $value) = explode('=', $part, 2);
425
+ $value = urldecode($value);
426
+ if (isset($return[$key])) {
427
+ if (!is_array($return[$key])) {
428
+ $return[$key] = array($return[$key]);
429
+ }
430
+ $return[$key][] = $value;
431
+ } else {
432
+ $return[$key] = $value;
433
+ }
434
+ }
435
+ return $return;
436
+ }
437
+
438
+ /**
439
+ * A version of build query that allows for multiple
440
+ * duplicate keys.
441
+ * @param $parts array of key value pairs
442
+ */
443
+ private function buildQuery($parts)
444
+ {
445
+ $return = array();
446
+ foreach ($parts as $key => $value) {
447
+ if (is_array($value)) {
448
+ foreach ($value as $v) {
449
+ $return[] = urlencode($key) . "=" . urlencode($v);
450
+ }
451
+ } else {
452
+ $return[] = urlencode($key) . "=" . urlencode($value);
453
+ }
454
+ }
455
+ return implode('&', $return);
456
+ }
457
+
458
+ /**
459
+ * If we're POSTing and have no body to send, we can send the query
460
+ * parameters in there, which avoids length issues with longer query
461
+ * params.
462
+ */
463
+ public function maybeMoveParametersToBody()
464
+ {
465
+ if ($this->getRequestMethod() == "POST" && empty($this->postBody)) {
466
+ $this->setRequestHeaders(
467
+ array(
468
+ "content-type" =>
469
+ "application/x-www-form-urlencoded; charset=UTF-8"
470
+ )
471
+ );
472
+ $this->setPostBody($this->buildQuery($this->queryParams));
473
+ $this->queryParams = array();
474
+ }
475
+ }
476
+ }
includes/Google/IO/Abstract.php ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Abstract IO base class
20
+ */
21
+
22
+ require_once 'Google/Client.php';
23
+ require_once 'Google/IO/Exception.php';
24
+ require_once 'Google/Http/CacheParser.php';
25
+ require_once 'Google/Http/Request.php';
26
+
27
+ abstract class Google_IO_Abstract
28
+ {
29
+ const UNKNOWN_CODE = 0;
30
+ const FORM_URLENCODED = 'application/x-www-form-urlencoded';
31
+ const CONNECTION_ESTABLISHED = "HTTP/1.0 200 Connection established\r\n\r\n";
32
+ private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
33
+
34
+ /** @var Google_Client */
35
+ protected $client;
36
+
37
+ public function __construct(Google_Client $client)
38
+ {
39
+ $this->client = $client;
40
+ $timeout = $client->getClassConfig('Google_IO_Abstract', 'request_timeout_seconds');
41
+ if ($timeout > 0) {
42
+ $this->setTimeout($timeout);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Executes a Google_Http_Request and returns the resulting populated Google_Http_Request
48
+ * @param Google_Http_Request $request
49
+ * @return Google_Http_Request $request
50
+ */
51
+ abstract public function executeRequest(Google_Http_Request $request);
52
+
53
+ /**
54
+ * Set options that update the transport implementation's behavior.
55
+ * @param $options
56
+ */
57
+ abstract public function setOptions($options);
58
+
59
+ /**
60
+ * Set the maximum request time in seconds.
61
+ * @param $timeout in seconds
62
+ */
63
+ abstract public function setTimeout($timeout);
64
+
65
+ /**
66
+ * Get the maximum request time in seconds.
67
+ * @return timeout in seconds
68
+ */
69
+ abstract public function getTimeout();
70
+
71
+ /**
72
+ * Determine whether "Connection Established" quirk is needed
73
+ * @return boolean
74
+ */
75
+ abstract protected function needsQuirk();
76
+
77
+ /**
78
+ * @visible for testing.
79
+ * Cache the response to an HTTP request if it is cacheable.
80
+ * @param Google_Http_Request $request
81
+ * @return bool Returns true if the insertion was successful.
82
+ * Otherwise, return false.
83
+ */
84
+ public function setCachedRequest(Google_Http_Request $request)
85
+ {
86
+ // Determine if the request is cacheable.
87
+ if (Google_Http_CacheParser::isResponseCacheable($request)) {
88
+ $this->client->getCache()->set($request->getCacheKey(), $request);
89
+ return true;
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Execute an HTTP Request
97
+ *
98
+ * @param Google_HttpRequest $request the http request to be executed
99
+ * @return Google_HttpRequest http request with the response http code,
100
+ * response headers and response body filled in
101
+ * @throws Google_IO_Exception on curl or IO error
102
+ */
103
+ public function makeRequest(Google_Http_Request $request)
104
+ {
105
+ // First, check to see if we have a valid cached version.
106
+ $cached = $this->getCachedRequest($request);
107
+ if ($cached !== false && $cached instanceof Google_Http_Request) {
108
+ if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
109
+ return $cached;
110
+ }
111
+ }
112
+
113
+ if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
114
+ $request = $this->processEntityRequest($request);
115
+ }
116
+
117
+ list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
118
+
119
+ if ($respHttpCode == 304 && $cached) {
120
+ // If the server responded NOT_MODIFIED, return the cached request.
121
+ $this->updateCachedRequest($cached, $responseHeaders);
122
+ return $cached;
123
+ }
124
+
125
+ if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
126
+ $responseHeaders['Date'] = date("r");
127
+ }
128
+
129
+ $request->setResponseHttpCode($respHttpCode);
130
+ $request->setResponseHeaders($responseHeaders);
131
+ $request->setResponseBody($responseData);
132
+ // Store the request in cache (the function checks to see if the request
133
+ // can actually be cached)
134
+ $this->setCachedRequest($request);
135
+ return $request;
136
+ }
137
+
138
+ /**
139
+ * @visible for testing.
140
+ * @param Google_Http_Request $request
141
+ * @return Google_Http_Request|bool Returns the cached object or
142
+ * false if the operation was unsuccessful.
143
+ */
144
+ public function getCachedRequest(Google_Http_Request $request)
145
+ {
146
+ if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
147
+ return false;
148
+ }
149
+
150
+ return $this->client->getCache()->get($request->getCacheKey());
151
+ }
152
+
153
+ /**
154
+ * @visible for testing
155
+ * Process an http request that contains an enclosed entity.
156
+ * @param Google_Http_Request $request
157
+ * @return Google_Http_Request Processed request with the enclosed entity.
158
+ */
159
+ public function processEntityRequest(Google_Http_Request $request)
160
+ {
161
+ $postBody = $request->getPostBody();
162
+ $contentType = $request->getRequestHeader("content-type");
163
+
164
+ // Set the default content-type as application/x-www-form-urlencoded.
165
+ if (false == $contentType) {
166
+ $contentType = self::FORM_URLENCODED;
167
+ $request->setRequestHeaders(array('content-type' => $contentType));
168
+ }
169
+
170
+ // Force the payload to match the content-type asserted in the header.
171
+ if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
172
+ $postBody = http_build_query($postBody, '', '&');
173
+ $request->setPostBody($postBody);
174
+ }
175
+
176
+ // Make sure the content-length header is set.
177
+ if (!$postBody || is_string($postBody)) {
178
+ $postsLength = strlen($postBody);
179
+ $request->setRequestHeaders(array('content-length' => $postsLength));
180
+ }
181
+
182
+ return $request;
183
+ }
184
+
185
+ /**
186
+ * Check if an already cached request must be revalidated, and if so update
187
+ * the request with the correct ETag headers.
188
+ * @param Google_Http_Request $cached A previously cached response.
189
+ * @param Google_Http_Request $request The outbound request.
190
+ * return bool If the cached object needs to be revalidated, false if it is
191
+ * still current and can be re-used.
192
+ */
193
+ protected function checkMustRevalidateCachedRequest($cached, $request)
194
+ {
195
+ if (Google_Http_CacheParser::mustRevalidate($cached)) {
196
+ $addHeaders = array();
197
+ if ($cached->getResponseHeader('etag')) {
198
+ // [13.3.4] If an entity tag has been provided by the origin server,
199
+ // we must use that entity tag in any cache-conditional request.
200
+ $addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
201
+ } elseif ($cached->getResponseHeader('date')) {
202
+ $addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
203
+ }
204
+
205
+ $request->setRequestHeaders($addHeaders);
206
+ return true;
207
+ } else {
208
+ return false;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Update a cached request, using the headers from the last response.
214
+ * @param Google_HttpRequest $cached A previously cached response.
215
+ * @param mixed Associative array of response headers from the last request.
216
+ */
217
+ protected function updateCachedRequest($cached, $responseHeaders)
218
+ {
219
+ if (isset($responseHeaders['connection'])) {
220
+ $hopByHop = array_merge(
221
+ self::$HOP_BY_HOP,
222
+ explode(
223
+ ',',
224
+ $responseHeaders['connection']
225
+ )
226
+ );
227
+
228
+ $endToEnd = array();
229
+ foreach ($hopByHop as $key) {
230
+ if (isset($responseHeaders[$key])) {
231
+ $endToEnd[$key] = $responseHeaders[$key];
232
+ }
233
+ }
234
+ $cached->setResponseHeaders($endToEnd);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Used by the IO lib and also the batch processing.
240
+ *
241
+ * @param $respData
242
+ * @param $headerSize
243
+ * @return array
244
+ */
245
+ public function parseHttpResponse($respData, $headerSize)
246
+ {
247
+ // only strip this header if the sub-class needs this quirk
248
+ if ($this->needsQuirk() && stripos($respData, self::CONNECTION_ESTABLISHED) !== false) {
249
+ $respData = str_ireplace(self::CONNECTION_ESTABLISHED, '', $respData);
250
+ $headerSize -= strlen(self::CONNECTION_ESTABLISHED);
251
+ }
252
+
253
+ if ($headerSize) {
254
+ $responseBody = substr($respData, $headerSize);
255
+ $responseHeaders = substr($respData, 0, $headerSize);
256
+ } else {
257
+ list($responseHeaders, $responseBody) = explode("\r\n\r\n", $respData, 2);
258
+ }
259
+
260
+ $responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
261
+ return array($responseHeaders, $responseBody);
262
+ }
263
+
264
+ /**
265
+ * Parse out headers from raw headers
266
+ * @param rawHeaders array or string
267
+ * @return array
268
+ */
269
+ public function getHttpResponseHeaders($rawHeaders)
270
+ {
271
+ if (is_array($rawHeaders)) {
272
+ return $this->parseArrayHeaders($rawHeaders);
273
+ } else {
274
+ return $this->parseStringHeaders($rawHeaders);
275
+ }
276
+ }
277
+
278
+ private function parseStringHeaders($rawHeaders)
279
+ {
280
+ $headers = array();
281
+ $responseHeaderLines = explode("\r\n", $rawHeaders);
282
+ foreach ($responseHeaderLines as $headerLine) {
283
+ if ($headerLine && strpos($headerLine, ':') !== false) {
284
+ list($header, $value) = explode(': ', $headerLine, 2);
285
+ $header = strtolower($header);
286
+ if (isset($responseHeaders[$header])) {
287
+ $headers[$header] .= "\n" . $value;
288
+ } else {
289
+ $headers[$header] = $value;
290
+ }
291
+ }
292
+ }
293
+ return $headers;
294
+ }
295
+
296
+ private function parseArrayHeaders($rawHeaders)
297
+ {
298
+ $header_count = count($rawHeaders);
299
+ $headers = array();
300
+
301
+ for ($i = 0; $i < $header_count; $i++) {
302
+ $header = $rawHeaders[$i];
303
+ // Times will have colons in - so we just want the first match.
304
+ $header_parts = explode(': ', $header, 2);
305
+ if (count($header_parts) == 2) {
306
+ $headers[$header_parts[0]] = $header_parts[1];
307
+ }
308
+ }
309
+
310
+ return $headers;
311
+ }
312
+ }
includes/Google/IO/Curl.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2014 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Curl based implementation of Google_IO.
20
+ *
21
+ * @author Stuart Langley <slangley@google.com>
22
+ */
23
+
24
+ require_once 'Google/IO/Abstract.php';
25
+
26
+ class Google_IO_Curl extends Google_IO_Abstract
27
+ {
28
+ // hex for version 7.31.0
29
+ const NO_QUIRK_VERSION = 0x071F00;
30
+
31
+ private $options = array();
32
+ /**
33
+ * Execute an HTTP Request
34
+ *
35
+ * @param Google_HttpRequest $request the http request to be executed
36
+ * @return Google_HttpRequest http request with the response http code,
37
+ * response headers and response body filled in
38
+ * @throws Google_IO_Exception on curl or IO error
39
+ */
40
+ public function executeRequest(Google_Http_Request $request)
41
+ {
42
+ $curl = curl_init();
43
+
44
+ if ($request->getPostBody()) {
45
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getPostBody());
46
+ }
47
+
48
+ $requestHeaders = $request->getRequestHeaders();
49
+ if ($requestHeaders && is_array($requestHeaders)) {
50
+ $curlHeaders = array();
51
+ foreach ($requestHeaders as $k => $v) {
52
+ $curlHeaders[] = "$k: $v";
53
+ }
54
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
55
+ }
56
+
57
+ curl_setopt($curl, CURLOPT_URL, $request->getUrl());
58
+
59
+ curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod());
60
+ curl_setopt($curl, CURLOPT_USERAGENT, $request->getUserAgent());
61
+
62
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
63
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
64
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
65
+ curl_setopt($curl, CURLOPT_HEADER, true);
66
+
67
+
68
+ if ($request->canGzip()) {
69
+ curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
70
+ }
71
+
72
+ foreach ($this->options as $key => $var) {
73
+ curl_setopt($curl, $key, $var);
74
+ }
75
+
76
+ if (!isset($this->options[CURLOPT_CAINFO])) {
77
+ curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem');
78
+ }
79
+
80
+ $response = curl_exec($curl);
81
+ if ($response === false) {
82
+ throw new Google_IO_Exception(curl_error($curl));
83
+ }
84
+ $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
85
+
86
+ $responseBody = substr($response, $headerSize);
87
+ $responseHeaderString = substr($response, 0, $headerSize);
88
+ $responseHeaders = $this->getHttpResponseHeaders($responseHeaderString);
89
+ $responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
90
+
91
+ return array($responseBody, $responseHeaders, $responseCode);
92
+ }
93
+
94
+ /**
95
+ * Set options that update the transport implementation's behavior.
96
+ * @param $options
97
+ */
98
+ public function setOptions($options)
99
+ {
100
+ $this->options = $options + $this->options;
101
+ }
102
+
103
+ /**
104
+ * Set the maximum request time in seconds.
105
+ * @param $timeout in seconds
106
+ */
107
+ public function setTimeout($timeout)
108
+ {
109
+ // Since this timeout is really for putting a bound on the time
110
+ // we'll set them both to the same. If you need to specify a longer
111
+ // CURLOPT_TIMEOUT, or a tigher CONNECTTIMEOUT, the best thing to
112
+ // do is use the setOptions method for the values individually.
113
+ $this->options[CURLOPT_CONNECTTIMEOUT] = $timeout;
114
+ $this->options[CURLOPT_TIMEOUT] = $timeout;
115
+ }
116
+
117
+ /**
118
+ * Get the maximum request time in seconds.
119
+ * @return timeout in seconds
120
+ */
121
+ public function getTimeout()
122
+ {
123
+ return $this->options[CURLOPT_TIMEOUT];
124
+ }
125
+
126
+ /**
127
+ * Determine whether "Connection Established" quirk is needed
128
+ * @return boolean
129
+ */
130
+ protected function needsQuirk()
131
+ {
132
+ $ver = curl_version();
133
+ $versionNum = $ver['version_number'];
134
+ return $versionNum < Google_IO_Curl::NO_QUIRK_VERSION;
135
+ }
136
+ }
includes/Google/IO/Exception.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ require_once 'Google/Exception.php';
19
+
20
+ class Google_IO_Exception extends Google_Exception
21
+ {
22
+ }
includes/Google/IO/Stream.php ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2013 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Http Streams based implementation of Google_IO.
20
+ *
21
+ * @author Stuart Langley <slangley@google.com>
22
+ */
23
+
24
+ require_once 'Google/IO/Abstract.php';
25
+
26
+ class Google_IO_Stream extends Google_IO_Abstract
27
+ {
28
+ const TIMEOUT = "timeout";
29
+ const ZLIB = "compress.zlib://";
30
+ private $options = array();
31
+
32
+ private static $DEFAULT_HTTP_CONTEXT = array(
33
+ "follow_location" => 0,
34
+ "ignore_errors" => 1,
35
+ );
36
+
37
+ private static $DEFAULT_SSL_CONTEXT = array(
38
+ "verify_peer" => true,
39
+ );
40
+
41
+ /**
42
+ * Execute an HTTP Request
43
+ *
44
+ * @param Google_HttpRequest $request the http request to be executed
45
+ * @return Google_HttpRequest http request with the response http code,
46
+ * response headers and response body filled in
47
+ * @throws Google_IO_Exception on curl or IO error
48
+ */
49
+ public function executeRequest(Google_Http_Request $request)
50
+ {
51
+ $default_options = stream_context_get_options(stream_context_get_default());
52
+
53
+ $requestHttpContext = array_key_exists('http', $default_options) ?
54
+ $default_options['http'] : array();
55
+
56
+ if ($request->getPostBody()) {
57
+ $requestHttpContext["content"] = $request->getPostBody();
58
+ }
59
+
60
+ $requestHeaders = $request->getRequestHeaders();
61
+ if ($requestHeaders && is_array($requestHeaders)) {
62
+ $headers = "";
63
+ foreach ($requestHeaders as $k => $v) {
64
+ $headers .= "$k: $v\r\n";
65
+ }
66
+ $requestHttpContext["header"] = $headers;
67
+ }
68
+
69
+ $requestHttpContext["method"] = $request->getRequestMethod();
70
+ $requestHttpContext["user_agent"] = $request->getUserAgent();
71
+
72
+ $requestSslContext = array_key_exists('ssl', $default_options) ?
73
+ $default_options['ssl'] : array();
74
+
75
+ // if (!array_key_exists("cafile", $requestSslContext)) {
76
+ // $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
77
+ // }
78
+
79
+ // Added
80
+ if (empty($this->options['disable_verify_peer'])) {
81
+ $requestSslContext['verify_peer'] = true;
82
+ $requestSslContext['allow_self_signed'] = true;
83
+ }
84
+ if (!empty($this->options['cafile'])) $requestSslContext['cafile'] = $this->options['cafile'];
85
+
86
+ $options = array(
87
+ "http" => array_merge(
88
+ self::$DEFAULT_HTTP_CONTEXT,
89
+ $requestHttpContext
90
+ ),
91
+ # self::$DEFAULT_SSL_CONTEXT,
92
+ "ssl" => array_merge(
93
+ $requestSslContext
94
+ )
95
+ );
96
+
97
+ $context = stream_context_create($options);
98
+
99
+ $url = $request->getUrl();
100
+
101
+ if ($request->canGzip()) {
102
+ $url = self::ZLIB . $url;
103
+ }
104
+
105
+ // Not entirely happy about this, but supressing the warning from the
106
+ // fopen seems like the best situation here - we can't do anything
107
+ // useful with it, and failure to connect is a legitimate run
108
+ // time situation.
109
+ @$fh = fopen($url, 'r', false, $context);
110
+
111
+ $response_data = false;
112
+ $respHttpCode = self::UNKNOWN_CODE;
113
+ if ($fh) {
114
+ if (isset($this->options[self::TIMEOUT])) {
115
+ stream_set_timeout($fh, $this->options[self::TIMEOUT]);
116
+ }
117
+
118
+ $response_data = stream_get_contents($fh);
119
+ fclose($fh);
120
+
121
+ $respHttpCode = $this->getHttpResponseCode($http_response_header);
122
+ }
123
+
124
+ if (false === $response_data) {
125
+ throw new Google_IO_Exception(
126
+ sprintf(
127
+ "HTTP Error: Unable to connect: '%s'",
128
+ $respHttpCode
129
+ ),
130
+ $respHttpCode
131
+ );
132
+ }
133
+
134
+ $responseHeaders = $this->getHttpResponseHeaders($http_response_header);
135
+
136
+ return array($response_data, $responseHeaders, $respHttpCode);
137
+ }
138
+
139
+ /**
140
+ * Set options that update the transport implementation's behavior.
141
+ * @param $options
142
+ */
143
+ public function setOptions($options)
144
+ {
145
+ $this->options = $options + $this->options;
146
+ }
147
+
148
+ /**
149
+ * Set the maximum request time in seconds.
150
+ * @param $timeout in seconds
151
+ */
152
+ public function setTimeout($timeout)
153
+ {
154
+ $this->options[self::TIMEOUT] = $timeout;
155
+ }
156
+
157
+ /**
158
+ * Get the maximum request time in seconds.
159
+ * @return timeout in seconds
160
+ */
161
+ public function getTimeout()
162
+ {
163
+ return $this->options[self::TIMEOUT];
164
+ }
165
+
166
+ /**
167
+ * Determine whether "Connection Established" quirk is needed
168
+ * @return boolean
169
+ */
170
+ protected function needsQuirk()
171
+ {
172
+ // Stream needs the special quirk
173
+ return true;
174
+ }
175
+
176
+ protected function getHttpResponseCode($response_headers)
177
+ {
178
+ $header_count = count($response_headers);
179
+
180
+ for ($i = 0; $i < $header_count; $i++) {
181
+ $header = $response_headers[$i];
182
+ if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) {
183
+ $response = explode(' ', $header);
184
+ return $response[1];
185
+ }
186
+ }
187
+ return self::UNKNOWN_CODE;
188
+ }
189
+ }
includes/Google/Model.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2011 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * This class defines attributes, valid values, and usage which is generated
20
+ * from a given json schema.
21
+ * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
22
+ *
23
+ * @author Chirag Shah <chirags@google.com>
24
+ *
25
+ */
26
+ class Google_Model implements ArrayAccess
27
+ {
28
+ protected $modelData = array();
29
+ protected $processed = array();
30
+
31
+ /**
32
+ * Polymorphic - accepts a variable number of arguments dependent
33
+ * on the type of the model subclass.
34
+ */
35
+ public function __construct()
36
+ {
37
+ if (func_num_args() == 1 && is_array(func_get_arg(0))) {
38
+ // Initialize the model with the array's contents.
39
+ $array = func_get_arg(0);
40
+ $this->mapTypes($array);
41
+ }
42
+ }
43
+
44
+ public function __get($key)
45
+ {
46
+ $keyTypeName = $this->keyType($key);
47
+ $keyDataType = $this->dataType($key);
48
+ if (isset($this->$keyTypeName) && !isset($this->processed[$key])) {
49
+ if (isset($this->modelData[$key])) {
50
+ $val = $this->modelData[$key];
51
+ } else {
52
+ $val = null;
53
+ }
54
+
55
+ if ($this->isAssociativeArray($val)) {
56
+ if (isset($this->$keyDataType) && 'map' == $this->$keyDataType) {
57
+ foreach ($val as $arrayKey => $arrayItem) {
58
+ $this->modelData[$key][$arrayKey] =
59
+ $this->createObjectFromName($keyTypeName, $arrayItem);
60
+ }
61
+ } else {
62
+ $this->modelData[$key] = $this->createObjectFromName($keyTypeName, $val);
63
+ }
64
+ } else if (is_array($val)) {
65
+ $arrayObject = array();
66
+ foreach ($val as $arrayIndex => $arrayItem) {
67
+ $arrayObject[$arrayIndex] =
68
+ $this->createObjectFromName($keyTypeName, $arrayItem);
69
+ }
70
+ $this->modelData[$key] = $arrayObject;
71
+ }
72
+ $this->processed[$key] = true;
73
+ }
74
+
75
+ return $this->modelData[$key];
76
+ }
77
+
78
+ /**
79
+ * Initialize this object's properties from an array.
80
+ *
81
+ * @param array $array Used to seed this object's properties.
82
+ * @return void
83
+ */
84
+ protected function mapTypes($array)
85
+ {
86
+ // Hard initilise simple types, lazy load more complex ones.
87
+ foreach ($array as $key => $val) {
88
+ if ( !property_exists($this, $this->keyType($key)) &&
89
+ property_exists($this, $key)) {
90
+ $this->$key = $val;
91
+ unset($array[$key]);
92
+ } elseif (property_exists($this, $camelKey = Google_Utils::camelCase($key))) {
93
+ // This checks if property exists as camelCase, leaving it in array as snake_case
94
+ // in case of backwards compatibility issues.
95
+ $this->$camelKey = $val;
96
+ }
97
+ }
98
+ $this->modelData = $array;
99
+ }
100
+
101
+ /**
102
+ * Create a simplified object suitable for straightforward
103
+ * conversion to JSON. This is relatively expensive
104
+ * due to the usage of reflection, but shouldn't be called
105
+ * a whole lot, and is the most straightforward way to filter.
106
+ */
107
+ public function toSimpleObject()
108
+ {
109
+ $object = new stdClass();
110
+
111
+ // Process all other data.
112
+ foreach ($this->modelData as $key => $val) {
113
+ $result = $this->getSimpleValue($val);
114
+ if ($result !== null) {
115
+ $object->$key = $result;
116
+ }
117
+ }
118
+
119
+ // Process all public properties.
120
+ $reflect = new ReflectionObject($this);
121
+ $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
122
+ foreach ($props as $member) {
123
+ $name = $member->getName();
124
+ $result = $this->getSimpleValue($this->$name);
125
+ if ($result !== null) {
126
+ $object->$name = $result;
127
+ }
128
+ }
129
+
130
+ return $object;
131
+ }
132
+
133
+ /**
134
+ * Handle different types of values, primarily
135
+ * other objects and map and array data types.
136
+ */
137
+ private function getSimpleValue($value)
138
+ {
139
+ if ($value instanceof Google_Model) {
140
+ return $value->toSimpleObject();
141
+ } else if (is_array($value)) {
142
+ $return = array();
143
+ foreach ($value as $key => $a_value) {
144
+ $a_value = $this->getSimpleValue($a_value);
145
+ if ($a_value !== null) {
146
+ $return[$key] = $a_value;
147
+ }
148
+ }
149
+ return $return;
150
+ }
151
+ return $value;
152
+ }
153
+
154
+ /**
155
+ * Returns true only if the array is associative.
156
+ * @param array $array
157
+ * @return bool True if the array is associative.
158
+ */
159
+ protected function isAssociativeArray($array)
160
+ {
161
+ if (!is_array($array)) {
162
+ return false;
163
+ }
164
+ $keys = array_keys($array);
165
+ foreach ($keys as $key) {
166
+ if (is_string($key)) {
167
+ return true;
168
+ }
169
+ }
170
+ return false;
171
+ }
172
+
173
+ /**
174
+ * Given a variable name, discover its type.
175
+ *
176
+ * @param $name
177
+ * @param $item
178
+ * @return object The object from the item.
179
+ */
180
+ private function createObjectFromName($name, $item)
181
+ {
182
+ $type = $this->$name;
183
+ return new $type($item);
184
+ }
185
+
186
+ /**
187
+ * Verify if $obj is an array.
188
+ * @throws Google_Exception Thrown if $obj isn't an array.
189
+ * @param array $obj Items that should be validated.
190
+ * @param string $method Method expecting an array as an argument.
191
+ */
192
+ public function assertIsArray($obj, $method)
193
+ {
194
+ if ($obj && !is_array($obj)) {
195
+ throw new Google_Exception(
196
+ "Incorrect parameter type passed to $method(). Expected an array."
197
+ );
198
+ }
199
+ }
200
+
201
+ public function offsetExists($offset)
202
+ {
203
+ return isset($this->$offset) || isset($this->modelData[$offset]);
204
+ }
205
+
206
+ public function offsetGet($offset)
207
+ {
208
+ return isset($this->$offset) ?
209
+ $this->$offset :
210
+ $this->__get($offset);
211
+ }
212
+
213
+ public function offsetSet($offset, $value)
214
+ {
215
+ if (property_exists($this, $offset)) {
216
+ $this->$offset = $value;
217
+ } else {
218
+ $this->modelData[$offset] = $value;
219
+ $this->processed[$offset] = true;
220
+ }
221
+ }
222
+
223
+ public function offsetUnset($offset)
224
+ {
225
+ unset($this->modelData[$offset]);
226
+ }
227
+
228
+ protected function keyType($key)
229
+ {
230
+ return $key . "Type";
231
+ }
232
+
233
+ protected function dataType($key)
234
+ {
235
+ return $key . "DataType";
236
+ }
237
+
238
+ public function __isset($key)
239
+ {
240
+ return isset($this->modelData[$key]);
241
+ }
242
+
243
+ public function __unset($key)
244
+ {
245
+ unset($this->modelData[$key]);
246
+ }
247
+ }
includes/Google/Service.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ * Copyright 2010 Google Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ class Google_Service
19
+ {
20
+ public $version;
21
+ public $servicePath;
22
+ public $availableScopes;
23
+ public $resource;
24
+ private $client;
25
+
26
+ public function __construct(Google_Client $client)
27
+ {
28
+ $this->client = $client;
29
+ }
30
+
31
+ /**
32
+ * Return the associated Google_Client class.
33
+ * @return Google_Client
34
+ */
35
+ public function getClient()
36
+ {
37
+ return $this->client;
38
+ }
39
+ }
includes/Google/Service/Drive.php ADDED
@@ -0,0 +1,5732 @@