浏览代码

Printing with Google Cloud Print

frederic34 9 年之前
父节点
当前提交
1694c690ed
共有 100 个文件被更改,包括 10583 次插入176 次删除
  1. 6 0
      .tx/config
  2. 335 0
      htdocs/admin/oauth.php
  3. 6 5
      htdocs/core/actions_printing.inc.php
  4. 123 0
      htdocs/core/modules/modOauth.class.php
  5. 110 0
      htdocs/core/modules/oauth/getgoogleoauthcallback.php
  6. 0 0
      htdocs/core/modules/oauth/index.html
  7. 3 3
      htdocs/core/modules/printing/modules_printing.php
  8. 163 164
      htdocs/core/modules/printing/printgcp.modules.php
  9. 6 4
      htdocs/core/modules/printing/printipp.modules.php
  10. 81 0
      htdocs/includes/OAuth/Common/AutoLoader.php
  11. 60 0
      htdocs/includes/OAuth/Common/Consumer/Credentials.php
  12. 24 0
      htdocs/includes/OAuth/Common/Consumer/CredentialsInterface.php
  13. 10 0
      htdocs/includes/OAuth/Common/Exception/Exception.php
  14. 73 0
      htdocs/includes/OAuth/Common/Http/Client/AbstractClient.php
  15. 32 0
      htdocs/includes/OAuth/Common/Http/Client/ClientInterface.php
  16. 142 0
      htdocs/includes/OAuth/Common/Http/Client/CurlClient.php
  17. 95 0
      htdocs/includes/OAuth/Common/Http/Client/StreamClient.php
  18. 12 0
      htdocs/includes/OAuth/Common/Http/Exception/TokenResponseException.php
  19. 408 0
      htdocs/includes/OAuth/Common/Http/Uri/Uri.php
  20. 168 0
      htdocs/includes/OAuth/Common/Http/Uri/UriFactory.php
  21. 42 0
      htdocs/includes/OAuth/Common/Http/Uri/UriFactoryInterface.php
  22. 133 0
      htdocs/includes/OAuth/Common/Http/Uri/UriInterface.php
  23. 100 0
      htdocs/includes/OAuth/Common/Service/AbstractService.php
  24. 49 0
      htdocs/includes/OAuth/Common/Service/ServiceInterface.php
  25. 254 0
      htdocs/includes/OAuth/Common/Storage/DoliStorage.php
  26. 10 0
      htdocs/includes/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php
  27. 12 0
      htdocs/includes/OAuth/Common/Storage/Exception/StorageException.php
  28. 10 0
      htdocs/includes/OAuth/Common/Storage/Exception/TokenNotFoundException.php
  29. 139 0
      htdocs/includes/OAuth/Common/Storage/Memory.php
  30. 230 0
      htdocs/includes/OAuth/Common/Storage/Redis.php
  31. 188 0
      htdocs/includes/OAuth/Common/Storage/Session.php
  32. 200 0
      htdocs/includes/OAuth/Common/Storage/SymfonySession.php
  33. 98 0
      htdocs/includes/OAuth/Common/Storage/TokenStorageInterface.php
  34. 128 0
      htdocs/includes/OAuth/Common/Token/AbstractToken.php
  35. 12 0
      htdocs/includes/OAuth/Common/Token/Exception/ExpiredTokenException.php
  36. 64 0
      htdocs/includes/OAuth/Common/Token/TokenInterface.php
  37. 320 0
      htdocs/includes/OAuth/OAuth1/Service/AbstractService.php
  38. 96 0
      htdocs/includes/OAuth/OAuth1/Service/BitBucket.php
  39. 132 0
      htdocs/includes/OAuth/OAuth1/Service/Etsy.php
  40. 96 0
      htdocs/includes/OAuth/OAuth1/Service/FitBit.php
  41. 120 0
      htdocs/includes/OAuth/OAuth1/Service/FiveHundredPx.php
  42. 133 0
      htdocs/includes/OAuth/OAuth1/Service/Flickr.php
  43. 120 0
      htdocs/includes/OAuth/OAuth1/Service/QuickBooks.php
  44. 96 0
      htdocs/includes/OAuth/OAuth1/Service/Redmine.php
  45. 96 0
      htdocs/includes/OAuth/OAuth1/Service/ScoopIt.php
  46. 45 0
      htdocs/includes/OAuth/OAuth1/Service/ServiceInterface.php
  47. 96 0
      htdocs/includes/OAuth/OAuth1/Service/Tumblr.php
  48. 123 0
      htdocs/includes/OAuth/OAuth1/Service/Twitter.php
  49. 97 0
      htdocs/includes/OAuth/OAuth1/Service/Xing.php
  50. 131 0
      htdocs/includes/OAuth/OAuth1/Service/Yahoo.php
  51. 12 0
      htdocs/includes/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php
  52. 132 0
      htdocs/includes/OAuth/OAuth1/Signature/Signature.php
  53. 28 0
      htdocs/includes/OAuth/OAuth1/Signature/SignatureInterface.php
  54. 75 0
      htdocs/includes/OAuth/OAuth1/Token/StdOAuth1Token.php
  55. 41 0
      htdocs/includes/OAuth/OAuth1/Token/TokenInterface.php
  56. 364 0
      htdocs/includes/OAuth/OAuth2/Service/AbstractService.php
  57. 97 0
      htdocs/includes/OAuth/OAuth2/Service/Amazon.php
  58. 111 0
      htdocs/includes/OAuth/OAuth2/Service/Bitly.php
  59. 118 0
      htdocs/includes/OAuth/OAuth2/Service/Bitrix24.php
  60. 88 0
      htdocs/includes/OAuth/OAuth2/Service/Box.php
  61. 151 0
      htdocs/includes/OAuth/OAuth2/Service/Buffer.php
  62. 129 0
      htdocs/includes/OAuth/OAuth2/Service/Dailymotion.php
  63. 139 0
      htdocs/includes/OAuth/OAuth2/Service/Delicious.php
  64. 99 0
      htdocs/includes/OAuth/OAuth2/Service/DeviantArt.php
  65. 111 0
      htdocs/includes/OAuth/OAuth2/Service/Dropbox.php
  66. 100 0
      htdocs/includes/OAuth/OAuth2/Service/EveOnline.php
  67. 12 0
      htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php
  68. 10 0
      htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAuthorizationStateException.php
  69. 17 0
      htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidScopeException.php
  70. 17 0
      htdocs/includes/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php
  71. 208 0
      htdocs/includes/OAuth/OAuth2/Service/Facebook.php
  72. 81 0
      htdocs/includes/OAuth/OAuth2/Service/Foursquare.php
  73. 216 0
      htdocs/includes/OAuth/OAuth2/Service/GitHub.php
  74. 188 0
      htdocs/includes/OAuth/OAuth2/Service/Google.php
  75. 157 0
      htdocs/includes/OAuth/OAuth2/Service/Harvest.php
  76. 123 0
      htdocs/includes/OAuth/OAuth2/Service/Heroku.php
  77. 155 0
      htdocs/includes/OAuth/OAuth2/Service/Hubic.php
  78. 85 0
      htdocs/includes/OAuth/OAuth2/Service/Instagram.php
  79. 144 0
      htdocs/includes/OAuth/OAuth2/Service/JawboneUP.php
  80. 103 0
      htdocs/includes/OAuth/OAuth2/Service/Linkedin.php
  81. 114 0
      htdocs/includes/OAuth/OAuth2/Service/Mailchimp.php
  82. 120 0
      htdocs/includes/OAuth/OAuth2/Service/Microsoft.php
  83. 106 0
      htdocs/includes/OAuth/OAuth2/Service/Nest.php
  84. 117 0
      htdocs/includes/OAuth/OAuth2/Service/Netatmo.php
  85. 142 0
      htdocs/includes/OAuth/OAuth2/Service/ParrotFlowerPower.php
  86. 103 0
      htdocs/includes/OAuth/OAuth2/Service/Paypal.php
  87. 125 0
      htdocs/includes/OAuth/OAuth2/Service/Pocket.php
  88. 114 0
      htdocs/includes/OAuth/OAuth2/Service/Reddit.php
  89. 105 0
      htdocs/includes/OAuth/OAuth2/Service/RunKeeper.php
  90. 92 0
      htdocs/includes/OAuth/OAuth2/Service/Salesforce.php
  91. 38 0
      htdocs/includes/OAuth/OAuth2/Service/ServiceInterface.php
  92. 77 0
      htdocs/includes/OAuth/OAuth2/Service/SoundCloud.php
  93. 109 0
      htdocs/includes/OAuth/OAuth2/Service/Spotify.php
  94. 147 0
      htdocs/includes/OAuth/OAuth2/Service/Strava.php
  95. 98 0
      htdocs/includes/OAuth/OAuth2/Service/Ustream.php
  96. 156 0
      htdocs/includes/OAuth/OAuth2/Service/Vimeo.php
  97. 109 0
      htdocs/includes/OAuth/OAuth2/Service/Vkontakte.php
  98. 78 0
      htdocs/includes/OAuth/OAuth2/Service/Yahoo.php
  99. 82 0
      htdocs/includes/OAuth/OAuth2/Service/Yammer.php
  100. 13 0
      htdocs/includes/OAuth/OAuth2/Token/StdOAuth2Token.php

+ 6 - 0
.tx/config

@@ -218,6 +218,12 @@ source_file = htdocs/langs/en_US/members.lang
 source_lang = en_US
 type = MOZILLAPROPERTIES
 
+[dolibarr.oauth]
+file_filter = htdocs/langs/<lang>/oauth.lang
+source_file = htdocs/langs/en_US/oauth.lang
+source_lang = en_US
+type = MOZILLAPROPERTIES
+
 [dolibarr.opensurvey]
 file_filter = htdocs/langs/<lang>/opensurvey.lang
 source_file = htdocs/langs/en_US/opensurvey.lang

+ 335 - 0
htdocs/admin/oauth.php

@@ -0,0 +1,335 @@
+<?php
+/* Copyright (C) 2015       Frederic France     <frederic.france@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * \file        htdocs/admin/oauth.php
+ * \ingroup     oauth
+ * \brief       Setup page to configure oauth access api
+ */
+
+require '../main.inc.php';
+
+// required Class
+require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+
+$langs->load("admin");
+$langs->load("oauth");
+
+// Security check
+if (!$user->admin)
+    accessforbidden();
+
+$action = GETPOST('action', 'alpha');
+
+// API access parameters OAUTH
+$list = array (
+            array(
+                'OAUTH_AMAZON_NAME',
+                'OAUTH_AMAZON_ID',
+                'OAUTH_AMAZON_SECRET',
+            ),
+            array(
+                'OAUTH_BITBUCKET_NAME',
+                'OAUTH_BITBUCKET_ID',
+                'OAUTH_BITBUCKET_SECRET',
+            ),
+            array(
+                'OAUTH_BITLY_NAME',
+                'OAUTH_BITLY_ID',
+                'OAUTH_BITLY_SECRET',
+            ),
+            array(
+                'OAUTH_BITRIX24_NAME',
+                'OAUTH_BITRIX24_ID',
+                'OAUTH_BITRIX24_SECRET',
+            ),
+            array(
+                'OAUTH_BOX_NAME',
+                'OAUTH_BOX_ID',
+                'OAUTH_BOX_SECRET',
+            ),
+            array(
+                'OAUTH_BUFFER_NAME',
+                'OAUTH_BUFFER_ID',
+                'OAUTH_BUFFER_SECRET',
+            ),
+            array(
+                'OAUTH_DAILYMOTION_NAME',
+                'OAUTH_DAILYMOTION_ID',
+                'OAUTH_DAILYMOTION_SECRET',
+            ),
+            array(
+                'OAUTH_DEVIANTART_NAME',
+                'OAUTH_DEVIANTART_ID',
+                'OAUTH_DEVIANTART_SECRET',
+            ),
+            array(
+                'OAUTH_DROPBOX_NAME',
+                'OAUTH_DROPBOX_ID',
+                'OAUTH_DROPBOX_SECRET',
+            ),
+            array(
+                'OAUTH_ETSY_NAME',
+                'OAUTH_ETSY_ID',
+                'OAUTH_ETSY_SECRET',
+            ),
+            array(
+                'OAUTH_EVEONLINE_NAME',
+                'OAUTH_EVEONLINE_ID',
+                'OAUTH_EVEONLINE_SECRET',
+            ),
+            array(
+                'OAUTH_FACEBOOK_NAME',
+                'OAUTH_FACEBOOK_ID',
+                'OAUTH_FACEBOOK_SECRET',
+            ),
+            array(
+                'OAUTH_FITBIT_NAME',
+                'OAUTH_FITBIT_ID',
+                'OAUTH_FITBIT_SECRET',
+            ),
+            array(
+                'OAUTH_FIVEHUNDREDPX_NAME',
+                'OAUTH_FIVEHUNDREDPX_ID',
+                'OAUTH_FIVEHUNDREDPX_SECRET',
+            ),
+            array(
+                'OAUTH_FLICKR_NAME',
+                'OAUTH_FLICKR_ID',
+                'OAUTH_FLICKR_SECRET',
+            ),
+            array(
+                'OAUTH_FOURSQUARE_NAME',
+                'OAUTH_FOURSQUARE_ID',
+                'OAUTH_FOURSQUARE_SECRET',
+            ),
+            array(
+                'OAUTH_GITHUB_NAME',
+                'OAUTH_GITHUB_ID',
+                'OAUTH_GITHUB_SECRET',
+            ),
+            array(
+                'OAUTH_GOOGLE_NAME',
+                'OAUTH_GOOGLE_ID',
+                'OAUTH_GOOGLE_SECRET',
+            ),
+            array(
+                'OAUTH_HUBIC_NAME',
+                'OAUTH_HUBIC_ID',
+                'OAUTH_HUBIC_SECRET',
+            ),
+            array(
+                'OAUTH_INSTAGRAM_NAME',
+                'OAUTH_INSTAGRAM_ID',
+                'OAUTH_INSTAGRAM_SECRET',
+            ),
+            array(
+                'OAUTH_LINKEDIN_NAME',
+                'OAUTH_LINKEDIN_ID',
+                'OAUTH_LINKEDIN_SECRET',
+            ),
+            array(
+                'OAUTH_MAILCHIMP_NAME',
+                'OAUTH_MAILCHIMP_ID',
+                'OAUTH_MAILCHIMP_SECRET',
+            ),
+            array(
+                'OAUTH_MICROSOFT_NAME',
+                'OAUTH_MICROSOFT_ID',
+                'OAUTH_MICROSOFT_SECRET',
+            ),
+            array(
+                'OAUTH_NEST_NAME',
+                'OAUTH_NEST_ID',
+                'OAUTH_NEST_SECRET',
+            ),
+            array(
+                'OAUTH_NETATMO_NAME',
+                'OAUTH_NETATMO_ID',
+                'OAUTH_NETATMO_SECRET',
+            ),
+            array(
+                'OAUTH_PARROTFLOWERPOWER_NAME',
+                'OAUTH_PARROTFLOWERPOWER_ID',
+                'OAUTH_PARROTFLOWERPOWER_SECRET',
+            ),
+            array(
+                'OAUTH_PAYPAL_NAME',
+                'OAUTH_PAYPAL_ID',
+                'OAUTH_PAYPAL_SECRET',
+            ),
+            array(
+                'OAUTH_POCKET_NAME',
+                'OAUTH_POCKET_ID',
+                'OAUTH_POCKET_SECRET',
+            ),
+            array(
+                'OAUTH_QUICKBOOKS_NAME',
+                'OAUTH_QUICKBOOKS_ID',
+                'OAUTH_QUICKBOOKS_SECRET',
+            ),
+            array(
+                'OAUTH_REDDIT_NAME',
+                'OAUTH_REDDIT_ID',
+                'OAUTH_REDDIT_SECRET',
+            ),
+            array(
+                'OAUTH_REDMINE_NAME',
+                'OAUTH_REDMINE_ID',
+                'OAUTH_REDMINE_SECRET',
+            ),
+            array(
+                'OAUTH_RUNKEEPER_NAME',
+                'OAUTH_RUNKEEPER_ID',
+                'OAUTH_RUNKEEPER_SECRET',
+            ),
+            array(
+                'OAUTH_SCOOPIT_NAME',
+                'OAUTH_SCOOPIT_ID',
+                'OAUTH_SCOOPIT_SECRET',
+            ),
+            array(
+                'OAUTH_SOUNDCLOUD_NAME',
+                'OAUTH_SOUNDCLOUD_ID',
+                'OAUTH_SOUNDCLOUD_SECRET',
+            ),
+            array(
+                'OAUTH_SPOTIFY_NAME',
+                'OAUTH_SPOTIFY_ID',
+                'OAUTH_SPOTIFY_SECRET',
+            ),
+            array(
+                'OAUTH_STRAVA_NAME',
+                'OAUTH_STRAVA_ID',
+                'OAUTH_STRAVA_SECRET',
+            ),
+            array(
+                'OAUTH_TUMBLR_NAME',
+                'OAUTH_TUMBLR_ID',
+                'OAUTH_TUMBLR_SECRET',
+            ),
+            array(
+                'OAUTH_TWITTER_NAME',
+                'OAUTH_TWITTER_ID',
+                'OAUTH_TWITTER_SECRET',
+            ),
+            array(
+                'OAUTH_USTREAM_NAME',
+                'OAUTH_USTREAM_ID',
+                'OAUTH_USTREAM_SECRET',
+            ),
+            array(
+                'OAUTH_VIMEO_NAME',
+                'OAUTH_VIMEO_ID',
+                'OAUTH_VIMEO_SECRET',
+            ),
+            array(
+                'OAUTH_YAHOO_NAME',
+                'OAUTH_YAHOO_ID',
+                'OAUTH_YAHOO_SECRET',
+            ),
+            array(
+                'OAUTH_YAMMER_NAME',
+                'OAUTH_YAMMER_ID',
+                'OAUTH_YAMMER_SECRET',
+            ),
+);
+
+
+/*
+ * Actions
+ */
+ 
+if ($action == 'update')
+{
+    $error = 0;
+
+    foreach ($list as $constname) {
+        $constvalue = GETPOST($constname[1], 'alpha');
+        if (!dolibarr_set_const($db, $constname[1], $constvalue, 'chaine', 0, '', $conf->entity))
+            $error++;
+        $constvalue = GETPOST($constname[2], 'alpha');
+        if (!dolibarr_set_const($db, $constname[2], $constvalue, 'chaine', 0, '', $conf->entity))
+            $error++;
+    }
+
+    if (! $error)
+    {
+        setEventMessage($langs->trans("SetupSaved"));
+    } else {
+        setEventMessage($langs->trans("Error"),'errors');
+    }
+}
+
+/*
+ * View
+ */
+
+llxHeader();
+
+$form = new Form($db);
+
+$linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
+print load_fiche_titre($langs->trans('ConfigOAuth'),$linkback,'title_setup');
+
+print '<form action="'.$_SERVER["PHP_SELF"].'" method="post">';
+print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
+print '<input type="hidden" name="action" value="update">';
+
+/*
+ *  Parameters
+ */
+dol_fiche_head($head, $mode, $langs->trans("ModuleSetup"), 0, 'technic');
+
+print '<table class="noborder" width="100%">';
+
+$var = true;
+
+foreach ($list as $key)
+{
+    print '<tr class="liste_titre">';
+    // Api Name
+    $label = $langs->trans($key[0]); 
+    print '<td colspan="2">'.$label.'</td></tr>';
+
+    // Api Id
+    $var = !$var;
+    print '<tr '.$bc[$var].' class="value">';
+    print '<td><label for="'.$key[1].'">'.$langs->trans($key[1]).'</label></td>';
+    print '<td><input type="text" size="100" id="'.$key[1].'" name="'.$key[1].'" value="'.$conf->global->$key[1].'">';
+    print '</td></tr>';
+
+    // Api Secret
+    $var = !$var;
+    print '<tr '.$bc[$var].' class="value">';
+    print '<td><label for="'.$key[2].'">'.$langs->trans($key[2]).'</label></td>';
+    print '<td><input type="password" size="100" id="'.$key[2].'" name="'.$key[2].'" value="'.$conf->global->$key[2].'">';
+    print '</td></tr>';
+
+}
+
+print '</tr>';
+
+print '</form>';
+print '</table>'."\n";
+
+print '<br /><div style="text-align:center"><input type="submit" class="button" value="'.$langs->trans('Modify').'" name="button"></div>';
+dol_fiche_end();
+
+llxFooter();
+$db->close();

+ 6 - 5
htdocs/core/actions_printing.inc.php

@@ -46,11 +46,12 @@ if ($action == 'print_file' and $user->rights->printing->read)
 
             if (! empty($conf->global->{$printer->active})) {
                 $subdir=(GETPOST('printer', 'alpha')=='expedition'?'sending':'');
-                $errorprint = $printer->print_file(GETPOST('file', 'alpha'), GETPOST('printer', 'alpha'), $subdir);
-                //if ($errorprint < 0) {
-                //    setEventMessage($interface->errors, 'errors');
-                //}
-                if ($errorprint=='') {
+                $ret = $printer->print_file(GETPOST('file', 'alpha'), GETPOST('printer', 'alpha'), $subdir);
+                if ($ret < 0) {
+                    setEventMessage($printer->error, 'errors');
+                }
+                if ($ret==0) {
+                    setEventMessage($printer->error, 'warning');
                     setEventMessage($langs->trans("FileWasSentToPrinter", basename(GETPOST('file'))).' '.$langs->trans("ViaModule").' '.$printer->name);
                     $printed++;
                 }

+ 123 - 0
htdocs/core/modules/modOauth.class.php

@@ -0,0 +1,123 @@
+<?php
+/* Copyright (C) 2014-2015 Laurent Destailleur  <eldy@users.sourceforge.net>
+ * Copyright (C) 2015      Frederic France      <frederic.france@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \defgroup   oauth     Module oauth
+ *  \brief      Module for activation of Oauth for several Api Access
+ */
+
+/**
+ *  \file       htdocs/core/modules/modOauth.class.php
+ *  \ingroup    oauth
+ *  \brief      File of class to describe and activate module Oauth
+ */
+include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php';
+
+
+
+/**
+ *  Class to describe and activate module Direct Printing
+ */
+class modOauth extends DolibarrModules
+{
+
+    /**
+     *  Constructor
+     *
+     *  @param      DoliDB      $db      Database handler
+     */
+    function  __construct($db)
+    {
+        $this->db = $db ;
+        $this->numero = 66000;
+        // Family can be 'crm','financial','hr','projects','products','ecm','technic','other'
+        // It is used to group modules in module setup page
+        $this->family = "technic";
+        // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
+        $this->name = preg_replace('/^mod/i','',get_class($this));
+		// Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module)
+        $this->description = "Enable Oauth.";
+        $this->version = 'dolibarr';    // 'development' or 'experimental' or 'dolibarr' or version
+        $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
+        // Where to store the module in setup page (0=common,1=interface,2=others,3=very specific)
+        $this->special = 1;
+        // Name of image file used for this module.
+        // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
+        // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
+        $this->picto = 'technic';
+
+        // Data directories to create when module is enabled.
+        $this->dirs = array();
+
+        // Config pages
+        $this->config_page_url = array("oauth.php");
+
+        // Dependencies
+        $this->depends = array();
+        $this->requiredby = array();
+        $this->phpmin = array(5,1);                     // Minimum version of PHP required by module
+        $this->need_dolibarr_version = array(3,7,-2);   // Minimum version of Dolibarr required by module
+        $this->conflictwith = array();
+        $this->langfiles = array("oauth");
+
+        // Constants
+        $this->const = array();
+
+        // Boxes
+        $this->boxes = array();
+
+        // Permissions
+        $this->rights = array();
+        $this->rights_class = 'oauth';
+
+        $r=0;
+        // $this->rights[$r][0]     Id permission (unique tous modules confondus)
+        // $this->rights[$r][1]     Libelle par defaut si traduction de cle "PermissionXXX" non trouvee (XXX = Id permission)
+        // $this->rights[$r][2]     Non utilise
+        // $this->rights[$r][3]     1=Permis par defaut, 0=Non permis par defaut
+        // $this->rights[$r][4]     Niveau 1 pour nommer permission dans code
+        // $this->rights[$r][5]     Niveau 2 pour nommer permission dans code
+
+        $r++;
+        $this->rights[$r][0] = 66001;
+        $this->rights[$r][1] = 'OauthAccess';
+        $this->rights[$r][2] = 'r';
+        $this->rights[$r][3] = 1;
+        $this->rights[$r][4] = 'read';
+
+        // Main menu entries
+        $this->menus = array();         // List of menus to add
+        $r=0;
+
+        // This is to declare the Top Menu entry:
+        //$this->menu[$r]=array(  'fk_menu'=>'fk_mainmenu=home,fk_leftmenu=modulesadmintools',               // Put 0 if this is a top menu
+        //                        'type'=>'left',                 // This is a Top menu entry
+        //                        'titre'=>'MenuOauth',
+        //                        'mainmenu'=>'oauth',
+        //                        'url'=>'/oauth/index.php',
+        //                        'langs'=>'oauth',            // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+        //                        'position'=>300,
+        //                        'enabled'=>'$conf->oauth->enabled && $leftmenu==\'modulesadmintools\'',
+        //                        'perms'=>'$user->rights->oauth->read',    // Use 'perms'=>'1' if you want your menu with no permission rules
+        //                        'target'=>'',
+        //                        'user'=>0);                     // 0=Menu for internal users, 1=external users, 2=both
+
+        //$r++;
+
+
+    }
+}

+ 110 - 0
htdocs/core/modules/oauth/getgoogleoauthcallback.php

@@ -0,0 +1,110 @@
+<?php
+/*
+ * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ *      \file       htdocs/core/modules/oauth/getoauthcallback.php
+ *      \ingroup    oauth
+ *      \brief      Page to get oauth callback
+ */
+
+require '../../../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
+use OAuth\Common\Storage\Session;
+use OAuth\Common\Storage\DoliStorage;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\OAuth2\Service\Google;
+
+/**
+ * Create a new instance of the URI class with the current URI, stripping the query string
+ */
+$uriFactory = new \OAuth\Common\Http\Uri\UriFactory();
+$currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
+$currentUri->setQuery('');
+
+/**
+ * Load the credential for the service
+ */
+
+/** @var $serviceFactory \OAuth\ServiceFactory An OAuth service factory. */
+$serviceFactory = new \OAuth\ServiceFactory();
+// Dolibarr storage
+$storage = new DoliStorage($db, $conf);
+// Setup the credentials for the requests
+$credentials = new Credentials(
+    $conf->global->OAUTH_GOOGLE_ID,
+    $conf->global->OAUTH_GOOGLE_SECRET,
+    $currentUri->getAbsoluteUri()
+);
+
+// Instantiate the Api service using the credentials, http client and storage mechanism for the token
+/** @var $apiService Service */
+// TODO remove hardcoded array
+$apiService = $serviceFactory->createService('Google', $credentials, $storage, array('userinfo_email', 'userinfo_profile', 'cloud_print'));
+
+// access type needed for google refresh token
+$apiService->setAccessType('offline');
+//print '<pre>'.print_r($apiService,true).'</pre>';
+//print 'Has access Token: '.($storage->hasAccessToken('Google')?'Yes':'No').'</ br>';
+//print 'Has Author State: '.($storage->hasAuthorizationState('Google')?'Yes':'No').'</ br>';
+//print 'Authorization State: '.$storage->retrieveAuthorizationState('Google').'</ br>';
+//print '<td><pre>'.print_r($token,true).'</pre></td>';
+if (! empty($_GET['code'])) {
+    llxHeader('',$langs->trans("OAuthSetup"));
+
+    $linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
+    print load_fiche_titre($langs->trans("OAuthSetup"),$linkback,'title_setup');
+    // retrieve the CSRF state parameter
+    $state = isset($_GET['state']) ? $_GET['state'] : null;
+    try {
+        //$token = $storage->retrieveAccessToken('Google');
+    } catch (Exception $e) {
+        print $e->getMessage();
+    }
+    //print '<pre>'.print_r($token->getRefreshToken(),true).'</pre>';
+    //$refreshtoken = $token->getRefreshToken();
+    // This was a callback request from service, get the token
+    $apiService->requestAccessToken($_GET['code'], $state);
+    //print '<pre>'.print_r($apiService,true).'</pre>';
+
+    try {
+        $token = $storage->retrieveAccessToken('Google');
+    } catch (Exception $e) {
+        print $e->getMessage();
+    }
+    $newrefreshtoken = $token->getRefreshToken();
+    if (empty($newrefreshtoken) && ! empty($refreshtoken)) {
+        $token->setRefreshToken($refreshtoken);
+        $storage->storeAccessToken('Google', $token);
+    }
+    print '<td><pre>'.print_r($token,true).'</pre></td>';
+    //$apiService->refreshAccessToken($token);
+    //print '<pre>'.print_r($apiService,true).'</pre>';
+    //$token = $storage->retrieveAccessToken('Google');
+    //print '<td><pre>'.print_r($token,true).'</pre></td>';
+
+} else {
+    $url = $apiService->getAuthorizationUri();
+    // we go on google authorization page
+    header('Location: ' . $url);
+    exit();
+}
+
+llxFooter();
+
+$db->close();

+ 0 - 0
htdocs/core/modules/oauth/index.html


+ 3 - 3
htdocs/core/modules/printing/modules_printing.php

@@ -1,6 +1,6 @@
 <?php
 /*
- * Copyright (C) 2014 Frederic France      <frederic.france@free.fr>
+ * Copyright (C) 2014-2015 Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -56,8 +56,8 @@ class PrintingDriver
     {
         global $conf;
 
-        $type='printing';
-        $liste=array();
+        $type = 'printing';
+        $list = array();
 
         $moduledir=DOL_DOCUMENT_ROOT."/core/modules/printing/";
         $tmpfiles=dol_dir_list($moduledir,'all',0,'\modules.php','','name',SORT_ASC,0);

+ 163 - 164
htdocs/core/modules/printing/printgcp.modules.php

@@ -1,6 +1,6 @@
 <?php
 /*
- * Copyright (C) 2014 Frederic France      <frederic.france@free.fr>
+ * Copyright (C) 2014-2015  Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,11 @@
  */
 
 include_once DOL_DOCUMENT_ROOT.'/core/modules/printing/modules_printing.php';
+require_once DOL_DOCUMENT_ROOT.'/includes/OAuth/bootstrap.php';
+use OAuth\Common\Storage\Session;
+use OAuth\Common\Storage\DoliStorage;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\OAuth2\Service\Google;
 
 /**
  *     Class to provide printing with Google Cloud Print
@@ -35,14 +40,13 @@ class printing_printgcp extends PrintingDriver
     var $picto = 'printer';
     var $active = 'PRINTING_PRINTGCP';
     var $conf = array();
-    var $login = '';
-    var $password = '';
-    var $authtoken = '';
+    var $google_id = '';
+    var $google_secret = '';
     var $db;
 
-    const LOGIN_URL = 'https://www.google.com/accounts/ClientLogin';
-    const PRINTERS_SEARCH_URL = 'https://www.google.com/cloudprint/interface/search';
-    const PRINT_URL = 'https://www.google.com/cloudprint/interface/submit';
+    const LOGIN_URL = 'https://accounts.google.com/o/oauth2/token';
+    const PRINTERS_SEARCH_URL = 'https://www.google.com/cloudprint/search';
+    const PRINT_URL = 'https://www.google.com/cloudprint/submit';
 
     /**
      *  Constructor
@@ -54,11 +58,66 @@ class printing_printgcp extends PrintingDriver
         global $conf;
 
         $this->db = $db;
-        $this->login = $conf->global->PRINTGCP_LOGIN;
-        $this->password = $conf->global->PRINTGCP_PASSWORD;
-        $this->authtoken = $conf->global->PRINTGCP_AUTHTOKEN;
-        $this->conf[] = array('varname'=>'PRINTGCP_LOGIN', 'required'=>1, 'example'=>'user@gmail.com', 'type'=>'text');
-        $this->conf[] = array('varname'=>'PRINTGCP_PASSWORD', 'required'=>1, 'example'=>'', 'type'=>'password');
+        $this->google_id = $conf->global->OAUTH_GOOGLE_ID;
+        $this->google_secret = $conf->global->OAUTH_GOOGLE_SECRET;
+        // Token storage
+        $storage = new DoliStorage($this->db, $this->conf);
+        //$storage->clearToken('Google');
+        // Setup the credentials for the requests
+        $credentials = new Credentials(
+            $this->google_id,
+            $this->google_secret,
+            DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php'
+        );
+        $access = ($storage->hasAccessToken('Google')?'HasAccessToken':'NoAccessToken');
+        $serviceFactory = new \OAuth\ServiceFactory();
+        $apiService = $serviceFactory->createService('Google', $credentials, $storage, array());
+        $token_ok=true;
+        try {
+            $token = $storage->retrieveAccessToken('Google');
+        } catch (Exception $e) {
+            $this->errors[] = $e->getMessage();
+            $token_ok = false;
+        }
+        $expire = false;
+        // Is token expired or will token expire in the next 30 seconds
+        if ($token_ok) {
+            $expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30));
+        }
+
+        // Token expired so we refresh it
+        if ($token_ok && $expire) {
+            try {
+                // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
+                $refreshtoken = $token->getRefreshToken();
+                $token = $apiService->refreshAccessToken($token);
+                $token->setRefreshToken($refreshtoken);
+                $storage->storeAccessToken('Google', $token);
+            } catch (Exception $e) {
+                $this->errors[] = $e->getMessage();
+                print '<pre>MissingRefreshToken: '.$e->getMessage().'</pre>';
+                //print '<pre>'.print_r($e,true).'</pre>';
+            }
+        }
+        if (!$conf->oauth->enabled) {
+            $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'ModuleAuthNotActive', 'type'=>'info');
+        } else {
+            if ($this->google_id != '' && $this->google_secret != '') {
+                $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthConfigured', 'type'=>'info');
+                $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_ACCESS', 'info'=>$access, 'type'=>'info');
+                if ($token_ok) {
+                    $refreshtoken = $token->getRefreshToken();
+                    $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_REFRESH', 'info'=>((! empty($refreshtoken))?'Yes':'No'), 'type'=>'info');
+                    $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_EXPIRED', 'info'=>($expire?'Yes':'No'), 'type'=>'info');
+                    $this->conf[] = array('varname'=>'PRINTGCP_TOKEN_EXPIRE_AT', 'info'=>(date("Y-m-d H:i:s", $token->getEndOfLife())), 'type'=>'info');
+                }
+                $this->conf[] = array('varname'=>'PRINTGCP_AUTHLINK', 'link'=>DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php', 'type'=>'authlink');
+            } else {
+                $this->conf[] = array('varname'=>'PRINTGCP_INFO', 'info'=>'GoogleAuthNotConfigured', 'type'=>'info');
+            }
+        }
+        // do not display submit button
+        $this->conf[] = array('enabled'=>0, 'type'=>'submit');
     }
 
     /**
@@ -111,48 +170,71 @@ class printing_printgcp extends PrintingDriver
         return $html;
     }
 
+
     /**
      *  Return list of available printers
      *
-     *  @return array                list of printers
+     *  @return array      list of printers
      */
     function getlist_available_printers()
     {
-        global $conf,$db;
-        if ($this->authtoken=='') {
-            $this->GoogleLogin();
+        // Token storage
+        $storage = new DoliStorage($this->db, $this->conf);
+        // Setup the credentials for the requests
+        $credentials = new Credentials(
+            $this->google_id,
+            $this->google_secret,
+            DOL_MAIN_URL_ROOT.'/core/modules/oauth/getgoogleoauthcallback.php'
+        );
+        $serviceFactory = new \OAuth\ServiceFactory();
+        $apiService = $serviceFactory->createService('Google', $credentials, $storage, array());
+        // Check if we have auth token
+        $token_ok=true;
+        try {
+            $token = $storage->retrieveAccessToken('Google');
+        } catch (Exception $e) {
+            $this->errors[] = $e->getMessage();
+            $token_ok = false;
+        }
+        $expire = false;
+        // Is token expired or will token expire in the next 30 seconds
+        if ($token_ok) {
+            $expire = ($token->getEndOfLife() !== -9002 && $token->getEndOfLife() !== -9001 && time() > ($token->getEndOfLife() - 30));
         }
-        $ret['available'] = $this->get_printer_detail();
-        return $ret;
-    }
 
-    /**
-     *  List of printers
-     *
-     *  @return array      list of printers
-     */
-    function get_printer_detail()
-    {
-        // Check if we have auth token
-        if(empty($this->authtoken)) {
-            // We don't have auth token so throw exception
-            throw new Exception("Please first login to Google by calling loginToGoogle function");
+        // Token expired so we refresh it
+        if ($token_ok && $expire) {
+            try {
+                // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
+                $refreshtoken = $token->getRefreshToken();
+                $token = $apiService->refreshAccessToken($token);
+                $token->setRefreshToken($refreshtoken);
+                $storage->storeAccessToken('Google', $token);
+            } catch (Exception $e) {
+                $this->errors[] = $e->getMessage();
+                print '<pre>MissingRefreshToken: '.$e->getMessage().'</pre>';
+                //print '<pre>'.print_r($e,true).'</pre>';
+            }
         }
-        // Prepare auth headers with auth token
-        $authheaders = array("Authorization: GoogleLogin auth=".$this->authtoken,
-                             "GData-Version: 3.0",
-                            );
-        // Make Http call to get printers added by user to Google Cloud Print
-        $responsedata = $this->makeCurl(self::PRINTERS_SEARCH_URL,array(),$authheaders);
-        $printers = json_decode($responsedata);
+        // Send a request with api
+        try {
+            $response = $apiService->request(self::PRINTERS_SEARCH_URL);
+        } catch (Exception $e) {
+            $this->errors[] = $e->getMessage();
+            print '<pre>'.print_r($e->getMessage(),true).'</pre>';
+        }
+        //print '<tr><td><pre>'.print_r($response, true).'</pre></td></tr>';
+        $responsedata = json_decode($response, true);
+        $printers = $responsedata['printers'];
         // Check if we have printers?
-        if(is_null($printers)) {
+        if(count($printers)==0) {
             // We dont have printers so return blank array
-            return array();
+            $ret['available'] =  array();
         } else {
             // We have printers so returns printers as array
-            return $this->parsePrinters($printers);
+            $ret['available'] =  $printers;
         }
+        return $ret;
     }
 
     /**
@@ -161,14 +243,11 @@ class printing_printgcp extends PrintingDriver
      * @param   string      $file       file
      * @param   string      $module     module
      * @param   string      $subdir     subdir for file
-     * @return  string                  '' if OK, Error message if KO
+     * @return  int                     0 if OK, <0 if KO
      */
     function print_file($file, $module, $subdir='')
     {
         global $conf, $user, $db;
-        if ($this->authtoken=='') {
-            $this->GoogleLogin();
-        }
         // si $module=commande_fournisseur alors $conf->fournisseur->commande->dir_output
         $fileprint=$conf->{$module}->dir_output;
         if ($subdir!='') $fileprint.='/'.$subdir;
@@ -181,7 +260,7 @@ class printing_printgcp extends PrintingDriver
             $obj = $this->db->fetch_object($result);
             if ($obj)
             {
-                $printer_id=$obj->printer_id;
+                $printer_id = $obj->printer_id;
             }
             else
             {
@@ -197,7 +276,12 @@ class printing_printgcp extends PrintingDriver
         }
         else dol_print_error($db);
 
-        $this->sendPrintToPrinter($printer_id, $file, $fileprint, 'application/pdf');
+        $ret = $this->sendPrintToPrinter($printer_id, $file, $fileprint, 'application/pdf');
+        $this->error = 'PRINTGCP: '.$ret['errormessage'];
+        if ($ret['status']==1) 
+            return 0;
+        else
+            return -1;
     }
 
     /**
@@ -209,24 +293,16 @@ class printing_printgcp extends PrintingDriver
      *  @param  string      $contenttype    File content type by example application/pdf, image/png
      *  @return array                       status array
      */
-    public function sendPrintToPrinter($printerid,$printjobtitle,$filepath,$contenttype)
+    public function sendPrintToPrinter($printerid, $printjobtitle, $filepath, $contenttype)
     {
-        $errors=0;
-        // Check auth token
-        if(empty($this->authtoken)) {
-            $errors++;
-            setEventMessage('Please first login to Google', 'warning');
-        }
         // Check if printer id
         if(empty($printerid)) {
-            $errors++;
-            setEventMessage('No provided printer ID', 'warning');
+            return array('status' =>0, 'errorcode' =>'','errormessage'=>'No provided printer ID');
         }
         // Open the file which needs to be print
         $handle = fopen($filepath, "rb");
         if(!$handle) {
-            $errors++;
-            setEventMessage('Could not read the file.');
+            return array('status' =>0, 'errorcode' =>'','errormessage'=>'Could not read the file.');
         }
         // Read file content
         $contents = fread($handle, filesize($filepath));
@@ -238,118 +314,41 @@ class printing_printgcp extends PrintingDriver
                              'content' => base64_encode($contents), // encode file content as base64
                              'contentType' => $contenttype
                             );
-        // Prepare authorization headers
-        $authheaders = array("Authorization: GoogleLogin auth=" . $this->authtoken);
-        // Make http call for sending print Job
-        $response = json_decode($this->makeCurl(self::PRINT_URL,$post_fields,$authheaders));
-        // Has document been successfully sent?
-        if($response->success=="1") {
-            return array('status' =>true,'errorcode' =>'','errormessage'=>"");
-        } else {
-            return array('status' =>false,'errorcode' =>$response->errorCode,'errormessage'=>$response->message);
-        }
-    }
-
+        // Token storage
+        $storage = new DoliStorage($this->db, $this->conf);
+        // Setup the credentials for the requests
+        $credentials = new Credentials(
+            $this->google_id,
+            $this->google_secret,
+            DOL_MAIN_URL_ROOT.'/core/modules/oauth/getoauthcallback.php?service=google'
+        );
+        $serviceFactory = new \OAuth\ServiceFactory();
+        $apiService = $serviceFactory->createService('Google', $credentials, $storage, array());
 
-    /**
-     *  Login into Google Account
-     *
-     *  @return boolean           true or false
-     */
-    function GoogleLogin()
-    {
-        global $db, $conf;
-        // Prepare post fields required for the login
-        $loginpostfields = array("accountType" => "HOSTED_OR_GOOGLE",
-                                 "Email" => $this->login,
-                                 "Passwd" => $this->password,
-                                 "service" => "cloudprint",
-                                 "source" => "GCP"
-                                );
-        // Get the Auth token
-        $loginresponse = $this->makeCurl(self::LOGIN_URL,$loginpostfields);
-        $token = $this->getAuthToken($loginresponse);
-        if(! empty($token)&&!is_null($token)) {
-            $this->authtoken = $token;
-            $result=dolibarr_set_const($db, 'PRINTGCP_AUTHTOKEN', $token, 'chaine', 0, '', $conf->entity);
-            return true;
-        } else {
-            return false;
+        // Check if we have auth token and refresh it
+        $token_ok=true;
+        try {
+            $token = $storage->retrieveAccessToken('Google');
+        } catch (Exception $e) {
+            $this->errors[] = $e->getMessage();
+            $token_ok = false;
         }
-
-    }
-
-    /**
-     *  Parse json response and return printers array
-     *
-     *  @param  string    $jsonobj  Json response object
-     *  @return array               return array of printers
-     */
-    private function parsePrinters($jsonobj)
-    {
-        $printers = array();
-        if (isset($jsonobj->printers)) {
-            foreach ($jsonobj->printers as $gcpprinter) {
-                $printers[] = array('id' =>$gcpprinter->id,
-                                    'name' =>$gcpprinter->name,
-                                    'defaultDisplayName' =>$gcpprinter->defaultDisplayName,
-                                    'displayName' =>$gcpprinter->displayName,
-                                    'ownerId' =>$gcpprinter->ownerId,
-                                    'ownerName' =>$gcpprinter->ownerName,
-                                    'connectionStatus' =>$gcpprinter->connectionStatus,
-                                    'status' =>$gcpprinter->status,
-                                    'type' =>$gcpprinter->type
-                                    );
+        if ($token_ok) {
+            try {
+                // il faut sauvegarder le refresh token car google ne le donne qu'une seule fois
+                $refreshtoken = $token->getRefreshToken();
+                $token = $apiService->refreshAccessToken($token);
+                $token->setRefreshToken($refreshtoken);
+                $storage->storeAccessToken('Google', $token);
+            } catch (Exception $e) {
+                $this->errors[] = $e->getMessage();
             }
         }
-        return $printers;
-    }
-
-    /**
-     *  Parse data to get auth token
-     *
-     *  @param      string  $response   response from curl
-     *  @return     string              token
-     */
-    private function getAuthToken($response)
-    {
-        // Search Auth tag
-        preg_match("/Auth=([a-z0-9_-]+)/i", $response, $matches);
-        $authtoken = @$matches[1];
-        return $authtoken;
-    }
 
-    /**
-     *  Make a curl request
-     *
-     *  @param  string  	$url            url to hit
-     *  @param  array   	$postfields     array of post fields
-     *  @param  string[]   	$headers        array of http headers
-     *  @return string                   	response from curl
-     */
-    private function makeCurl($url, $postfields=array(), $headers=array())
-    {
-        // Curl Init
-        $curl = curl_init($url);
-        // Curl post request
-        if(! empty($postfields)) {
-            // As is HTTP post curl request so set post fields
-            curl_setopt($curl, CURLOPT_POST, true);
-            curl_setopt($curl, CURLOPT_POSTFIELDS, $postfields);
-        }
-        // Curl request headers
-        if(! empty($headers)) {
-            // As curl requires header so set headers here
-            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
-        }
-        curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
-        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
-        // Execute the curl and return response
-        $response = curl_exec($curl);
-        curl_close($curl);
-        return $response;
+        // Send a request with api
+        $response = json_decode($apiService->request(self::PRINT_URL, 'POST', $post_fields), true);
+        //print '<tr><td><pre>'.print_r($response, true).'</pre></td></tr>';
+        return array('status' =>$response['success'],'errorcode' =>$response['errorCode'],'errormessage'=>$response['message']);
     }
 
-
 }

+ 6 - 4
htdocs/core/modules/printing/printipp.modules.php

@@ -1,6 +1,6 @@
 <?php
 /*
- * Copyright (C) 2014 Frederic France      <frederic.france@free.fr>
+ * Copyright (C) 2014-2015  Frederic France      <frederic.france@free.fr>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -62,6 +62,7 @@ class printing_printipp extends PrintingDriver
         $this->conf[] = array('varname'=>'PRINTIPP_PORT', 'required'=>1, 'example'=>'631', 'type'=>'text');
         $this->conf[] = array('varname'=>'PRINTIPP_USER', 'required'=>0, 'example'=>'', 'type'=>'text', 'moreattributes'=>'autocomplete="off"');
         $this->conf[] = array('varname'=>'PRINTIPP_PASSWORD', 'required'=>0, 'example'=>'', 'type'=>'password', 'moreattributes'=>'autocomplete="off"');
+        $this->conf[] = array('enabled'=>1, 'type'=>'submit');
     }
 
     /**
@@ -71,7 +72,7 @@ class printing_printipp extends PrintingDriver
      * @param   string      $module     module
      * @param   string      $subdir     subdirectory of document like for expedition subdir is sendings
      *
-     * @return  string                  '' if OK, Error message if KO
+     * @return  int                     0 if OK, <0 if KO
      */
     function print_file($file, $module, $subdir='')
     {
@@ -107,7 +108,8 @@ class printing_printipp extends PrintingDriver
                 }
                 else
 				{
-                    return 'NoDefaultPrinterDefined';
+                    $this->error = 'NoDefaultPrinterDefined';
+                    return -1;
                 }
             }
         }
@@ -121,7 +123,7 @@ class printing_printipp extends PrintingDriver
         $ipp->setData($fileprint);
         $ipp->printJob();
 
-        return '';
+        return 0;
     }
 
     /**

+ 81 - 0
htdocs/includes/OAuth/Common/AutoLoader.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace OAuth\Common;
+
+/**
+ * PSR-0 Autoloader
+ *
+ * @author ieter Hordijk <info@pieterhordijk.com>
+ */
+class AutoLoader
+{
+    /**
+     * @var string The namespace prefix for this instance.
+     */
+    protected $namespace = '';
+
+    /**
+     * @var string The filesystem prefix to use for this instance
+     */
+    protected $path = '';
+
+    /**
+     * Build the instance of the autoloader
+     *
+     * @param string $namespace The prefixed namespace this instance will load
+     * @param string $path      The filesystem path to the root of the namespace
+     */
+    public function __construct($namespace, $path)
+    {
+        $this->namespace = ltrim($namespace, '\\');
+        $this->path      = rtrim($path, '/\\') . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * Try to load a class
+     *
+     * @param string $class The class name to load
+     *
+     * @return boolean If the loading was successful
+     */
+    public function load($class)
+    {
+        $class = ltrim($class, '\\');
+
+        if (strpos($class, $this->namespace) === 0) {
+            $nsparts   = explode('\\', $class);
+            $class     = array_pop($nsparts);
+            $nsparts[] = '';
+            $path      = $this->path . implode(DIRECTORY_SEPARATOR, $nsparts);
+            $path     .= str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+
+            if (file_exists($path)) {
+                require $path;
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Register the autoloader to PHP
+     *
+     * @return boolean The status of the registration
+     */
+    public function register()
+    {
+        return spl_autoload_register(array($this, 'load'));
+    }
+
+    /**
+     * Unregister the autoloader to PHP
+     *
+     * @return boolean The status of the unregistration
+     */
+    public function unregister()
+    {
+        return spl_autoload_unregister(array($this, 'load'));
+    }
+}

+ 60 - 0
htdocs/includes/OAuth/Common/Consumer/Credentials.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace OAuth\Common\Consumer;
+
+/**
+ * Value object for the credentials of an OAuth service.
+ */
+class Credentials implements CredentialsInterface
+{
+    /**
+     * @var string
+     */
+    protected $consumerId;
+
+    /**
+     * @var string
+     */
+    protected $consumerSecret;
+
+    /**
+     * @var string
+     */
+    protected $callbackUrl;
+
+    /**
+     * @param string $consumerId
+     * @param string $consumerSecret
+     * @param string $callbackUrl
+     */
+    public function __construct($consumerId, $consumerSecret, $callbackUrl)
+    {
+        $this->consumerId = $consumerId;
+        $this->consumerSecret = $consumerSecret;
+        $this->callbackUrl = $callbackUrl;
+    }
+
+    /**
+     * @return string
+     */
+    public function getCallbackUrl()
+    {
+        return $this->callbackUrl;
+    }
+
+    /**
+     * @return string
+     */
+    public function getConsumerId()
+    {
+        return $this->consumerId;
+    }
+
+    /**
+     * @return string
+     */
+    public function getConsumerSecret()
+    {
+        return $this->consumerSecret;
+    }
+}

+ 24 - 0
htdocs/includes/OAuth/Common/Consumer/CredentialsInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace OAuth\Common\Consumer;
+
+/**
+ * Credentials Interface, credentials should implement this.
+ */
+interface CredentialsInterface
+{
+    /**
+     * @return string
+     */
+    public function getCallbackUrl();
+
+    /**
+     * @return string
+     */
+    public function getConsumerId();
+
+    /**
+     * @return string
+     */
+    public function getConsumerSecret();
+}

+ 10 - 0
htdocs/includes/OAuth/Common/Exception/Exception.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace OAuth\Common\Exception;
+
+/**
+ * Generic library-level exception.
+ */
+class Exception extends \Exception
+{
+}

+ 73 - 0
htdocs/includes/OAuth/Common/Http/Client/AbstractClient.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace OAuth\Common\Http\Client;
+
+/**
+ * Abstract HTTP client
+ */
+abstract class AbstractClient implements ClientInterface
+{
+    /**
+     * @var string The user agent string passed to services
+     */
+    protected $userAgent;
+
+    /**
+     * @var int The maximum number of redirects
+     */
+    protected $maxRedirects = 5;
+
+    /**
+     * @var int The maximum timeout
+     */
+    protected $timeout = 15;
+
+    /**
+     * Creates instance
+     *
+     * @param string $userAgent The UA string the client will use
+     */
+    public function __construct($userAgent = 'PHPoAuthLib')
+    {
+        $this->userAgent = $userAgent;
+    }
+
+    /**
+     * @param int $redirects Maximum redirects for client
+     *
+     * @return ClientInterface
+     */
+    public function setMaxRedirects($redirects)
+    {
+        $this->maxRedirects = $redirects;
+
+        return $this;
+    }
+
+    /**
+     * @param int $timeout Request timeout time for client in seconds
+     *
+     * @return ClientInterface
+     */
+    public function setTimeout($timeout)
+    {
+        $this->timeout = $timeout;
+
+        return $this;
+    }
+
+    /**
+     * @param array $headers
+     */
+    public function normalizeHeaders(&$headers)
+    {
+        // Normalize headers
+        array_walk(
+            $headers,
+            function (&$val, &$key) {
+                $key = ucfirst(strtolower($key));
+                $val = ucfirst(strtolower($key)) . ': ' . $val;
+            }
+        );
+    }
+}

+ 32 - 0
htdocs/includes/OAuth/Common/Http/Client/ClientInterface.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace OAuth\Common\Http\Client;
+
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+
+/**
+ * Any HTTP clients to be used with the library should implement this interface.
+ */
+interface ClientInterface
+{
+    /**
+     * Any implementing HTTP providers should send a request to the provided endpoint with the parameters.
+     * They should return, in string form, the response body and throw an exception on error.
+     *
+     * @param UriInterface $endpoint
+     * @param mixed        $requestBody
+     * @param array        $extraHeaders
+     * @param string       $method
+     *
+     * @return string
+     *
+     * @throws TokenResponseException
+     */
+    public function retrieveResponse(
+        UriInterface $endpoint,
+        $requestBody,
+        array $extraHeaders = array(),
+        $method = 'POST'
+    );
+}

+ 142 - 0
htdocs/includes/OAuth/Common/Http/Client/CurlClient.php

@@ -0,0 +1,142 @@
+<?php
+
+namespace OAuth\Common\Http\Client;
+
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Client implementation for cURL
+ */
+class CurlClient extends AbstractClient
+{
+    /**
+     * If true, explicitly sets cURL to use SSL version 3. Use this if cURL
+     * compiles with GnuTLS SSL.
+     *
+     * @var bool
+     */
+    private $forceSSL3 = false;
+
+    /**
+     * Additional parameters (as `key => value` pairs) to be passed to `curl_setopt`
+     *
+     * @var array
+     */
+    private $parameters = array();
+
+    /**
+     * Additional `curl_setopt` parameters
+     *
+     * @param array $parameters
+     */
+    public function setCurlParameters(array $parameters)
+    {
+        $this->parameters = $parameters;
+    }
+
+    /**
+     * @param bool $force
+     *
+     * @return CurlClient
+     */
+    public function setForceSSL3($force)
+    {
+        $this->forceSSL3 = $force;
+
+        return $this;
+    }
+
+    /**
+     * Any implementing HTTP providers should send a request to the provided endpoint with the parameters.
+     * They should return, in string form, the response body and throw an exception on error.
+     *
+     * @param UriInterface $endpoint
+     * @param mixed        $requestBody
+     * @param array        $extraHeaders
+     * @param string       $method
+     *
+     * @return string
+     *
+     * @throws TokenResponseException
+     * @throws \InvalidArgumentException
+     */
+    public function retrieveResponse(
+        UriInterface $endpoint,
+        $requestBody,
+        array $extraHeaders = array(),
+        $method = 'POST'
+    ) {
+        // Normalize method name
+        $method = strtoupper($method);
+
+        $this->normalizeHeaders($extraHeaders);
+
+        if ($method === 'GET' && !empty($requestBody)) {
+            throw new \InvalidArgumentException('No body expected for "GET" request.');
+        }
+
+        if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) {
+            $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded';
+        }
+
+        $extraHeaders['Host']       = 'Host: '.$endpoint->getHost();
+        $extraHeaders['Connection'] = 'Connection: close';
+
+        $ch = curl_init();
+
+        curl_setopt($ch, CURLOPT_URL, $endpoint->getAbsoluteUri());
+
+        if ($method === 'POST' || $method === 'PUT') {
+            if ($requestBody && is_array($requestBody)) {
+                $requestBody = http_build_query($requestBody, '', '&');
+            }
+
+            if ($method === 'PUT') {
+                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
+            } else {
+                curl_setopt($ch, CURLOPT_POST, true);
+            }
+
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
+        } else {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
+        }
+
+        if ($this->maxRedirects > 0) {
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->maxRedirects);
+        }
+
+        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_HEADER, false);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $extraHeaders);
+        curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent);
+
+        foreach ($this->parameters as $key => $value) {
+            curl_setopt($ch, $key, $value);
+        }
+
+        if ($this->forceSSL3) {
+            curl_setopt($ch, CURLOPT_SSLVERSION, 3);
+        }
+
+        $response     = curl_exec($ch);
+        $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        if (false === $response) {
+            $errNo  = curl_errno($ch);
+            $errStr = curl_error($ch);
+            curl_close($ch);
+            if (empty($errStr)) {
+                throw new TokenResponseException('Failed to request resource.', $responseCode);
+            }
+            throw new TokenResponseException('cURL Error # '.$errNo.': '.$errStr, $responseCode);
+        }
+
+        curl_close($ch);
+
+        return $response;
+    }
+}

+ 95 - 0
htdocs/includes/OAuth/Common/Http/Client/StreamClient.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace OAuth\Common\Http\Client;
+
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Client implementation for streams/file_get_contents
+ */
+class StreamClient extends AbstractClient
+{
+    /**
+     * Any implementing HTTP providers should send a request to the provided endpoint with the parameters.
+     * They should return, in string form, the response body and throw an exception on error.
+     *
+     * @param UriInterface $endpoint
+     * @param mixed        $requestBody
+     * @param array        $extraHeaders
+     * @param string       $method
+     *
+     * @return string
+     *
+     * @throws TokenResponseException
+     * @throws \InvalidArgumentException
+     */
+    public function retrieveResponse(
+        UriInterface $endpoint,
+        $requestBody,
+        array $extraHeaders = array(),
+        $method = 'POST'
+    ) {
+        // Normalize method name
+        $method = strtoupper($method);
+
+        $this->normalizeHeaders($extraHeaders);
+
+        if ($method === 'GET' && !empty($requestBody)) {
+            throw new \InvalidArgumentException('No body expected for "GET" request.');
+        }
+
+        if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) {
+            $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded';
+        }
+
+        $host = 'Host: '.$endpoint->getHost();
+        // Append port to Host if it has been specified
+        if ($endpoint->hasExplicitPortSpecified()) {
+            $host .= ':'.$endpoint->getPort();
+        }
+
+        $extraHeaders['Host']       = $host;
+        $extraHeaders['Connection'] = 'Connection: close';
+
+        if (is_array($requestBody)) {
+            $requestBody = http_build_query($requestBody, '', '&');
+        }
+        $extraHeaders['Content-length'] = 'Content-length: '.strlen($requestBody);
+
+        $context = $this->generateStreamContext($requestBody, $extraHeaders, $method);
+
+        $level = error_reporting(0);
+        $response = file_get_contents($endpoint->getAbsoluteUri(), false, $context);
+        error_reporting($level);
+        if (false === $response) {
+            $lastError = error_get_last();
+            if (is_null($lastError)) {
+                throw new TokenResponseException(
+                    'Failed to request resource. HTTP Code: ' .
+                    ((isset($http_response_header[0]))?$http_response_header[0]:'No response')
+                );
+            }
+            throw new TokenResponseException($lastError['message']);
+        }
+
+        return $response;
+    }
+
+    private function generateStreamContext($body, $headers, $method)
+    {
+        return stream_context_create(
+            array(
+                'http' => array(
+                    'method'           => $method,
+                    'header'           => implode("\r\n", array_values($headers)),
+                    'content'          => $body,
+                    'protocol_version' => '1.1',
+                    'user_agent'       => $this->userAgent,
+                    'max_redirects'    => $this->maxRedirects,
+                    'timeout'          => $this->timeout
+                ),
+            )
+        );
+    }
+}

+ 12 - 0
htdocs/includes/OAuth/Common/Http/Exception/TokenResponseException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth\Common\Http\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Exception relating to token response from service.
+ */
+class TokenResponseException extends Exception
+{
+}

+ 408 - 0
htdocs/includes/OAuth/Common/Http/Uri/Uri.php

@@ -0,0 +1,408 @@
+<?php
+
+namespace OAuth\Common\Http\Uri;
+
+use InvalidArgumentException;
+
+/**
+ * Standards-compliant URI class.
+ */
+class Uri implements UriInterface
+{
+    /**
+     * @var string
+     */
+    private $scheme = 'http';
+
+    /**
+     * @var string
+     */
+    private $userInfo = '';
+
+    /**
+     * @var string
+     */
+    private $rawUserInfo = '';
+
+    /**
+     * @var string
+     */
+    private $host;
+
+    /**
+     * @var int
+     */
+    private $port = 80;
+
+    /**
+     * @var string
+     */
+    private $path = '/';
+
+    /**
+     * @var string
+     */
+    private $query = '';
+
+    /**
+     * @var string
+     */
+    private $fragment = '';
+
+    /**
+     * @var bool
+     */
+    private $explicitPortSpecified = false;
+
+    /**
+     * @var bool
+     */
+    private $explicitTrailingHostSlash = false;
+
+    /**
+     * @param string $uri
+     */
+    public function __construct($uri = null)
+    {
+        if (null !== $uri) {
+            $this->parseUri($uri);
+        }
+    }
+
+    /**
+     * @param string $uri
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function parseUri($uri)
+    {
+        if (false === ($uriParts = parse_url($uri))) {
+            // congratulations if you've managed to get parse_url to fail,
+            // it seems to always return some semblance of a parsed url no matter what
+            throw new InvalidArgumentException("Invalid URI: $uri");
+        }
+
+        if (!isset($uriParts['scheme'])) {
+            throw new InvalidArgumentException('Invalid URI: http|https scheme required');
+        }
+
+        $this->scheme = $uriParts['scheme'];
+        $this->host = $uriParts['host'];
+
+        if (isset($uriParts['port'])) {
+            $this->port = $uriParts['port'];
+            $this->explicitPortSpecified = true;
+        } else {
+            $this->port = strcmp('https', $uriParts['scheme']) ? 80 : 443;
+            $this->explicitPortSpecified = false;
+        }
+
+        if (isset($uriParts['path'])) {
+            $this->path = $uriParts['path'];
+            if ('/' === $uriParts['path']) {
+                $this->explicitTrailingHostSlash = true;
+            }
+        } else {
+            $this->path = '/';
+        }
+
+        $this->query = isset($uriParts['query']) ? $uriParts['query'] : '';
+        $this->fragment = isset($uriParts['fragment']) ? $uriParts['fragment'] : '';
+
+        $userInfo = '';
+        if (!empty($uriParts['user'])) {
+            $userInfo .= $uriParts['user'];
+        }
+        if ($userInfo && !empty($uriParts['pass'])) {
+            $userInfo .= ':' . $uriParts['pass'];
+        }
+
+        $this->setUserInfo($userInfo);
+    }
+
+    /**
+     * @param string $rawUserInfo
+     *
+     * @return string
+     */
+    protected function protectUserInfo($rawUserInfo)
+    {
+        $colonPos = strpos($rawUserInfo, ':');
+
+        // rfc3986-3.2.1 | http://tools.ietf.org/html/rfc3986#section-3.2
+        // "Applications should not render as clear text any data
+        // after the first colon (":") character found within a userinfo
+        // subcomponent unless the data after the colon is the empty string
+        // (indicating no password)"
+        if ($colonPos !== false && strlen($rawUserInfo)-1 > $colonPos) {
+            return substr($rawUserInfo, 0, $colonPos) . ':********';
+        } else {
+            return $rawUserInfo;
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getScheme()
+    {
+        return $this->scheme;
+    }
+
+    /**
+     * @return string
+     */
+    public function getUserInfo()
+    {
+        return $this->userInfo;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRawUserInfo()
+    {
+        return $this->rawUserInfo;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHost()
+    {
+        return $this->host;
+    }
+
+    /**
+     * @return int
+     */
+    public function getPort()
+    {
+        return $this->port;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * @return string
+     */
+    public function getQuery()
+    {
+        return $this->query;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFragment()
+    {
+        return $this->fragment;
+    }
+
+    /**
+     * Uses protected user info by default as per rfc3986-3.2.1
+     * Uri::getRawAuthority() is available if plain-text password information is desirable.
+     *
+     * @return string
+     */
+    public function getAuthority()
+    {
+        $authority = $this->userInfo ? $this->userInfo.'@' : '';
+        $authority .= $this->host;
+
+        if ($this->explicitPortSpecified) {
+            $authority .= ":{$this->port}";
+        }
+
+        return $authority;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRawAuthority()
+    {
+        $authority = $this->rawUserInfo ? $this->rawUserInfo.'@' : '';
+        $authority .= $this->host;
+
+        if ($this->explicitPortSpecified) {
+            $authority .= ":{$this->port}";
+        }
+
+        return $authority;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAbsoluteUri()
+    {
+        $uri = $this->scheme . '://' . $this->getRawAuthority();
+
+        if ('/' === $this->path) {
+            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
+        } else {
+            $uri .= $this->path;
+        }
+
+        if (!empty($this->query)) {
+            $uri .= "?{$this->query}";
+        }
+
+        if (!empty($this->fragment)) {
+            $uri .= "#{$this->fragment}";
+        }
+
+        return $uri;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRelativeUri()
+    {
+        $uri = '';
+
+        if ('/' === $this->path) {
+            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
+        } else {
+            $uri .= $this->path;
+        }
+
+        return $uri;
+    }
+
+    /**
+     * Uses protected user info by default as per rfc3986-3.2.1
+     * Uri::getAbsoluteUri() is available if plain-text password information is desirable.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        $uri = $this->scheme . '://' . $this->getAuthority();
+
+        if ('/' === $this->path) {
+            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
+        } else {
+            $uri .= $this->path;
+        }
+
+        if (!empty($this->query)) {
+            $uri .= "?{$this->query}";
+        }
+
+        if (!empty($this->fragment)) {
+            $uri .= "#{$this->fragment}";
+        }
+
+        return $uri;
+    }
+
+    /**
+     * @param $path
+     */
+    public function setPath($path)
+    {
+        if (empty($path)) {
+            $this->path = '/';
+            $this->explicitTrailingHostSlash = false;
+        } else {
+            $this->path = $path;
+            if ('/' === $this->path) {
+                $this->explicitTrailingHostSlash = true;
+            }
+        }
+    }
+
+    /**
+     * @param string $query
+     */
+    public function setQuery($query)
+    {
+        $this->query = $query;
+    }
+
+    /**
+     * @param string $var
+     * @param string $val
+     */
+    public function addToQuery($var, $val)
+    {
+        if (strlen($this->query) > 0) {
+            $this->query .= '&';
+        }
+        $this->query .= http_build_query(array($var => $val), '', '&');
+    }
+
+    /**
+     * @param string $fragment
+     */
+    public function setFragment($fragment)
+    {
+        $this->fragment = $fragment;
+    }
+
+    /**
+     * @param string $scheme
+     */
+    public function setScheme($scheme)
+    {
+        $this->scheme = $scheme;
+    }
+
+
+    /**
+     * @param string $userInfo
+     */
+    public function setUserInfo($userInfo)
+    {
+        $this->userInfo = $userInfo ? $this->protectUserInfo($userInfo) : '';
+        $this->rawUserInfo = $userInfo;
+    }
+
+
+    /**
+     * @param int $port
+     */
+    public function setPort($port)
+    {
+        $this->port = intval($port);
+
+        if (('https' === $this->scheme && $this->port === 443) || ('http' === $this->scheme && $this->port === 80)) {
+            $this->explicitPortSpecified = false;
+        } else {
+            $this->explicitPortSpecified = true;
+        }
+    }
+
+    /**
+     * @param string $host
+     */
+    public function setHost($host)
+    {
+        $this->host = $host;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasExplicitTrailingHostSlash()
+    {
+        return $this->explicitTrailingHostSlash;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasExplicitPortSpecified()
+    {
+        return $this->explicitPortSpecified;
+    }
+}

+ 168 - 0
htdocs/includes/OAuth/Common/Http/Uri/UriFactory.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace OAuth\Common\Http\Uri;
+
+use RuntimeException;
+
+/**
+ * Factory class for uniform resource indicators
+ */
+class UriFactory implements UriFactoryInterface
+{
+    /**
+     * Factory method to build a URI from a super-global $_SERVER array.
+     *
+     * @param array $_server
+     *
+     * @return UriInterface
+     */
+    public function createFromSuperGlobalArray(array $_server)
+    {
+        if ($uri = $this->attemptProxyStyleParse($_server)) {
+            return $uri;
+        }
+
+        $scheme = $this->detectScheme($_server);
+        $host = $this->detectHost($_server);
+        $port = $this->detectPort($_server);
+        $path = $this->detectPath($_server);
+        $query = $this->detectQuery($_server);
+
+        return $this->createFromParts($scheme, '', $host, $port, $path, $query);
+    }
+
+    /**
+     * @param string $absoluteUri
+     *
+     * @return UriInterface
+     */
+    public function createFromAbsolute($absoluteUri)
+    {
+        return new Uri($absoluteUri);
+    }
+
+    /**
+     * Factory method to build a URI from parts
+     *
+     * @param string $scheme
+     * @param string $userInfo
+     * @param string $host
+     * @param string $port
+     * @param string $path
+     * @param string $query
+     * @param string $fragment
+     *
+     * @return UriInterface
+     */
+    public function createFromParts($scheme, $userInfo, $host, $port, $path = '', $query = '', $fragment = '')
+    {
+        $uri = new Uri();
+        $uri->setScheme($scheme);
+        $uri->setUserInfo($userInfo);
+        $uri->setHost($host);
+        $uri->setPort($port);
+        $uri->setPath($path);
+        $uri->setQuery($query);
+        $uri->setFragment($fragment);
+
+        return $uri;
+    }
+
+    /**
+     * @param array $_server
+     *
+     * @return UriInterface|null
+     */
+    private function attemptProxyStyleParse($_server)
+    {
+        // If the raw HTTP request message arrives with a proxy-style absolute URI in the
+        // initial request line, the absolute URI is stored in $_SERVER['REQUEST_URI'] and
+        // we only need to parse that.
+        if (isset($_server['REQUEST_URI']) && parse_url($_server['REQUEST_URI'], PHP_URL_SCHEME)) {
+            return new Uri($_server['REQUEST_URI']);
+        }
+
+        return null;
+    }
+
+    /**
+     * @param array $_server
+     *
+     * @return string
+     *
+     * @throws RuntimeException
+     */
+    private function detectPath($_server)
+    {
+        if (isset($_server['REQUEST_URI'])) {
+            $uri = $_server['REQUEST_URI'];
+        } elseif (isset($_server['REDIRECT_URL'])) {
+            $uri = $_server['REDIRECT_URL'];
+        } else {
+            throw new RuntimeException('Could not detect URI path from superglobal');
+        }
+
+        $queryStr = strpos($uri, '?');
+        if ($queryStr !== false) {
+            $uri = substr($uri, 0, $queryStr);
+        }
+
+        return $uri;
+    }
+
+    /**
+     * @param array $_server
+     *
+     * @return string
+     */
+    private function detectHost(array $_server)
+    {
+        $host = isset($_server['HTTP_HOST']) ? $_server['HTTP_HOST'] : '';
+
+        if (strstr($host, ':')) {
+            $host = parse_url($host, PHP_URL_HOST);
+        }
+
+        return $host;
+    }
+
+    /**
+     * @param array $_server
+     *
+     * @return string
+     */
+    private function detectPort(array $_server)
+    {
+        return isset($_server['SERVER_PORT']) ? $_server['SERVER_PORT'] : 80;
+    }
+
+    /**
+     * @param array $_server
+     *
+     * @return string
+     */
+    private function detectQuery(array $_server)
+    {
+        return isset($_server['QUERY_STRING']) ? $_server['QUERY_STRING'] : '';
+    }
+
+    /**
+     * Determine URI scheme component from superglobal array
+     *
+     * When using ISAPI with IIS, the value will be "off" if the request was
+     * not made through the HTTPS protocol. As a result, we filter the
+     * value to a bool.
+     *
+     * @param array $_server A super-global $_SERVER array
+     *
+     * @return string Returns http or https depending on the URI scheme
+     */
+    private function detectScheme(array $_server)
+    {
+        if (isset($_server['HTTPS']) && filter_var($_server['HTTPS'], FILTER_VALIDATE_BOOLEAN)) {
+            return 'https';
+        } else {
+            return 'http';
+        }
+    }
+}

+ 42 - 0
htdocs/includes/OAuth/Common/Http/Uri/UriFactoryInterface.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace OAuth\Common\Http\Uri;
+
+/**
+ * Factory interface for uniform resource indicators
+ */
+interface UriFactoryInterface
+{
+    /**
+     * Factory method to build a URI from a super-global $_SERVER array.
+     *
+     * @param array $_server
+     *
+     * @return UriInterface
+     */
+    public function createFromSuperGlobalArray(array $_server);
+
+    /**
+     * Creates a URI from an absolute URI
+     *
+     * @param string $absoluteUri
+     *
+     * @return UriInterface
+     */
+    public function createFromAbsolute($absoluteUri);
+
+    /**
+     * Factory method to build a URI from parts
+     *
+     * @param string $scheme
+     * @param string $userInfo
+     * @param string $host
+     * @param string $port
+     * @param string $path
+     * @param string $query
+     * @param string $fragment
+     *
+     * @return UriInterface
+     */
+    public function createFromParts($scheme, $userInfo, $host, $port, $path = '', $query = '', $fragment = '');
+}

+ 133 - 0
htdocs/includes/OAuth/Common/Http/Uri/UriInterface.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace OAuth\Common\Http\Uri;
+
+interface UriInterface
+{
+    /**
+     * @return string
+     */
+    public function getScheme();
+
+    /**
+     * @param string $scheme
+     */
+    public function setScheme($scheme);
+
+    /**
+     * @return string
+     */
+    public function getHost();
+
+    /**
+     * @param string $host
+     */
+    public function setHost($host);
+
+    /**
+     * @return int
+     */
+    public function getPort();
+
+    /**
+     * @param int $port
+     */
+    public function setPort($port);
+
+    /**
+     * @return string
+     */
+    public function getPath();
+
+    /**
+     * @param string $path
+     */
+    public function setPath($path);
+
+    /**
+     * @return string
+     */
+    public function getQuery();
+
+    /**
+     * @param string $query
+     */
+    public function setQuery($query);
+
+    /**
+     * Adds a param to the query string.
+     *
+     * @param string $var
+     * @param string $val
+     */
+    public function addToQuery($var, $val);
+
+    /**
+     * @return string
+     */
+    public function getFragment();
+
+    /**
+     * Should return URI user info, masking protected user info data according to rfc3986-3.2.1
+     *
+     * @return string
+     */
+    public function getUserInfo();
+
+    /**
+     * @param string $userInfo
+     */
+    public function setUserInfo($userInfo);
+
+    /**
+     * Should return the URI Authority, masking protected user info data according to rfc3986-3.2.1
+     *
+     * @return string
+     */
+    public function getAuthority();
+
+    /**
+     * Should return the URI string, masking protected user info data according to rfc3986-3.2.1
+     *
+     * @return string the URI string with user protected info masked
+     */
+    public function __toString();
+
+    /**
+     * Should return the URI Authority without masking protected user info data
+     *
+     * @return string
+     */
+    public function getRawAuthority();
+
+    /**
+     * Should return the URI user info without masking protected user info data
+     *
+     * @return string
+     */
+    public function getRawUserInfo();
+
+    /**
+     * Build the full URI based on all the properties
+     *
+     * @return string The full URI without masking user info
+     */
+    public function getAbsoluteUri();
+
+    /**
+     * Build the relative URI based on all the properties
+     *
+     * @return string The relative URI
+     */
+    public function getRelativeUri();
+
+    /**
+     * @return bool
+     */
+    public function hasExplicitTrailingHostSlash();
+
+    /**
+     * @return bool
+     */
+    public function hasExplicitPortSpecified();
+}

+ 100 - 0
htdocs/includes/OAuth/Common/Service/AbstractService.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace OAuth\Common\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Exception\Exception;
+use OAuth\Common\Storage\TokenStorageInterface;
+
+/**
+ * Abstract OAuth service, version-agnostic
+ */
+abstract class AbstractService implements ServiceInterface
+{
+    /** @var Credentials */
+    protected $credentials;
+
+    /** @var ClientInterface */
+    protected $httpClient;
+
+    /** @var TokenStorageInterface */
+    protected $storage;
+
+    /**
+     * @param CredentialsInterface  $credentials
+     * @param ClientInterface       $httpClient
+     * @param TokenStorageInterface $storage
+     */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage
+    ) {
+        $this->credentials = $credentials;
+        $this->httpClient = $httpClient;
+        $this->storage = $storage;
+    }
+
+    /**
+     * @param UriInterface|string $path
+     * @param UriInterface        $baseApiUri
+     *
+     * @return UriInterface
+     *
+     * @throws Exception
+     */
+    protected function determineRequestUriFromPath($path, UriInterface $baseApiUri = null)
+    {
+        if ($path instanceof UriInterface) {
+            $uri = $path;
+        } elseif (stripos($path, 'http://') === 0 || stripos($path, 'https://') === 0) {
+            $uri = new Uri($path);
+        } else {
+            if (null === $baseApiUri) {
+                throw new Exception(
+                    'An absolute URI must be passed to ServiceInterface::request as no baseApiUri is set.'
+                );
+            }
+
+            $uri = clone $baseApiUri;
+            if (false !== strpos($path, '?')) {
+                $parts = explode('?', $path, 2);
+                $path = $parts[0];
+                $query = $parts[1];
+                $uri->setQuery($query);
+            }
+
+            if ($path[0] === '/') {
+                $path = substr($path, 1);
+            }
+
+            $uri->setPath($uri->getPath() . $path);
+        }
+
+        return $uri;
+    }
+
+    /**
+     * Accessor to the storage adapter to be able to retrieve tokens
+     *
+     * @return TokenStorageInterface
+     */
+    public function getStorage()
+    {
+        return $this->storage;
+    }
+
+    /**
+     * @return string
+     */
+    public function service()
+    {
+        // get class name without backslashes
+        $classname = get_class($this);
+
+        return preg_replace('/^.*\\\\/', '', $classname);
+    }
+}

+ 49 - 0
htdocs/includes/OAuth/Common/Service/ServiceInterface.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace OAuth\Common\Service;
+
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Defines methods common among all OAuth services.
+ */
+interface ServiceInterface
+{
+    /**
+     * Sends an authenticated API request to the path provided.
+     * If the path provided is not an absolute URI, the base API Uri (service-specific) will be used.
+     *
+     * @param string|UriInterface $path
+     * @param string              $method       HTTP method
+     * @param array               $body         Request body if applicable (an associative array will
+     *                                          automatically be converted into a urlencoded body)
+     * @param array               $extraHeaders Extra headers if applicable. These will override service-specific
+     *                                          any defaults.
+     *
+     * @return string
+     */
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array());
+
+    /**
+     * Returns the url to redirect to for authorization purposes.
+     *
+     * @param array $additionalParameters
+     *
+     * @return UriInterface
+     */
+    public function getAuthorizationUri(array $additionalParameters = array());
+
+    /**
+     * Returns the authorization API endpoint.
+     *
+     * @return UriInterface
+     */
+    public function getAuthorizationEndpoint();
+
+    /**
+     * Returns the access token API endpoint.
+     *
+     * @return UriInterface
+     */
+    public function getAccessTokenEndpoint();
+}

+ 254 - 0
htdocs/includes/OAuth/Common/Storage/DoliStorage.php

@@ -0,0 +1,254 @@
+<?php
+/*
+ * Copyright (C) 2015       Frederic France      <frederic.france@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ *      \file       htdocs/includes/OAuth/Common/Storage/DoliStorage.php
+ *      \ingroup    oauth
+ *      \brief      Dolibarr token storage class
+ */
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
+use DoliDB;
+
+class DoliStorage implements TokenStorageInterface
+{
+    /**
+     * @var DoliDB Database handler
+     */
+    protected $db;
+
+    /**
+     * @var object|TokenInterface
+     */
+    protected $tokens;
+
+    /**
+     * @var string Error code (or message)
+     */
+    public $error;
+    /**
+     * @var string[] Several error codes (or messages)
+     */
+    public $errors = array();
+
+    private $conf;
+    private $key;
+    private $stateKey;
+
+    /**
+     * @param Conf   $conf
+     * @param string $key
+     * @param string $stateKey
+     */
+    public function __construct(DoliDB $db, $conf)
+    {
+        $this->db = $db;
+        $this->conf = $conf;
+        $this->tokens = array();
+        $this->states = array();
+        //$this->key = $key;
+        //$this->stateKey = $stateKey;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAccessToken($service)
+    {
+        if ($this->hasAccessToken($service)) {
+            return $this->tokens[$service];
+        }
+
+        throw new TokenNotFoundException('Token not found in db, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAccessToken($service, TokenInterface $token)
+    {
+        $serializedToken = serialize($token);
+        $this->tokens[$service] = $token;
+
+        if (!is_array($this->tokens)) {
+            $this->tokens = array();
+        }
+        $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."oauth_token";
+        $sql.= " WHERE service='".$service."' AND entity=1";
+        $resql = $this->db->query($sql);
+        $obj = $this->db->fetch_array($resql);
+        if ($obj) {
+            // update
+            $sql = "UPDATE ".MAIN_DB_PREFIX."oauth_token";
+            $sql.= " SET token='".$this->db->escape($serializedToken)."'";
+            $sql.= " WHERE rowid='".$obj['rowid']."'";
+            $resql = $this->db->query($sql);
+        } else {
+            // save
+            $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_token (service, token, entity)";
+            $sql.= " VALUES ('".$service."', '".$this->db->escape($serializedToken)."', 1)";
+            $resql = $this->db->query($sql);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAccessToken($service)
+    {
+        // get from db
+        $sql = "SELECT token FROM ".MAIN_DB_PREFIX."oauth_token";
+        $sql.= " WHERE service='".$service."'";
+        $resql = $this->db->query($sql);
+        $result = $this->db->fetch_array($resql);
+        $token = unserialize($result[token]);
+        $this->tokens[$service] = $token;
+
+        return is_array($this->tokens)
+            && isset($this->tokens[$service])
+            && $this->tokens[$service] instanceof TokenInterface;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearToken($service)
+    {
+        // TODO
+        // get previously saved tokens
+        $tokens = $this->session->get($this->key);
+
+        if (is_array($tokens) && array_key_exists($service, $tokens)) {
+            unset($tokens[$service]);
+
+            // Replace the stored tokens array
+            $this->conf->set($this->key, $tokens);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllTokens()
+    {
+        // TODO
+        $this->conf->remove($this->key);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAuthorizationState($service)
+    {
+        if ($this->hasAuthorizationState($service)) {
+            return $this->states[$service];
+
+        }
+
+        throw new AuthorizationStateNotFoundException('State not found in conf, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAuthorizationState($service, $state)
+    {
+        // TODO save or update
+        // get previously saved tokens
+        //$states = $this->conf->get($this->stateKey);
+
+        if (!is_array($states)) {
+            $states = array();
+        }
+
+        $states[$service] = $state;
+        $this->states[$service] = $state;
+
+        // save
+        $sql = "INSERT INTO ".MAIN_DB_PREFIX."oauth_state (service, state, entity)";
+        $sql.= " VALUES ('".$service."', '".$state."', 1)";
+        $resql = $this->db->query($sql);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorizationState($service)
+    {
+        // get from db
+        $sql = "SELECT state FROM ".MAIN_DB_PREFIX."oauth_state";
+        $sql.= " WHERE service='".$service."'";
+        $resql = $this->db->query($sql);
+        $result = $this->db->fetch_array($resql);
+        $states[$service] = $result[state];
+        $this->states[$service] = $states[$service];
+
+        return is_array($states)
+        && isset($states[$service])
+        && null !== $states[$service];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAuthorizationState($service)
+    {
+        // TODO
+        // get previously saved tokens
+        //$states = $this->conf->get($this->stateKey);
+
+        if (is_array($states) && array_key_exists($service, $states)) {
+            unset($states[$service]);
+
+            // Replace the stored tokens array
+            //$this->conf->set($this->stateKey, $states);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllAuthorizationStates()
+    {
+        // TODO
+        //$this->conf->remove($this->stateKey);
+
+        // allow chaining
+        return $this;
+    }
+
+}

+ 10 - 0
htdocs/includes/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace OAuth\Common\Storage\Exception;
+
+/**
+ * Exception thrown when a state is not found in storage.
+ */
+class AuthorizationStateNotFoundException extends StorageException
+{
+}

+ 12 - 0
htdocs/includes/OAuth/Common/Storage/Exception/StorageException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth\Common\Storage\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Generic storage exception.
+ */
+class StorageException extends Exception
+{
+}

+ 10 - 0
htdocs/includes/OAuth/Common/Storage/Exception/TokenNotFoundException.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace OAuth\Common\Storage\Exception;
+
+/**
+ * Exception thrown when a token is not found in storage.
+ */
+class TokenNotFoundException extends StorageException
+{
+}

+ 139 - 0
htdocs/includes/OAuth/Common/Storage/Memory.php

@@ -0,0 +1,139 @@
+<?php
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
+
+/*
+ * Stores a token in-memory only (destroyed at end of script execution).
+ */
+class Memory implements TokenStorageInterface
+{
+    /**
+     * @var object|TokenInterface
+     */
+    protected $tokens;
+
+    /**
+     * @var array
+     */
+    protected $states;
+
+    public function __construct()
+    {
+        $this->tokens = array();
+        $this->states = array();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAccessToken($service)
+    {
+        if ($this->hasAccessToken($service)) {
+            return $this->tokens[$service];
+        }
+
+        throw new TokenNotFoundException('Token not stored');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAccessToken($service, TokenInterface $token)
+    {
+        $this->tokens[$service] = $token;
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAccessToken($service)
+    {
+        return isset($this->tokens[$service]) && $this->tokens[$service] instanceof TokenInterface;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearToken($service)
+    {
+        if (array_key_exists($service, $this->tokens)) {
+            unset($this->tokens[$service]);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllTokens()
+    {
+        $this->tokens = array();
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAuthorizationState($service)
+    {
+        if ($this->hasAuthorizationState($service)) {
+            return $this->states[$service];
+        }
+
+        throw new AuthorizationStateNotFoundException('State not stored');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAuthorizationState($service, $state)
+    {
+        $this->states[$service] = $state;
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorizationState($service)
+    {
+        return isset($this->states[$service]) && null !== $this->states[$service];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAuthorizationState($service)
+    {
+        if (array_key_exists($service, $this->states)) {
+            unset($this->states[$service]);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllAuthorizationStates()
+    {
+        $this->states = array();
+
+        // allow chaining
+        return $this;
+    }
+}

+ 230 - 0
htdocs/includes/OAuth/Common/Storage/Redis.php

@@ -0,0 +1,230 @@
+<?php
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
+use Predis\Client as Predis;
+
+/*
+ * Stores a token in a Redis server. Requires the Predis library available at https://github.com/nrk/predis
+ */
+class Redis implements TokenStorageInterface
+{
+    /**
+     * @var string
+     */
+    protected $key;
+
+    protected $stateKey;
+
+    /**
+     * @var object|\Redis
+     */
+    protected $redis;
+
+    /**
+     * @var object|TokenInterface
+     */
+    protected $cachedTokens;
+
+    /**
+     * @var object
+     */
+    protected $cachedStates;
+
+    /**
+     * @param Predis $redis An instantiated and connected redis client
+     * @param string $key The key to store the token under in redis
+     * @param string $stateKey The key to store the state under in redis.
+     */
+    public function __construct(Predis $redis, $key, $stateKey)
+    {
+        $this->redis = $redis;
+        $this->key = $key;
+        $this->stateKey = $stateKey;
+        $this->cachedTokens = array();
+        $this->cachedStates = array();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAccessToken($service)
+    {
+        if (!$this->hasAccessToken($service)) {
+            throw new TokenNotFoundException('Token not found in redis');
+        }
+
+        if (isset($this->cachedTokens[$service])) {
+            return $this->cachedTokens[$service];
+        }
+
+        $val = $this->redis->hget($this->key, $service);
+
+        return $this->cachedTokens[$service] = unserialize($val);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAccessToken($service, TokenInterface $token)
+    {
+        // (over)write the token
+        $this->redis->hset($this->key, $service, serialize($token));
+        $this->cachedTokens[$service] = $token;
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAccessToken($service)
+    {
+        if (isset($this->cachedTokens[$service])
+            && $this->cachedTokens[$service] instanceof TokenInterface
+        ) {
+            return true;
+        }
+
+        return $this->redis->hexists($this->key, $service);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearToken($service)
+    {
+        $this->redis->hdel($this->key, $service);
+        unset($this->cachedTokens[$service]);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllTokens()
+    {
+        // memory
+        $this->cachedTokens = array();
+
+        // redis
+        $keys = $this->redis->hkeys($this->key);
+        $me = $this; // 5.3 compat
+
+        // pipeline for performance
+        $this->redis->pipeline(
+            function ($pipe) use ($keys, $me) {
+                foreach ($keys as $k) {
+                    $pipe->hdel($me->getKey(), $k);
+                }
+            }
+        );
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAuthorizationState($service)
+    {
+        if (!$this->hasAuthorizationState($service)) {
+            throw new AuthorizationStateNotFoundException('State not found in redis');
+        }
+
+        if (isset($this->cachedStates[$service])) {
+            return $this->cachedStates[$service];
+        }
+
+        $val = $this->redis->hget($this->stateKey, $service);
+
+        return $this->cachedStates[$service] = $val;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAuthorizationState($service, $state)
+    {
+        // (over)write the token
+        $this->redis->hset($this->stateKey, $service, $state);
+        $this->cachedStates[$service] = $state;
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorizationState($service)
+    {
+        if (isset($this->cachedStates[$service])
+            && null !== $this->cachedStates[$service]
+        ) {
+            return true;
+        }
+
+        return $this->redis->hexists($this->stateKey, $service);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAuthorizationState($service)
+    {
+        $this->redis->hdel($this->stateKey, $service);
+        unset($this->cachedStates[$service]);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllAuthorizationStates()
+    {
+        // memory
+        $this->cachedStates = array();
+
+        // redis
+        $keys = $this->redis->hkeys($this->stateKey);
+        $me = $this; // 5.3 compat
+
+        // pipeline for performance
+        $this->redis->pipeline(
+            function ($pipe) use ($keys, $me) {
+                foreach ($keys as $k) {
+                    $pipe->hdel($me->getKey(), $k);
+                }
+            }
+        );
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * @return Predis $redis
+     */
+    public function getRedis()
+    {
+        return $this->redis;
+    }
+
+    /**
+     * @return string $key
+     */
+    public function getKey()
+    {
+        return $this->key;
+    }
+}

+ 188 - 0
htdocs/includes/OAuth/Common/Storage/Session.php

@@ -0,0 +1,188 @@
+<?php
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
+
+/**
+ * Stores a token in a PHP session.
+ */
+class Session implements TokenStorageInterface
+{
+    /**
+     * @var bool
+     */
+    protected $startSession;
+
+    /**
+     * @var string
+     */
+    protected $sessionVariableName;
+
+    /**
+     * @var string
+     */
+    protected $stateVariableName;
+
+    /**
+     * @param bool $startSession Whether or not to start the session upon construction.
+     * @param string $sessionVariableName the variable name to use within the _SESSION superglobal
+     * @param string $stateVariableName
+     */
+    public function __construct(
+        $startSession = true,
+        $sessionVariableName = 'lusitanian-oauth-token',
+        $stateVariableName = 'lusitanian-oauth-state'
+    ) {
+        if ($startSession && !isset($_SESSION)) {
+            session_start();
+        }
+
+        $this->startSession = $startSession;
+        $this->sessionVariableName = $sessionVariableName;
+        $this->stateVariableName = $stateVariableName;
+        if (!isset($_SESSION[$sessionVariableName])) {
+            $_SESSION[$sessionVariableName] = array();
+        }
+        if (!isset($_SESSION[$stateVariableName])) {
+            $_SESSION[$stateVariableName] = array();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAccessToken($service)
+    {
+        if ($this->hasAccessToken($service)) {
+            return unserialize($_SESSION[$this->sessionVariableName][$service]);
+        }
+
+        throw new TokenNotFoundException('Token not found in session, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAccessToken($service, TokenInterface $token)
+    {
+        $serializedToken = serialize($token);
+
+        if (isset($_SESSION[$this->sessionVariableName])
+            && is_array($_SESSION[$this->sessionVariableName])
+        ) {
+            $_SESSION[$this->sessionVariableName][$service] = $serializedToken;
+        } else {
+            $_SESSION[$this->sessionVariableName] = array(
+                $service => $serializedToken,
+            );
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAccessToken($service)
+    {
+        return isset($_SESSION[$this->sessionVariableName], $_SESSION[$this->sessionVariableName][$service]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearToken($service)
+    {
+        if (array_key_exists($service, $_SESSION[$this->sessionVariableName])) {
+            unset($_SESSION[$this->sessionVariableName][$service]);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllTokens()
+    {
+        unset($_SESSION[$this->sessionVariableName]);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAuthorizationState($service, $state)
+    {
+        if (isset($_SESSION[$this->stateVariableName])
+            && is_array($_SESSION[$this->stateVariableName])
+        ) {
+            $_SESSION[$this->stateVariableName][$service] = $state;
+        } else {
+            $_SESSION[$this->stateVariableName] = array(
+                $service => $state,
+            );
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorizationState($service)
+    {
+        return isset($_SESSION[$this->stateVariableName], $_SESSION[$this->stateVariableName][$service]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAuthorizationState($service)
+    {
+        if ($this->hasAuthorizationState($service)) {
+            return $_SESSION[$this->stateVariableName][$service];
+        }
+
+        throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAuthorizationState($service)
+    {
+        if (array_key_exists($service, $_SESSION[$this->stateVariableName])) {
+            unset($_SESSION[$this->stateVariableName][$service]);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllAuthorizationStates()
+    {
+        unset($_SESSION[$this->stateVariableName]);
+
+        // allow chaining
+        return $this;
+    }
+
+    public function __destruct()
+    {
+        if ($this->startSession) {
+            session_write_close();
+        }
+    }
+}

+ 200 - 0
htdocs/includes/OAuth/Common/Storage/SymfonySession.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+class SymfonySession implements TokenStorageInterface
+{
+    private $session;
+    private $sessionVariableName;
+    private $stateVariableName;
+
+    /**
+     * @param SessionInterface $session
+     * @param bool $startSession
+     * @param string $sessionVariableName
+     * @param string $stateVariableName
+     */
+    public function __construct(
+        SessionInterface $session,
+        $startSession = true,
+        $sessionVariableName = 'lusitanian_oauth_token',
+        $stateVariableName = 'lusitanian_oauth_state'
+    ) {
+        $this->session = $session;
+        $this->sessionVariableName = $sessionVariableName;
+        $this->stateVariableName = $stateVariableName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAccessToken($service)
+    {
+        if ($this->hasAccessToken($service)) {
+            // get from session
+            $tokens = $this->session->get($this->sessionVariableName);
+
+            // one item
+            return $tokens[$service];
+        }
+
+        throw new TokenNotFoundException('Token not found in session, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAccessToken($service, TokenInterface $token)
+    {
+        // get previously saved tokens
+        $tokens = $this->session->get($this->sessionVariableName);
+
+        if (!is_array($tokens)) {
+            $tokens = array();
+        }
+
+        $tokens[$service] = $token;
+
+        // save
+        $this->session->set($this->sessionVariableName, $tokens);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAccessToken($service)
+    {
+        // get from session
+        $tokens = $this->session->get($this->sessionVariableName);
+
+        return is_array($tokens)
+            && isset($tokens[$service])
+            && $tokens[$service] instanceof TokenInterface;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearToken($service)
+    {
+        // get previously saved tokens
+        $tokens = $this->session->get($this->sessionVariableName);
+
+        if (is_array($tokens) && array_key_exists($service, $tokens)) {
+            unset($tokens[$service]);
+
+            // Replace the stored tokens array
+            $this->session->set($this->sessionVariableName, $tokens);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllTokens()
+    {
+        $this->session->remove($this->sessionVariableName);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function retrieveAuthorizationState($service)
+    {
+        if ($this->hasAuthorizationState($service)) {
+            // get from session
+            $states = $this->session->get($this->stateVariableName);
+
+            // one item
+            return $states[$service];
+        }
+
+        throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?');
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function storeAuthorizationState($service, $state)
+    {
+        // get previously saved tokens
+        $states = $this->session->get($this->stateVariableName);
+
+        if (!is_array($states)) {
+            $states = array();
+        }
+
+        $states[$service] = $state;
+
+        // save
+        $this->session->set($this->stateVariableName, $states);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorizationState($service)
+    {
+        // get from session
+        $states = $this->session->get($this->stateVariableName);
+
+        return is_array($states)
+        && isset($states[$service])
+        && null !== $states[$service];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAuthorizationState($service)
+    {
+        // get previously saved tokens
+        $states = $this->session->get($this->stateVariableName);
+
+        if (is_array($states) && array_key_exists($service, $states)) {
+            unset($states[$service]);
+
+            // Replace the stored tokens array
+            $this->session->set($this->stateVariableName, $states);
+        }
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clearAllAuthorizationStates()
+    {
+        $this->session->remove($this->stateVariableName);
+
+        // allow chaining
+        return $this;
+    }
+
+    /**
+     * @return Session
+     */
+    public function getSession()
+    {
+        return $this->session;
+    }
+}

+ 98 - 0
htdocs/includes/OAuth/Common/Storage/TokenStorageInterface.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace OAuth\Common\Storage;
+
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Storage\Exception\TokenNotFoundException;
+
+/**
+ * All token storage providers must implement this interface.
+ */
+interface TokenStorageInterface
+{
+    /**
+     * @param string $service
+     *
+     * @return TokenInterface
+     *
+     * @throws TokenNotFoundException
+     */
+    public function retrieveAccessToken($service);
+
+    /**
+     * @param string         $service
+     * @param TokenInterface $token
+     *
+     * @return TokenStorageInterface
+     */
+    public function storeAccessToken($service, TokenInterface $token);
+
+    /**
+     * @param string $service
+     *
+     * @return bool
+     */
+    public function hasAccessToken($service);
+
+    /**
+     * Delete the users token. Aka, log out.
+     *
+     * @param string $service
+     *
+     * @return TokenStorageInterface
+     */
+    public function clearToken($service);
+
+    /**
+     * Delete *ALL* user tokens. Use with care. Most of the time you will likely
+     * want to use clearToken() instead.
+     *
+     * @return TokenStorageInterface
+     */
+    public function clearAllTokens();
+
+    /**
+     * Store the authorization state related to a given service
+     *
+     * @param string $service
+     * @param string $state
+     *
+     * @return TokenStorageInterface
+     */
+    public function storeAuthorizationState($service, $state);
+
+    /**
+     * Check if an authorization state for a given service exists
+     *
+     * @param string $service
+     *
+     * @return bool
+     */
+    public function hasAuthorizationState($service);
+
+    /**
+     * Retrieve the authorization state for a given service
+     *
+     * @param string $service
+     *
+     * @return string
+     */
+    public function retrieveAuthorizationState($service);
+
+    /**
+     * Clear the authorization state of a given service
+     *
+     * @param string $service
+     *
+     * @return TokenStorageInterface
+     */
+    public function clearAuthorizationState($service);
+
+    /**
+     * Delete *ALL* user authorization states. Use with care. Most of the time you will likely
+     * want to use clearAuthorization() instead.
+     *
+     * @return TokenStorageInterface
+     */
+    public function clearAllAuthorizationStates();
+}

+ 128 - 0
htdocs/includes/OAuth/Common/Token/AbstractToken.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace OAuth\Common\Token;
+
+/**
+ * Base token implementation for any OAuth version.
+ */
+abstract class AbstractToken implements TokenInterface
+{
+    /**
+     * @var string
+     */
+    protected $accessToken;
+
+    /**
+     * @var string
+     */
+    protected $refreshToken;
+
+    /**
+     * @var int
+     */
+    protected $endOfLife;
+
+    /**
+     * @var array
+     */
+    protected $extraParams = array();
+
+    /**
+     * @param string $accessToken
+     * @param string $refreshToken
+     * @param int    $lifetime
+     * @param array  $extraParams
+     */
+    public function __construct($accessToken = null, $refreshToken = null, $lifetime = null, $extraParams = array())
+    {
+        $this->accessToken = $accessToken;
+        $this->refreshToken = $refreshToken;
+        $this->setLifetime($lifetime);
+        $this->extraParams = $extraParams;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAccessToken()
+    {
+        return $this->accessToken;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRefreshToken()
+    {
+        return $this->refreshToken;
+    }
+
+    /**
+     * @return int
+     */
+    public function getEndOfLife()
+    {
+        return $this->endOfLife;
+    }
+
+    /**
+     * @param array $extraParams
+     */
+    public function setExtraParams(array $extraParams)
+    {
+        $this->extraParams = $extraParams;
+    }
+
+    /**
+     * @return array
+     */
+    public function getExtraParams()
+    {
+        return $this->extraParams;
+    }
+
+    /**
+     * @param string $accessToken
+     */
+    public function setAccessToken($accessToken)
+    {
+        $this->accessToken = $accessToken;
+    }
+
+    /**
+     * @param int $endOfLife
+     */
+    public function setEndOfLife($endOfLife)
+    {
+        $this->endOfLife = $endOfLife;
+    }
+
+    /**
+     * @param int $lifetime
+     */
+    public function setLifetime($lifetime)
+    {
+        if (0 === $lifetime || static::EOL_NEVER_EXPIRES === $lifetime) {
+            $this->endOfLife = static::EOL_NEVER_EXPIRES;
+        } elseif (null !== $lifetime) {
+            $this->endOfLife = intval($lifetime) + time();
+        } else {
+            $this->endOfLife = static::EOL_UNKNOWN;
+        }
+    }
+
+    /**
+     * @param string $refreshToken
+     */
+    public function setRefreshToken($refreshToken)
+    {
+        $this->refreshToken = $refreshToken;
+    }
+
+    public function isExpired()
+    {
+        return ($this->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES
+        && $this->getEndOfLife() !== TokenInterface::EOL_UNKNOWN
+        && time() > $this->getEndOfLife());
+    }
+}

+ 12 - 0
htdocs/includes/OAuth/Common/Token/Exception/ExpiredTokenException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth\Common\Token\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Exception thrown when an expired token is attempted to be used.
+ */
+class ExpiredTokenException extends Exception
+{
+}

+ 64 - 0
htdocs/includes/OAuth/Common/Token/TokenInterface.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace OAuth\Common\Token;
+
+/**
+ * Base token interface for any OAuth version.
+ */
+interface TokenInterface
+{
+    /**
+     * Denotes an unknown end of life time.
+     */
+    const EOL_UNKNOWN = -9001;
+
+    /**
+     * Denotes a token which never expires, should only happen in OAuth1.
+     */
+    const EOL_NEVER_EXPIRES = -9002;
+
+    /**
+     * @return string
+     */
+    public function getAccessToken();
+
+    /**
+     * @return int
+     */
+    public function getEndOfLife();
+
+    /**
+     * @return array
+     */
+    public function getExtraParams();
+
+    /**
+     * @param string $accessToken
+     */
+    public function setAccessToken($accessToken);
+
+    /**
+     * @param int $endOfLife
+     */
+    public function setEndOfLife($endOfLife);
+
+    /**
+     * @param int $lifetime
+     */
+    public function setLifetime($lifetime);
+
+    /**
+     * @param array $extraParams
+     */
+    public function setExtraParams(array $extraParams);
+
+    /**
+     * @return string
+     */
+    public function getRefreshToken();
+
+    /**
+     * @param string $refreshToken
+     */
+    public function setRefreshToken($refreshToken);
+}

+ 320 - 0
htdocs/includes/OAuth/OAuth1/Service/AbstractService.php

@@ -0,0 +1,320 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\TokenInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Service\AbstractService as BaseAbstractService;
+
+abstract class AbstractService extends BaseAbstractService implements ServiceInterface
+{
+    /** @const OAUTH_VERSION */
+    const OAUTH_VERSION = 1;
+
+    /** @var SignatureInterface */
+    protected $signature;
+
+    /** @var UriInterface|null */
+    protected $baseApiUri;
+
+    /**
+     * {@inheritDoc}
+     */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage);
+
+        $this->signature = $signature;
+        $this->baseApiUri = $baseApiUri;
+
+        $this->signature->setHashingAlgorithm($this->getSignatureMethod());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function requestRequestToken()
+    {
+        $authorizationHeader = array('Authorization' => $this->buildAuthorizationHeaderForTokenRequest());
+        $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders());
+
+        $responseBody = $this->httpClient->retrieveResponse($this->getRequestTokenEndpoint(), array(), $headers);
+
+        $token = $this->parseRequestTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($additionalParameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function requestAccessToken($token, $verifier, $tokenSecret = null)
+    {
+        if (is_null($tokenSecret)) {
+            $storedRequestToken = $this->storage->retrieveAccessToken($this->service());
+            $tokenSecret = $storedRequestToken->getRequestTokenSecret();
+        }
+        $this->signature->setTokenSecret($tokenSecret);
+
+        $bodyParams = array(
+            'oauth_verifier' => $verifier,
+        );
+
+        $authorizationHeader = array(
+            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest(
+                'POST',
+                $this->getAccessTokenEndpoint(),
+                $this->storage->retrieveAccessToken($this->service()),
+                $bodyParams
+            )
+        );
+
+        $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders());
+
+        $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers);
+
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * Refreshes an OAuth1 access token
+     * @param  TokenInterface $token
+     * @return TokenInterface $token
+     */
+    public function refreshAccessToken(TokenInterface $token)
+    {
+    }
+
+    /**
+     * Sends an authenticated API request to the path provided.
+     * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used.
+     *
+     * @param string|UriInterface $path
+     * @param string              $method       HTTP method
+     * @param array               $body         Request body if applicable (key/value pairs)
+     * @param array               $extraHeaders Extra headers if applicable.
+     *                                          These will override service-specific any defaults.
+     *
+     * @return string
+     */
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri);
+
+        /** @var $token StdOAuth1Token */
+        $token = $this->storage->retrieveAccessToken($this->service());
+        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
+        $authorizationHeader = array(
+            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body)
+        );
+        $headers = array_merge($authorizationHeader, $extraHeaders);
+
+        return $this->httpClient->retrieveResponse($uri, $body, $headers, $method);
+    }
+
+    /**
+     * Return any additional headers always needed for this service implementation's OAuth calls.
+     *
+     * @return array
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array();
+    }
+
+    /**
+     * Return any additional headers always needed for this service implementation's API calls.
+     *
+     * @return array
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array();
+    }
+
+    /**
+     * Builds the authorization header for getting an access or request token.
+     *
+     * @param array $extraParameters
+     *
+     * @return string
+     */
+    protected function buildAuthorizationHeaderForTokenRequest(array $extraParameters = array())
+    {
+        $parameters = $this->getBasicAuthorizationHeaderInfo();
+        $parameters = array_merge($parameters, $extraParameters);
+        $parameters['oauth_signature'] = $this->signature->getSignature(
+            $this->getRequestTokenEndpoint(),
+            $parameters,
+            'POST'
+        );
+
+        $authorizationHeader = 'OAuth ';
+        $delimiter = '';
+        foreach ($parameters as $key => $value) {
+            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
+
+            $delimiter = ', ';
+        }
+
+        return $authorizationHeader;
+    }
+
+    /**
+     * Builds the authorization header for an authenticated API request
+     *
+     * @param string         $method
+     * @param UriInterface   $uri        The uri the request is headed
+     * @param TokenInterface $token
+     * @param array          $bodyParams Request body if applicable (key/value pairs)
+     *
+     * @return string
+     */
+    protected function buildAuthorizationHeaderForAPIRequest(
+        $method,
+        UriInterface $uri,
+        TokenInterface $token,
+        $bodyParams = null
+    ) {
+        $this->signature->setTokenSecret($token->getAccessTokenSecret());
+        $authParameters = $this->getBasicAuthorizationHeaderInfo();
+        if (isset($authParameters['oauth_callback'])) {
+            unset($authParameters['oauth_callback']);
+        }
+
+        $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken()));
+
+        $authParameters = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters;
+        $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $authParameters, $method);
+
+        if (isset($bodyParams['oauth_session_handle'])) {
+            $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle'];
+            unset($bodyParams['oauth_session_handle']);
+        }
+
+        $authorizationHeader = 'OAuth ';
+        $delimiter = '';
+
+        foreach ($authParameters as $key => $value) {
+            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
+            $delimiter = ', ';
+        }
+
+        return $authorizationHeader;
+    }
+
+    /**
+     * Builds the authorization header array.
+     *
+     * @return array
+     */
+    protected function getBasicAuthorizationHeaderInfo()
+    {
+        $dateTime = new \DateTime();
+        $headerParameters = array(
+            'oauth_callback'         => $this->credentials->getCallbackUrl(),
+            'oauth_consumer_key'     => $this->credentials->getConsumerId(),
+            'oauth_nonce'            => $this->generateNonce(),
+            'oauth_signature_method' => $this->getSignatureMethod(),
+            'oauth_timestamp'        => $dateTime->format('U'),
+            'oauth_version'          => $this->getVersion(),
+        );
+
+        return $headerParameters;
+    }
+
+    /**
+     * Pseudo random string generator used to build a unique string to sign each request
+     *
+     * @param int $length
+     *
+     * @return string
+     */
+    protected function generateNonce($length = 32)
+    {
+        $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+
+        $nonce = '';
+        $maxRand = strlen($characters)-1;
+        for ($i = 0; $i < $length; $i++) {
+            $nonce.= $characters[rand(0, $maxRand)];
+        }
+
+        return $nonce;
+    }
+
+    /**
+     * @return string
+     */
+    protected function getSignatureMethod()
+    {
+        return 'HMAC-SHA1';
+    }
+
+    /**
+     * This returns the version used in the authorization header of the requests
+     *
+     * @return string
+     */
+    protected function getVersion()
+    {
+        return '1.0';
+    }
+
+    /**
+     * Parses the request token response and returns a TokenInterface.
+     * This is only needed to verify the `oauth_callback_confirmed` parameter. The actual
+     * parsing logic is contained in the access token parser.
+     *
+     * @abstract
+     *
+     * @param string $responseBody
+     *
+     * @return TokenInterface
+     *
+     * @throws TokenResponseException
+     */
+    abstract protected function parseRequestTokenResponse($responseBody);
+
+    /**
+     * Parses the access token response and returns a TokenInterface.
+     *
+     * @abstract
+     *
+     * @param string $responseBody
+     *
+     * @return TokenInterface
+     *
+     * @throws TokenResponseException
+     */
+    abstract protected function parseAccessTokenResponse($responseBody);
+}

+ 96 - 0
htdocs/includes/OAuth/OAuth1/Service/BitBucket.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class BitBucket extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://bitbucket.org/api/1.0/');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://bitbucket.org/!api/1.0/oauth/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://bitbucket.org/!api/1.0/oauth/authenticate');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://bitbucket.org/!api/1.0/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 132 - 0
htdocs/includes/OAuth/OAuth1/Service/Etsy.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Etsy extends AbstractService
+{
+
+    protected $scopes = array();
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://openapi.etsy.com/v2/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        $uri = new Uri($this->baseApiUri . 'oauth/request_token');
+        $scopes = $this->getScopes();
+
+        if (count($scopes)) {
+            $uri->setQuery('scope=' . implode('%20', $scopes));
+        }
+
+        return $uri;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri($this->baseApiUri);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri . 'oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * Set the scopes for permissions
+     * @see https://www.etsy.com/developers/documentation/getting_started/oauth#section_permission_scopes
+     * @param array $scopes
+     *
+     * @return $this
+     */
+    public function setScopes(array $scopes)
+    {
+        if (!is_array($scopes)) {
+            $scopes = array();
+        }
+
+        $this->scopes = $scopes;
+        return $this;
+    }
+
+    /**
+     * Return the defined scopes
+     * @return array
+     */
+    public function getScopes()
+    {
+        return $this->scopes;
+    }
+}

+ 96 - 0
htdocs/includes/OAuth/OAuth1/Service/FitBit.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class FitBit extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.fitbit.com/1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://api.fitbit.com/oauth/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.fitbit.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.fitbit.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 120 - 0
htdocs/includes/OAuth/OAuth1/Service/FiveHundredPx.php

@@ -0,0 +1,120 @@
+<?php
+/**
+ * 500px service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developers.500px.com/
+ */
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+/**
+ * 500px service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developers.500px.com/
+ */
+class FiveHundredPx extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $signature,
+            $baseApiUri
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.500px.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://api.500px.com/v1/oauth/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.500px.com/v1/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.500px.com/v1/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed'])
+            || $data['oauth_callback_confirmed'] !== 'true'
+        ) {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 133 - 0
htdocs/includes/OAuth/OAuth1/Service/Flickr.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Flickr extends AbstractService
+{
+    protected $format;
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+        if ($baseApiUri === null) {
+            $this->baseApiUri = new Uri('https://api.flickr.com/services/rest/');
+        }
+    }
+
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://www.flickr.com/services/oauth/request_token');
+    }
+
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.flickr.com/services/oauth/authorize');
+    }
+
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.flickr.com/services/oauth/access_token');
+    }
+
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+        if ($data === null || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $uri = $this->determineRequestUriFromPath('/', $this->baseApiUri);
+        $uri->addToQuery('method', $path);
+
+        if (!empty($this->format)) {
+            $uri->addToQuery('format', $this->format);
+
+            if ($this->format === 'json') {
+                $uri->addToQuery('nojsoncallback', 1);
+            }
+        }
+
+        $token = $this->storage->retrieveAccessToken($this->service());
+        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
+        $authorizationHeader = array(
+            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body)
+        );
+        $headers = array_merge($authorizationHeader, $extraHeaders);
+
+        return $this->httpClient->retrieveResponse($uri, $body, $headers, $method);
+    }
+
+    public function requestRest($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        return $this->request($path, $method, $body, $extraHeaders);
+    }
+
+    public function requestXmlrpc($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $this->format = 'xmlrpc';
+
+        return $this->request($path, $method, $body, $extraHeaders);
+    }
+
+    public function requestSoap($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $this->format = 'soap';
+
+        return $this->request($path, $method, $body, $extraHeaders);
+    }
+
+    public function requestJson($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $this->format = 'json';
+
+        return $this->request($path, $method, $body, $extraHeaders);
+    }
+
+    public function requestPhp($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $this->format = 'php_serial';
+
+        return $this->request($path, $method, $body, $extraHeaders);
+    }
+}

+ 120 - 0
htdocs/includes/OAuth/OAuth1/Service/QuickBooks.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth1\Signature\SignatureInterface;
+
+class QuickBooks extends AbstractService
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $signature,
+            $baseApiUri
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://quickbooks.api.intuit.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://oauth.intuit.com/oauth/v1/get_request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://appcenter.intuit.com/Connect/Begin');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://oauth.intuit.com/oauth/v1/get_access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed'])
+            || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            $message = 'Error in retrieving token: "' . $data['error'] . '"';
+            throw new TokenResponseException($message);
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function request(
+        $path,
+        $method = 'GET',
+        $body = null,
+        array $extraHeaders = array()
+    ) {
+        $extraHeaders['Accept'] = 'application/json';
+        return parent::request($path, $method, $body, $extraHeaders);
+    }
+}

+ 96 - 0
htdocs/includes/OAuth/OAuth1/Service/Redmine.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Redmine extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            throw new \Exception('baseApiUri is a required argument.');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri->getAbsoluteUri() . '/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri($this->baseApiUri->getAbsoluteUri() . '/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri->getAbsoluteUri() . '/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 96 - 0
htdocs/includes/OAuth/OAuth1/Service/ScoopIt.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class ScoopIt extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://www.scoop.it/api/1/');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://www.scoop.it/oauth/request');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.scoop.it/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.scoop.it/oauth/access');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 45 - 0
htdocs/includes/OAuth/OAuth1/Service/ServiceInterface.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Service\ServiceInterface as BaseServiceInterface;
+use OAuth\OAuth1\Signature\SignatureInterface;
+
+/**
+ * Defines the common methods across OAuth 1 services.
+ */
+interface ServiceInterface extends BaseServiceInterface
+{
+    /**
+     * Retrieves and stores/returns the OAuth1 request token obtained from the service.
+     *
+     * @return TokenInterface $token
+     *
+     * @throws TokenResponseException
+     */
+    public function requestRequestToken();
+
+    /**
+     * Retrieves and stores/returns the OAuth1 access token after a successful authorization.
+     *
+     * @param string $token       The request token from the callback.
+     * @param string $verifier
+     * @param string $tokenSecret
+     *
+     * @return TokenInterface $token
+     *
+     * @throws TokenResponseException
+     */
+    public function requestAccessToken($token, $verifier, $tokenSecret);
+
+    /**
+     * @return UriInterface
+     */
+    public function getRequestTokenEndpoint();
+}

+ 96 - 0
htdocs/includes/OAuth/OAuth1/Service/Tumblr.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Tumblr extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.tumblr.com/v2/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://www.tumblr.com/oauth/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.tumblr.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.tumblr.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 123 - 0
htdocs/includes/OAuth/OAuth1/Service/Twitter.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Exception\Exception;
+
+class Twitter extends AbstractService
+{
+    const ENDPOINT_AUTHENTICATE = "https://api.twitter.com/oauth/authenticate";
+    const ENDPOINT_AUTHORIZE    = "https://api.twitter.com/oauth/authorize";
+
+    protected $authorizationEndpoint   = self::ENDPOINT_AUTHENTICATE;
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.twitter.com/1.1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://api.twitter.com/oauth/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        if ($this->authorizationEndpoint != self::ENDPOINT_AUTHENTICATE
+        && $this->authorizationEndpoint != self::ENDPOINT_AUTHORIZE) {
+            $this->authorizationEndpoint = self::ENDPOINT_AUTHENTICATE;
+        }
+        return new Uri($this->authorizationEndpoint);
+    }
+
+    /**
+     * @param string $authorizationEndpoint
+     *
+     * @throws Exception
+     */
+    public function setAuthorizationEndpoint($endpoint)
+    {
+        if ($endpoint != self::ENDPOINT_AUTHENTICATE && $endpoint != self::ENDPOINT_AUTHORIZE) {
+            throw new Exception(
+                sprintf("'%s' is not a correct Twitter authorization endpoint.", $endpoint)
+            );
+        }
+        $this->authorizationEndpoint = $endpoint;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.twitter.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response: ' . $responseBody);
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        } elseif (!isset($data["oauth_token"]) || !isset($data["oauth_token_secret"])) {
+            throw new TokenResponseException('Invalid response. OAuth Token data not set: ' . $responseBody);
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 97 - 0
htdocs/includes/OAuth/OAuth1/Service/Xing.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Xing extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.xing.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.xing.com/v1/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.xing.com/v1/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://api.xing.com/v1/request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+        $errors = json_decode($responseBody);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif ($errors) {
+            throw new TokenResponseException('Error in retrieving token: "' . $errors->error_name . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 131 - 0
htdocs/includes/OAuth/OAuth1/Service/Yahoo.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace OAuth\OAuth1\Service;
+
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\OAuth1\Token\TokenInterface;
+
+class Yahoo extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        SignatureInterface $signature,
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://social.yahooapis.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://api.login.yahoo.com/oauth/v2/get_request_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.login.yahoo.com/oauth/v2/request_auth');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.login.yahoo.com/oauth/v2/get_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function refreshAccessToken(TokenInterface $token)
+    {
+        $extraParams = $token->getExtraParams();
+        $bodyParams = array('oauth_session_handle' => $extraParams['oauth_session_handle']);
+
+        $authorizationHeader = array(
+            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest(
+                'POST',
+                $this->getAccessTokenEndpoint(),
+                $this->storage->retrieveAccessToken($this->service()),
+                $bodyParams
+            )
+        );
+
+
+        
+        $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders(), array());
+
+        $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers);
+
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth1Token();
+
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+
+        if (isset($data['oauth_expires_in'])) {
+            $token->setLifetime($data['oauth_expires_in']);
+        } else {
+            $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        }
+
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 12 - 0
htdocs/includes/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth\OAuth1\Signature\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Thrown when an unsupported hash mechanism is requested in signature class.
+ */
+class UnsupportedHashAlgorithmException extends Exception
+{
+}

+ 132 - 0
htdocs/includes/OAuth/OAuth1/Signature/Signature.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace OAuth\OAuth1\Signature;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth1\Signature\Exception\UnsupportedHashAlgorithmException;
+
+class Signature implements SignatureInterface
+{
+    /**
+     * @var Credentials
+     */
+    protected $credentials;
+
+    /**
+     * @var string
+     */
+    protected $algorithm;
+
+    /**
+     * @var string
+     */
+    protected $tokenSecret = null;
+
+    /**
+     * @param CredentialsInterface $credentials
+     */
+    public function __construct(CredentialsInterface $credentials)
+    {
+        $this->credentials = $credentials;
+    }
+
+    /**
+     * @param string $algorithm
+     */
+    public function setHashingAlgorithm($algorithm)
+    {
+        $this->algorithm = $algorithm;
+    }
+
+    /**
+     * @param string $token
+     */
+    public function setTokenSecret($token)
+    {
+        $this->tokenSecret = $token;
+    }
+
+    /**
+     * @param UriInterface $uri
+     * @param array        $params
+     * @param string       $method
+     *
+     * @return string
+     */
+    public function getSignature(UriInterface $uri, array $params, $method = 'POST')
+    {
+        parse_str($uri->getQuery(), $queryStringData);
+
+        foreach (array_merge($queryStringData, $params) as $key => $value) {
+            $signatureData[rawurlencode($key)] = rawurlencode($value);
+        }
+
+        ksort($signatureData);
+
+        // determine base uri
+        $baseUri = $uri->getScheme() . '://' . $uri->getRawAuthority();
+
+        if ('/' === $uri->getPath()) {
+            $baseUri .= $uri->hasExplicitTrailingHostSlash() ? '/' : '';
+        } else {
+            $baseUri .= $uri->getPath();
+        }
+
+        $baseString = strtoupper($method) . '&';
+        $baseString .= rawurlencode($baseUri) . '&';
+        $baseString .= rawurlencode($this->buildSignatureDataString($signatureData));
+
+        return base64_encode($this->hash($baseString));
+    }
+
+    /**
+     * @param array $signatureData
+     *
+     * @return string
+     */
+    protected function buildSignatureDataString(array $signatureData)
+    {
+        $signatureString = '';
+        $delimiter = '';
+        foreach ($signatureData as $key => $value) {
+            $signatureString .= $delimiter . $key . '=' . $value;
+
+            $delimiter = '&';
+        }
+
+        return $signatureString;
+    }
+
+    /**
+     * @return string
+     */
+    protected function getSigningKey()
+    {
+        $signingKey = rawurlencode($this->credentials->getConsumerSecret()) . '&';
+        if ($this->tokenSecret !== null) {
+            $signingKey .= rawurlencode($this->tokenSecret);
+        }
+
+        return $signingKey;
+    }
+
+    /**
+     * @param string $data
+     *
+     * @return string
+     *
+     * @throws UnsupportedHashAlgorithmException
+     */
+    protected function hash($data)
+    {
+        switch (strtoupper($this->algorithm)) {
+            case 'HMAC-SHA1':
+                return hash_hmac('sha1', $data, $this->getSigningKey(), true);
+            default:
+                throw new UnsupportedHashAlgorithmException(
+                    'Unsupported hashing algorithm (' . $this->algorithm . ') used.'
+                );
+        }
+    }
+}

+ 28 - 0
htdocs/includes/OAuth/OAuth1/Signature/SignatureInterface.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace OAuth\OAuth1\Signature;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+interface SignatureInterface
+{
+    /**
+     * @param string $algorithm
+     */
+    public function setHashingAlgorithm($algorithm);
+
+    /**
+     * @param string $token
+     */
+    public function setTokenSecret($token);
+
+    /**
+     * @param UriInterface $uri
+     * @param array        $params
+     * @param string       $method
+     *
+     * @return string
+     */
+    public function getSignature(UriInterface $uri, array $params, $method = 'POST');
+}

+ 75 - 0
htdocs/includes/OAuth/OAuth1/Token/StdOAuth1Token.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace OAuth\OAuth1\Token;
+
+use OAuth\Common\Token\AbstractToken;
+
+/**
+ * Standard OAuth1 token implementation.
+ * Implements OAuth\OAuth1\Token\TokenInterface in case of any OAuth1 specific features.
+ */
+class StdOAuth1Token extends AbstractToken implements TokenInterface
+{
+    /**
+     * @var string
+     */
+    protected $requestToken;
+
+    /**
+     * @var string
+     */
+    protected $requestTokenSecret;
+
+    /**
+     * @var string
+     */
+    protected $accessTokenSecret;
+
+    /**
+     * @param string $requestToken
+     */
+    public function setRequestToken($requestToken)
+    {
+        $this->requestToken = $requestToken;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRequestToken()
+    {
+        return $this->requestToken;
+    }
+
+    /**
+     * @param string $requestTokenSecret
+     */
+    public function setRequestTokenSecret($requestTokenSecret)
+    {
+        $this->requestTokenSecret = $requestTokenSecret;
+    }
+
+    /**
+     * @return string
+     */
+    public function getRequestTokenSecret()
+    {
+        return $this->requestTokenSecret;
+    }
+
+    /**
+     * @param string $accessTokenSecret
+     */
+    public function setAccessTokenSecret($accessTokenSecret)
+    {
+        $this->accessTokenSecret = $accessTokenSecret;
+    }
+
+    /**
+     * @return string
+     */
+    public function getAccessTokenSecret()
+    {
+        return $this->accessTokenSecret;
+    }
+}

+ 41 - 0
htdocs/includes/OAuth/OAuth1/Token/TokenInterface.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace OAuth\OAuth1\Token;
+
+use OAuth\Common\Token\TokenInterface as BaseTokenInterface;
+
+/**
+ * OAuth1 specific token interface
+ */
+interface TokenInterface extends BaseTokenInterface
+{
+    /**
+     * @return string
+     */
+    public function getAccessTokenSecret();
+
+    /**
+     * @param string $accessTokenSecret
+     */
+    public function setAccessTokenSecret($accessTokenSecret);
+
+    /**
+     * @return string
+     */
+    public function getRequestTokenSecret();
+
+    /**
+     * @param string $requestTokenSecret
+     */
+    public function setRequestTokenSecret($requestTokenSecret);
+
+    /**
+     * @return string
+     */
+    public function getRequestToken();
+
+    /**
+     * @param string $requestToken
+     */
+    public function setRequestToken($requestToken);
+}

+ 364 - 0
htdocs/includes/OAuth/OAuth2/Service/AbstractService.php

@@ -0,0 +1,364 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Exception\Exception;
+use OAuth\Common\Service\AbstractService as BaseAbstractService;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth2\Service\Exception\InvalidAuthorizationStateException;
+use OAuth\OAuth2\Service\Exception\InvalidScopeException;
+use OAuth\OAuth2\Service\Exception\MissingRefreshTokenException;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Token\Exception\ExpiredTokenException;
+
+abstract class AbstractService extends BaseAbstractService implements ServiceInterface
+{
+    /** @const OAUTH_VERSION */
+    const OAUTH_VERSION = 2;
+
+    /** @var array */
+    protected $scopes;
+
+    /** @var UriInterface|null */
+    protected $baseApiUri;
+
+    /** @var bool */
+    protected $stateParameterInAuthUrl;
+
+    /** @var string */
+    protected $apiVersion;
+
+    /**
+     * @param CredentialsInterface  $credentials
+     * @param ClientInterface       $httpClient
+     * @param TokenStorageInterface $storage
+     * @param array                 $scopes
+     * @param UriInterface|null     $baseApiUri
+     * @param bool $stateParameterInAutUrl
+     * @param string                $apiVersion
+     *
+     * @throws InvalidScopeException
+     */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null,
+        $stateParameterInAutUrl = false,
+        $apiVersion = ""
+    ) {
+        parent::__construct($credentials, $httpClient, $storage);
+        $this->stateParameterInAuthUrl = $stateParameterInAutUrl;
+
+        foreach ($scopes as $scope) {
+            if (!$this->isValidScope($scope)) {
+                throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this));
+            }
+        }
+
+        $this->scopes = $scopes;
+
+        $this->baseApiUri = $baseApiUri;
+
+        $this->apiVersion = $apiVersion;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'type'          => 'web_server',
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        $parameters['scope'] = implode($this->getScopesDelimiter(), $this->scopes);
+
+        if ($this->needsStateParameterInAuthUrl()) {
+            if (!isset($parameters['state'])) {
+                $parameters['state'] = $this->generateAuthorizationState();
+            }
+            $this->storeAuthorizationState($parameters['state']);
+        }
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function requestAccessToken($code, $state = null)
+    {
+        if (null !== $state) {
+            $this->validateAuthorizationState($state);
+        }
+
+        $bodyParams = array(
+            'code'          => $code,
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'redirect_uri'  => $this->credentials->getCallbackUrl(),
+            'grant_type'    => 'authorization_code',
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $bodyParams,
+            $this->getExtraOAuthHeaders()
+        );
+
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * Sends an authenticated API request to the path provided.
+     * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used.
+     *
+     * @param string|UriInterface $path
+     * @param string              $method       HTTP method
+     * @param array               $body         Request body if applicable.
+     * @param array               $extraHeaders Extra headers if applicable. These will override service-specific
+     *                                          any defaults.
+     *
+     * @return string
+     *
+     * @throws ExpiredTokenException
+     * @throws Exception
+     */
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri);
+        $token = $this->storage->retrieveAccessToken($this->service());
+
+        if ($token->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES
+            && $token->getEndOfLife() !== TokenInterface::EOL_UNKNOWN
+            && time() > $token->getEndOfLife()
+        ) {
+            throw new ExpiredTokenException(
+                sprintf(
+                    'Token expired on %s at %s',
+                    date('m/d/Y', $token->getEndOfLife()),
+                    date('h:i:s A', $token->getEndOfLife())
+                )
+            );
+        }
+
+        // add the token where it may be needed
+        if (static::AUTHORIZATION_METHOD_HEADER_OAUTH === $this->getAuthorizationMethod()) {
+            $extraHeaders = array_merge(array('Authorization' => 'OAuth ' . $token->getAccessToken()), $extraHeaders);
+        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING === $this->getAuthorizationMethod()) {
+            $uri->addToQuery('access_token', $token->getAccessToken());
+        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V2 === $this->getAuthorizationMethod()) {
+            $uri->addToQuery('oauth2_access_token', $token->getAccessToken());
+        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V3 === $this->getAuthorizationMethod()) {
+            $uri->addToQuery('apikey', $token->getAccessToken());
+        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V4 === $this->getAuthorizationMethod()) {
+            $uri->addToQuery('auth', $token->getAccessToken());
+        } elseif (static::AUTHORIZATION_METHOD_HEADER_BEARER === $this->getAuthorizationMethod()) {
+            $extraHeaders = array_merge(array('Authorization' => 'Bearer ' . $token->getAccessToken()), $extraHeaders);
+        }
+
+        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
+
+        return $this->httpClient->retrieveResponse($uri, $body, $extraHeaders, $method);
+    }
+
+    /**
+     * Accessor to the storage adapter to be able to retrieve tokens
+     *
+     * @return TokenStorageInterface
+     */
+    public function getStorage()
+    {
+        return $this->storage;
+    }
+
+    /**
+     * Refreshes an OAuth2 access token.
+     *
+     * @param TokenInterface $token
+     *
+     * @return TokenInterface $token
+     *
+     * @throws MissingRefreshTokenException
+     */
+    public function refreshAccessToken(TokenInterface $token)
+    {
+        $refreshToken = $token->getRefreshToken();
+
+        if (empty($refreshToken)) {
+            throw new MissingRefreshTokenException();
+        }
+
+        $parameters = array(
+            'grant_type'    => 'refresh_token',
+            'type'          => 'web_server',
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'refresh_token' => $refreshToken,
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $parameters,
+            $this->getExtraOAuthHeaders()
+        );
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * Return whether or not the passed scope value is valid.
+     *
+     * @param string $scope
+     *
+     * @return bool
+     */
+    public function isValidScope($scope)
+    {
+        $reflectionClass = new \ReflectionClass(get_class($this));
+
+        return in_array($scope, $reflectionClass->getConstants(), true);
+    }
+
+    /**
+     * Check if the given service need to generate a unique state token to build the authorization url
+     *
+     * @return bool
+     */
+    public function needsStateParameterInAuthUrl()
+    {
+        return $this->stateParameterInAuthUrl;
+    }
+
+    /**
+     * Validates the authorization state against a given one
+     *
+     * @param string $state
+     * @throws InvalidAuthorizationStateException
+     */
+    protected function validateAuthorizationState($state)
+    {
+        if ($this->retrieveAuthorizationState() !== $state) {
+            throw new InvalidAuthorizationStateException();
+        }
+    }
+
+    /**
+     * Generates a random string to be used as state
+     *
+     * @return string
+     */
+    protected function generateAuthorizationState()
+    {
+        return md5(rand());
+    }
+
+    /**
+     * Retrieves the authorization state for the current service
+     *
+     * @return string
+     */
+    protected function retrieveAuthorizationState()
+    {
+        return $this->storage->retrieveAuthorizationState($this->service());
+    }
+
+    /**
+     * Stores a given authorization state into the storage
+     *
+     * @param string $state
+     */
+    protected function storeAuthorizationState($state)
+    {
+        $this->storage->storeAuthorizationState($this->service(), $state);
+    }
+
+    /**
+     * Return any additional headers always needed for this service implementation's OAuth calls.
+     *
+     * @return array
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array();
+    }
+
+    /**
+     * Return any additional headers always needed for this service implementation's API calls.
+     *
+     * @return array
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array();
+    }
+
+    /**
+     * Parses the access token response and returns a TokenInterface.
+     *
+     * @abstract
+     *
+     * @param string $responseBody
+     *
+     * @return TokenInterface
+     *
+     * @throws TokenResponseException
+     */
+    abstract protected function parseAccessTokenResponse($responseBody);
+
+    /**
+     * Returns a class constant from ServiceInterface defining the authorization method used for the API
+     * Header is the sane default.
+     *
+     * @return int
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_OAUTH;
+    }
+
+    /**
+     * Returns api version string if is set else retrun empty string
+     *
+     * @return string
+     */
+    protected function getApiVersionString()
+    {
+        return !(empty($this->apiVersion)) ? "/".$this->apiVersion : "" ;
+    }
+
+    /**
+     * Returns delimiter to scopes in getAuthorizationUri
+     * For services that do not fully respect the Oauth's RFC,
+     * and use scopes with commas as delimiter
+     *
+     * @return string
+     */
+    protected function getScopesDelimiter()
+    {
+        return ' ';
+    }
+}

+ 97 - 0
htdocs/includes/OAuth/OAuth2/Service/Amazon.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Amazon service.
+ *
+ * @author Flávio Heleno <flaviohbatista@gmail.com>
+ * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf
+ */
+class Amazon extends AbstractService
+{
+    /**
+     * Defined scopes
+     * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf
+     */
+    const SCOPE_PROFILE     = 'profile';
+    const SCOPE_POSTAL_CODE = 'postal_code';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.amazon.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.amazon.com/ap/oa');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.amazon.com/ap/oatoken');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 111 - 0
htdocs/includes/OAuth/OAuth2/Service/Bitly.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Bitly extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api-ssl.bitly.com/v3/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://bitly.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api-ssl.bitly.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        // I'm invincible!!!
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function requestAccessToken($code, $state = null)
+    {
+        if (null !== $state) {
+            $this->validateAuthorizationState($state);
+        }
+
+        $bodyParams = array(
+            'code'          => $code,
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'redirect_uri'  => $this->credentials->getCallbackUrl(),
+            'grant_type'    => 'authorization_code',
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $bodyParams,
+            $this->getExtraOAuthHeaders()
+        );
+
+        // we can scream what we want that we want bitly to return a json encoded string (format=json), but the
+        // WOAH WATCH YOUR LANGUAGE ;) service doesn't seem to like screaming, hence we need to manually
+        // parse the result
+        $parsedResult = array();
+        parse_str($responseBody, $parsedResult);
+
+        $token = $this->parseAccessTokenResponse(json_encode($parsedResult));
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+}

+ 118 - 0
htdocs/includes/OAuth/OAuth2/Service/Bitrix24.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Bitrix24 extends AbstractService
+{
+    const SCOPE_DEPARTMENT = 'department';
+    const SCOPE_CRM = 'crm';
+    const SCOPE_CALENDAR = 'calendar';
+    const SCOPE_USER = 'user';
+    const SCOPE_ENTITY = 'entity';
+    const SCOPE_TASK = 'task';
+    const SCOPE_TASKS_EXTENDED = 'tasks_extended';
+    const SCOPE_IM = 'im';
+    const SCOPE_LOG = 'log';
+    const SCOPE_SONET_GROUP = 'sonet_group';
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri(sprintf('%s/oauth/authorize/', $this->baseApiUri));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri(sprintf('%s/oauth/token/', $this->baseApiUri));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function requestAccessToken($code, $state = null)
+    {
+        if (null !== $state) {
+            $this->validateAuthorizationState($state);
+        }
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenUri($code),
+            array(),
+            $this->getExtraOAuthHeaders(),
+            'GET'
+        );
+
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenUri($code)
+    {
+        $parameters = array(
+            'code'          => $code,
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'redirect_uri'  => $this->credentials->getCallbackUrl(),
+            'grant_type'    => 'authorization_code',
+            'scope'         => $this->scopes
+        );
+
+        $parameters['scope'] = implode(' ', $this->scopes);
+
+        // Build the url
+        $url = $this->getAccessTokenEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 88 - 0
htdocs/includes/OAuth/OAuth2/Service/Box.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Box service.
+ *
+ * @author Antoine Corcy <contact@sbin.dk>
+ * @link https://developers.box.com/oauth/
+ */
+class Box extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.box.com/2.0/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.box.com/api/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.box.com/api/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 151 - 0
htdocs/includes/OAuth/OAuth2/Service/Buffer.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+/**
+ * Buffer API.
+ * @author  Sumukh Sridhara <@sumukhsridhara>
+ * @link https://bufferapp.com/developers/api
+ */
+class Buffer extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+        if ($baseApiUri === null) {
+            $this->baseApiUri = new Uri('https://api.bufferapp.com/1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://bufferapp.com/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.bufferapp.com/1/oauth2/token.json');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function requestRequestToken()
+    {
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getRequestTokenEndpoint(),
+            array(
+                'client_key' => $this->credentials->getConsumerId(),
+                'redirect_uri' => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        $code = $this->parseRequestTokenResponse($responseBody);
+
+        return $code;
+    }
+
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['code'])) {
+            throw new TokenResponseException('Error in retrieving code.');
+        }
+        return $data['code'];
+    }
+
+    public function requestAccessToken($code)
+    {
+        $bodyParams = array(
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'redirect_uri' => $this->credentials->getCallbackUrl(),
+            'code'          => $code,
+            'grant_type'    => 'authorization_code',
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $bodyParams,
+            $this->getExtraOAuthHeaders()
+        );
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if ($data === null || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 129 - 0
htdocs/includes/OAuth/OAuth2/Service/Dailymotion.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Dailymotion service.
+ *
+ * @author Mouhamed SEYE <mouhamed@seye.pro>
+ * @link http://www.dailymotion.com/doc/api/authentication.html
+ */
+class Dailymotion extends AbstractService
+{
+    /**
+     * Scopes
+     *
+     * @var string
+     */
+    const SCOPE_EMAIL         = 'email',
+          SCOPE_PROFILE       = 'userinfo',
+          SCOPE_VIDEOS        = 'manage_videos',
+          SCOPE_COMMENTS      = 'manage_comments',
+          SCOPE_PLAYLIST      = 'manage_playlists',
+          SCOPE_TILES         = 'manage_tiles',
+          SCOPE_SUBSCRIPTIONS = 'manage_subscriptions',
+          SCOPE_FRIENDS       = 'manage_friends',
+          SCOPE_FAVORITES     = 'manage_favorites',
+          SCOPE_GROUPS        = 'manage_groups';
+
+    /**
+     * Dialog form factors
+     *
+     * @var string
+     */
+    const DISPLAY_PAGE   = 'page',
+          DISPLAY_POPUP  = 'popup',
+          DISPLAY_MOBILE = 'mobile';
+
+    /**
+    * {@inheritdoc}
+    */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.dailymotion.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.dailymotion.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.dailymotion.com/oauth/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_OAUTH;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description']) || isset($data['error'])) {
+            throw new TokenResponseException(
+                sprintf(
+                    'Error in retrieving token: "%s"',
+                    isset($data['error_description']) ? $data['error_description'] : $data['error']
+                )
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => 'application/json');
+    }
+}

+ 139 - 0
htdocs/includes/OAuth/OAuth2/Service/Delicious.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * Delicious service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html  MIT License
+ * @link    https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Delicious service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html  MIT License
+ * @link    https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md
+ */
+class Delicious extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.del.icio.us/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://delicious.com/auth/authorize');
+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://avosapi.delicious.com/api/v1/oauth/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifetime($data['expires_in']);
+            unset($data['expires_in']);
+        }
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+
+    // Special, delicious didn't respect the oauth2 RFC and need a grant_type='code'
+    /**
+     * {@inheritdoc}
+     */
+    public function requestAccessToken($code, $state = null)
+    {
+        if (null !== $state) {
+            $this->validateAuthorizationState($state);
+        }
+
+        $bodyParams = array(
+            'code'          => $code,
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'redirect_uri'  => $this->credentials->getCallbackUrl(),
+            'grant_type'    => 'code',
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $bodyParams,
+            $this->getExtraOAuthHeaders()
+        );
+
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+}

+ 99 - 0
htdocs/includes/OAuth/OAuth2/Service/DeviantArt.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Exception\Exception;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class DeviantArt extends AbstractService
+{
+    /**
+     * DeviantArt www url - used to build dialog urls
+     */
+    const WWW_URL = 'https://www.deviantart.com/';
+
+    /**
+     * Defined scopes
+     *
+     * If you don't think this is scary you should not be allowed on the web at all
+     *
+     * @link https://www.deviantart.com/developers/authentication
+     * @link https://www.deviantart.com/developers/http/v1/20150217
+     */
+    const SCOPE_FEED                       = 'feed';
+    const SCOPE_BROWSE                     = 'browse';
+    const SCOPE_COMMENT                    = 'comment.post';
+    const SCOPE_STASH                      = 'stash';
+    const SCOPE_USER                       = 'user';
+    const SCOPE_USERMANAGE                 = 'user.manage';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://www.deviantart.com/api/v1/oauth2/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.deviantart.com/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.deviantart.com/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifeTime($data['expires_in']);
+        }
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 111 - 0
htdocs/includes/OAuth/OAuth2/Service/Dropbox.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Dropbox service.
+ *
+ * @author Flávio Heleno <flaviohbatista@gmail.com>
+ * @link https://www.dropbox.com/developers/core/docs
+ */
+class Dropbox extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.dropbox.com/1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        $parameters['scope'] = implode(' ', $this->scopes);
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.dropbox.com/1/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.dropbox.com/1/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 100 - 0
htdocs/includes/OAuth/OAuth2/Service/EveOnline.php

@@ -0,0 +1,100 @@
+<?php
+/**
+ * Contains EveOnline class.
+ * PHP version 5.4
+ * @copyright 2014 Michael Cummings
+ * @author    Michael Cummings <mgcummings@yahoo.com>
+ */
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+
+/**
+ * Class EveOnline
+ */
+class EveOnline extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://login.eveonline.com');
+        }
+    }
+
+    /**
+     * Returns the authorization API endpoint.
+     * @return UriInterface
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri($this->baseApiUri . '/oauth/authorize');
+    }
+
+    /**
+     * Returns the access token API endpoint.
+     * @return UriInterface
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri . '/oauth/token');
+    }
+
+    /**
+     * Parses the access token response and returns a TokenInterface.
+     *
+     * @param string $responseBody
+     *
+     * @return TokenInterface
+     * @throws TokenResponseException
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+}

+ 12 - 0
htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace OAuth\OAuth2\Service\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Exception thrown when an invalid accessType for the Google Service is specified
+ */
+class InvalidAccessTypeException extends Exception
+{
+}

+ 10 - 0
htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidAuthorizationStateException.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace OAuth\OAuth2\Service\Exception;
+
+/**
+ * Exception thrown when the state parameter received during the authorization process is invalid.
+ */
+class InvalidAuthorizationStateException extends \Exception
+{
+}

+ 17 - 0
htdocs/includes/OAuth/OAuth2/Service/Exception/InvalidScopeException.php

@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @author David Desberg <david@daviddesberg.com>
+ * Released under the MIT license.
+ */
+
+namespace OAuth\OAuth2\Service\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Exception thrown when a scope provided to a service is invalid.
+ */
+class InvalidScopeException extends Exception
+{
+}

+ 17 - 0
htdocs/includes/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php

@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @author David Desberg <david@daviddesberg.com>
+ * Released under the MIT license.
+ */
+
+namespace OAuth\OAuth2\Service\Exception;
+
+use OAuth\Common\Exception\Exception;
+
+/**
+ * Exception thrown when service is requested to refresh the access token but no refresh token can be found.
+ */
+class MissingRefreshTokenException extends Exception
+{
+}

+ 208 - 0
htdocs/includes/OAuth/OAuth2/Service/Facebook.php

@@ -0,0 +1,208 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Exception\Exception;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Facebook extends AbstractService
+{
+    /**
+     * Facebook www url - used to build dialog urls
+     */
+    const WWW_URL = 'https://www.facebook.com/';
+
+    /**
+     * Defined scopes
+     *
+     * If you don't think this is scary you should not be allowed on the web at all
+     *
+     * @link https://developers.facebook.com/docs/reference/login/
+     * @link https://developers.facebook.com/tools/explorer For a list of permissions use 'Get Access Token'
+     */
+    // Default scope
+    const SCOPE_PUBLIC_PROFILE                = 'public_profile';
+    // Email scopes
+    const SCOPE_EMAIL                         = 'email';
+    // Extended permissions
+    const SCOPE_READ_FRIENDLIST               = 'read_friendlists';
+    const SCOPE_READ_INSIGHTS                 = 'read_insights';
+    const SCOPE_READ_MAILBOX                  = 'read_mailbox';
+    const SCOPE_READ_PAGE_MAILBOXES           = 'read_page_mailboxes';
+    const SCOPE_READ_REQUESTS                 = 'read_requests';
+    const SCOPE_READ_STREAM                   = 'read_stream';
+    const SCOPE_VIDEO_UPLOAD                  = 'video_upload';
+    const SCOPE_XMPP_LOGIN                    = 'xmpp_login';
+    const SCOPE_USER_ONLINE_PRESENCE          = 'user_online_presence';
+    const SCOPE_FRIENDS_ONLINE_PRESENCE       = 'friends_online_presence';
+    const SCOPE_ADS_MANAGEMENT                = 'ads_management';
+    const SCOPE_ADS_READ                      = 'ads_read';
+    const SCOPE_CREATE_EVENT                  = 'create_event';
+    const SCOPE_CREATE_NOTE                   = 'create_note';
+    const SCOPE_EXPORT_STREAM                 = 'export_stream';
+    const SCOPE_MANAGE_FRIENDLIST             = 'manage_friendlists';
+    const SCOPE_MANAGE_NOTIFICATIONS          = 'manage_notifications';
+    const SCOPE_PHOTO_UPLOAD                  = 'photo_upload';
+    const SCOPE_PUBLISH_ACTIONS               = 'publish_actions';
+    const SCOPE_PUBLISH_CHECKINS              = 'publish_checkins';
+    const SCOPE_PUBLISH_STREAM                = 'publish_stream';
+    const SCOPE_RSVP_EVENT                    = 'rsvp_event';
+    const SCOPE_SHARE_ITEM                    = 'share_item';
+    const SCOPE_SMS                           = 'sms';
+    const SCOPE_STATUS_UPDATE                 = 'status_update';
+    // Extended Profile Properties
+    const SCOPE_USER_POSTS                    = 'user_posts';
+    const SCOPE_USER_FRIENDS                  = 'user_friends';
+    const SCOPE_USER_ABOUT                    = 'user_about_me';
+    const SCOPE_USER_TAGGED_PLACES            = 'user_tagged_places';
+    const SCOPE_FRIENDS_ABOUT                 = 'friends_about_me';
+    const SCOPE_USER_ACTIVITIES               = 'user_activities';
+    const SCOPE_FRIENDS_ACTIVITIES            = 'friends_activities';
+    const SCOPE_USER_BIRTHDAY                 = 'user_birthday';
+    const SCOPE_FRIENDS_BIRTHDAY              = 'friends_birthday';
+    const SCOPE_USER_CHECKINS                 = 'user_checkins';
+    const SCOPE_FRIENDS_CHECKINS              = 'friends_checkins';
+    const SCOPE_USER_EDUCATION                = 'user_education_history';
+    const SCOPE_FRIENDS_EDUCATION             = 'friends_education_history';
+    const SCOPE_USER_EVENTS                   = 'user_events';
+    const SCOPE_FRIENDS_EVENTS                = 'friends_events';
+    const SCOPE_USER_GROUPS                   = 'user_groups';
+    const SCOPE_USER_MANAGED_GROUPS           = 'user_managed_groups';
+    const SCOPE_FRIENDS_GROUPS                = 'friends_groups';
+    const SCOPE_USER_HOMETOWN                 = 'user_hometown';
+    const SCOPE_FRIENDS_HOMETOWN              = 'friends_hometown';
+    const SCOPE_USER_INTERESTS                = 'user_interests';
+    const SCOPE_FRIEND_INTERESTS              = 'friends_interests';
+    const SCOPE_USER_LIKES                    = 'user_likes';
+    const SCOPE_FRIENDS_LIKES                 = 'friends_likes';
+    const SCOPE_USER_LOCATION                 = 'user_location';
+    const SCOPE_FRIENDS_LOCATION              = 'friends_location';
+    const SCOPE_USER_NOTES                    = 'user_notes';
+    const SCOPE_FRIENDS_NOTES                 = 'friends_notes';
+    const SCOPE_USER_PHOTOS                   = 'user_photos';
+    const SCOPE_USER_PHOTO_VIDEO_TAGS         = 'user_photo_video_tags';
+    const SCOPE_FRIENDS_PHOTOS                = 'friends_photos';
+    const SCOPE_FRIENDS_PHOTO_VIDEO_TAGS      = 'friends_photo_video_tags';
+    const SCOPE_USER_QUESTIONS                = 'user_questions';
+    const SCOPE_FRIENDS_QUESTIONS             = 'friends_questions';
+    const SCOPE_USER_RELATIONSHIPS            = 'user_relationships';
+    const SCOPE_FRIENDS_RELATIONSHIPS         = 'friends_relationships';
+    const SCOPE_USER_RELATIONSHIPS_DETAILS    = 'user_relationship_details';
+    const SCOPE_FRIENDS_RELATIONSHIPS_DETAILS = 'friends_relationship_details';
+    const SCOPE_USER_RELIGION                 = 'user_religion_politics';
+    const SCOPE_FRIENDS_RELIGION              = 'friends_religion_politics';
+    const SCOPE_USER_STATUS                   = 'user_status';
+    const SCOPE_FRIENDS_STATUS                = 'friends_status';
+    const SCOPE_USER_SUBSCRIPTIONS            = 'user_subscriptions';
+    const SCOPE_FRIENDS_SUBSCRIPTIONS         = 'friends_subscriptions';
+    const SCOPE_USER_VIDEOS                   = 'user_videos';
+    const SCOPE_FRIENDS_VIDEOS                = 'friends_videos';
+    const SCOPE_USER_WEBSITE                  = 'user_website';
+    const SCOPE_FRIENDS_WEBSITE               = 'friends_website';
+    const SCOPE_USER_WORK                     = 'user_work_history';
+    const SCOPE_FRIENDS_WORK                  = 'friends_work_history';
+    // Open Graph Permissions
+    const SCOPE_USER_MUSIC                    = 'user_actions.music';
+    const SCOPE_FRIENDS_MUSIC                 = 'friends_actions.music';
+    const SCOPE_USER_NEWS                     = 'user_actions.news';
+    const SCOPE_FRIENDS_NEWS                  = 'friends_actions.news';
+    const SCOPE_USER_VIDEO                    = 'user_actions.video';
+    const SCOPE_FRIENDS_VIDEO                 = 'friends_actions.video';
+    const SCOPE_USER_APP                      = 'user_actions:APP_NAMESPACE';
+    const SCOPE_FRIENDS_APP                   = 'friends_actions:APP_NAMESPACE';
+    const SCOPE_USER_GAMES                    = 'user_games_activity';
+    const SCOPE_FRIENDS_GAMES                 = 'friends_games_activity';
+    //Page Permissions
+    const SCOPE_PAGES                         = 'manage_pages';
+    const SCOPE_PUBLISH_PAGES                 = 'publish_pages';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null,
+        $apiVersion = ""
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true, $apiVersion);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.facebook.com'.$this->getApiVersionString().'/dialog/oauth');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        // Facebook gives us a query string ... Oh wait. JSON is too simple, understand ?
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        
+        if (isset($data['expires'])) {
+            $token->setLifeTime($data['expires']);
+        }
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    public function getDialogUri($dialogPath, array $parameters)
+    {
+        if (!isset($parameters['redirect_uri'])) {
+            throw new Exception("Redirect uri is mandatory for this request");
+        }
+        $parameters['app_id'] = $this->credentials->getConsumerId();
+        $baseUrl = self::WWW_URL .$this->getApiVersionString(). '/dialog/' . $dialogPath;
+        $query = http_build_query($parameters);
+        return new Uri($baseUrl . '?' . $query);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getScopesDelimiter()
+    {
+        return ',';
+    }
+}

+ 81 - 0
htdocs/includes/OAuth/OAuth2/Service/Foursquare.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Foursquare extends AbstractService
+{
+    private $apiVersionDate = '20130829';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.foursquare.com/v2/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://foursquare.com/oauth2/authenticate');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://foursquare.com/oauth2/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        // Foursquare tokens evidently never expire...
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri);
+        $uri->addToQuery('v', $this->apiVersionDate);
+
+        return parent::request($uri, $method, $body, $extraHeaders);
+    }
+}

+ 216 - 0
htdocs/includes/OAuth/OAuth2/Service/GitHub.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class GitHub extends AbstractService
+{
+    /**
+     * Defined scopes, see http://developer.github.com/v3/oauth/ for definitions.
+     */
+
+    /**
+     * Public read-only access (includes public user profile info, public repo info, and gists)
+     */
+    const SCOPE_READONLY = '';
+
+    /**
+     * Read/write access to profile info only.
+     *
+     * Includes SCOPE_USER_EMAIL and SCOPE_USER_FOLLOW.
+     */
+    const SCOPE_USER = 'user';
+
+    /**
+     * Read access to a user’s email addresses.
+     */
+    const SCOPE_USER_EMAIL = 'user:email';
+
+    /**
+     * Access to follow or unfollow other users.
+     */
+    const SCOPE_USER_FOLLOW = 'user:follow';
+
+    /**
+     * Read/write access to public repos and organizations.
+     */
+    const SCOPE_PUBLIC_REPO = 'public_repo';
+
+    /**
+     * Read/write access to public and private repos and organizations.
+     *
+     * Includes SCOPE_REPO_STATUS.
+     */
+    const SCOPE_REPO = 'repo';
+
+    /**
+     * Grants access to deployment statuses for public and private repositories.
+     * This scope is only necessary to grant other users or services access to deployment statuses,
+     * without granting access to the code.
+     */
+    const SCOPE_REPO_DEPLOYMENT = 'repo_deployment';
+
+    /**
+     * Read/write access to public and private repository commit statuses. This scope is only necessary to grant other
+     * users or services access to private repository commit statuses without granting access to the code. The repo and
+     * public_repo scopes already include access to commit status for private and public repositories, respectively.
+     */
+    const SCOPE_REPO_STATUS = 'repo:status';
+
+    /**
+     * Delete access to adminable repositories.
+     */
+    const SCOPE_DELETE_REPO = 'delete_repo';
+
+    /**
+     * Read access to a user’s notifications. repo is accepted too.
+     */
+    const SCOPE_NOTIFICATIONS = 'notifications';
+
+    /**
+     * Write access to gists.
+     */
+    const SCOPE_GIST = 'gist';
+
+    /**
+     * Grants read and ping access to hooks in public or private repositories.
+     */
+    const SCOPE_HOOKS_READ = 'read:repo_hook';
+
+    /**
+     * Grants read, write, and ping access to hooks in public or private repositories.
+     */
+    const SCOPE_HOOKS_WRITE = 'write:repo_hook';
+
+    /**
+     * Grants read, write, ping, and delete access to hooks in public or private repositories.
+     */
+    const SCOPE_HOOKS_ADMIN = 'admin:repo_hook';
+
+    /**
+     * Read-only access to organization, teams, and membership.
+     */
+    const SCOPE_ORG_READ = 'read:org';
+
+    /**
+     * Publicize and unpublicize organization membership.
+     */
+    const SCOPE_ORG_WRITE = 'write:org';
+
+    /**
+     * Fully manage organization, teams, and memberships.
+     */
+    const SCOPE_ORG_ADMIN = 'admin:org';
+
+    /**
+     * List and view details for public keys.
+     */
+    const SCOPE_PUBLIC_KEY_READ = 'read:public_key';
+
+    /**
+     * Create, list, and view details for public keys.
+     */
+    const SCOPE_PUBLIC_KEY_WRITE = 'write:public_key';
+
+    /**
+     * Fully manage public keys.
+     */
+    const SCOPE_PUBLIC_KEY_ADMIN = 'admin:public_key';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.github.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://github.com/login/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://github.com/login/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        // Github tokens evidently never expire...
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * Used to configure response type -- we want JSON from github, default is query string format
+     *
+     * @return array
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => 'application/json');
+    }
+
+    /**
+     * Required for GitHub API calls.
+     *
+     * @return array
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array('Accept' => 'application/vnd.github.beta+json');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getScopesDelimiter()
+    {
+        return ',';
+    }
+}

+ 188 - 0
htdocs/includes/OAuth/OAuth2/Service/Google.php

@@ -0,0 +1,188 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\OAuth2\Service\Exception\InvalidAccessTypeException;
+use OAuth\Common\Http\Uri\Uri;
+
+class Google extends AbstractService
+{
+    /**
+     * Defined scopes - More scopes are listed here:
+     * https://developers.google.com/oauthplayground/
+     *
+     * Make a pull request if you need more scopes.
+     */
+
+    // Basic
+    const SCOPE_EMAIL                       = 'email';
+    const SCOPE_PROFILE                     = 'profile';
+
+    const SCOPE_USERINFO_EMAIL              = 'https://www.googleapis.com/auth/userinfo.email';
+    const SCOPE_USERINFO_PROFILE            = 'https://www.googleapis.com/auth/userinfo.profile';
+
+    // Google+
+    const SCOPE_GPLUS_ME                    = 'https://www.googleapis.com/auth/plus.me';
+    const SCOPE_GPLUS_LOGIN                 = 'https://www.googleapis.com/auth/plus.login';
+    const SCOPE_GPLUS_CIRCLES_READ          = 'https://www.googleapis.com/auth/plus.circles.read';
+    const SCOPE_GPLUS_CIRCLES_WRITE         = 'https://www.googleapis.com/auth/plus.circles.write';
+    const SCOPE_GPLUS_STREAM_READ           = 'https://www.googleapis.com/auth/plus.stream.read';
+    const SCOPE_GPLUS_STREAM_WRITE          = 'https://www.googleapis.com/auth/plus.stream.write';
+    const SCOPE_GPLUS_MEDIA                 = 'https://www.googleapis.com/auth/plus.media.upload';
+
+    // Google Drive
+    const SCOPE_DOCUMENTSLIST               = 'https://docs.google.com/feeds/';
+    const SCOPE_SPREADSHEETS                = 'https://spreadsheets.google.com/feeds/';
+    const SCOPE_GOOGLEDRIVE                 = 'https://www.googleapis.com/auth/drive';
+    const SCOPE_DRIVE_APPS                  = 'https://www.googleapis.com/auth/drive.appdata';
+    const SCOPE_DRIVE_APPS_READ_ONLY        = 'https://www.googleapis.com/auth/drive.apps.readonly';
+    const SCOPE_GOOGLEDRIVE_FILES           = 'https://www.googleapis.com/auth/drive.file';
+    const SCOPE_DRIVE_METADATA_READ_ONLY    = 'https://www.googleapis.com/auth/drive.metadata.readonly';
+    const SCOPE_DRIVE_READ_ONLY             = 'https://www.googleapis.com/auth/drive.readonly';
+    const SCOPE_DRIVE_SCRIPTS               = 'https://www.googleapis.com/auth/drive.scripts';
+
+    // Cloud Print
+    const SCOPE_CLOUD_PRINT                 = 'https://www.googleapis.com/auth/cloudprint';
+
+    // Adwords
+    const SCOPE_ADSENSE                     = 'https://www.googleapis.com/auth/adsense';
+    const SCOPE_ADWORDS                     = 'https://www.googleapis.com/auth/adwords/';
+    const SCOPE_GAN                         = 'https://www.googleapis.com/auth/gan'; // google affiliate network...?
+
+    // Google Analytics
+    const SCOPE_ANALYTICS                   = 'https://www.googleapis.com/auth/analytics';
+    const SCOPE_ANALYTICS_EDIT              = 'https://www.googleapis.com/auth/analytics.edit';
+    const SCOPE_ANALYTICS_MANAGE_USERS      = 'https://www.googleapis.com/auth/analytics.manage.users';
+    const SCOPE_ANALYTICS_READ_ONLY         = 'https://www.googleapis.com/auth/analytics.readonly';
+
+    //Gmail
+    const SCOPE_GMAIL_MODIFY                = 'https://www.googleapis.com/auth/gmail.modify';
+    const SCOPE_GMAIL_READONLY              = 'https://www.googleapis.com/auth/gmail.readonly';
+    const SCOPE_GMAIL_COMPOSE               = 'https://www.googleapis.com/auth/gmail.compose';
+    const SCOPE_GMAIL_SEND                  = 'https://www.googleapis.com/auth/gmail.send';
+    const SCOPE_GMAIL_INSERT                = 'https://www.googleapis.com/auth/gmail.insert';
+    const SCOPE_GMAIL_LABELS                = 'https://www.googleapis.com/auth/gmail.labels';
+    const SCOPE_GMAIL_FULL                  = 'https://mail.google.com/';
+
+    // Other services
+    const SCOPE_BOOKS                       = 'https://www.googleapis.com/auth/books';
+    const SCOPE_BLOGGER                     = 'https://www.googleapis.com/auth/blogger';
+    const SCOPE_CALENDAR                    = 'https://www.googleapis.com/auth/calendar';
+    const SCOPE_CALENDAR_READ_ONLY          = 'https://www.googleapis.com/auth/calendar.readonly';
+    const SCOPE_CONTACT                     = 'https://www.google.com/m8/feeds/';
+    const SCOPE_CONTACTS_RO                 = 'https://www.googleapis.com/auth/contacts.readonly';
+    const SCOPE_CHROMEWEBSTORE              = 'https://www.googleapis.com/auth/chromewebstore.readonly';
+    const SCOPE_GMAIL                       = 'https://mail.google.com/mail/feed/atom';
+    const SCOPE_GMAIL_IMAP_SMTP             = 'https://mail.google.com';
+    const SCOPE_PICASAWEB                   = 'https://picasaweb.google.com/data/';
+    const SCOPE_SITES                       = 'https://sites.google.com/feeds/';
+    const SCOPE_URLSHORTENER                = 'https://www.googleapis.com/auth/urlshortener';
+    const SCOPE_WEBMASTERTOOLS              = 'https://www.google.com/webmasters/tools/feeds/';
+    const SCOPE_TASKS                       = 'https://www.googleapis.com/auth/tasks';
+
+    // Cloud services
+    const SCOPE_CLOUDSTORAGE                = 'https://www.googleapis.com/auth/devstorage.read_write';
+    const SCOPE_CONTENTFORSHOPPING          = 'https://www.googleapis.com/auth/structuredcontent'; // what even is this
+    const SCOPE_USER_PROVISIONING           = 'https://apps-apis.google.com/a/feeds/user/';
+    const SCOPE_GROUPS_PROVISIONING         = 'https://apps-apis.google.com/a/feeds/groups/';
+    const SCOPE_NICKNAME_PROVISIONING       = 'https://apps-apis.google.com/a/feeds/alias/';
+
+    // Old
+    const SCOPE_ORKUT                       = 'https://www.googleapis.com/auth/orkut';
+    const SCOPE_GOOGLELATITUDE =
+        'https://www.googleapis.com/auth/latitude.all.best https://www.googleapis.com/auth/latitude.all.city';
+    const SCOPE_OPENID                      = 'openid';
+
+    // YouTube
+    const SCOPE_YOUTUBE_GDATA               = 'https://gdata.youtube.com';
+    const SCOPE_YOUTUBE_ANALYTICS_MONETARY  = 'https://www.googleapis.com/auth/yt-analytics-monetary.readonly';
+    const SCOPE_YOUTUBE_ANALYTICS           = 'https://www.googleapis.com/auth/yt-analytics.readonly';
+    const SCOPE_YOUTUBE                     = 'https://www.googleapis.com/auth/youtube';
+    const SCOPE_YOUTUBE_READ_ONLY           = 'https://www.googleapis.com/auth/youtube.readonly';
+    const SCOPE_YOUTUBE_UPLOAD              = 'https://www.googleapis.com/auth/youtube.upload';
+    const SCOPE_YOUTUBE_PARTNER             = 'https://www.googleapis.com/auth/youtubepartner';
+    const SCOPE_YOUTUBE_PARTNER_AUDIT       = 'https://www.googleapis.com/auth/youtubepartner-channel-audit';
+
+    // Google Glass
+    const SCOPE_GLASS_TIMELINE              = 'https://www.googleapis.com/auth/glass.timeline';
+    const SCOPE_GLASS_LOCATION              = 'https://www.googleapis.com/auth/glass.location';
+
+    // Android Publisher
+    const SCOPE_ANDROID_PUBLISHER           = 'https://www.googleapis.com/auth/androidpublisher';
+
+    protected $accessType = 'online';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/');
+        }
+    }
+
+    public function setAccessType($accessType)
+    {
+        if (!in_array($accessType, array('online', 'offline'), true)) {
+            throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline');
+        }
+        $this->accessType = $accessType;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://accounts.google.com/o/oauth2/auth?access_type=' . $this->accessType);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://accounts.google.com/o/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 157 - 0
htdocs/includes/OAuth/OAuth2/Service/Harvest.php

@@ -0,0 +1,157 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+
+class Harvest extends AbstractService
+{
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.harvestapp.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'state' => 'optional-csrf-token',
+                'response_type' => 'code',
+            )
+        );
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.harvestapp.com/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.harvestapp.com/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || ! is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+        $token->setRefreshToken($data['refresh_token']);
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * Refreshes an OAuth2 access token.
+     *
+     * @param TokenInterface $token
+     *
+     * @return TokenInterface $token
+     *
+     * @throws MissingRefreshTokenException
+     */
+    public function refreshAccessToken(TokenInterface $token)
+    {
+        $refreshToken = $token->getRefreshToken();
+
+        if (empty($refreshToken)) {
+            throw new MissingRefreshTokenException();
+        }
+
+        $parameters = array(
+            'grant_type'    => 'refresh_token',
+            'type'          => 'web_server',
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'refresh_token' => $refreshToken,
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $parameters,
+            $this->getExtraOAuthHeaders()
+        );
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+
+    /**
+     * @return array
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => 'application/json');
+    }
+
+    /**
+     * Return any additional headers always needed for this service implementation's API calls.
+     *
+     * @return array
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array('Accept' => 'application/json');
+    }
+}

+ 123 - 0
htdocs/includes/OAuth/OAuth2/Service/Heroku.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Heroku service.
+ *
+ * @author Thomas Welton <thomaswelton@me.com>
+ * @link https://devcenter.heroku.com/articles/oauth
+ */
+class Heroku extends AbstractService
+{
+    /**
+     * Defined scopes
+     * @link https://devcenter.heroku.com/articles/oauth#scopes
+     */
+    const SCOPE_GLOBAL          = 'global';
+    const SCOPE_IDENTITY        = 'identity';
+    const SCOPE_READ            = 'read';
+    const SCOPE_WRITE           = 'write';
+    const SCOPE_READ_PROTECTED  = 'read-protected';
+    const SCOPE_WRITE_PROTECTED = 'write-protected';
+
+    /**
+    * {@inheritdoc}
+    */
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.heroku.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://id.heroku.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://id.heroku.com/oauth/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description']) || isset($data['error'])) {
+            throw new TokenResponseException(
+                sprintf(
+                    'Error in retrieving token: "%s"',
+                    isset($data['error_description']) ? $data['error_description'] : $data['error']
+                )
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => 'application/vnd.heroku+json; version=3');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array('Accept' => 'application/vnd.heroku+json; version=3', 'Content-Type' => 'application/json');
+    }
+}

+ 155 - 0
htdocs/includes/OAuth/OAuth2/Service/Hubic.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Hubic service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://api.hubic.com/docs/
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Hubic service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://api.hubic.com/docs/
+ */
+class Hubic extends AbstractService
+{
+
+    // Scopes
+    const SCOPE_USAGE_GET       = 'usage.r';
+    const SCOPE_ACCOUNT_GET     = 'account.r';
+    const SCOPE_GETALLLINKS_GET = 'getAllLinks.r';
+    const SCOPE_CREDENTIALS_GET = 'credentials.r';
+    const SCOPE_SPONSORCODE_GET = 'sponsorCode.r';
+    const SCOPE_ACTIVATE_POST   = 'activate.w';
+    const SCOPE_SPONSORED_GET   = 'sponsored.r';
+    const SCOPE_LINKS_GET       = 'links.r';
+    const SCOPE_LINKS_POST      = 'links.rw';
+    const SCOPE_LINKS_ALL       = 'links.drw';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.hubic.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.hubic.com/oauth/auth');
+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.hubic.com/oauth/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'type'          => 'web_server',
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        // special, hubic use a param scope with commas
+        // between scopes instead of spaces
+        $parameters['scope'] = implode(',', $this->scopes);
+
+        if ($this->needsStateParameterInAuthUrl()) {
+            if (!isset($parameters['state'])) {
+                $parameters['state'] = $this->generateAuthorizationState();
+            }
+            $this->storeAuthorizationState($parameters['state']);
+        }
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+}

+ 85 - 0
htdocs/includes/OAuth/OAuth2/Service/Instagram.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Instagram extends AbstractService
+{
+    /**
+     * Defined scopes
+     * @link http://instagram.com/developer/authentication/#scope
+     */
+    const SCOPE_BASIC         = 'basic';
+    const SCOPE_COMMENTS      = 'comments';
+    const SCOPE_RELATIONSHIPS = 'relationships';
+    const SCOPE_LIKES         = 'likes';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.instagram.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.instagram.com/oauth/authorize/');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.instagram.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        // Instagram tokens evidently never expire...
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 144 - 0
htdocs/includes/OAuth/OAuth2/Service/JawboneUP.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Jawbone UP service.
+ *
+ * @author Andrii Gakhov <andrii.gakhov@gmail.com>
+ * @link https://jawbone.com/up/developer/authentication
+ */
+class JawboneUP extends AbstractService
+{
+    /**
+     * Defined scopes
+     *
+     *
+     * @link https://jawbone.com/up/developer/authentication
+     */
+    // general information scopes
+    const SCOPE_BASIC_READ          = 'basic_read';
+    const SCOPE_EXTENDED_READ       = 'extended_read';
+    const SCOPE_LOCATION_READ       = 'location_read';
+    const SCOPE_FRIENDS_READ        = 'friends_read';
+    // mood scopes
+    const SCOPE_MOOD_READ           = 'mood_read';
+    const SCOPE_MOOD_WRITE          = 'mood_write';
+    // move scopes
+    const SCOPE_MOVE_READ           = 'move_read';
+    const SCOPE_MOVE_WRITE          = 'move_write';
+    // sleep scopes
+    const SCOPE_SLEEP_READ          = 'sleep_read';
+    const SCOPE_SLEEP_WRITE         = 'sleep_write';
+    // meal scopes
+    const SCOPE_MEAL_READ           = 'meal_read';
+    const SCOPE_MEAL_WRITE          = 'meal_write';
+    // weight scopes
+    const SCOPE_WEIGHT_READ         = 'weight_read';
+    const SCOPE_WEIGHT_WRITE        = 'weight_write';
+    // generic event scopes
+    const SCOPE_GENERIC_EVENT_READ  = 'generic_event_read';
+    const SCOPE_GENERIC_EVENT_WRITE = 'generic_event_write';
+
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://jawbone.com/nudge/api/v.1.1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        $parameters['scope'] = implode(' ', $this->scopes);
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://jawbone.com/auth/oauth2/auth');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://jawbone.com/auth/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 103 - 0
htdocs/includes/OAuth/OAuth2/Service/Linkedin.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Linkedin service.
+ *
+ * @author Antoine Corcy <contact@sbin.dk>
+ * @link http://developer.linkedin.com/documents/authentication
+ */
+class Linkedin extends AbstractService
+{
+    /**
+     * Defined scopes
+     * @link http://developer.linkedin.com/documents/authentication#granting
+     */
+    const SCOPE_R_BASICPROFILE      = 'r_basicprofile';
+    const SCOPE_R_FULLPROFILE       = 'r_fullprofile';
+    const SCOPE_R_EMAILADDRESS      = 'r_emailaddress';
+    const SCOPE_R_NETWORK           = 'r_network';
+    const SCOPE_R_CONTACTINFO       = 'r_contactinfo';
+    const SCOPE_RW_NUS              = 'rw_nus';
+    const SCOPE_RW_COMPANY_ADMIN    = 'rw_company_admin';
+    const SCOPE_RW_GROUPS           = 'rw_groups';
+    const SCOPE_W_MESSAGES          = 'w_messages';
+    const SCOPE_W_SHARE             = 'w_share';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.linkedin.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.linkedin.com/uas/oauth2/authorization');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.linkedin.com/uas/oauth2/accessToken');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING_V2;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 114 - 0
htdocs/includes/OAuth/OAuth2/Service/Mailchimp.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Mailchimp extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (is_null($this->baseApiUri) && $storage->hasAccessToken($this->service())) {
+            $this->setBaseApiUri($storage->retrieveAccessToken($this->service()));
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING_V3;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://login.mailchimp.com/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://login.mailchimp.com/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        // Parse JSON
+        $data = json_decode($responseBody, true);
+
+        // Do validation.
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        // Create token object.
+        $token = new StdOAuth2Token($data['access_token']);
+
+        // Set the right API endpoint.
+        $this->setBaseApiUri($token);
+
+        // Mailchimp tokens evidently never expire...
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
+    {
+        if (is_null($this->baseApiUri)) {
+            $this->setBaseApiUri($this->storage->retrieveAccessToken($this->service()));
+        }
+
+        return parent::request($path, $method, $body, $extraHeaders);
+    }
+
+    /**
+     * Set the right base endpoint.
+     *
+     * @param StdOAuth2Token $token
+     */
+    protected function setBaseApiUri(StdOAuth2Token $token)
+    {
+        // Make request uri.
+        $endpoint = 'https://login.mailchimp.com/oauth2/metadata?oauth_token='. $token->getAccessToken();
+
+        // Grab meta data about the token.
+        $response = $this->httpClient->retrieveResponse(new Uri($endpoint), array(), array(), 'GET');
+
+        // Parse JSON.
+        $meta = json_decode($response, true);
+
+        // Set base api uri.
+        $this->baseApiUri = new Uri('https://'. $meta['dc'] .'.api.mailchimp.com/2.0/');
+
+        // Allow chaining.
+        return $this;
+    }
+}

+ 120 - 0
htdocs/includes/OAuth/OAuth2/Service/Microsoft.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Microsoft extends AbstractService
+{
+    const SCOPE_BASIC = 'wl.basic';
+    const SCOPE_OFFLINE = 'wl.offline_access';
+    const SCOPE_SIGNIN = 'wl.signin';
+    const SCOPE_BIRTHDAY = 'wl.birthday';
+    const SCOPE_CALENDARS = 'wl.calendars';
+    const SCOPE_CALENDARS_UPDATE = 'wl.calendars_update';
+    const SCOPE_CONTACTS_BIRTHDAY = 'wl.contacts_birthday';
+    const SCOPE_CONTACTS_CREATE = 'wl.contacts_create';
+    const SCOPE_CONTACTS_CALENDARS = 'wl.contacts_calendars';
+    const SCOPE_CONTACTS_PHOTOS = 'wl.contacts_photos';
+    const SCOPE_CONTACTS_SKYDRIVE = 'wl.contacts_skydrive';
+    const SCOPE_EMAILS = 'wl.emails';
+    const SCOPE_EVENTS_CREATE = 'wl.events_create';
+    const SCOPE_MESSENGER = 'wl.messenger';
+    const SCOPE_PHONE_NUMBERS = 'wl.phone_numbers';
+    const SCOPE_PHOTOS = 'wl.photos';
+    const SCOPE_POSTAL_ADDRESSES = 'wl.postal_addresses';
+    const SCOPE_SHARE = 'wl.share';
+    const SCOPE_SKYDRIVE = 'wl.skydrive';
+    const SCOPE_SKYDRIVE_UPDATE = 'wl.skydrive_update';
+    const SCOPE_WORK_PROFILE = 'wl.work_profile';
+    const SCOPE_APPLICATIONS = 'wl.applications';
+    const SCOPE_APPLICATIONS_CREATE = 'wl.applications_create';
+    const SCOPE_IMAP = 'wl.imap';
+
+    /**
+     * MS uses some magical not officialy supported scope to get even moar info like full emailaddresses.
+     * They agree that giving 3rd party apps access to 3rd party emailaddresses is a pretty lame thing to do so in all
+     * their wisdom they added this scope because fuck you that's why.
+     *
+     * https://github.com/Lusitanian/PHPoAuthLib/issues/214
+     * http://social.msdn.microsoft.com/Forums/live/en-US/c6dcb9ab-aed4-400a-99fb-5650c393a95d/how-retrieve-users-
+     *                                  contacts-email-address?forum=messengerconnect
+     *
+     * Considering this scope is not officially supported: use with care
+     */
+    const SCOPE_CONTACTS_EMAILS = 'wl.contacts_emails';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://apis.live.net/v5.0/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://login.live.com/oauth20_authorize.srf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://login.live.com/oauth20_token.srf');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 106 - 0
htdocs/includes/OAuth/OAuth2/Service/Nest.php

@@ -0,0 +1,106 @@
+<?php
+/**
+ * Nest service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developer.nest.com/documentation
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Nest service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developer.nest.com/documentation
+ */
+class Nest extends AbstractService
+{
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://developer-api.nest.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://home.nest.com/login/oauth2');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.home.nest.com/oauth2/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING_V4;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 117 - 0
htdocs/includes/OAuth/OAuth2/Service/Netatmo.php

@@ -0,0 +1,117 @@
+<?php
+/**
+ * Netatmo service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://dev.netatmo.com/doc/
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Netatmo service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://dev.netatmo.com/doc/
+ */
+class Netatmo extends AbstractService
+{
+
+    // SCOPES
+    // @link https://dev.netatmo.com/doc/authentication/scopes
+
+    // Used to read weather station's data (devicelist, getmeasure)
+    const SCOPE_STATION_READ        = 'read_station';
+    // Used to read thermostat's data (devicelist, getmeasure, getthermstate)
+    const SCOPE_THERMOSTAT_READ     = 'read_thermostat';
+    // Used to configure the thermostat (syncschedule, setthermpoint)
+    const SCOPE_THERMOSTAT_WRITE    = 'write_thermostat';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true // use parameter state
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.netatmo.net/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri($this->baseApiUri.'oauth2/authorize');
+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri.'oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 142 - 0
htdocs/includes/OAuth/OAuth2/Service/ParrotFlowerPower.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * ParrotFlowerPower service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth2\Service\Exception\MissingRefreshTokenException;
+use OAuth\Common\Token\TokenInterface;
+
+/**
+ * ParrotFlowerPower service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki
+ */
+class ParrotFlowerPower extends AbstractService
+{
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://apiflowerpower.parrot.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri($this->baseApiUri.'oauth2/v1/authorize');
+
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri($this->baseApiUri.'user/v1/authenticate');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+
+    /**
+     * Parrot use a different endpoint for refresh a token
+     *
+     * {@inheritdoc}
+     */
+    public function refreshAccessToken(TokenInterface $token)
+    {
+        $refreshToken = $token->getRefreshToken();
+
+        if (empty($refreshToken)) {
+            throw new MissingRefreshTokenException();
+        }
+
+        $parameters = array(
+            'grant_type'    => 'refresh_token',
+            'type'          => 'web_server',
+            'client_id'     => $this->credentials->getConsumerId(),
+            'client_secret' => $this->credentials->getConsumerSecret(),
+            'refresh_token' => $refreshToken,
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            new Uri($this->baseApiUri.'user/v1/refresh'),
+            $parameters,
+            $this->getExtraOAuthHeaders()
+        );
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+}

+ 103 - 0
htdocs/includes/OAuth/OAuth2/Service/Paypal.php

@@ -0,0 +1,103 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * PayPal service.
+ *
+ * @author Flávio Heleno <flaviohbatista@gmail.com>
+ * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/
+ */
+class Paypal extends AbstractService
+{
+    /**
+     * Defined scopes
+     * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/
+     * @see  #attributes
+     */
+    const SCOPE_OPENID           = 'openid';
+    const SCOPE_PROFILE          = 'profile';
+    const SCOPE_PAYPALATTRIBUTES = 'https://uri.paypal.com/services/paypalattributes';
+    const SCOPE_EMAIL            = 'email';
+    const SCOPE_ADDRESS          = 'address';
+    const SCOPE_PHONE            = 'phone';
+    const SCOPE_EXPRESSCHECKOUT  = 'https://uri.paypal.com/services/expresscheckout';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.paypal.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.paypal.com/v1/identity/openidconnect/tokenservice');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['message'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['message'] . '"');
+        } elseif (isset($data['name'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['name'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 125 - 0
htdocs/includes/OAuth/OAuth2/Service/Pocket.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+
+class Pocket extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+        if ($baseApiUri === null) {
+            $this->baseApiUri = new Uri('https://getpocket.com/v3/');
+        }
+    }
+    
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri('https://getpocket.com/v3/oauth/request');
+    }
+    
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://getpocket.com/auth/authorize');
+    }
+    
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://getpocket.com/v3/oauth/authorize');
+    }
+    
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'redirect_uri' => $this->credentials->getCallbackUrl(),
+            )
+        );
+        
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+    
+    public function requestRequestToken()
+    {
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getRequestTokenEndpoint(),
+            array(
+                'consumer_key' => $this->credentials->getConsumerId(),
+                'redirect_uri' => $this->credentials->getCallbackUrl(),
+            )
+        );
+        
+        $code = $this->parseRequestTokenResponse($responseBody);
+
+        return $code;
+    }
+    
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+        
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['code'])) {
+            throw new TokenResponseException('Error in retrieving code.');
+        }
+        return $data['code'];
+    }
+    
+    public function requestAccessToken($code)
+    {
+        $bodyParams = array(
+            'consumer_key'     => $this->credentials->getConsumerId(),
+            'code'             => $code,
+        );
+
+        $responseBody = $this->httpClient->retrieveResponse(
+            $this->getAccessTokenEndpoint(),
+            $bodyParams,
+            $this->getExtraOAuthHeaders()
+        );
+        $token = $this->parseAccessTokenResponse($responseBody);
+        $this->storage->storeAccessToken($this->service(), $token);
+
+        return $token;
+    }
+    
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+        
+        if ($data === null || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+        
+        $token = new StdOAuth2Token();
+        #$token->setRequestToken($data['access_token']);
+        $token->setAccessToken($data['access_token']);
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+        $token->setExtraParams($data);
+        
+        return $token;
+    }
+}

+ 114 - 0
htdocs/includes/OAuth/OAuth2/Service/Reddit.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Reddit extends AbstractService
+{
+    /**
+     * Defined scopes
+     *
+     * @link http://www.reddit.com/dev/api/oauth
+     */
+    // User scopes
+    const SCOPE_EDIT                         = 'edit';
+    const SCOPE_HISTORY                      = 'history';
+    const SCOPE_IDENTITY                     = 'identity';
+    const SCOPE_MYSUBREDDITS                 = 'mysubreddits';
+    const SCOPE_PRIVATEMESSAGES              = 'privatemessages';
+    const SCOPE_READ                         = 'read';
+    const SCOPE_SAVE                         = 'save';
+    const SCOPE_SUBMIT                       = 'submit';
+    const SCOPE_SUBSCRIBE                    = 'subscribe';
+    const SCOPE_VOTE                         = 'vote';
+    // Mod Scopes
+    const SCOPE_MODCONFIG                    = 'modconfig';
+    const SCOPE_MODFLAIR                     = 'modflair';
+    const SCOPE_MODLOG                       = 'modlog';
+    const SCOPE_MODPOST                      = 'modpost';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://oauth.reddit.com');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://ssl.reddit.com/api/v1/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://ssl.reddit.com/api/v1/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        // Reddit uses a Basic OAuth header
+        return array('Authorization' => 'Basic ' .
+            base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret()));
+    }
+}

+ 105 - 0
htdocs/includes/OAuth/OAuth2/Service/RunKeeper.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * RunKeeper service.
+ *
+ * @link http://runkeeper.com/developer/healthgraph/registration-authorization
+ */
+class RunKeeper extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.runkeeper.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationUri(array $additionalParameters = array())
+    {
+        $parameters = array_merge(
+            $additionalParameters,
+            array(
+                'client_id'     => $this->credentials->getConsumerId(),
+                'redirect_uri'  => $this->credentials->getCallbackUrl(),
+                'response_type' => 'code',
+            )
+        );
+
+        $parameters['scope'] = implode(' ', $this->scopes);
+
+        // Build the url
+        $url = clone $this->getAuthorizationEndpoint();
+        foreach ($parameters as $key => $val) {
+            $url->addToQuery($key, $val);
+        }
+
+        return $url;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://runkeeper.com/apps/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://runkeeper.com/apps/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 92 - 0
htdocs/includes/OAuth/OAuth2/Service/Salesforce.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Service\AbstractService;
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Salesforce extends AbstractService
+{
+    /**
+     * Scopes
+     *
+     * @var string
+     */
+    const   SCOPE_API           =   'api',
+            SCOPE_REFRESH_TOKEN =   'refresh_token';
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://login.salesforce.com/services/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://na1.salesforce.com/services/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        parse_str($responseBody, $data);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') {
+            throw new TokenResponseException('Error in retrieving token.');
+        }
+
+        return $this->parseAccessTokenResponse($responseBody);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        // Salesforce tokens evidently never expire...
+        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
+        unset($data['access_token']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => 'application/json');
+    }
+}

+ 38 - 0
htdocs/includes/OAuth/OAuth2/Service/ServiceInterface.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Token\TokenInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Service\ServiceInterface as BaseServiceInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Defines the common methods across OAuth 2 services.
+ */
+interface ServiceInterface extends BaseServiceInterface
+{
+    /**
+     * Authorization methods for various services
+     */
+    const AUTHORIZATION_METHOD_HEADER_OAUTH    = 0;
+    const AUTHORIZATION_METHOD_HEADER_BEARER   = 1;
+    const AUTHORIZATION_METHOD_QUERY_STRING    = 2;
+    const AUTHORIZATION_METHOD_QUERY_STRING_V2 = 3;
+    const AUTHORIZATION_METHOD_QUERY_STRING_V3 = 4;
+    const AUTHORIZATION_METHOD_QUERY_STRING_V4 = 5;
+
+    /**
+     * Retrieves and stores/returns the OAuth2 access token after a successful authorization.
+     *
+     * @param string $code The access code from the callback.
+     *
+     * @return TokenInterface $token
+     *
+     * @throws TokenResponseException
+     */
+    public function requestAccessToken($code);
+}

+ 77 - 0
htdocs/includes/OAuth/OAuth2/Service/SoundCloud.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class SoundCloud extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.soundcloud.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://soundcloud.com/connect');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.soundcloud.com/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifetime($data['expires_in']);
+            unset($data['expires_in']);
+        }
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 109 - 0
htdocs/includes/OAuth/OAuth2/Service/Spotify.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Spotify extends AbstractService
+{
+    /**
+     * Scopes
+     *
+     * @var string
+     */
+    const SCOPE_PLAYLIST_MODIFY_PUBLIC = 'playlist-modify-public';
+    const SCOPE_PLAYLIST_MODIFY_PRIVATE = 'playlist-modify-private';
+    const SCOPE_PLAYLIST_READ_PRIVATE = 'playlist-read-private';
+    const SCOPE_STREAMING = 'streaming';
+    const SCOPE_USER_LIBRARY_MODIFY = 'user-library-modify';
+    const SCOPE_USER_LIBRARY_READ = 'user-library-read';
+    const SCOPE_USER_READ_PRIVATE = 'user-read-private';
+    const SCOPE_USER_READ_EMAIL = 'user-read-email';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.spotify.com/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://accounts.spotify.com/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://accounts.spotify.com/api/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifetime($data['expires_in']);
+            unset($data['expires_in']);
+        }
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Authorization' => 'Basic ' .
+            base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret()));
+    }
+}

+ 147 - 0
htdocs/includes/OAuth/OAuth2/Service/Strava.php

@@ -0,0 +1,147 @@
+<?php
+/**
+ * Strava service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    http://strava.github.io/
+ * @link    http://strava.github.io/api/v3/oauth/
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\OAuth2\Service\Exception\InvalidAccessTypeException;
+
+/**
+ * Strava service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    http://strava.github.io/
+ * @link    http://strava.github.io/api/v3/oauth/
+ */
+class Strava extends AbstractService
+{
+    /**
+     * Scopes
+     */
+    // default
+    const SCOPE_PUBLIC       = 'public';
+    // Modify activities, upload on the user’s behalf
+    const SCOPE_WRITE        = 'write';
+    // View private activities and data within privacy zones
+    const SCOPE_VIEW_PRIVATE = 'view_private';
+
+    protected $approvalPrompt = 'auto';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        if (empty($scopes)) {
+            $scopes = array(self::SCOPE_PUBLIC);
+        }
+
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://www.strava.com/api/v3/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.strava.com/oauth/authorize?approval_prompt=' . $this->approvalPrompt);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.strava.com/oauth/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error_description'] . '"'
+            );
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifeTime($data['expires_in']);
+            unset($data['expires_in']);
+        }
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    public function setApprouvalPrompt($prompt)
+    {
+        if (!in_array($prompt, array('auto', 'force'), true)) {
+            // @todo Maybe could we rename this exception
+            throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.');
+        }
+        $this->approvalPrompt = $prompt;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getScopesDelimiter()
+    {
+        return ',';
+    }
+}

+ 98 - 0
htdocs/includes/OAuth/OAuth2/Service/Ustream.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Ustream extends AbstractService
+{
+    /**
+     * Scopes
+     *
+     * @var string
+     */
+    const SCOPE_OFFLINE     = 'offline';
+    const SCOPE_BROADCASTER = 'broadcaster';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.ustream.tv/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.ustream.tv/oauth2/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.ustream.tv/oauth2/token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Authorization' => 'Basic ' . $this->credentials->getConsumerSecret());
+    }
+}

+ 156 - 0
htdocs/includes/OAuth/OAuth2/Service/Vimeo.php

@@ -0,0 +1,156 @@
+<?php
+/**
+ * Vimeo service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developer.vimeo.com/
+ * @link    https://developer.vimeo.com/api/authentication
+ */
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Vimeo service.
+ *
+ * @author  Pedro Amorim <contact@pamorim.fr>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link    https://developer.vimeo.com/
+ * @link    https://developer.vimeo.com/api/authentication
+ */
+class Vimeo extends AbstractService
+{
+    // API version
+    const VERSION = '3.2';
+    // API Header Accept
+    const HEADER_ACCEPT = 'application/vnd.vimeo.*+json;version=3.2';
+
+    /**
+     * Scopes
+     * @see  https://developer.vimeo.com/api/authentication#scope
+     */
+    // View public videos
+    const SCOPE_PUBLIC    = 'public';
+    // View private videos
+    const SCOPE_PRIVATE   = 'private';
+    // View Vimeo On Demand purchase history
+    const SCOPE_PURCHASED = 'purchased';
+    // Create new videos, groups, albums, etc.
+    const SCOPE_CREATE    = 'create';
+    // Edit videos, groups, albums, etc.
+    const SCOPE_EDIT      = 'edit';
+    // Delete videos, groups, albums, etc.
+    const SCOPE_DELETE    = 'delete';
+    // Interact with a video on behalf of a user, such as liking
+    // a video or adding it to your watch later queue
+    const SCOPE_INTERACT  = 'interact';
+    // Upload a video
+    const SCOPE_UPLOAD    = 'upload';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct(
+            $credentials,
+            $httpClient,
+            $storage,
+            $scopes,
+            $baseApiUri,
+            true
+        );
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.vimeo.com/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.vimeo.com/oauth/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.vimeo.com/oauth/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error_description'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error_description'] . '"'
+            );
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException(
+                'Error in retrieving token: "' . $data['error'] . '"'
+            );
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+
+        if (isset($data['expires_in'])) {
+            $token->setLifeTime($data['expires_in']);
+            unset($data['expires_in']);
+        }
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraOAuthHeaders()
+    {
+        return array('Accept' => self::HEADER_ACCEPT);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExtraApiHeaders()
+    {
+        return array('Accept' => self::HEADER_ACCEPT);
+    }
+}

+ 109 - 0
htdocs/includes/OAuth/OAuth2/Service/Vkontakte.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Vkontakte extends AbstractService
+{
+    /**
+     * Defined scopes
+     *
+     * @link http://vk.com/dev/permissions
+     */
+    const SCOPE_EMAIL         = 'email';
+    const SCOPE_NOTIFY        = 'notify';
+    const SCOPE_FRIENDS       = 'friends';
+    const SCOPE_PHOTOS        = 'photos';
+    const SCOPE_AUDIO         = 'audio';
+    const SCOPE_VIDEO         = 'video';
+    const SCOPE_DOCS          = 'docs';
+    const SCOPE_NOTES         = 'notes';
+    const SCOPE_PAGES         = 'pages';
+    const SCOPE_APP_LINK      = '';
+    const SCOPE_STATUS        = 'status';
+    const SCOPE_OFFERS        = 'offers';
+    const SCOPE_QUESTIONS     = 'questions';
+    const SCOPE_WALL          = 'wall';
+    const SCOPE_GROUPS        = 'groups';
+    const SCOPE_MESSAGES      = 'messages';
+    const SCOPE_NOTIFICATIONS = 'notifications';
+    const SCOPE_STATS         = 'stats';
+    const SCOPE_ADS           = 'ads';
+    const SCOPE_OFFLINE       = 'offline';
+    const SCOPE_NOHTTPS       = 'nohttps';
+
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://api.vk.com/method/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://oauth.vk.com/authorize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://oauth.vk.com/access_token');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifeTime($data['expires_in']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_QUERY_STRING;
+    }
+}

+ 78 - 0
htdocs/includes/OAuth/OAuth2/Service/Yahoo.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+
+class Yahoo extends AbstractService
+{
+
+    /**
+    * {@inheritdoc}
+    */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://api.login.yahoo.com/oauth2/request_auth');
+    }
+
+    /**
+    * {@inheritdoc}
+    */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://api.login.yahoo.com/oauth2/get_token');
+    }
+
+    /**
+    * {@inheritdoc}
+    */
+    protected function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+    * {@inheritdoc}
+    */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data))
+        {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error']))
+        {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']);
+        $token->setLifetime($data['expires_in']);
+
+        if (isset($data['refresh_token']))
+        {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+
+    /**
+    * {@inheritdoc}
+    */
+    protected function getExtraOAuthHeaders()
+    {
+        $encodedCredentials = base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret());
+        return array('Authorization' => 'Basic ' . $encodedCredentials);
+    }
+
+}

+ 82 - 0
htdocs/includes/OAuth/OAuth2/Service/Yammer.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace OAuth\OAuth2\Service;
+
+use OAuth\OAuth2\Token\StdOAuth2Token;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Consumer\CredentialsInterface;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\Common\Http\Uri\UriInterface;
+
+class Yammer extends AbstractService
+{
+    public function __construct(
+        CredentialsInterface $credentials,
+        ClientInterface $httpClient,
+        TokenStorageInterface $storage,
+        $scopes = array(),
+        UriInterface $baseApiUri = null
+    ) {
+        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
+
+        if (null === $baseApiUri) {
+            $this->baseApiUri = new Uri('https://www.yammer.com/api/v1/');
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationEndpoint()
+    {
+        return new Uri('https://www.yammer.com/dialog/oauth');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri('https://www.yammer.com/oauth2/access_token.json');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAuthorizationMethod()
+    {
+        return static::AUTHORIZATION_METHOD_HEADER_BEARER;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        $data = json_decode($responseBody, true);
+
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
+        }
+
+        $token = new StdOAuth2Token();
+        $token->setAccessToken($data['access_token']['token']);
+        $token->setLifetime($data['access_token']['expires_at']);
+
+        if (isset($data['refresh_token'])) {
+            $token->setRefreshToken($data['refresh_token']);
+            unset($data['refresh_token']);
+        }
+
+        unset($data['access_token']);
+        unset($data['expires_in']);
+
+        $token->setExtraParams($data);
+
+        return $token;
+    }
+}

+ 13 - 0
htdocs/includes/OAuth/OAuth2/Token/StdOAuth2Token.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace OAuth\OAuth2\Token;
+
+use OAuth\Common\Token\AbstractToken;
+
+/**
+ * Standard OAuth2 token implementation.
+ * Implements OAuth\OAuth2\Token\TokenInterface for any functionality that might not be provided by AbstractToken.
+ */
+class StdOAuth2Token extends AbstractToken implements TokenInterface
+{
+}

部分文件因为文件数量过多而无法显示