init: add v 7.15.0

This commit is contained in:
André Menrath 2024-10-15 12:04:03 +02:00
commit 80a9592cea
3727 changed files with 866442 additions and 0 deletions

BIN
MEC-Installation-Guide.pdf Executable file

Binary file not shown.

59
app/addons/ACF.php Executable file
View file

@ -0,0 +1,59 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC ACF addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_ACF extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
}
/**
* Initialize the ACF addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
$this->factory->action('mec_wc_product_created', [$this, 'sync_acf_fields'], 10, 2);
$this->factory->action('mec_wc_product_updated', [$this, 'sync_acf_fields'], 10, 2);
}
/**
* @param int $product_id
* @param int $event_id
* @return void
*/
public function sync_acf_fields($product_id, $event_id)
{
// ACF Plugin is not installed and activated
if(!class_exists('ACF')) return;
// Event ACF Data
$data = get_fields($event_id, false);
// Data is invalid
if(!is_array($data)) return;
if(!count($data)) return;
// Store data for Product
foreach($data as $key => $value)
{
update_field($key, $value, $product_id);
}
}
}

85
app/addons/KC.php Executable file
View file

@ -0,0 +1,85 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC King Composer addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_KC extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the KC addon
* @author Webnus <info@webnus.net>
* @return boolean
*/
public function init()
{
// King Composer is not installed
if(!function_exists('kc_add_map')) return false;
$this->factory->action('init', array($this, 'map'));
return true;
}
/**
* Register the addon in KC
* @author Webnus <info@webnus.net>
*/
public function map()
{
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$calendars_name = $calendars_number = [];
foreach($calendar_posts as $calendar_post)
{
$calendars_name[] = $calendar_post->post_title;
$calendars_number[] = $calendar_post->ID;
}
$calendars_array = array_combine($calendars_number, $calendars_name);
kc_add_map(array
(
'MEC' => array(
'name' => esc_html__('Modern Events Calendar', 'modern-events-calendar-lite'),
'icon' => 'mec-kingcomposer-icon',
'category' => esc_html__('Content', 'modern-events-calendar-lite'),
'params' => array(
'General' => array(
array(
'name' => 'id',
'label' => esc_html__('Shortcode', 'modern-events-calendar-lite'),
'type' => 'select',
'options' => $calendars_array,
'description' => esc_html__('Select from predefined shortcodes', 'modern-events-calendar-lite'),
),
),
)
),
));
}
}

201
app/addons/PMP.php Executable file
View file

@ -0,0 +1,201 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC Paid Membership Pro addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_PMP extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
public $settings;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
// MEC Settings
$this->settings = $this->main->get_settings();
}
/**
* Initialize the PMP addon
* @author Webnus <info@webnus.net>
* @return boolean
*/
public function init()
{
$event_restriction = isset($this->settings['pmp_status']) && $this->settings['pmp_status'];
$booking_restriction = isset($this->settings['pmp_booking_restriction']) && $this->settings['pmp_booking_restriction'];
$ticket_restriction = isset($this->settings['pmp_ticket_restrictions']) && $this->settings['pmp_ticket_restrictions'];
// Module is not enabled
if(!$event_restriction && !$booking_restriction && !$ticket_restriction) return false;
// Event Restriction
if($event_restriction)
{
// Metabox
add_action('admin_menu', [$this, 'metabox']);
// Display Access Error
add_filter('mec_show_event_details_page', [$this, 'check'], 10, 2);
}
// Booking Restriction
if($booking_restriction)
{
add_filter('mec_booking_module_abort', [$this, 'booking_abort'], 10, 2);
}
// Ticket Restrictions
if($ticket_restriction)
{
add_action('mec_ticket_extra_options', [$this, 'ticket_plans_fields'], 10, 3);
add_filter('mec_get_tickets_availability', [$this, 'ticket_availability'], 10, 5);
}
return true;
}
public function metabox()
{
if(!defined('PMPRO_VERSION')) return;
// Register
add_meta_box('pmpro_page_meta', esc_html__('Require Membership', 'modern-events-calendar-lite'), 'pmpro_page_meta', $this->main->get_main_post_type(), 'side', 'high');
}
public function check($status, $event_id)
{
if(!defined('PMPRO_VERSION')) return $status;
// Has Access
if(function_exists('pmpro_has_membership_access'))
{
$response = pmpro_has_membership_access($event_id, NULL, true);
$available = $response[0] ?? true;
if(!$available)
{
$post_membership_levels_ids = $response[1];
$post_membership_levels_names = $response[2];
$content = pmpro_get_no_access_message('', $post_membership_levels_ids, $post_membership_levels_names);
$status = '<div class="mec-wrap mec-no-access-error"><h1>'.get_the_title($event_id).'</h1>'.MEC_kses::page($content).'</div>';
}
}
return $status;
}
public function booking_abort($abort, $event)
{
if(!function_exists('pmpro_hasMembershipLevel')) return $abort;
// Event ID
$event_id = $event->ID;
if(!$event_id) return $abort;
// Event Categories
$categories = (isset($event->data) and isset($event->data->categories) and is_array($event->data->categories)) ? $event->data->categories : [];
// Event has no category
if(!count($categories)) return $abort;
// User ID
$user_id = get_current_user_id();
// Booking Restriction Options
$options = isset($this->settings['pmp_booking']) && is_array($this->settings['pmp_booking']) ? $this->settings['pmp_booking'] : [];
$needed_levels = [];
foreach($options as $level_id => $cats)
{
foreach($categories as $category)
{
if(in_array($category['id'], $cats)) $needed_levels[] = $level_id;
}
}
$needed_levels = array_unique($needed_levels);
if($needed_levels and !pmpro_hasMembershipLevel($needed_levels, $user_id))
{
return pmpro_get_no_access_message('', $needed_levels);
}
return $abort;
}
public function ticket_plans_fields($ticket_id, $data, $args = [])
{
if(!function_exists('pmpro_getAllLevels')) return;
$name_prefix = $args['name_prefix'] ?? 'mec[tickets]';
$advanced_class = $args['advanced_class'] ?? 'mec-basvanced-advanced w-hidden';
// Levels
$levels = pmpro_getAllLevels();
?>
<div id="mec_ticket_pmp_plans_container" class="<?php echo $advanced_class; ?>">
<div class="mec-form-row">
<h5><?php esc_html_e('Membership Levels', 'modern-events-calendar-lite'); ?></h5>
<p class="description"><?php esc_html_e('If you leave the following fields empty then the ticket will be available to all plans.'); ?></p><br />
<ul>
<?php foreach($levels as $level): ?>
<li>
<label>
<input value="<?php echo esc_attr($level->id); ?>" type="checkbox" name="<?php echo $name_prefix; ?>[<?php echo esc_attr($ticket_id); ?>][pmp][]" <?php if(in_array($level->id, $data['pmp'] ?? [])) echo 'checked="checked"'; ?>>
<span><?php echo esc_html($level->name); ?></span>
</label>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php
}
public function ticket_availability($availability, $event_id, $timestamp, $mode, $tickets)
{
if(!function_exists('pmpro_hasMembershipLevel') || !$event_id) return $availability;
// User Id
$user_id = get_current_user_id();
foreach ($tickets as $ticket)
{
// Required Level for Ticket
$levels = $ticket['pmp'] ?? [];
// Available to All
if(!count($levels)) continue;
if(!pmpro_hasMembershipLevel($levels, $user_id))
{
$availability[$ticket['id']] = 0;
$availability['stop_selling_'.$ticket['id']] = true;
$availability['stop_selling_'.$ticket['id'].'_message'] = esc_html__("You do not have access to book %s ticket.", 'modern-events-calendar-lite');
}
}
return $availability;
}
}

57
app/addons/TNP.php Executable file
View file

@ -0,0 +1,57 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC The Newsletter Plugin addon class
* @link https://www.thenewsletterplugin.com/
* @author Webnus <info@webnus.net>
*/
class MEC_addon_TNP extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
public $settings;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
// MEC Settings
$this->settings = $this->main->get_settings();
}
/**
* Initialize the TNP addon
* @author Webnus <info@webnus.net>
* @return boolean
*/
public function init()
{
$this->factory->action('newsletter_register_blocks', array($this, 'register'));
return true;
}
public function register()
{
if(!class_exists('TNP_Composer')) return;
TNP_Composer::register_block(MEC_ABSPATH.'app'.DS.'addons'.DS.'tnp'.DS.'simple');
}
}

78
app/addons/VC.php Executable file
View file

@ -0,0 +1,78 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC VC addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_VC extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the VC addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
// Visual Composer is not installed
if(!function_exists('vc_map')) return false;
$this->factory->action('vc_before_init', array($this, 'map'));
return true;
}
/**
* Register the addon in VC
* @author Webnus <info@webnus.net>
*/
public function map()
{
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$calendars = [];
foreach($calendar_posts as $calendar_post) $calendars[$calendar_post->post_title] = $calendar_post->ID;
vc_map(array(
'name'=>esc_html__('Modern Events Calendar', 'modern-events-calendar-lite'),
'base'=>'MEC',
'class'=>'',
'controls'=>'full',
'icon'=>$this->main->asset('img/ico-mec-vc.png'),
'category'=>esc_html__('Content', 'modern-events-calendar-lite'),
'params'=>array(
array(
'type'=>'dropdown',
'holder'=>'div',
'class'=>'',
'heading'=>esc_html__('Shortcode', 'modern-events-calendar-lite'),
'param_name'=>'id',
'value'=>$calendars,
'description'=>esc_html__('Select from predefined shortcodes', 'modern-events-calendar-lite')
)
)
));
}
}

72
app/addons/avada.php Executable file
View file

@ -0,0 +1,72 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC Avada Builder addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_avada extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the Elementor addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
$this->factory->action('fusion_builder_before_init', array($this, 'register'));
}
public function register()
{
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$shortcodes = [];
foreach($calendar_posts as $calendar_post)
{
$shortcodes[$calendar_post->ID] = $calendar_post->post_title;
}
fusion_builder_map([
'name' => esc_attr__('MEC', 'modern-events-calendar-lite'),
'shortcode' => 'MEC',
'icon' => 'fusiona-code',
'preview' => MEC_ABSPATH.'app/addons/avada/preview.php',
'preview_id' => 'mec-avada-shortcode-element',
'allow_generator' => true,
'params' => [
[
'type' => 'select',
'heading' => esc_attr__('Shortcode', 'modern-events-calendar-lite'),
'description' => esc_attr__('Select one of created shortcodes.', 'modern-events-calendar-lite'),
'param_name' => 'id',
'value' => $shortcodes,
'default' => '',
],
],
]);
}
}

11
app/addons/avada/preview.php Executable file
View file

@ -0,0 +1,11 @@
<?php
/**
* Underscore.js template.
*
* @package fusion-builder
*/
?>
<script type="text/template" id="mec-avada-shortcode-element">
<h4 class="fusion_module_title">MEC Shortcode ({{ params.id }})</h4>
</script>

54
app/addons/beaver.php Executable file
View file

@ -0,0 +1,54 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC Beaver Builder addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_beaver extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the Elementor addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
// Beaver Builder is not installed
if(!class_exists( 'FLBuilder' ) ) return false;
define( 'MEC_BEAVER_DIR', plugin_dir_path( __FILE__ ) );
define( 'MEC_BEAVER_URL', plugins_url( '/', __FILE__ ) );
add_action( 'init', array($this,'mec_beaver_builder_shortcode') );
return true;
}
public function mec_beaver_builder_shortcode() {
if ( class_exists( 'FLBuilder' ) ) {
require_once MEC_ABSPATH.'app/addons/mec-beaver-builder/mec-beaver-builder.php';
}
}
}

67
app/addons/divi.php Executable file
View file

@ -0,0 +1,67 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC Divi addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_divi extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the Elementor addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
// Divi is not installed
$theme = wp_get_theme(); // gets the current theme
if('Divi' != $theme->get_template()) return false;
add_action('divi_extensions_init', array($this, 'mecdivi_initialize_extension'));
add_filter('et_builder_load_actions', array($this, 'add_ajax_actions'));
return true;
}
/**
* Creates the extension's main class instance.
*
* @since 1.0.0
*/
public function mecdivi_initialize_extension()
{
require_once plugin_dir_path( __FILE__ ) . 'divi/includes/Divi.php';
require_once plugin_dir_path( __FILE__ ) . 'divi/includes/MECShortcodesForDivi.php';
}
public function add_ajax_actions($actions)
{
$actions[] = 'mec_load_single_page';
return $actions;
}
}

View file

@ -0,0 +1,45 @@
<?php
class MECDIVI_Divi extends DiviExtension {
/**
* The gettext domain for the extension's translations.
*
* @since 1.0.0
*
* @var string
*/
public $gettext_domain = 'mecdivi-divi';
/**
* The extension's WP Plugin name.
*
* @since 1.0.0
*
* @var string
*/
public $name = 'divi';
/**
* The extension's version
*
* @since 1.0.0
*
* @var string
*/
public $version = '1.0.0';
/**
* MECDIVI_Divi constructor.
*
* @param string $name
* @param array $args
*/
public function __construct( $name = 'divi', $args = array() ) {
$this->plugin_dir = plugin_dir_path( __FILE__ );
$this->plugin_dir_url = plugin_dir_url( $this->plugin_dir );
parent::__construct( $name, $args );
}
}
new MECDIVI_Divi;

View file

@ -0,0 +1,20 @@
<?php
if(! function_exists( 'MECDIVI_et_builder_load_actions' )) {
function MECDIVI_et_builder_load_actions( $actions ) {
$actions[] = 'MECDIVI_load_mec_shortcode';
return $actions;
}
}
add_filter( 'et_builder_load_actions', 'MECDIVI_et_builder_load_actions' );
if(! function_exists( 'MECDIVI_load_mec_shortcode' )) {
function MECDIVI_load_mec_shortcode() {
if(!current_user_can('manage_options')) return;
$post_id = sanitize_text_field($_POST['shortcode_id']);
echo do_shortcode( '[MEC id="'.$post_id.'"]' );
wp_die();
}
}
add_action( 'wp_ajax_MECDIVI_load_mec_shortcode', 'MECDIVI_load_mec_shortcode' );

View file

@ -0,0 +1,9 @@
// External Dependencies
import $ from 'jquery';
// Internal Dependencies
import modules from './modules';
$(window).on('et_builder_api_ready', (event, API) => {
API.registerModules(modules);
});

View file

@ -0,0 +1,14 @@
<?php
if ( ! class_exists( 'ET_Builder_Element' ) ) {
return;
}
$module_files = glob( __DIR__ . '/modules/*/*.php' );
// Load custom Divi Builder modules
foreach ( (array) $module_files as $module_file ) {
if ( $module_file && preg_match( "/\/modules\/\b([^\/]+)\/\\1\.php$/", $module_file ) ) {
require_once $module_file;
}
}

View file

@ -0,0 +1,27 @@
// External Dependencies
import React, { Component } from 'react';
import $ from 'jquery';
class MECShortcodes extends Component {
static slug = 'mecdivi_MECShortcodes';
render() {
$.ajax({
url: window.ETBuilderBackend.ajaxUrl,
type: 'post',
data: {
action: 'MECDIVI_load_mec_shortcode',
nonce: 'et_admin_load_nonce',
shortcode_id: this.props.shortcode_id,
},
success: function (response) {
$('.mec-shortcode').html(response);
}
});
return (
<div class="mec-shortcode"></div>
);
}
}
export default MECShortcodes;

View file

@ -0,0 +1,37 @@
<?php
class MECDIVI_MECShortcodes extends ET_Builder_Module {
public $slug = 'mecdivi_MECShortcodes';
public $vb_support = 'on';
protected $module_credits = array(
'module_uri' => 'https://webnus.net',
'author' => 'Webnus',
'author_uri' => 'https://webnus.net',
);
public function init() {
$this->name = esc_html__( 'MEC Shortcodes', 'mecdivi-divi' );
}
public function get_fields() {
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$calendars = [];
foreach($calendar_posts as $calendar_post) $calendars[$calendar_post->ID] = $calendar_post->post_title;
return array(
'shortcode_id' => array(
'label' => esc_html__( 'MEC Shortcodes', 'mecdivi-divi' ),
'type' => 'select',
'options' => $calendars,
'description' => esc_html__( 'Enter the shortcode_id of your choosing here.', 'mecdivi-divi' ),
),
);
}
public function render( $attrs, $content = NULL, $render_slug = NULL ) {
return do_shortcode('[MEC id="'.$this->props['shortcode_id'].'"]');
}
}
new MECDIVI_MECShortcodes;

View file

@ -0,0 +1,5 @@
import MECShortcodes from './MECShortcodes/MECShortcodes';
export default [
MECShortcodes,
];

201
app/addons/divi/scripts/builder-bundle.min.js vendored Executable file
View file

@ -0,0 +1,201 @@
!(function(e) {
var t = {};
function n(o) {
if (t[o]) return t[o].exports;
var r = (t[o] = { i: o, l: !1, exports: {} });
return e[o].call(r.exports, r, r.exports, n), (r.l = !0), r.exports;
}
(n.m = e),
(n.c = t),
(n.d = function(e, t, o) {
n.o(e, t) ||
Object.defineProperty(e, t, {
configurable: !1,
enumerable: !0,
get: o
});
}),
(n.n = function(e) {
var t =
e && e.__esModule
? function() {
return e.default;
}
: function() {
return e;
};
return n.d(t, "a", t), t;
}),
(n.o = function(e, t) {
return Object.prototype.hasOwnProperty.call(e, t);
}),
(n.p = "/"),
n((n.s = 1));
})([
function(e, t) {
e.exports = jQuery;
},
function(e, t, n) {
n(2), (e.exports = n(3));
},
function(e, t, n) {
"use strict";
},
function(e, t, n) {
"use strict";
Object.defineProperty(t, "__esModule", { value: !0 });
var o = n(0),
r = n.n(o),
c = n(4);
r()(window).on("et_builder_api_ready", function(e, t) {
t.registerModules(c.a);
});
},
function(e, t, n) {
"use strict";
var o = n(5);
t.a = [o.a];
},
function(e, t, n) {
"use strict";
var o = n(6),
r = n.n(o),
c = n(0),
u = n.n(c);
function i(e) {
return (i =
"function" === typeof Symbol && "symbol" === typeof Symbol.iterator
? function(e) {
return typeof e;
}
: function(e) {
return e &&
"function" === typeof Symbol &&
e.constructor === Symbol &&
e !== Symbol.prototype
? "symbol"
: typeof e;
})(e);
}
function a(e, t) {
for (var n = 0; n < t.length; n++) {
var o = t[n];
(o.enumerable = o.enumerable || !1),
(o.configurable = !0),
"value" in o && (o.writable = !0),
Object.defineProperty(e, o.key, o);
}
}
function f(e, t) {
return !t || ("object" !== i(t) && "function" !== typeof t)
? (function(e) {
if (void 0 === e)
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
return e;
})(e)
: t;
}
var s = (function(e) {
function t() {
return (
(function(e, t) {
if (!(e instanceof t))
throw new TypeError("Cannot call a class as a function");
})(this, t),
f(
this,
(t.__proto__ || Object.getPrototypeOf(t)).apply(this, arguments)
)
);
}
var n, c, i;
return (
(function(e, t) {
if ("function" !== typeof t && null !== t)
throw new TypeError(
"Super expression must either be null or a function"
);
(e.prototype = Object.create(t && t.prototype, {
constructor: {
value: e,
enumerable: !1,
writable: !0,
configurable: !0
}
})),
t &&
(Object.setPrototypeOf
? Object.setPrototypeOf(e, t)
: (e.__proto__ = t));
})(t, o["Component"]),
(n = t),
(c = [
{
key: "render",
value: function() {
return (
u.a.ajax({
url: window.ETBuilderBackend.ajaxUrl,
type: "post",
data: {
action: "MECDIVI_load_mec_shortcode",
nonce: "et_admin_load_nonce",
shortcode_id: this.props.shortcode_id
},
success: function(e) {
u()(".mec-shortcode").html(e);
var node = jQuery(".mec-event-masonry");
if(typeof node !== 'undefined')
{
// var masonryShuffle = window.Shuffle;
if (node === null) {
return;
}
// var masonryShuffle = new Shuffle(node, {
// itemSelector: '.mec-masonry-item-wrap',
// });
// masonryShuffle.sort({
// by: node.data('created')
// });
// var $container = $("#mec_skin_" + settings.id + " .mec-event-masonry");
var $grid = node.isotope({
filter: '*',
itemSelector: '.mec-masonry-item-wrap',
getSortData: {
date: '[data-sort-masonry]',
},
sortBy: 'date',
animationOptions: {
duration: 750,
easing: 'linear',
queue: false
},
});
}
}
}),
r.a.createElement("div", { class: "mec-shortcode" })
);
}
}
]) && a(n.prototype, c),
i && a(n, i),
t
);
})();
Object.defineProperty(s, "slug", {
configurable: !0,
enumerable: !0,
writable: !0,
value: "mecdivi_MECShortcodes"
}),
(t.a = s);
},
function(e, t) {
e.exports = React;
}
]);

View file

@ -0,0 +1 @@
!function(n){var t={};function r(e){if(t[e])return t[e].exports;var o=t[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=n,r.c=t,r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},r.p="/",r(r.s=7)}({7:function(n,t,r){n.exports=r(8)},8:function(n,t){jQuery(function(n){})}});

View file

@ -0,0 +1,3 @@
// This script is loaded both on the frontend page and in the Visual Builder.
jQuery(function($) {});

0
app/addons/divi/styles/style-dbp.min.css vendored Executable file
View file

1
app/addons/divi/styles/style.min.css vendored Executable file
View file

@ -0,0 +1 @@
.mec-owl-carousel {display: none;width: 100%;-webkit-tap-highlight-color: transparent;position: relative;z-index: 1;}.mec-owl-carousel .owl-stage {position: relative;-ms-touch-action: pan-Y;-moz-backface-visibility: hidden;}.mec-owl-carousel .owl-stage:after {content: ".";display: block;clear: both;visibility: hidden;line-height: 0;height: 0;}.mec-owl-carousel .owl-stage-outer {position: relative;overflow: hidden;-webkit-transform: translate3d(0px, 0px, 0px);}.mec-owl-carousel .owl-wrapper, .mec-owl-carousel .owl-item {-webkit-backface-visibility: hidden;-moz-backface-visibility: hidden;-ms-backface-visibility: hidden;-webkit-transform: translate3d(0, 0, 0);-moz-transform: translate3d(0, 0, 0);-ms-transform: translate3d(0, 0, 0);}.mec-owl-carousel .owl-item {position: relative;min-height: 1px;float: left;-webkit-backface-visibility: hidden;-webkit-tap-highlight-color: transparent;-webkit-touch-callout: none;}.mec-owl-carousel .owl-item img {display: block;width: 100%;}.mec-owl-carousel .owl-nav.disabled, .mec-owl-carousel .owl-dots.disabled {display: none;}.mec-owl-carousel .owl-nav .owl-prev, .mec-owl-carousel .owl-nav .owl-next, .mec-owl-carousel .owl-dot {cursor: pointer;cursor: hand;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}.mec-owl-carousel.owl-loaded {display: block;}.mec-owl-carousel.owl-loading {opacity: 0;display: block;}.mec-owl-carousel.owl-hidden {opacity: 0;}.mec-owl-carousel.owl-refresh .owl-item {visibility: hidden;}.mec-owl-carousel.owl-drag .owl-item {-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}.mec-owl-carousel.owl-grab {cursor: move;cursor: grab;}.mec-owl-carousel.owl-rtl {direction: rtl;}.mec-owl-carousel.owl-rtl .owl-item {float: right;}.no-js .mec-owl-carousel {display: block;}.mec-owl-carousel .animated {animation-duration: 1000ms;animation-fill-mode: both;}.mec-owl-carousel .owl-animated-in {z-index: 0;}.mec-owl-carousel .owl-animated-out {z-index: 1;}.mec-owl-carousel .fadeOut {animation-name: fadeOut;}@keyframes fadeOut {0% {opacity: 1;}100% {opacity: 0;}}.owl-height {transition: height 500ms ease-in-out;}.mec-owl-carousel .owl-item .owl-lazy {opacity: 0;transition: opacity 400ms ease;}.mec-owl-carousel .owl-item img.owl-lazy {transform-style: preserve-3d;}.mec-owl-carousel .owl-video-wrapper {position: relative;height: 100%;background: #000;}.mec-owl-carousel .owl-video-play-icon {position: absolute;height: 80px;width: 80px;left: 50%;top: 50%;margin-left: -40px;margin-top: -40px;background: url("owl.video.play.png") no-repeat;cursor: pointer;z-index: 1;-webkit-backface-visibility: hidden;transition: transform 100ms ease;}.mec-owl-carousel .owl-video-play-icon:hover {-ms-transform: scale(1.3, 1.3);transform: scale(1.3, 1.3);}.mec-owl-carousel .owl-video-playing .owl-video-tn, .mec-owl-carousel .owl-video-playing .owl-video-play-icon {display: none;}.mec-owl-carousel .owl-video-tn {opacity: 0;height: 100%;background-position: center center;background-repeat: no-repeat;background-size: contain;transition: opacity 400ms ease;}.mec-owl-carousel .owl-video-frame {position: relative;z-index: 1;height: 100%;width: 100%;}

77
app/addons/elementor.php Executable file
View file

@ -0,0 +1,77 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC elementor addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_elementor extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
}
/**
* Initialize the Elementor addon
* @author Webnus <info@webnus.net>
*/
public function init()
{
// Elementor is not installed
if(!did_action('elementor/loaded')) return false;
add_action('elementor/widgets/register', array($this, 'register_shortcode'));
add_action( 'elementor/preview/enqueue_styles', function()
{
wp_enqueue_style( 'mec-owl-carousel-style' );
wp_enqueue_style( 'mec-frontend-style' );
});
add_action('elementor/editor/after_enqueue_scripts', function()
{
wp_enqueue_script('mec-owl-carousel-script');
wp_enqueue_script('mec-frontend-script');
});
return true;
}
/**
* Register MEC Elementor Shortcode
* @author Webnus <info@webnus.net>
*/
public function register_shortcode()
{
require_once MEC_ABSPATH.'app/addons/elementor/shortcode.php';
\Elementor\Plugin::instance()->widgets_manager->register(new \Elementor\MEC_addon_elementor_shortcode());
}
/**
* Register the addon in Elementor
* @author Webnus <info@webnus.net>
*/
public function map()
{
}
}

View file

View file

@ -0,0 +1,110 @@
<?php
namespace Elementor;
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC elementor addon shortcode class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_elementor_shortcode extends \Elementor\Widget_Base
{
/**
* Retrieve MEC widget name.
*
* @since 1.0.0
* @access public
* @return string Widget name.
*/
public function get_name()
{
return 'MEC';
}
/**
* Retrieve MEC widget title.
*
* @since 1.0.0
* @access public
* @return string Widget title.
*/
public function get_title()
{
return esc_html__('Modern Events Calendar (MEC)', 'modern-events-calendar-lite');
}
/**
* Retrieve MEC widget icon.
*
* @since 1.0.0
* @access public
* @return string Widget icon.
*/
public function get_icon()
{
return 'eicon-archive-posts';
}
/**
* Set widget category.
*
* @since 1.0.0
* @access public
* @return array Widget category.
*/
public function get_categories()
{
return array('general');
}
/**
* Register MEC widget controls.
*
* @since 1.0.0
* @access protected
*/
protected function register_controls()
{
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$calendars = [];
foreach($calendar_posts as $calendar_post) $calendars[$calendar_post->ID] = $calendar_post->post_title;
// Content Tab
$this->start_controls_section(
'content_section',
array(
'label' => esc_html__('General', 'modern-events-calendar-lite'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
)
);
// Select Type Section
$this->add_control(
'type',
array(
'label' => esc_html__('Select Type', 'modern-events-calendar-lite'),
'type' => \Elementor\Controls_Manager::SELECT,
'options' => $calendars,
)
);
$this->end_controls_section();
}
/**
* Render MEC widget output on the frontend.
*
* @since 1.0.0
* @access protected
*/
protected function render()
{
$settings = $this->get_settings_for_display();
if(!empty($settings['type']))
{
echo do_shortcode('[MEC id="'.$settings['type'].'"]');
}
}
}

0
app/addons/index.html Executable file
View file

158
app/addons/learndash.php Executable file
View file

@ -0,0 +1,158 @@
<?php
/** no direct access **/
defined('MECEXEC') or die();
/**
* Webnus MEC LearnDash addon class
* @author Webnus <info@webnus.net>
*/
class MEC_addon_learndash extends MEC_base
{
/**
* @var MEC_factory
*/
public $factory;
/**
* @var MEC_main
*/
public $main;
public $settings;
/**
* Constructor method
* @author Webnus <info@webnus.net>
*/
public function __construct()
{
// MEC Factory class
$this->factory = $this->getFactory();
// MEC Main class
$this->main = $this->getMain();
// MEC Settings
$this->settings = $this->main->get_settings();
}
/**
* Initialize the LD addon
* @author Webnus <info@webnus.net>
* @return boolean
*/
public function init()
{
// Module is not enabled
if(!isset($this->settings['ld_status']) or (isset($this->settings['ld_status']) and !$this->settings['ld_status'])) return false;
// Tickets
add_action('custom_field_ticket', array($this, 'add_courses_dropdown_to_tickets'), 10, 2);
add_action('custom_field_dynamic_ticket', array($this, 'add_courses_dropdown_to_raw_tickets'));
// Enrollment Method
$enroll_method = (isset($this->settings['ld_enrollment_method']) and trim($this->settings['ld_enrollment_method'])) ? $this->settings['ld_enrollment_method'] : 'booking';
// Enroll
if($enroll_method === 'booking') add_action('mec_booking_completed', array($this, 'assign'), 10, 1);
elseif($enroll_method === 'confirm') add_action('mec_booking_confirmed', array($this, 'assign'), 10, 1);
elseif($enroll_method === 'verification') add_action('mec_booking_verified', array($this, 'assign'), 10, 1);
elseif($enroll_method === 'confirm_verification')
{
add_action('mec_booking_confirmed', array($this, 'pre_enroll'), 10, 1);
add_action('mec_booking_verified', array($this, 'pre_enroll'), 10, 1);
}
return true;
}
public function add_courses_dropdown_to_tickets($ticket, $key)
{
// LearnDash is not installed
if(!defined('LEARNDASH_VERSION')) return;
$courses = $this->get_courses();
if(!count($courses)) return;
?>
<div class="mec-form-row">
<label for="mec_tickets_<?php echo esc_attr($key); ?>_ld_course"><?php esc_html_e('LearnDash Course', 'modern-events-calendar-lite'); ?></label>
<select name="mec[tickets][<?php echo esc_attr($key); ?>][ld_course]" id="mec_tickets_<?php echo esc_attr($key); ?>_ld_course">
<option>-----</option>
<?php foreach($courses as $course_id => $course_name): ?>
<option value="<?php echo esc_attr($course_id); ?>"<?php echo ((isset($ticket['ld_course']) and $course_id == $ticket['ld_course']) ? 'selected="selected"' : ''); ?>><?php echo esc_html($course_name); ?></option>
<?php endforeach; ?>
</select>
</div>
<?php
}
public function add_courses_dropdown_to_raw_tickets()
{
// LearnDash is not installed
if(!defined('LEARNDASH_VERSION')) return;
$this->add_courses_dropdown_to_tickets(array(), ':i:');
}
public function get_courses()
{
$courses = [];
$args = ['post_type' => 'sfwd-courses', 'posts_per_page' => -1];
if(!current_user_can('manage_options') and isset($this->settings['ld_course_access']) and $this->settings['ld_course_access'] === 'user') $args['author'] = get_current_user_id();
$posts = get_posts($args);
if($posts) foreach($posts as $post) $courses[$post->ID] = $post->post_title;
return $courses;
}
public function assign($book_id)
{
// LearnDash is not installed
if(!defined('LEARNDASH_VERSION')) return;
// MEC User
$u = $this->getUser();
$event_id = get_post_meta($book_id, 'mec_event_id', true);
$ticket_ids = explode(',', get_post_meta($book_id, 'mec_ticket_id', true));
$attendees = get_post_meta($book_id, 'mec_attendees', true);
if(!is_array($attendees)) $attendees = [];
$tickets = get_post_meta($event_id, 'mec_tickets', true);
foreach($attendees as $key => $attendee)
{
if($key === 'attachments') continue;
if(!isset($attendee['id'])) continue;
$ticket_id = $attendee['id'];
if(!is_numeric($ticket_id)) continue;
if(!in_array($ticket_id, $ticket_ids)) continue;
if(!isset($tickets[$ticket_id])) continue;
$ticket = $tickets[$ticket_id];
// Course ID
$course_id = $ticket['ld_course'];
// User ID
$user_id = $u->register($attendee, [
'event_id' => $event_id,
]);
// Associate Course
ld_update_course_access($user_id, $course_id, false);
}
}
public function pre_enroll($booking_id)
{
$confirmed = get_post_meta($booking_id, 'mec_confirmed', true);
$verified = get_post_meta($booking_id, 'mec_verified', true);
if($confirmed == 1 and $verified == 1) $this->assign($booking_id);
}
}

View file

@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20"><path d="M15 4h3v14h-16v-14h3v-1c0-0.83 0.67-1.5 1.5-1.5s1.5 0.67 1.5 1.5v1h4v-1c0-0.83 0.67-1.5 1.5-1.5s1.5 0.67 1.5 1.5v1zM6 3v2.5c0 0.28 0.22 0.5 0.5 0.5s0.5-0.22 0.5-0.5v-2.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5zM13 3v2.5c0 0.28 0.22 0.5 0.5 0.5s0.5-0.22 0.5-0.5v-2.5c0-0.28-0.22-0.5-0.5-0.5s-0.5 0.22-0.5 0.5zM17 17v-9h-14v9h14zM7 16v-7h-2v7h2zM11 16v-7h-2v7h2zM15 16v-7h-2v7h2z"></path></svg>

After

Width:  |  Height:  |  Size: 545 B

View file

@ -0,0 +1,3 @@
<div class="fl-example-text">
<?php echo do_shortcode('[MEC id="'.$settings->mec_shortcode.'"]') ?>
</div>

View file

@ -0,0 +1,39 @@
<?php
class mecBeaverBuilderShortcode extends FLBuilderModule {
public function __construct()
{
parent::__construct(array(
'name' => esc_html__( 'Modern Events Calendar (MEC)', 'modern-events-calendar-lite'),
'description' => esc_html__( 'MEC Shortcodes', 'modern-events-calendar-lite'),
'category' => esc_html__( 'Basic', 'modern-events-calendar-lite'),
'dir' => MEC_BEAVER_DIR . 'mec-beaver-builder/',
'url' => MEC_BEAVER_URL . 'mec-beaver-builder/',
'icon' => 'button.svg',
'editor_export' => true, // Defaults to true and can be omitted.
'enabled' => true, // Defaults to true and can be omitted.
'partial_refresh' => false, // Defaults to false and can be omitted.
));
}
}
$calendar_posts = get_posts(array('post_type'=>'mec_calendars', 'posts_per_page'=>'-1'));
$calendars = [];
foreach($calendar_posts as $calendar_post) $calendars[$calendar_post->ID] = $calendar_post->post_title;
FLBuilder::register_module( 'mecBeaverBuilderShortcode', array(
'my-tab-1' => array(
'title' => esc_html__( 'Content', 'modern-events-calendar-lite'),
'sections' => array(
'my-section-1' => array(
'title' => esc_html__( 'Select Shortcode', 'modern-events-calendar-lite'),
'fields' => array(
'mec_shortcode' => array(
'type' => 'select',
'label' => esc_html__( 'Select Shortcode', 'modern-events-calendar-lite'),
'options' => $calendars,
),
)
)
)
)
) );

0
app/addons/tnp/index.html Executable file
View file

View file

@ -0,0 +1 @@
<p><?php esc_html_e('Please select an event.', 'modern-events-calendar-lite'); ?></p>

View file

@ -0,0 +1,54 @@
<style>
.title {
font-family: <?php echo $title_style->font_family ?>;
font-size: <?php echo $title_style->font_size ?>px;
font-weight: <?php echo $title_style->font_weight ?>;
color: <?php echo $title_style->font_color ?>;
line-height: normal;
margin: 0;
}
.text {
font-family: <?php echo $text_style->font_family ?>;
font-size: <?php echo $text_style->font_size ?>px;
font-weight: <?php echo $text_style->font_weight ?>;
color: <?php echo $text_style->font_color ?>;
padding: 20px 0 0 0;
line-height: 1.5em;
margin: 0;
}
.image {
padding-bottom: 20px;
}
.button {
padding-top: 15px;
}
</style>
<table width="100%" class="responsive" border="0" cellspacing="0" cellpadding="0">
<?php if($media): ?>
<tr>
<td align="center" inline-class="image">
<?php echo TNP_Composer::image($media); ?>
</td>
</tr>
<?php endif; ?>
<tr>
<td align="center" inline-class="title">
<?php echo $event->post_title; ?>
</td>
</tr>
<?php if($event->post_content): ?>
<tr>
<td align="center" inline-class="text">
<?php echo $event->post_content; ?>
</td>
</tr>
<?php endif; ?>
<tr>
<td align="center" inline-class="button">
<?php echo TNP_Composer::button($button_options) ?>
</td>
</tr>
</table>

View file

@ -0,0 +1,47 @@
<style>
.title {
<?php $title_style->echo_css(0.8)?>
margin: 0;
text-align: center;
line-height: normal;
padding: 10px 0;
}
.text {
<?php $text_style->echo_css()?>
padding: 10px 0;
line-height: 1.5em;
margin: 0;
text-align: center;
}
.button {
padding: 10px 0;
}
</style>
<table width="<?php echo $td_width ?>" align="left" class="responsive" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="top">
<?php echo $media ? TNP_Composer::image($media) : ''; ?>
</td>
</tr>
</table>
<table width="<?php echo $td_width ?>" align="right" class="responsive" border="0" cellspacing="0" cellpadding="0">
<tr>
<td inline-class="title">
<?php echo $event->post_title; ?>
</td>
</tr>
<?php if($event->post_content): ?>
<tr>
<td inline-class="text">
<?php echo $event->post_content; ?>
</td>
</tr>
<?php endif; ?>
<tr>
<td align="center" inline-class="button">
<?php echo TNP_Composer::button($button_options) ?>
</td>
</tr>
</table>

View file

@ -0,0 +1,51 @@
<style>
.title {
<?php $title_style->echo_css(0.8)?>
line-height: normal!important;
margin: 0;
text-align: center;
padding: 10px 0;
}
.text {
<?php $text_style->echo_css()?>
padding: 10px 0;
line-height: 1.5em!important;
text-align: center;
margin: 0;
}
.button {
padding: 10px 0;
}
</style>
<div dir="rtl">
<table width="<?php echo $td_width ?>" align="right" class="responsive" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="top">
<?php echo $media ? TNP_Composer::image($media) : ''; ?>
</td>
</tr>
</table>
<table width="<?php echo $td_width ?>" align="left" class="responsive" border="0" cellspacing="0" cellpadding="0">
<tr>
<td inline-class="title" dir="ltr">
<?php echo $event->post_title; ?>
</td>
</tr>
<?php if($event->post_content): ?>
<tr>
<td inline-class="text" dir="ltr">
<?php echo $event->post_content; ?>
</td>
</tr>
<?php endif; ?>
<tr>
<td align="center" inline-class="button" dir="ltr">
<?php echo TNP_Composer::button($button_options) ?>
</td>
</tr>
</table>
</div>

71
app/addons/tnp/simple/block.php Executable file
View file

@ -0,0 +1,71 @@
<?php
/*
* Name: Modern Events Calendar
* Section: content
* Description: Include MEC events into your newsletter
*/
/* @var $options array */
$default_options = array(
'event_id' => NULL,
'layout' => 'full',
// block_ prefixed options are reserved for Newsletter and the ones below managed directly by Newsletter
'block_padding_left' => 15,
'block_padding_right' => 15,
'block_padding_top' => 15,
'block_padding_bottom' => 15,
'block_background' => '#ffffff',
);
$options = array_merge($default_options, $options);
/** @var MEC_main $main */
$main = MEC_base::getInstance('app.libraries.main');
$event_id = $options['event_id'];
$layout = $options['layout'];
if(!$event_id) $layout = 'empty';
$td_width = round((600 - $options['block_padding_left'] - $options['block_padding_right'] - 20)/2);
$featured_image_id = $event_id ? get_post_thumbnail_id($event_id) : NULL;
if($event_id and $featured_image_id)
{
if($layout === 'full')
{
$image_width = 600 - $options['block_padding_left'] - $options['block_padding_right'];
$media = tnp_resize_2x($featured_image_id, [$image_width, 0]);
}
else
{
$media = tnp_resize_2x($featured_image_id, [$td_width, 0]);
}
}
else $media = false;
$title_style = TNP_Composer::get_title_style($options, 'title', $composer);
$text_style = TNP_Composer::get_text_style($options, '', $composer);
$event = $event_id ? get_post($event_id) : NULL;
$button_options = $options;
$button_options['button_url'] = $event_id ? get_permalink($event) : NULL;
$button_options['button_label'] = esc_html__('Click Here', 'modern-events-calendar-lite');
switch ($layout) {
case 'left':
include __DIR__ . '/block-left.php';
return;
case 'right':
include __DIR__ . '/block-right.php';
return;
case 'empty':
include __DIR__ . '/block-empty.php';
return;
default:
include __DIR__ . '/block-full.php';
return;
}

BIN
app/addons/tnp/simple/icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

View file

@ -0,0 +1,26 @@
<?php
/* @var $options array containing all the options of the current block */
/* @var $fields NewsletterFields */
/** @var MEC_main $main */
$main = MEC_base::getInstance('app.libraries.main');
?>
<?php
$posts = get_posts(array(
'post_type' => $main->get_main_post_type(),
'posts_per_page' => -1
));
$options = array('' => '-----');
foreach($posts as $post) $options['' . $post->ID] = $post->post_title;
$fields->select('event_id', esc_html__('Event', 'modern-events-calendar-lite'), $options); ?>
<?php $fields->select('layout', esc_html__('Layout', 'modern-events-calendar-lite'), array(
'full' => 'Full',
'left' => 'Image left',
'right' => 'Image right'
)); ?>
<?php $fields->block_commons();

View file

@ -0,0 +1,317 @@
<?php
require_once dirname(__FILE__).'/serialisation.php';
require_once dirname(__FILE__).'/transport.php';
require_once dirname(__FILE__).'/log.php';
defined('CS_REST_WRAPPER_VERSION') or define('CS_REST_WRAPPER_VERSION', '6.0.1');
defined('CS_HOST') or define('CS_HOST', 'api.createsend.com');
defined('CS_OAUTH_BASE_URI') or define('CS_OAUTH_BASE_URI', 'https://'.CS_HOST.'/oauth');
defined('CS_OAUTH_TOKEN_URI') or define('CS_OAUTH_TOKEN_URI', CS_OAUTH_BASE_URI.'/token');
defined('CS_REST_WEBHOOK_FORMAT_JSON') or define('CS_REST_WEBHOOK_FORMAT_JSON', 'json');
defined('CS_REST_WEBHOOK_FORMAT_XML') or define('CS_REST_WEBHOOK_FORMAT_XML', 'xml');
/**
* A general result object returned from all Campaign Monitor API calls.
* @author tobyb
*
*/
if (!class_exists('CS_REST_Wrapper_Result')) {
class CS_REST_Wrapper_Result {
/**
* The deserialised result of the API call
* @var mixed
*/
var $response;
/**
* The http status code of the API call
* @var int
*/
var $http_status_code;
function __construct($response, $code) {
$this->response = $response;
$this->http_status_code = $code;
}
/**
* Can be used to check if a call to the api resulted in a successful response.
* @return boolean False if the call failed. Check the response property for the failure reason.
* @access public
*/
function was_successful() {
return $this->http_status_code >= 200 && $this->http_status_code < 300;
}
}
}
/**
* Base class for the create send PHP wrapper.
* This class includes functions to access the general data,
* i.e timezones, clients and getting your API Key from username and password
* @author tobyb
*
*/
if (!class_exists('CS_REST_Wrapper_Base')) {
class CS_REST_Wrapper_Base {
/**
* The protocol to use while accessing the api
* @var string http or https
* @access private
*/
var $_protocol;
/**
* The base route of the create send api.
* @var string
* @access private
*/
var $_base_route;
/**
* The serialiser to use for serialisation and deserialisation
* of API request and response data
* @var CS_REST_JsonSerialiser or CS_REST_XmlSerialiser
* @access private
*/
var $_serialiser;
/**
* The transport to use to send API requests
* @var CS_REST_CurlTransport or CS_REST_SocketTransport or your own custom transport.
* @access private
*/
var $_transport;
/**
* The logger to use for debugging of all API requests
* @var CS_REST_Log
* @access private
*/
var $_log;
/**
* The default options to use for each API request.
* These can be overridden by passing in an array as the call_options argument
* to a single api request.
* Valid options are:
*
* deserialise boolean:
* Set this to false if you want to get the raw response.
* This can be useful if your passing json directly to javascript.
*
* While there are clearly other options there is no need to change them.
* @var array
* @access private
*/
var $_default_call_options;
/**
* Constructor.
* @param $auth_details array Authentication details to use for API calls.
* This array must take one of the following forms:
* If using OAuth to authenticate:
* array(
* 'access_token' => 'your access token',
* 'refresh_token' => 'your refresh token')
*
* Or if using an API key:
* array('api_key' => 'your api key')
*
* Note that this method will continue to work in the deprecated
* case when $auth_details is passed in as a string containing an
* API key.
* @param $protocol string The protocol to use for requests (http|https)
* @param $debug_level int The level of debugging required CS_REST_LOG_NONE | CS_REST_LOG_ERROR | CS_REST_LOG_WARNING | CS_REST_LOG_VERBOSE
* @param $host string The host to send API requests to. There is no need to change this
* @param $log CS_REST_Log The logger to use. Used for dependency injection
* @param $serialiser The serialiser to use. Used for dependency injection
* @param $transport The transport to use. Used for dependency injection
* @access public
*/
function __construct(
$auth_details,
$protocol = 'https',
$debug_level = CS_REST_LOG_NONE,
$host = CS_HOST,
$log = NULL,
$serialiser = NULL,
$transport = NULL) {
if (is_string($auth_details)) {
# If $auth_details is a string, assume it is an API key
$auth_details = array('api_key' => $auth_details);
}
$this->_log = is_null($log) ? new CS_REST_Log($debug_level) : $log;
$this->_protocol = $protocol;
$this->_base_route = $protocol.'://'.$host.'/api/v3.2/';
$this->_log->log_message('Creating wrapper for '.$this->_base_route, get_class($this), CS_REST_LOG_VERBOSE);
$this->_transport = is_null($transport) ?
CS_REST_TRANSPORT_get_available($this->is_secure(), $this->_log) :
$transport;
$transport_type = method_exists($this->_transport, 'get_type') ? $this->_transport->get_type() : 'Unknown';
$this->_log->log_message('Using '.$transport_type.' for transport', get_class($this), CS_REST_LOG_WARNING);
$this->_serialiser = is_null($serialiser) ?
CS_REST_SERIALISATION_get_available($this->_log) : $serialiser;
$this->_log->log_message('Using '.$this->_serialiser->get_type().' json serialising', get_class($this), CS_REST_LOG_WARNING);
$this->_default_call_options = array (
'authdetails' => $auth_details,
'userAgent' => 'createsend-php v'.CS_REST_WRAPPER_VERSION.
' PHPv'.phpversion().' over '.$transport_type.' with '.$this->_serialiser->get_type(),
'contentType' => 'application/json; charset=utf-8',
'deserialise' => true,
'host' => $host,
'protocol' => $protocol
);
}
/**
* Refresh the current OAuth token using the current refresh token.
* @access public
*/
function refresh_token() {
if (!isset($this->_default_call_options['authdetails']) ||
!isset($this->_default_call_options['authdetails']['refresh_token'])) {
trigger_error(
'Error refreshing token. There is no refresh token set on this object.',
E_USER_ERROR);
return array(NULL, NULL, NULL);
}
$body = "grant_type=refresh_token&refresh_token=".urlencode(
$this->_default_call_options['authdetails']['refresh_token']);
$options = array('contentType' => 'application/x-www-form-urlencoded');
$wrap = new CS_REST_Wrapper_Base(
NULL, 'https', CS_REST_LOG_NONE, CS_HOST, NULL,
new CS_REST_DoNothingSerialiser(), NULL);
$result = $wrap->post_request(CS_OAUTH_TOKEN_URI, $body, $options);
if ($result->was_successful()) {
$access_token = $result->response->access_token;
$expires_in = $result->response->expires_in;
$refresh_token = $result->response->refresh_token;
$this->_default_call_options['authdetails'] = array(
'access_token' => $access_token,
'refresh_token' => $refresh_token
);
return array($access_token, $expires_in, $refresh_token);
} else {
trigger_error(
'Error refreshing token. '.$result->response->error.': '.$result->response->error_description,
E_USER_ERROR);
return array(NULL, NULL, NULL);
}
}
/**
* @return boolean True if the wrapper is using SSL.
* @access public
*/
function is_secure() {
return $this->_protocol === 'https';
}
function put_request($route, $data, $call_options = array()) {
return $this->_call($call_options, CS_REST_PUT, $route, $data);
}
function post_request($route, $data, $call_options = array()) {
return $this->_call($call_options, CS_REST_POST, $route, $data);
}
function delete_request($route, $call_options = array()) {
return $this->_call($call_options, CS_REST_DELETE, $route);
}
function get_request($route, $include_tracking_pref = NULL, $call_options = array()) {
if(isset($include_tracking_pref)
&& is_bool($include_tracking_pref)) {
$route .= '&includeTrackingPreference='.($include_tracking_pref ? "true" : "false");
}
return $this->_call($call_options, CS_REST_GET, $route);
}
function get_request_with_params($route, $params) {
if(!is_null($params)) {
# http_build_query coerces booleans to 1 and 0, not helpful
foreach($params as $key=>$value) {
if(is_bool($value)) {
$params[$key] = ($value) ? 'true' : 'false';
}
}
$route = $route . '?' . http_build_query($params);
}
return $this->get_request($route);
}
function get_request_paged($route, $page_number, $page_size, $order_field, $order_direction, $include_tracking_pref = NULL,
$join_char = 'deprecated') {
// Stores our query values
$query = [];
// Extract any initial queries in the route into our local query
if(strpos($route, '?') !== false) {
$parts = parse_url($route);
$route = current(explode('?', $route));
if(array_key_exists('query', $parts) && !empty($parts['query'])) {
parse_str($parts['query'], $query);
}
}
// Now selectively add supplied vars to the query
if(!is_null($page_number)) {
$query['page'] = $page_number;
}
if(!is_null($page_size)) {
$query['pageSize'] = $page_size;
}
if(!is_null($order_field)) {
$query['orderField'] = $order_field;
}
if(!is_null($order_direction)) {
$query['orderDirection'] = $order_direction;
}
// If we ended up with a query, add it back to the route
if(!empty($query)) {
$route .= '?'.http_build_query($query);
}
return $this->get_request($route, $include_tracking_pref);
}
/**
* Internal method to make a general API request based on the provided options
* @param $call_options
* @access private
*/
function _call($call_options, $method, $route, $data = NULL) {
$call_options['route'] = $route;
$call_options['method'] = $method;
if(!is_null($data)) {
$call_options['data'] = $this->_serialiser->serialise($data);
}
$call_options = array_merge($this->_default_call_options, $call_options);
$this->_log->log_message('Making '.$call_options['method'].' call to: '.$call_options['route'], get_class($this), CS_REST_LOG_WARNING);
$call_result = $this->_transport->make_call($call_options);
$this->_log->log_message('Call result: <pre>'.var_export($call_result, true).'</pre>',
get_class($this), CS_REST_LOG_VERBOSE);
if($call_options['deserialise']) {
$call_result['response'] = $this->_serialiser->deserialise($call_result['response']);
}
return new CS_REST_Wrapper_Result($call_result['response'], $call_result['code']);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
<?php
if (!class_exists('CurlException')) {
class CurlException extends \RuntimeException
{
public function __construct($curlMessage, $errorCode)
{
parent::__construct('Error making request with curl_error: ' . $curlMessage, $errorCode);
}
}
}

View file

@ -0,0 +1,21 @@
<?php
defined('CS_REST_LOG_VERBOSE') or define('CS_REST_LOG_VERBOSE', 1000);
defined('CS_REST_LOG_WARNING') or define('CS_REST_LOG_WARNING', 500);
defined('CS_REST_LOG_ERROR') or define('CS_REST_LOG_ERROR', 250);
defined('CS_REST_LOG_NONE') or define('CS_REST_LOG_NONE', 0);
if (!class_exists('CS_REST_Log')) {
class CS_REST_Log {
var $_level;
function __construct($level) {
$this->_level = $level;
}
function log_message($message, $module, $level) {
if($this->_level >= $level) {
echo date('G:i:s').' - '.$module.': '.$message."<br />\n";
}
}
}
}

View file

@ -0,0 +1,136 @@
<?php
if(!function_exists("CS_REST_SERIALISATION_get_available")) {
function CS_REST_SERIALISATION_get_available($log) {
$log->log_message('Getting serialiser', __FUNCTION__, CS_REST_LOG_VERBOSE);
if(function_exists('json_decode') && function_exists('json_encode')) {
return new CS_REST_NativeJsonSerialiser($log);
} else {
return new CS_REST_ServicesJsonSerialiser($log);
}
}
}
if (!class_exists('CS_REST_BaseSerialiser')) {
class CS_REST_BaseSerialiser {
var $_log;
function __construct($log) {
$this->_log = $log;
}
/**
* Recursively ensures that all data values are utf-8 encoded.
* @param array $data All values of this array are checked for utf-8 encoding.
*/
function check_encoding($data) {
foreach($data as $k => $v) {
// If the element is a sub-array then recusively encode the array
if(is_array($v)) {
$data[$k] = $this->check_encoding($v);
// Otherwise if the element is a string then we need to check the encoding
} else if(is_string($v)) {
if((function_exists('mb_detect_encoding') && mb_detect_encoding($v) !== 'UTF-8') ||
(function_exists('mb_check_encoding') && !mb_check_encoding($v, 'UTF-8'))) {
// The string is using some other encoding, make sure we utf-8 encode
$v = utf8_encode($v);
}
$data[$k] = $v;
}
}
return $data;
}
}
}
if (!class_exists('CS_REST_DoNothingSerialiser')) {
class CS_REST_DoNothingSerialiser extends CS_REST_BaseSerialiser {
function __construct() {}
function get_type() { return 'do_nothing'; }
function serialise($data) { return $data; }
function deserialise($text) {
$data = json_decode($text);
return is_null($data) ? $text : $data;
}
function check_encoding($data) { return $data; }
}
}
if (!class_exists('CS_REST_NativeJsonSerialiser')) {
class CS_REST_NativeJsonSerialiser extends CS_REST_BaseSerialiser {
function get_format() {
return 'json';
}
function get_type() {
return 'native';
}
function serialise($data) {
if(is_null($data) || $data == '') return '';
return json_encode($this->check_encoding($data));
}
function deserialise($text) {
$data = json_decode($text);
return $this->strip_surrounding_quotes(is_null($data) ? $text : $data);
}
/**
* We've had sporadic reports of people getting ID's from create routes with the surrounding quotes present.
* There is no case where these should be present. Just get rid of it.
*/
function strip_surrounding_quotes($data) {
if(is_string($data)) {
return trim($data, '"');
}
return $data;
}
}
}
if (!class_exists('CS_REST_ServicesJsonSerialiser')) {
class CS_REST_ServicesJsonSerialiser extends CS_REST_BaseSerialiser {
var $_serialiser;
function __construct($log) {
parent::__construct($log);
if (!class_exists('Services_JSON', false)) {
require_once dirname(__FILE__).'/services_json.php';
}
$this->_serialiser = new Services_JSON();
}
function get_content_type() {
return 'application/json';
}
function get_format() {
return 'json';
}
function get_type() {
return 'services_json';
}
function serialise($data) {
if(is_null($data) || $data == '') return '';
return $this->_serialiser->encode($this->check_encoding($data));
}
function deserialise($text) {
$data = $this->_serialiser->decode($text);
return is_null($data) ? $text : $data;
}
}
}

View file

@ -0,0 +1,774 @@
<?php
/**
* Converts to and from JSON format.
*
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @category
* @package Services_JSON
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
defined('SERVICES_JSON_SLICE') or define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
defined('SERVICES_JSON_IN_STR') or define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
defined('SERVICES_JSON_IN_ARR') or define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
defined('SERVICES_JSON_IN_OBJ') or define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
defined('SERVICES_JSON_IN_CMT') or define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
defined('SERVICES_JSON_LOOSE_TYPE') or define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
defined('SERVICES_JSON_SUPPRESS_ERRORS') or define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Converts to and from JSON format.
*
* Brief example of use:
*
* <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
* // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
* print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
*
* // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input);
* </code>
*/
if (!class_exists('Services_JSON')) {
class Services_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
function __construct($use = 0)
{
$this->use = $use;
}
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
* @access private
*/
function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch(true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
* @access private
*/
function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch(strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8[0]) >> 2))
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8[0]) << 4))
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map(array($this, 'name_value'),
array_keys($var),
array_values($var));
foreach($properties as $property) {
if($this->isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map(array($this, 'encode'), $var);
foreach($elements as $element) {
if($this->isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
$vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'),
array_keys($vars),
array_values($vars));
foreach($properties as $property) {
if($this->isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->encode($value);
if($this->isError($encoded_value)) {
return $encoded_value;
}
return $this->encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
* @access public
*/
function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
$m = [];
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs[++$c];
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
. chr(hexdec(substr($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str[0] == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = [];
} else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = [];
} else {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = new stdClass();
}
}
array_push($stk, array('what' => SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false));
$chrs = substr($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = [];
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
} elseif (($chrs[$c] == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
} elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
} elseif (($chrs[$c] == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
} elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
}
}
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
function isError($data, $code = null)
{
if (is_object($data) && (get_class($data) == 'services_json_error' ||
is_subclass_of($data, 'services_json_error'))) {
return true;
}
return false;
}
}
}
if (!class_exists('Services_JSON_Error')) {
class Services_JSON_Error
{
function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
}
}
}
?>

View file

@ -0,0 +1,352 @@
<?php
defined('CS_REST_GET') or define('CS_REST_GET', 'GET');
defined('CS_REST_POST') or define('CS_REST_POST', 'POST');
defined('CS_REST_PUT') or define('CS_REST_PUT', 'PUT');
defined('CS_REST_DELETE') or define('CS_REST_DELETE', 'DELETE');
if (false === defined('CS_REST_SOCKET_TIMEOUT')) {
define('CS_REST_SOCKET_TIMEOUT', 20);
}
if (false === defined('CS_REST_CALL_TIMEOUT')) {
define('CS_REST_CALL_TIMEOUT', 20);
}
if(!function_exists("CS_REST_TRANSPORT_get_available")) {
function CS_REST_TRANSPORT_get_available($requires_ssl, $log) {
if(function_exists('curl_init') && function_exists('curl_exec')) {
return new CS_REST_CurlTransport($log);
} else if(CS_REST_TRANSPORT_can_use_raw_socket($requires_ssl)) {
return new CS_REST_SocketTransport($log);
} else {
$log->log_message('No transport is available', __FUNCTION__, CS_REST_LOG_ERROR);
trigger_error('No transport is available.'.
($requires_ssl ? ' Try using non-secure (http) mode or ' : ' Please ').
'ensure the cURL extension is loaded', E_USER_ERROR);
}
}
}
if(!function_exists("CS_REST_TRANSPORT_can_use_raw_socket")) {
function CS_REST_TRANSPORT_can_use_raw_socket($requires_ssl) {
if(function_exists('fsockopen')) {
if($requires_ssl) {
return extension_loaded('openssl');
}
return true;
}
return false;
}
}
if (!class_exists('CS_REST_BaseTransport')) {
class CS_REST_BaseTransport {
var $_log;
function __construct($log) {
$this->_log = $log;
}
function split_and_inflate($response, $may_be_compressed) {
$ra = explode("\r\n\r\n", $response);
$result = array_pop($ra);
$headers = array_pop($ra);
if($may_be_compressed && preg_match('/^Content-Encoding:\s+gzip\s+$/im', $headers)) {
$original_length = strlen($response);
$result = gzinflate(substr($result, 10, -8));
$this->_log->log_message('Inflated gzipped response: '.$original_length.' bytes ->'.
strlen($result).' bytes', get_class(), CS_REST_LOG_VERBOSE);
}
return array($headers, $result);
}
}
}
/**
* Provide HTTP request functionality via cURL extensions
*
* @author tobyb
* @since 1.0
*/
if (!class_exists('CS_REST_CurlTransport')) {
class CS_REST_CurlTransport extends CS_REST_BaseTransport {
var $_curl_zlib;
function __construct($log) {
parent::__construct($log);
$curl_version = curl_version();
$this->_curl_zlib = isset($curl_version['libz_version']);
}
/**
* @return string The type of transport used
*/
function get_type() {
return 'cURL';
}
function make_call($call_options) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $call_options['route']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$headers = [];
$headers[] = 'Content-Type: '.$call_options['contentType'];
if (array_key_exists('authdetails', $call_options) &&
isset($call_options['authdetails'])) {
if (array_key_exists('username', $call_options['authdetails']) &&
array_key_exists('password', $call_options['authdetails'])) {
# Authenticating using basic auth for retrieving user's API key.
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $call_options['authdetails']['username'].':'.$call_options['authdetails']['password']);
} elseif (array_key_exists('access_token', $call_options['authdetails'])) {
# Authenticating using OAuth.
$access_token = $call_options['authdetails']['access_token'];
$headers[] = 'Authorization: Bearer '.$access_token;
} elseif (array_key_exists('api_key', $call_options['authdetails'])) {
# Authenticating using an API key.
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
$api_key = $call_options['authdetails']['api_key'];
curl_setopt($ch, CURLOPT_USERPWD, $api_key.':nopass');
}
}
curl_setopt($ch, CURLOPT_USERAGENT, $call_options['userAgent']);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, CS_REST_SOCKET_TIMEOUT);
curl_setopt($ch, CURLOPT_TIMEOUT, CS_REST_CALL_TIMEOUT);
$inflate_response = false;
if($this->_curl_zlib) {
$this->_log->log_message('curl+zlib support available. Requesting gzipped response.',
get_class($this), CS_REST_LOG_VERBOSE);
curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
} else if(function_exists('gzinflate')) {
$headers[] = 'Accept-Encoding: gzip';
$inflate_response = true;
}
if($call_options['protocol'] === 'https') {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
if(strlen(ini_get('curl.cainfo')) === 0) {
curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__).'/cacert.pem');
}
}
switch($call_options['method']) {
case CS_REST_PUT:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, CS_REST_PUT);
$headers[] = 'Content-Length: '.strlen($call_options['data']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $call_options['data']);
break;
case CS_REST_POST:
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, isset($call_options['data']) ? $call_options['data'] : '');
break;
case CS_REST_DELETE:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, CS_REST_DELETE);
break;
}
if(count($headers) > 0) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$response = curl_exec($ch);
if(!$response && $response !== '') {
$this->_log->log_message('Error making request with curl_error: '.curl_errno($ch),
get_class($this), CS_REST_LOG_ERROR);
require_once dirname(__FILE__).'/exceptions.php';
throw new CurlException(curl_error($ch), curl_errno($ch));
}
list( $headers, $result ) = $this->split_and_inflate($response, $inflate_response);
$this->_log->log_message('API Call Info for '.$call_options['method'].' '.
curl_getinfo($ch, CURLINFO_EFFECTIVE_URL).': '.curl_getinfo($ch, CURLINFO_SIZE_UPLOAD).
' bytes uploaded. '.curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD).' bytes downloaded'.
' Total time (seconds): '.curl_getinfo($ch, CURLINFO_TOTAL_TIME),
get_class($this), CS_REST_LOG_VERBOSE);
$result = array(
'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
'response' => $result
);
curl_close($ch);
return $result;
}
}
}
if (!class_exists('CS_REST_SocketWrapper')) {
class CS_REST_SocketWrapper {
var $socket;
function open($domain, $port) {
$this->socket = fsockopen($domain, $port, $errno, $errstr, CS_REST_SOCKET_TIMEOUT);
if(!$this->socket) {
die('Error making request with '.$errno.': '.$errstr);
return false;
} else if(function_exists('stream_set_timeout')) {
stream_set_timeout($this->socket, CS_REST_SOCKET_TIMEOUT);
}
return true;
}
function write($data) {
fwrite($this->socket, $data);
}
function read() {
ob_start();
fpassthru($this->socket);
return ob_get_clean();
}
function close() {
fclose($this->socket);
}
}
}
if (!class_exists('CS_REST_SocketTransport')) {
class CS_REST_SocketTransport extends CS_REST_BaseTransport {
var $_socket_wrapper;
function __construct($log, $socket_wrapper = NULL) {
parent::__construct($log);
if(is_null($socket_wrapper)) {
$socket_wrapper = new CS_REST_SocketWrapper();
}
$this->_socket_wrapper = $socket_wrapper;
}
/**
* @return string The type of transport used
*/
function get_type() {
return 'Socket';
}
function make_call($call_options) {
$start_host = strpos($call_options['route'], $call_options['host']);
$host_len = strlen($call_options['host']);
$domain = substr($call_options['route'], $start_host, $host_len);
$host = $domain;
$path = substr($call_options['route'], $start_host + $host_len);
$protocol = substr($call_options['route'], 0, $start_host);
$port = 80;
$this->_log->log_message('Creating socket to '.$domain.' over '.$protocol.' for request to '.$path,
get_class($this), CS_REST_LOG_VERBOSE);
if($protocol === 'https://') {
$domain = 'ssl://'.$domain;
$port = 443;
}
if($this->_socket_wrapper->open($domain, $port)) {
$inflate_response = function_exists('gzinflate');
$request = $this->_build_request($call_options, $host, $path, $inflate_response);
$this->_log->log_message('Sending <pre>'.$request.'</pre> down the socket',
get_class($this), CS_REST_LOG_VERBOSE);
$this->_socket_wrapper->write($request);
$response = $this->_socket_wrapper->read();
$this->_socket_wrapper->close();
$this->_log->log_message('API Call Info for '.$call_options['method'].' '.
$call_options['route'].': '.strlen($request).
' bytes uploaded. '.strlen($response).' bytes downloaded',
get_class($this), CS_REST_LOG_VERBOSE);
list( $headers, $result ) = $this->split_and_inflate($response, $inflate_response);
$this->_log->log_message('Received headers <pre>'.$headers.'</pre>',
get_class($this), CS_REST_LOG_VERBOSE);
return array(
'code' => $this->_get_status_code($headers),
'response' => trim($result)
);
}
}
function _get_status_code($headers) {
if (preg_match('%^\s*HTTP/1\.1 (?P<code>\d{3})%', $headers, $regs)) {
$this->_log->log_message('Got HTTP Status Code: '.$regs['code'],
get_class($this), CS_REST_LOG_VERBOSE);
return $regs['code'];
}
$this->_log->log_message('Failed to get HTTP status code from request headers <pre>'.$headers.'</pre>',
get_class($this), CS_REST_LOG_ERROR);
trigger_error('Failed to get HTTP status code from request', E_USER_ERROR);
}
function _build_request($call_options, $host, $path, $accept_gzip) {
$request_auth_details = '';
if (array_key_exists('authdetails', $call_options)) {
if (array_key_exists('username', $call_options['authdetails']) &&
array_key_exists('password', $call_options['authdetails'])) {
# Authenticating using basic auth for retrieving user's API key.
$request_auth_details .= 'Authorization: Basic '.base64_encode($call_options['authdetails']['username'].':'.$call_options['authdetails']['password'])."\n";
} elseif (array_key_exists('access_token', $call_options['authdetails'])) {
# Authenticating using OAuth.
$access_token = $call_options['authdetails']['access_token'];
$request_auth_details .= 'Authorization: Bearer '.$access_token."\n";
} elseif (array_key_exists('api_key', $call_options['authdetails'])) {
# Authenticating using an API key.
$api_key = $call_options['authdetails']['api_key'];
$request_auth_details .= 'Authorization: Basic '.base64_encode($api_key.':nopass')."\n";
}
}
$request =
$call_options['method'].' '.$path." HTTP/1.1\n".
'Host: '.$host."\n".
$request_auth_details.
'User-Agent: '.$call_options['userAgent']."\n".
"Connection: Close\n".
'Content-Type: '.$call_options['contentType']."\n";
if($accept_gzip) {
$request .=
"Accept-Encoding: gzip\n";
}
if(isset($call_options['data'])) {
$request .=
'Content-Length: '.strlen($call_options['data'])."\n\n".
$call_options['data'];
}
return $request."\n\n";
}
}
}

View file

@ -0,0 +1,235 @@
<?php
require_once dirname(__FILE__).'/class/base_classes.php';
/**
* Class to access a subscribers resources from the create send API.
* This class includes functions to add and remove subscribers ,
* along with accessing statistics for a single subscriber
* @author tobyb
*
*/
if (!class_exists('CS_REST_Subscribers')) {
class CS_REST_Subscribers extends CS_REST_Wrapper_Base {
/**
* The base route of the subscriber resource.
* @var string
* @access private
*/
var $_subscribers_base_route;
/**
* Constructor.
* @param $list_id string The list id to access (Ignored for create requests)
* @param $auth_details array Authentication details to use for API calls.
* This array must take one of the following forms:
* If using OAuth to authenticate:
* array(
* 'access_token' => 'your access token',
* 'refresh_token' => 'your refresh token')
*
* Or if using an API key:
* array('api_key' => 'your api key')
* @param string $protocol The protocol to use for requests (http|https)
* @param int $debug_level The level of debugging required CS_REST_LOG_NONE | CS_REST_LOG_ERROR | CS_REST_LOG_WARNING | CS_REST_LOG_VERBOSE
* @param string $host The host to send API requests to. There is no need to change this
* @param CS_REST_Log $log The logger to use. Used for dependency injection
* @param object|null $serialiser The serialiser to use. Used for dependency injection
* @param object|null $transport The transport to use. Used for dependency injection
* @access public
*/
function __construct (
$list_id,
$auth_details,
$protocol = 'https',
$debug_level = CS_REST_LOG_NONE,
$host = 'api.createsend.com',
$log = NULL,
$serialiser = NULL,
$transport = NULL) {
parent::__construct($auth_details, $protocol, $debug_level, $host, $log, $serialiser, $transport);
$this->set_list_id($list_id);
}
/**
* Change the list id used for calls after construction
* @param $list_id
* @access public
*/
function set_list_id($list_id) {
$this->_subscribers_base_route = $this->_base_route.'subscribers/'.$list_id;
}
/**
* Adds a new subscriber to the specified list
* @param array $subscriber The subscriber details to use during creation.
* This array should be of the form
* array (
* 'EmailAddress' => The new subscribers email address
* 'Name' => The name of the new subscriber
* 'CustomFields' => array(
* array(
* 'Key' => The custom fields personalisation tag
* 'Value' => The value for this subscriber
* )
* )
* 'ConsentToTrack' => Subscriber tracking preference ("yes", "no")
* 'Resubscribe' => Whether we should resubscribe this subscriber if they already exist in the list
* 'RestartSubscriptionBasedAutoResponders' => Whether we should restart subscription based auto responders which are sent when the subscriber first subscribes to a list.
* )
* @access public
* @return CS_REST_Wrapper_Result A successful response will be empty
*/
function add($subscriber) {
return $this->post_request($this->_subscribers_base_route.'.json', $subscriber);
}
/**
* Updates an existing subscriber (email, name, state, or custom fields) in the specified list.
* The update is performed even for inactive subscribers, but will return an error in the event of the
* given email not existing in the list.
* @param string $email The email address of the susbcriber to be updated
* @param array $subscriber The subscriber details to use for the update. Empty parameters will remain unchanged
* This array should be of the form
* array (
* 'EmailAddress' => The new email address
* 'Name' => The name of the subscriber
* 'CustomFields' => array(
* array(
* 'Key' => The custom fields personalisation tag
* 'Value' => The value for this subscriber
* 'Clear' => true/false (pass true to remove this custom field. in the case of a [multi-option, select many] field, pass an option in the 'Value' field to clear that option or leave Value blank to remove all options)
* )
* )
* 'ConsentToTrack' => Subscriber tracking preference ("yes", "no")
* 'Resubscribe' => Whether we should resubscribe this subscriber if they already exist in the list
* 'RestartSubscriptionBasedAutoResponders' => Whether we should restart subscription based auto responders which are sent when the subscriber first subscribes to a list.
* )
* @access public
* @return CS_REST_Wrapper_Result A successful response will be empty
*/
function update($email, $subscriber) {
return $this->put_request($this->_subscribers_base_route.'.json?email='.urlencode($email), $subscriber);
}
/**
* Imports an array of subscribers into the current list
* @param array $subscribers An array of subscribers to import.
* This array should be of the form
* array (
* array (
* 'EmailAddress' => The new subscribers email address
* 'Name' => The name of the new subscriber
* 'CustomFields' => array(
* array(
* 'Key' => The custom fields personalisation tag
* 'Value' => The value for this subscriber
* 'Clear' => true/false (pass true to remove this custom field. in the case of a [multi-option, select many] field, pass an option in the 'Value' field to clear that option or leave Value blank to remove all options)
* )
* )
* )
* )
* @param bool $resubscribe Whether we should resubscribe any existing subscribers
* @param bool $queueSubscriptionBasedAutoResponders By default, subscription based auto responders do not trigger during an import. Pass a value of true to override this behaviour
* @param bool $restartSubscriptionBasedAutoResponders By default, subscription based auto responders will not be restarted
* @access public
* @return CS_REST_Wrapper_Result A successful response will be an object of the form
* {
* 'TotalUniqueEmailsSubmitted' => The number of unique emails submitted in the call
* 'TotalExistingSubscribers' => The number of subscribers who already existed in the list
* 'TotalNewSubscribers' => The number of new subscriptions to the list
* 'DuplicateEmailsInSubmission' => array<string> The emails which appeared more than once in the batch
* 'FailureDetails' => array (
* {
* 'EmailAddress' => The email address which failed
* 'Code' => The Create Send API Error code
* 'Message' => The reason for the failure
* }
* )
* }
*
*/
function import($subscribers, $resubscribe, $queueSubscriptionBasedAutoResponders = false, $restartSubscriptionBasedAutoResponders = false) {
$subscribers = array(
'Resubscribe' => $resubscribe,
'QueueSubscriptionBasedAutoResponders' => $queueSubscriptionBasedAutoResponders,
'Subscribers' => $subscribers,
'RestartSubscriptionBasedAutoresponders' => $restartSubscriptionBasedAutoResponders
);
return $this->post_request($this->_subscribers_base_route.'/import.json', $subscribers);
}
/**
* Gets a subscriber details, including custom fields
* @access public
* @return CS_REST_Wrapper_Result A successful response will be an object of the form
* {
* 'EmailAddress' => The subscriber email address
* 'Name' => The subscribers name
* 'Date' => The date the subscriber was added to the list
* 'State' => The current state of the subscriber
* 'CustomFields' => array(
* {
* 'Key' => The custom fields personalisation tag
* 'Value' => The custom field value for this subscriber
* }
* )
* }
*/
function get($email, $include_tracking_pref = NULL) {
return $this->get_request($this->_subscribers_base_route.'.json?email='.urlencode($email), $include_tracking_pref);
}
/**
* Gets the sending history to a specific subscriber
* @access public
* @return CS_REST_Wrapper_Result A successful response will be an object of the form
* array(
* {
* ID => The id of the email which was sent
* Type => 'Campaign'
* Name => The name of the email
* Actions => array(
* {
* Event => The type of action (Click, Open, Unsubscribe etc)
* Date => The date the event occurred
* IPAddress => The IP that the event originated from
* Detail => Any available details about the event i.e the URL for clicks
* }
* )
* }
* )
*/
function get_history($email) {
return $this->get_request($this->_subscribers_base_route.'/history.json?email='.urlencode($email));
}
/**
* Unsubscribes the given subscriber from the current list
* @param string $email The email address to unsubscribe
* @access public
* @return CS_REST_Wrapper_Result A successful response will be empty
*/
function unsubscribe($email) {
// We need to build the subscriber data structure.
$email = array(
'EmailAddress' => $email
);
return $this->post_request($this->_subscribers_base_route.'/unsubscribe.json', $email);
}
/**
* deletes the given subscriber from the current list
* @param string $email The email address to delete
* @access public
* @return CS_REST_Wrapper_Result A successful response will be empty
*/
function delete($email) {
return $this->delete_request($this->_subscribers_base_route.'.json?email='.urlencode($email));
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Abstract class for the Authentication in the API client
* @author Chris Chabot <chabotc@google.com>
*
*/
abstract class Google_Auth_Abstract
{
/**
* An utility function that first calls $this->auth->sign($request) and then
* executes makeRequest() on that signed request. Used for when a request
* should be authenticated
* @param Google_Http_Request $request
* @return Google_Http_Request $request
*/
abstract public function authenticatedRequest(Google_Http_Request $request);
abstract public function sign(Google_Http_Request $request);
}

View file

@ -0,0 +1,120 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* WARNING - this class depends on the Google App Engine PHP library
* which is 5.3 and above only, so if you include this in a PHP 5.2
* setup or one without 5.3 things will blow up.
*/
use google\appengine\api\app_identity\AppIdentityService;
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Authentication via the Google App Engine App Identity service.
*/
class Google_Auth_AppIdentity extends Google_Auth_Abstract
{
const CACHE_PREFIX = "Google_Auth_AppIdentity::";
private $client;
private $token = false;
private $tokenScopes = false;
public function __construct(Google_Client $client, $config = null)
{
$this->client = $client;
}
/**
* Retrieve an access token for the scopes supplied.
*/
public function authenticateForScope($scopes)
{
if ($this->token && $this->tokenScopes == $scopes) {
return $this->token;
}
$cacheKey = self::CACHE_PREFIX;
if (is_string($scopes)) {
$cacheKey .= $scopes;
} else if (is_array($scopes)) {
$cacheKey .= implode(":", $scopes);
}
$this->token = $this->client->getCache()->get($cacheKey);
if (!$this->token) {
$this->retrieveToken($scopes, $cacheKey);
} else if ($this->token['expiration_time'] < time()) {
$this->client->getCache()->delete($cacheKey);
$this->retrieveToken($scopes, $cacheKey);
}
$this->tokenScopes = $scopes;
return $this->token;
}
/**
* Retrieve a new access token and store it in cache
* @param mixed $scopes
* @param string $cacheKey
*/
private function retrieveToken($scopes, $cacheKey)
{
$this->token = AppIdentityService::getAccessToken($scopes);
if ($this->token) {
$this->client->getCache()->set(
$cacheKey,
$this->token
);
}
}
/**
* Perform an authenticated / signed apiHttpRequest.
* This function takes the apiHttpRequest, calls apiAuth->sign on it
* (which can modify the request in what ever way fits the auth mechanism)
* and then calls apiCurlIO::makeRequest on the signed request
*
* @param Google_Http_Request $request
* @return Google_Http_Request The resulting HTTP response including the
* responseHttpCode, responseHeaders and responseBody.
*/
public function authenticatedRequest(Google_Http_Request $request)
{
$request = $this->sign($request);
return $this->client->getIo()->makeRequest($request);
}
public function sign(Google_Http_Request $request)
{
if (!$this->token) {
// No token, so nothing to do.
return $request;
}
$this->client->getLogger()->debug('App Identity authentication');
// Add the OAuth2 header to the request
$request->setRequestHeaders(
array('Authorization' => 'Bearer ' . $this->token['access_token'])
);
return $request;
}
}

View file

@ -0,0 +1,136 @@
<?php
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Credentials object used for OAuth 2.0 Signed JWT assertion grants.
*/
class Google_Auth_AssertionCredentials
{
const MAX_TOKEN_LIFETIME_SECS = 3600;
public $serviceAccountName;
public $scopes;
public $privateKey;
public $privateKeyPassword;
public $assertionType;
public $sub;
/**
* @deprecated
* @link http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
*/
public $prn;
private $useCache;
/**
* @param $serviceAccountName
* @param $scopes array List of scopes
* @param $privateKey
* @param string $privateKeyPassword
* @param string $assertionType
* @param bool|string $sub The email address of the user for which the
* application is requesting delegated access.
* @param bool useCache Whether to generate a cache key and allow
* automatic caching of the generated token.
*/
public function __construct(
$serviceAccountName,
$scopes,
$privateKey,
$privateKeyPassword = 'notasecret',
$assertionType = 'http://oauth.net/grant_type/jwt/1.0/bearer',
$sub = false,
$useCache = true
) {
$this->serviceAccountName = $serviceAccountName;
$this->scopes = is_string($scopes) ? $scopes : implode(' ', $scopes);
$this->privateKey = $privateKey;
$this->privateKeyPassword = $privateKeyPassword;
$this->assertionType = $assertionType;
$this->sub = $sub;
$this->prn = $sub;
$this->useCache = $useCache;
}
/**
* Generate a unique key to represent this credential.
* @return string
*/
public function getCacheKey()
{
if (!$this->useCache) {
return false;
}
$h = $this->sub;
$h .= $this->assertionType;
$h .= $this->privateKey;
$h .= $this->scopes;
$h .= $this->serviceAccountName;
return md5($h);
}
public function generateAssertion()
{
$now = time();
$jwtParams = array(
'aud' => Google_Auth_OAuth2::OAUTH2_TOKEN_URI,
'scope' => $this->scopes,
'iat' => $now,
'exp' => $now + self::MAX_TOKEN_LIFETIME_SECS,
'iss' => $this->serviceAccountName,
);
if ($this->sub !== false) {
$jwtParams['sub'] = $this->sub;
} else if ($this->prn !== false) {
$jwtParams['prn'] = $this->prn;
}
return $this->makeSignedJwt($jwtParams);
}
/**
* Creates a signed JWT.
* @param array $payload
* @return string The signed JWT.
*/
private function makeSignedJwt($payload)
{
$header = array('typ' => 'JWT', 'alg' => 'RS256');
$payload = json_encode($payload);
// Handle some overzealous escaping in PHP json that seemed to cause some errors
// with claimsets.
$payload = str_replace('\/', '/', $payload);
$segments = array(
Google_Utils::urlSafeB64Encode(json_encode($header)),
Google_Utils::urlSafeB64Encode($payload)
);
$signingInput = implode('.', $segments);
$signer = new Google_Signer_P12($this->privateKey, $this->privateKeyPassword);
$signature = $signer->sign($signingInput);
$segments[] = Google_Utils::urlSafeB64Encode($signature);
return implode(".", $segments);
}
}

View file

@ -0,0 +1,146 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Authentication via built-in Compute Engine service accounts.
* The instance must be pre-configured with a service account
* and the appropriate scopes.
* @author Jonathan Parrott <jon.wayne.parrott@gmail.com>
*/
class Google_Auth_ComputeEngine extends Google_Auth_Abstract
{
const METADATA_AUTH_URL =
'http://metadata/computeMetadata/v1/instance/service-accounts/default/token';
private $client;
private $token;
public function __construct(Google_Client $client, $config = null)
{
$this->client = $client;
}
/**
* Perform an authenticated / signed apiHttpRequest.
* This function takes the apiHttpRequest, calls apiAuth->sign on it
* (which can modify the request in what ever way fits the auth mechanism)
* and then calls apiCurlIO::makeRequest on the signed request
*
* @param Google_Http_Request $request
* @return Google_Http_Request The resulting HTTP response including the
* responseHttpCode, responseHeaders and responseBody.
*/
public function authenticatedRequest(Google_Http_Request $request)
{
$request = $this->sign($request);
return $this->client->getIo()->makeRequest($request);
}
/**
* @param string $token
* @throws Google_Auth_Exception
*/
public function setAccessToken($token)
{
$token = json_decode($token, true);
if ($token == null) {
throw new Google_Auth_Exception('Could not json decode the token');
}
if (! isset($token['access_token'])) {
throw new Google_Auth_Exception("Invalid token format");
}
$token['created'] = time();
$this->token = $token;
}
public function getAccessToken()
{
return json_encode($this->token);
}
/**
* Acquires a new access token from the compute engine metadata server.
* @throws Google_Auth_Exception
*/
public function acquireAccessToken()
{
$request = new Google_Http_Request(
self::METADATA_AUTH_URL,
'GET',
array(
'Metadata-Flavor' => 'Google'
)
);
$request->disableGzip();
$response = $this->client->getIo()->makeRequest($request);
if ($response->getResponseHttpCode() == 200) {
$this->setAccessToken($response->getResponseBody());
$this->token['created'] = time();
return $this->getAccessToken();
} else {
throw new Google_Auth_Exception(
sprintf(
"Error fetching service account access token, message: '%s'",
$response->getResponseBody()
),
$response->getResponseHttpCode()
);
}
}
/**
* Include an accessToken in a given apiHttpRequest.
* @param Google_Http_Request $request
* @return Google_Http_Request
* @throws Google_Auth_Exception
*/
public function sign(Google_Http_Request $request)
{
if ($this->isAccessTokenExpired()) {
$this->acquireAccessToken();
}
$this->client->getLogger()->debug('Compute engine service account authentication');
$request->setRequestHeaders(
array('Authorization' => 'Bearer ' . $this->token['access_token'])
);
return $request;
}
/**
* Returns if the access_token is expired.
* @return bool Returns True if the access_token is expired.
*/
public function isAccessTokenExpired()
{
if (!$this->token || !isset($this->token['created'])) {
return true;
}
// If the token is set to expire in the next 30 seconds.
$expired = ($this->token['created']
+ ($this->token['expires_in'] - 30)) < time();
return $expired;
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Auth_Exception extends Google_Exception
{
}

View file

@ -0,0 +1,71 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Class to hold information about an authenticated login.
*
* @author Brian Eaton <beaton@google.com>
*/
class Google_Auth_LoginTicket
{
const USER_ATTR = "sub";
// Information from id token envelope.
private $envelope;
// Information from id token payload.
private $payload;
/**
* Creates a user based on the supplied token.
*
* @param string $envelope Header from a verified authentication token.
* @param string $payload Information from a verified authentication token.
*/
public function __construct($envelope, $payload)
{
$this->envelope = $envelope;
$this->payload = $payload;
}
/**
* Returns the numeric identifier for the user.
* @throws Google_Auth_Exception
* @return
*/
public function getUserId()
{
if (array_key_exists(self::USER_ATTR, $this->payload)) {
return $this->payload[self::USER_ATTR];
}
throw new Google_Auth_Exception("No user_id in token");
}
/**
* Returns attributes from the login ticket. This can contain
* various information about the user session.
* @return array
*/
public function getAttributes()
{
return array("envelope" => $this->envelope, "payload" => $this->payload);
}
}

646
app/api/Google/Auth/OAuth2.php Executable file
View file

@ -0,0 +1,646 @@
<?php
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Authentication class that deals with the OAuth 2 web-server authentication flow
*
*/
class Google_Auth_OAuth2 extends Google_Auth_Abstract
{
const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
const CLOCK_SKEW_SECS = 300; // five minutes in seconds
const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
const OAUTH2_ISSUER = 'accounts.google.com';
const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
/** @var Google_Auth_AssertionCredentials $assertionCredentials */
private $assertionCredentials;
/**
* @var string The state parameters for CSRF and other forgery protection.
*/
private $state;
/**
* @var array The token bundle.
*/
private $token = [];
/**
* @var Google_Client the base client
*/
private $client;
/**
* Instantiates the class, but does not initiate the login flow, leaving it
* to the discretion of the caller.
*/
public function __construct(Google_Client $client)
{
$this->client = $client;
}
/**
* Perform an authenticated / signed apiHttpRequest.
* This function takes the apiHttpRequest, calls apiAuth->sign on it
* (which can modify the request in what ever way fits the auth mechanism)
* and then calls apiCurlIO::makeRequest on the signed request
*
* @param Google_Http_Request $request
* @return Google_Http_Request The resulting HTTP response including the
* responseHttpCode, responseHeaders and responseBody.
*/
public function authenticatedRequest(Google_Http_Request $request)
{
$request = $this->sign($request);
return $this->client->getIo()->makeRequest($request);
}
/**
* @param string $code
* @param boolean $crossClient
* @throws Google_Auth_Exception
* @return string
*/
public function authenticate($code, $crossClient = false)
{
if (strlen($code) == 0) {
throw new Google_Auth_Exception("Invalid code");
}
$arguments = array(
'code' => $code,
'grant_type' => 'authorization_code',
'client_id' => $this->client->getClassConfig($this, 'client_id'),
'client_secret' => $this->client->getClassConfig($this, 'client_secret')
);
if ($crossClient !== true) {
$arguments['redirect_uri'] = $this->client->getClassConfig($this, 'redirect_uri');
}
// We got here from the redirect from a successful authorization grant,
// fetch the access token
$request = new Google_Http_Request(
self::OAUTH2_TOKEN_URI,
'POST',
array(),
$arguments
);
$request->disableGzip();
$response = $this->client->getIo()->makeRequest($request);
if ($response->getResponseHttpCode() == 200) {
$this->setAccessToken($response->getResponseBody());
$this->token['created'] = time();
return $this->getAccessToken();
} else {
$decodedResponse = json_decode($response->getResponseBody(), true);
if ($decodedResponse != null && $decodedResponse['error']) {
$errorText = $decodedResponse['error'];
if (isset($decodedResponse['error_description'])) {
$errorText .= ": " . $decodedResponse['error_description'];
}
}
throw new Google_Auth_Exception(
sprintf(
"Error fetching OAuth2 access token, message: '%s'",
$errorText
),
$response->getResponseHttpCode()
);
}
}
/**
* Create a URL to obtain user authorization.
* The authorization endpoint allows the user to first
* authenticate, and then grant/deny the access request.
* @param string $scope The scope is expressed as a list of space-delimited strings.
* @return string
*/
public function createAuthUrl($scope)
{
$params = array(
'response_type' => 'code',
'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
'client_id' => $this->client->getClassConfig($this, 'client_id'),
'scope' => $scope,
'access_type' => $this->client->getClassConfig($this, 'access_type'),
);
// Prefer prompt to approval prompt.
if ($this->client->getClassConfig($this, 'prompt')) {
$params = $this->maybeAddParam($params, 'prompt');
} else {
$params = $this->maybeAddParam($params, 'approval_prompt');
}
$params = $this->maybeAddParam($params, 'login_hint');
$params = $this->maybeAddParam($params, 'hd');
$params = $this->maybeAddParam($params, 'openid.realm');
$params = $this->maybeAddParam($params, 'include_granted_scopes');
// If the list of scopes contains plus.login, add request_visible_actions
// to auth URL.
$rva = $this->client->getClassConfig($this, 'request_visible_actions');
if (strpos($scope, 'plus.login') && strlen($rva) > 0) {
$params['request_visible_actions'] = $rva;
}
if (isset($this->state)) {
$params['state'] = $this->state;
}
return self::OAUTH2_AUTH_URL . "?" . http_build_query($params, '', '&');
}
/**
* @param string $token
* @throws Google_Auth_Exception
*/
public function setAccessToken($token)
{
$token = json_decode($token, true);
if ($token == null) {
throw new Google_Auth_Exception('Could not json decode the token');
}
if (! isset($token['access_token'])) {
throw new Google_Auth_Exception("Invalid token format");
}
$this->token = $token;
}
public function getAccessToken()
{
return json_encode($this->token);
}
public function getRefreshToken()
{
if (array_key_exists('refresh_token', $this->token)) {
return $this->token['refresh_token'];
} else {
return null;
}
}
public function setState($state)
{
$this->state = $state;
}
public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
{
$this->assertionCredentials = $creds;
}
/**
* Include an accessToken in a given apiHttpRequest.
* @param Google_Http_Request $request
* @return Google_Http_Request
* @throws Google_Auth_Exception
*/
public function sign(Google_Http_Request $request)
{
// add the developer key to the request before signing it
if ($this->client->getClassConfig($this, 'developer_key')) {
$request->setQueryParam('key', $this->client->getClassConfig($this, 'developer_key'));
}
// Cannot sign the request without an OAuth access token.
if (null == $this->token && null == $this->assertionCredentials) {
return $request;
}
// Check if the token is set to expire in the next 30 seconds
// (or has already expired).
if ($this->isAccessTokenExpired()) {
if ($this->assertionCredentials) {
$this->refreshTokenWithAssertion();
} else {
$this->client->getLogger()->debug('OAuth2 access token expired');
if (! array_key_exists('refresh_token', $this->token)) {
$error = "The OAuth 2.0 access token has expired,"
." and a refresh token is not available. Refresh tokens"
." are not returned for responses that were auto-approved.";
$this->client->getLogger()->error($error);
throw new Google_Auth_Exception($error);
}
$this->refreshToken($this->token['refresh_token']);
}
}
$this->client->getLogger()->debug('OAuth2 authentication');
// Add the OAuth2 header to the request
$request->setRequestHeaders(
array('Authorization' => 'Bearer ' . $this->token['access_token'])
);
return $request;
}
/**
* Fetches a fresh access token with the given refresh token.
* @param string $refreshToken
* @return void
*/
public function refreshToken($refreshToken)
{
$this->refreshTokenRequest(
array(
'client_id' => $this->client->getClassConfig($this, 'client_id'),
'client_secret' => $this->client->getClassConfig($this, 'client_secret'),
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
)
);
}
/**
* Fetches a fresh access token with a given assertion token.
* @param Google_Auth_AssertionCredentials $assertionCredentials optional.
* @return void
*/
public function refreshTokenWithAssertion($assertionCredentials = null)
{
if (!$assertionCredentials) {
$assertionCredentials = $this->assertionCredentials;
}
$cacheKey = $assertionCredentials->getCacheKey();
if ($cacheKey) {
// We can check whether we have a token available in the
// cache. If it is expired, we can retrieve a new one from
// the assertion.
$token = $this->client->getCache()->get($cacheKey);
if ($token) {
$this->setAccessToken($token);
}
if (!$this->isAccessTokenExpired()) {
return;
}
}
$this->client->getLogger()->debug('OAuth2 access token expired');
$this->refreshTokenRequest(
array(
'grant_type' => 'assertion',
'assertion_type' => $assertionCredentials->assertionType,
'assertion' => $assertionCredentials->generateAssertion(),
)
);
if ($cacheKey) {
// Attempt to cache the token.
$this->client->getCache()->set(
$cacheKey,
$this->getAccessToken()
);
}
}
private function refreshTokenRequest($params)
{
if (isset($params['assertion'])) {
$this->client->getLogger()->info(
'OAuth2 access token refresh with Signed JWT assertion grants.'
);
} else {
$this->client->getLogger()->info('OAuth2 access token refresh');
}
$http = new Google_Http_Request(
self::OAUTH2_TOKEN_URI,
'POST',
array(),
$params
);
$http->disableGzip();
$request = $this->client->getIo()->makeRequest($http);
$code = $request->getResponseHttpCode();
$body = $request->getResponseBody();
if (200 == $code) {
$token = json_decode($body, true);
if ($token == null) {
throw new Google_Auth_Exception("Could not json decode the access token");
}
if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
throw new Google_Auth_Exception("Invalid token format");
}
if (isset($token['id_token'])) {
$this->token['id_token'] = $token['id_token'];
}
$this->token['access_token'] = $token['access_token'];
$this->token['expires_in'] = $token['expires_in'];
$this->token['created'] = time();
} else {
throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
}
}
/**
* Revoke an OAuth2 access token or refresh token. This method will revoke the current access
* token, if a token isn't provided.
* @throws Google_Auth_Exception
* @param string|null $token The token (access token or a refresh token) that should be revoked.
* @return boolean Returns True if the revocation was successful, otherwise False.
*/
public function revokeToken($token = null)
{
if (!$token) {
if (!$this->token) {
// Not initialized, no token to actually revoke
return false;
} elseif (array_key_exists('refresh_token', $this->token)) {
$token = $this->token['refresh_token'];
} else {
$token = $this->token['access_token'];
}
}
$request = new Google_Http_Request(
self::OAUTH2_REVOKE_URI,
'POST',
array(),
"token=$token"
);
$request->disableGzip();
$response = $this->client->getIo()->makeRequest($request);
$code = $response->getResponseHttpCode();
if ($code == 200) {
$this->token = null;
return true;
}
return false;
}
/**
* Returns if the access_token is expired.
* @return bool Returns True if the access_token is expired.
*/
public function isAccessTokenExpired()
{
if (!$this->token || !isset($this->token['created'])) {
return true;
}
// If the token is set to expire in the next 30 seconds.
$expired = ($this->token['created']
+ ($this->token['expires_in'] - 30)) < time();
return $expired;
}
// Gets federated sign-on certificates to use for verifying identity tokens.
// Returns certs as array structure, where keys are key ids, and values
// are PEM encoded certificates.
private function getFederatedSignOnCerts()
{
return $this->retrieveCertsFromLocation(
$this->client->getClassConfig($this, 'federated_signon_certs_url')
);
}
/**
* Retrieve and cache a certificates file.
*
* @param $url string location
* @throws Google_Auth_Exception
* @return array certificates
*/
public function retrieveCertsFromLocation($url)
{
// If we're retrieving a local file, just grab it.
if ("http" != substr($url, 0, 4)) {
$file = file_get_contents($url);
if ($file) {
return json_decode($file, true);
} else {
throw new Google_Auth_Exception(
"Failed to retrieve verification certificates: '" .
$url . "'."
);
}
}
// This relies on makeRequest caching certificate responses.
$request = $this->client->getIo()->makeRequest(
new Google_Http_Request(
$url
)
);
if ($request->getResponseHttpCode() == 200) {
$certs = json_decode($request->getResponseBody(), true);
if ($certs) {
return $certs;
}
}
throw new Google_Auth_Exception(
"Failed to retrieve verification certificates: '" .
$request->getResponseBody() . "'.",
$request->getResponseHttpCode()
);
}
/**
* Verifies an id token and returns the authenticated apiLoginTicket.
* Throws an exception if the id token is not valid.
* The audience parameter can be used to control which id tokens are
* accepted. By default, the id token must have been issued to this OAuth2 client.
*
* @param $id_token
* @param $audience
* @return Google_Auth_LoginTicket
*/
public function verifyIdToken($id_token = null, $audience = null)
{
if (!$id_token) {
$id_token = $this->token['id_token'];
}
$certs = $this->getFederatedSignonCerts();
if (!$audience) {
$audience = $this->client->getClassConfig($this, 'client_id');
}
return $this->verifySignedJwtWithCerts(
$id_token,
$certs,
$audience,
array(self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS)
);
}
/**
* Verifies the id token, returns the verified token contents.
*
* @param $jwt string the token
* @param $certs array of certificates
* @param $required_audience string the expected consumer of the token
* @param [$issuer] the expected issues, defaults to Google
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
* @throws Google_Auth_Exception
* @return mixed token information if valid, false if not
*/
public function verifySignedJwtWithCerts(
$jwt,
$certs,
$required_audience,
$issuer = null,
$max_expiry = null
) {
if (!$max_expiry) {
// Set the maximum time we will accept a token for.
$max_expiry = self::MAX_TOKEN_LIFETIME_SECS;
}
$segments = explode(".", $jwt);
if (count($segments) != 3) {
throw new Google_Auth_Exception("Wrong number of segments in token: $jwt");
}
$signed = $segments[0] . "." . $segments[1];
$signature = Google_Utils::urlSafeB64Decode($segments[2]);
// Parse envelope.
$envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
if (!$envelope) {
throw new Google_Auth_Exception("Can't parse token envelope: " . $segments[0]);
}
// Parse token
$json_body = Google_Utils::urlSafeB64Decode($segments[1]);
$payload = json_decode($json_body, true);
if (!$payload) {
throw new Google_Auth_Exception("Can't parse token payload: " . $segments[1]);
}
// Check signature
$verified = false;
foreach ($certs as $keyName => $pem) {
$public_key = new Google_Verifier_Pem($pem);
if ($public_key->verify($signed, $signature)) {
$verified = true;
break;
}
}
if (!$verified) {
throw new Google_Auth_Exception("Invalid token signature: $jwt");
}
// Check issued-at timestamp
$iat = 0;
if (array_key_exists("iat", $payload)) {
$iat = $payload["iat"];
}
if (!$iat) {
throw new Google_Auth_Exception("No issue time in token: $json_body");
}
$earliest = $iat - self::CLOCK_SKEW_SECS;
// Check expiration timestamp
$now = time();
$exp = 0;
if (array_key_exists("exp", $payload)) {
$exp = $payload["exp"];
}
if (!$exp) {
throw new Google_Auth_Exception("No expiration time in token: $json_body");
}
if ($exp >= $now + $max_expiry) {
throw new Google_Auth_Exception(
sprintf("Expiration time too far in future: %s", $json_body)
);
}
$latest = $exp + self::CLOCK_SKEW_SECS;
if ($now < $earliest) {
throw new Google_Auth_Exception(
sprintf(
"Token used too early, %s < %s: %s",
$now,
$earliest,
$json_body
)
);
}
if ($now > $latest) {
throw new Google_Auth_Exception(
sprintf(
"Token used too late, %s > %s: %s",
$now,
$latest,
$json_body
)
);
}
// support HTTP and HTTPS issuers
// @see https://developers.google.com/identity/sign-in/web/backend-auth
$iss = $payload['iss'];
if ($issuer && !in_array($iss, (array) $issuer)) {
throw new Google_Auth_Exception(
sprintf(
"Invalid issuer, %s not in %s: %s",
$iss,
"[".implode(",", (array) $issuer)."]",
$json_body
)
);
}
// Check audience
$aud = $payload["aud"];
if ($aud != $required_audience) {
throw new Google_Auth_Exception(
sprintf(
"Wrong recipient, %s != %s:",
$aud,
$required_audience,
$json_body
)
);
}
// All good.
return new Google_Auth_LoginTicket($envelope, $payload);
}
/**
* Add a parameter to the auth params if not empty string.
*/
private function maybeAddParam($params, $name)
{
$param = $this->client->getClassConfig($this, $name);
if ($param != '') {
$params[$name] = $param;
}
return $params;
}
}

63
app/api/Google/Auth/Simple.php Executable file
View file

@ -0,0 +1,63 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Simple API access implementation. Can either be used to make requests
* completely unauthenticated, or by using a Simple API Access developer
* key.
*/
class Google_Auth_Simple extends Google_Auth_Abstract
{
private $client;
public function __construct(Google_Client $client, $config = null)
{
$this->client = $client;
}
/**
* Perform an authenticated / signed apiHttpRequest.
* This function takes the apiHttpRequest, calls apiAuth->sign on it
* (which can modify the request in what ever way fits the auth mechanism)
* and then calls apiCurlIO::makeRequest on the signed request
*
* @param Google_Http_Request $request
* @return Google_Http_Request The resulting HTTP response including the
* responseHttpCode, responseHeaders and responseBody.
*/
public function authenticatedRequest(Google_Http_Request $request)
{
$request = $this->sign($request);
return $this->io->makeRequest($request);
}
public function sign(Google_Http_Request $request)
{
$key = $this->client->getClassConfig($this, 'developer_key');
if ($key) {
$this->client->getLogger()->debug(
'Simple API Access developer key authentication'
);
$request->setQueryParam('key', $key);
}
return $request;
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Abstract storage class
*
* @author Chris Chabot <chabotc@google.com>
*/
abstract class Google_Cache_Abstract
{
abstract public function __construct(Google_Client $client);
/**
* Retrieves the data for the given key, or false if they
* key is unknown or expired
*
* @param String $key The key who's data to retrieve
* @param boolean|int $expiration Expiration time in seconds
*
*/
abstract public function get($key, $expiration = false);
/**
* Store the key => $value set. The $value is serialized
* by this function so can be of any type
*
* @param string $key Key of the data
* @param string $value data
*/
abstract public function set($key, $value);
/**
* Removes the key/data pair for the given $key
*
* @param String $key
*/
abstract public function delete($key);
}

113
app/api/Google/Cache/Apc.php Executable file
View file

@ -0,0 +1,113 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* A persistent storage class based on the APC cache, which is not
* really very persistent, as soon as you restart your web server
* the storage will be wiped, however for debugging and/or speed
* it can be useful, and cache is a lot cheaper then storage.
*
* @author Chris Chabot <chabotc@google.com>
*/
class Google_Cache_Apc extends Google_Cache_Abstract
{
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client)
{
if (! function_exists('apc_add') ) {
$error = "Apc functions not available";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
}
$this->client = $client;
}
/**
* @inheritDoc
*/
public function get($key, $expiration = false)
{
$ret = apc_fetch($key);
if ($ret === false) {
$this->client->getLogger()->debug(
'APC cache miss',
array('key' => $key)
);
return false;
}
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
$this->client->getLogger()->debug(
'APC cache miss (expired)',
array('key' => $key, 'var' => $ret)
);
$this->delete($key);
return false;
}
$this->client->getLogger()->debug(
'APC cache hit',
array('key' => $key, 'var' => $ret)
);
return $ret['data'];
}
/**
* @inheritDoc
*/
public function set($key, $value)
{
$var = array('time' => time(), 'data' => $value);
$rc = apc_store($key, $var);
if ($rc == false) {
$this->client->getLogger()->error(
'APC cache set failed',
array('key' => $key, 'var' => $var)
);
throw new Google_Cache_Exception("Couldn't store data");
}
$this->client->getLogger()->debug(
'APC cache set',
array('key' => $key, 'var' => $var)
);
}
/**
* @inheritDoc
* @param String $key
*/
public function delete($key)
{
$this->client->getLogger()->debug(
'APC cache delete',
array('key' => $key)
);
apc_delete($key);
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Cache_Exception extends Google_Exception
{
}

209
app/api/Google/Cache/File.php Executable file
View file

@ -0,0 +1,209 @@
<?php
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/*
* This class implements a basic on disk storage. While that does
* work quite well it's not the most elegant and scalable solution.
* It will also get you into a heap of trouble when you try to run
* this in a clustered environment.
*
* @author Chris Chabot <chabotc@google.com>
*/
class Google_Cache_File extends Google_Cache_Abstract
{
const MAX_LOCK_RETRIES = 10;
private $path;
private $fh;
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client)
{
$this->client = $client;
$this->path = $this->client->getClassConfig($this, 'directory');
}
public function get($key, $expiration = false)
{
$storageFile = $this->getCacheFile($key);
$data = false;
if (!file_exists($storageFile)) {
$this->client->getLogger()->debug(
'File cache miss',
array('key' => $key, 'file' => $storageFile)
);
return false;
}
if ($expiration) {
$mtime = filemtime($storageFile);
if ((time() - $mtime) >= $expiration) {
$this->client->getLogger()->debug(
'File cache miss (expired)',
array('key' => $key, 'file' => $storageFile)
);
$this->delete($key);
return false;
}
}
if ($this->acquireReadLock($storageFile)) {
if (filesize($storageFile) > 0) {
$data = fread($this->fh, filesize($storageFile));
$data = unserialize($data);
} else {
$this->client->getLogger()->debug(
'Cache file was empty',
array('file' => $storageFile)
);
}
$this->unlock($storageFile);
}
$this->client->getLogger()->debug(
'File cache hit',
array('key' => $key, 'file' => $storageFile, 'var' => $data)
);
return $data;
}
public function set($key, $value)
{
$storageFile = $this->getWriteableCacheFile($key);
if ($this->acquireWriteLock($storageFile)) {
// We serialize the whole request object, since we don't only want the
// responseContent but also the postBody used, headers, size, etc.
$data = serialize($value);
$result = fwrite($this->fh, $data);
$this->unlock($storageFile);
$this->client->getLogger()->debug(
'File cache set',
array('key' => $key, 'file' => $storageFile, 'var' => $value)
);
} else {
$this->client->getLogger()->notice(
'File cache set failed',
array('key' => $key, 'file' => $storageFile)
);
}
}
public function delete($key)
{
$file = $this->getCacheFile($key);
if (file_exists($file) && !unlink($file)) {
$this->client->getLogger()->error(
'File cache delete failed',
array('key' => $key, 'file' => $file)
);
throw new Google_Cache_Exception("Cache file could not be deleted");
}
$this->client->getLogger()->debug(
'File cache delete',
array('key' => $key, 'file' => $file)
);
}
private function getWriteableCacheFile($file)
{
return $this->getCacheFile($file, true);
}
private function getCacheFile($file, $forWrite = false)
{
return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
}
private function getCacheDir($file, $forWrite)
{
// use the first 2 characters of the hash as a directory prefix
// this should prevent slowdowns due to huge directory listings
// and thus give some basic amount of scalability
$storageDir = $this->path . '/' . substr(md5($file), 0, 2);
if ($forWrite && ! is_dir($storageDir)) {
if (! mkdir($storageDir, 0700, true)) {
$this->client->getLogger()->error(
'File cache creation failed',
array('dir' => $storageDir)
);
throw new Google_Cache_Exception("Could not create storage directory: $storageDir");
}
}
return $storageDir;
}
private function acquireReadLock($storageFile)
{
return $this->acquireLock(LOCK_SH, $storageFile);
}
private function acquireWriteLock($storageFile)
{
$rc = $this->acquireLock(LOCK_EX, $storageFile);
if (!$rc) {
$this->client->getLogger()->notice(
'File cache write lock failed',
array('file' => $storageFile)
);
$this->delete($storageFile);
}
return $rc;
}
private function acquireLock($type, $storageFile)
{
$mode = $type == LOCK_EX ? "w" : "r";
$this->fh = fopen($storageFile, $mode);
if (!$this->fh) {
$this->client->getLogger()->error(
'Failed to open file during lock acquisition',
array('file' => $storageFile)
);
return false;
}
if ($type == LOCK_EX) {
chmod($storageFile, 0600);
}
$count = 0;
while (!flock($this->fh, $type | LOCK_NB)) {
// Sleep for 10ms.
usleep(10000);
if (++$count < self::MAX_LOCK_RETRIES) {
return false;
}
}
return true;
}
public function unlock($storageFile)
{
if ($this->fh) {
flock($this->fh, LOCK_UN);
}
}
}

184
app/api/Google/Cache/Memcache.php Executable file
View file

@ -0,0 +1,184 @@
<?php
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* A persistent storage class based on the memcache, which is not
* really very persistent, as soon as you restart your memcache daemon
* the storage will be wiped.
*
* Will use either the memcache or memcached extensions, preferring
* memcached.
*
* @author Chris Chabot <chabotc@google.com>
*/
class Google_Cache_Memcache extends Google_Cache_Abstract
{
private $connection = false;
private $mc = false;
private $host;
private $port;
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client)
{
if (!function_exists('memcache_connect') && !class_exists("Memcached")) {
$error = "Memcache functions not available";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
}
$this->client = $client;
if ($client->isAppEngine()) {
// No credentials needed for GAE.
$this->mc = new Memcached();
$this->connection = true;
} else {
$this->host = $client->getClassConfig($this, 'host');
$this->port = $client->getClassConfig($this, 'port');
if (empty($this->host) || (empty($this->port) && (string) $this->port != "0")) {
$error = "You need to supply a valid memcache host and port";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
}
}
}
/**
* @inheritDoc
*/
public function get($key, $expiration = false)
{
$this->connect();
$ret = false;
if ($this->mc) {
$ret = $this->mc->get($key);
} else {
$ret = memcache_get($this->connection, $key);
}
if ($ret === false) {
$this->client->getLogger()->debug(
'Memcache cache miss',
array('key' => $key)
);
return false;
}
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
$this->client->getLogger()->debug(
'Memcache cache miss (expired)',
array('key' => $key, 'var' => $ret)
);
$this->delete($key);
return false;
}
$this->client->getLogger()->debug(
'Memcache cache hit',
array('key' => $key, 'var' => $ret)
);
return $ret['data'];
}
/**
* @inheritDoc
* @param string $key
* @param string $value
* @throws Google_Cache_Exception
*/
public function set($key, $value)
{
$this->connect();
// we store it with the cache_time default expiration so objects will at
// least get cleaned eventually.
$data = array('time' => time(), 'data' => $value);
$rc = false;
if ($this->mc) {
$rc = $this->mc->set($key, $data);
} else {
$rc = memcache_set($this->connection, $key, $data, false);
}
if ($rc == false) {
$this->client->getLogger()->error(
'Memcache cache set failed',
array('key' => $key, 'var' => $data)
);
throw new Google_Cache_Exception("Couldn't store data in cache");
}
$this->client->getLogger()->debug(
'Memcache cache set',
array('key' => $key, 'var' => $data)
);
}
/**
* @inheritDoc
* @param String $key
*/
public function delete($key)
{
$this->connect();
if ($this->mc) {
$this->mc->delete($key, 0);
} else {
memcache_delete($this->connection, $key, 0);
}
$this->client->getLogger()->debug(
'Memcache cache delete',
array('key' => $key)
);
}
/**
* Lazy initialiser for memcache connection. Uses pconnect for to take
* advantage of the persistence pool where possible.
*/
private function connect()
{
if ($this->connection) {
return;
}
if (class_exists("Memcached")) {
$this->mc = new Memcached();
$this->mc->addServer($this->host, $this->port);
$this->connection = true;
} else {
$this->connection = memcache_pconnect($this->host, $this->port);
}
if (! $this->connection) {
$error = "Couldn't connect to memcache server";
$this->client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
}
}
}

57
app/api/Google/Cache/Null.php Executable file
View file

@ -0,0 +1,57 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* A blank storage class, for cases where caching is not
* required.
*/
class Google_Cache_Null extends Google_Cache_Abstract
{
public function __construct(Google_Client $client)
{
}
/**
* @inheritDoc
*/
public function get($key, $expiration = false)
{
return false;
}
/**
* @inheritDoc
*/
public function set($key, $value)
{
// Nop.
}
/**
* @inheritDoc
* @param String $key
*/
public function delete($key)
{
// Nop.
}
}

715
app/api/Google/Client.php Executable file
View file

@ -0,0 +1,715 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/autoload.php';
}
/**
* The Google API Client
* https://github.com/google/google-api-php-client
*/
class Google_Client
{
const LIBVER = "1.1.5";
const USER_AGENT_SUFFIX = "google-api-php-client/";
/**
* @var Google_Auth_Abstract $auth
*/
private $auth;
/**
* @var Google_IO_Abstract $io
*/
private $io;
/**
* @var Google_Cache_Abstract $cache
*/
private $cache;
/**
* @var Google_Config $config
*/
private $config;
/**
* @var Google_Logger_Abstract $logger
*/
private $logger;
/**
* @var boolean $deferExecution
*/
private $deferExecution = false;
/** @var array $scopes */
// Scopes requested by the client
protected $requestedScopes = [];
// definitions of services that are discovered.
protected $services = [];
// Used to track authenticated state, can't discover services after doing authenticate()
private $authenticated = false;
/**
* Construct the Google Client.
*
* @param $config Google_Config or string for the ini file to load
*/
public function __construct($config = null)
{
if (is_string($config) && strlen($config)) {
$config = new Google_Config($config);
} else if ( !($config instanceof Google_Config)) {
$config = new Google_Config();
if ($this->isAppEngine()) {
// Automatically use Memcache if we're in AppEngine.
$config->setCacheClass('Google_Cache_Memcache');
}
if (version_compare(phpversion(), "5.3.4", "<=") || $this->isAppEngine()) {
// Automatically disable compress.zlib, as currently unsupported.
$config->setClassConfig('Google_Http_Request', 'disable_gzip', true);
}
}
if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) {
if (function_exists('curl_version') && function_exists('curl_exec')
&& !$this->isAppEngine()) {
$config->setIoClass("Google_IO_Curl");
} else {
$config->setIoClass("Google_IO_Stream");
}
}
$this->config = $config;
}
/**
* Get a string containing the version of the library.
*
* @return string
*/
public function getLibraryVersion()
{
return self::LIBVER;
}
/**
* Attempt to exchange a code for an valid authentication token.
* If $crossClient is set to true, the request body will not include
* the request_uri argument
* Helper wrapped around the OAuth 2.0 implementation.
*
* @param $code string code from accounts.google.com
* @param $crossClient boolean, whether this is a cross-client authentication
* @return string token
*/
public function authenticate($code, $crossClient = false)
{
$this->authenticated = true;
return $this->getAuth()->authenticate($code, $crossClient);
}
/**
* Loads a service account key and parameters from a JSON
* file from the Google Developer Console. Uses that and the
* given array of scopes to return an assertion credential for
* use with refreshTokenWithAssertionCredential.
*
* @param string $jsonLocation File location of the project-key.json.
* @param array $scopes The scopes to assert.
* @return Google_Auth_AssertionCredentials.
* @
*/
public function loadServiceAccountJson($jsonLocation, $scopes)
{
$data = json_decode(file_get_contents($jsonLocation));
if (isset($data->type) && $data->type == 'service_account') {
// Service Account format.
$cred = new Google_Auth_AssertionCredentials(
$data->client_email,
$scopes,
$data->private_key
);
return $cred;
} else {
throw new Google_Exception("Invalid service account JSON file.");
}
}
/**
* Set the auth config from the JSON string provided.
* This structure should match the file downloaded from
* the "Download JSON" button on in the Google Developer
* Console.
* @param string $json the configuration json
* @throws Google_Exception
*/
public function setAuthConfig($json)
{
$data = json_decode($json);
$key = isset($data->installed) ? 'installed' : 'web';
if (!isset($data->$key)) {
throw new Google_Exception("Invalid client secret JSON file.");
}
$this->setClientId($data->$key->client_id);
$this->setClientSecret($data->$key->client_secret);
if (isset($data->$key->redirect_uris)) {
$this->setRedirectUri($data->$key->redirect_uris[0]);
}
}
/**
* Set the auth config from the JSON file in the path
* provided. This should match the file downloaded from
* the "Download JSON" button on in the Google Developer
* Console.
* @param string $file the file location of the client json
*/
public function setAuthConfigFile($file)
{
$this->setAuthConfig(file_get_contents($file));
}
/**
* @throws Google_Auth_Exception
* @return array
* @visible For Testing
*/
public function prepareScopes()
{
if (empty($this->requestedScopes)) {
throw new Google_Auth_Exception("No scopes specified");
}
$scopes = implode(' ', $this->requestedScopes);
return $scopes;
}
/**
* Set the OAuth 2.0 access token using the string that resulted from calling createAuthUrl()
* or Google_Client#getAccessToken().
* @param string $accessToken JSON encoded string containing in the following format:
* {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
* "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
*/
public function setAccessToken($accessToken)
{
if ($accessToken == 'null') {
$accessToken = null;
}
$this->getAuth()->setAccessToken($accessToken);
}
/**
* Set the authenticator object
* @param Google_Auth_Abstract $auth
*/
public function setAuth(Google_Auth_Abstract $auth)
{
$this->config->setAuthClass(get_class($auth));
$this->auth = $auth;
}
/**
* Set the IO object
* @param Google_IO_Abstract $io
*/
public function setIo(Google_IO_Abstract $io)
{
$this->config->setIoClass(get_class($io));
$this->io = $io;
}
/**
* Set the Cache object
* @param Google_Cache_Abstract $cache
*/
public function setCache(Google_Cache_Abstract $cache)
{
$this->config->setCacheClass(get_class($cache));
$this->cache = $cache;
}
/**
* Set the Logger object
* @param Google_Logger_Abstract $logger
*/
public function setLogger(Google_Logger_Abstract $logger)
{
$this->config->setLoggerClass(get_class($logger));
$this->logger = $logger;
}
/**
* Construct the OAuth 2.0 authorization request URI.
* @return string
*/
public function createAuthUrl()
{
$scopes = $this->prepareScopes();
return $this->getAuth()->createAuthUrl($scopes);
}
/**
* Get the OAuth 2.0 access token.
* @return string $accessToken JSON encoded string in the following format:
* {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
* "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
*/
public function getAccessToken()
{
$token = $this->getAuth()->getAccessToken();
// The response is json encoded, so could be the string null.
// It is arguable whether this check should be here or lower
// in the library.
return (null == $token || 'null' == $token || '[]' == $token) ? null : $token;
}
/**
* Get the OAuth 2.0 refresh token.
* @return string $refreshToken refresh token or null if not available
*/
public function getRefreshToken()
{
return $this->getAuth()->getRefreshToken();
}
/**
* Returns if the access_token is expired.
* @return bool Returns True if the access_token is expired.
*/
public function isAccessTokenExpired()
{
return $this->getAuth()->isAccessTokenExpired();
}
/**
* Set OAuth 2.0 "state" parameter to achieve per-request customization.
* @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
* @param string $state
*/
public function setState($state)
{
$this->getAuth()->setState($state);
}
/**
* @param string $accessType Possible values for access_type include:
* {@code "offline"} to request offline access from the user.
* {@code "online"} to request online access from the user.
*/
public function setAccessType($accessType)
{
$this->config->setAccessType($accessType);
}
/**
* @param string $approvalPrompt Possible values for approval_prompt include:
* {@code "force"} to force the approval UI to appear. (This is the default value)
* {@code "auto"} to request auto-approval when possible.
*/
public function setApprovalPrompt($approvalPrompt)
{
$this->config->setApprovalPrompt($approvalPrompt);
}
/**
* Set the login hint, email address or sub id.
* @param string $loginHint
*/
public function setLoginHint($loginHint)
{
$this->config->setLoginHint($loginHint);
}
/**
* Set the application name, this is included in the User-Agent HTTP header.
* @param string $applicationName
*/
public function setApplicationName($applicationName)
{
$this->config->setApplicationName($applicationName);
}
/**
* Set the OAuth 2.0 Client ID.
* @param string $clientId
*/
public function setClientId($clientId)
{
$this->config->setClientId($clientId);
}
/**
* Set the OAuth 2.0 Client Secret.
* @param string $clientSecret
*/
public function setClientSecret($clientSecret)
{
$this->config->setClientSecret($clientSecret);
}
/**
* Set the OAuth 2.0 Redirect URI.
* @param string $redirectUri
*/
public function setRedirectUri($redirectUri)
{
$this->config->setRedirectUri($redirectUri);
}
/**
* If 'plus.login' is included in the list of requested scopes, you can use
* this method to define types of app activities that your app will write.
* You can find a list of available types here:
* @link https://developers.google.com/+/api/moment-types
*
* @param array $requestVisibleActions Array of app activity types
*/
public function setRequestVisibleActions($requestVisibleActions)
{
if (is_array($requestVisibleActions)) {
$requestVisibleActions = join(" ", $requestVisibleActions);
}
$this->config->setRequestVisibleActions($requestVisibleActions);
}
/**
* Set the developer key to use, these are obtained through the API Console.
* @see http://code.google.com/apis/console-help/#generatingdevkeys
* @param string $developerKey
*/
public function setDeveloperKey($developerKey)
{
$this->config->setDeveloperKey($developerKey);
}
/**
* Set the hd (hosted domain) parameter streamlines the login process for
* Google Apps hosted accounts. By including the domain of the user, you
* restrict sign-in to accounts at that domain.
* @param $hd string - the domain to use.
*/
public function setHostedDomain($hd)
{
$this->config->setHostedDomain($hd);
}
/**
* Set the prompt hint. Valid values are none, consent and select_account.
* If no value is specified and the user has not previously authorized
* access, then the user is shown a consent screen.
* @param $prompt string
*/
public function setPrompt($prompt)
{
$this->config->setPrompt($prompt);
}
/**
* openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
* 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
* an authentication request is valid.
* @param $realm string - the URL-space to use.
*/
public function setOpenidRealm($realm)
{
$this->config->setOpenidRealm($realm);
}
/**
* If this is provided with the value true, and the authorization request is
* granted, the authorization will include any previous authorizations
* granted to this user/application combination for other scopes.
* @param $include boolean - the URL-space to use.
*/
public function setIncludeGrantedScopes($include)
{
$this->config->setIncludeGrantedScopes($include);
}
/**
* Fetches a fresh OAuth 2.0 access token with the given refresh token.
* @param string $refreshToken
*/
public function refreshToken($refreshToken)
{
$this->getAuth()->refreshToken($refreshToken);
}
/**
* Revoke an OAuth2 access token or refresh token. This method will revoke the current access
* token, if a token isn't provided.
* @throws Google_Auth_Exception
* @param string|null $token The token (access token or a refresh token) that should be revoked.
* @return boolean Returns True if the revocation was successful, otherwise False.
*/
public function revokeToken($token = null)
{
return $this->getAuth()->revokeToken($token);
}
/**
* Verify an id_token. This method will verify the current id_token, if one
* isn't provided.
* @throws Google_Auth_Exception
* @param string|null $token The token (id_token) that should be verified.
* @return Google_Auth_LoginTicket Returns an apiLoginTicket if the verification was
* successful.
*/
public function verifyIdToken($token = null)
{
return $this->getAuth()->verifyIdToken($token);
}
/**
* Verify a JWT that was signed with your own certificates.
*
* @param $id_token string The JWT token
* @param $cert_location array of certificates
* @param $audience string the expected consumer of the token
* @param $issuer string the expected issuer, defaults to Google
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
* @return mixed token information if valid, false if not
*/
public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
{
$auth = new Google_Auth_OAuth2($this);
$certs = $auth->retrieveCertsFromLocation($cert_location);
return $auth->verifySignedJwtWithCerts($id_token, $certs, $audience, $issuer, $max_expiry);
}
/**
* @param $creds Google_Auth_AssertionCredentials
*/
public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
{
$this->getAuth()->setAssertionCredentials($creds);
}
/**
* Set the scopes to be requested. Must be called before createAuthUrl().
* Will remove any previously configured scopes.
* @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.login',
* 'https://www.googleapis.com/auth/moderator')
*/
public function setScopes($scopes)
{
$this->requestedScopes = [];
$this->addScope($scopes);
}
/**
* This functions adds a scope to be requested as part of the OAuth2.0 flow.
* Will append any scopes not previously requested to the scope parameter.
* A single string will be treated as a scope to request. An array of strings
* will each be appended.
* @param $scope_or_scopes string|array e.g. "profile"
*/
public function addScope($scope_or_scopes)
{
if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
$this->requestedScopes[] = $scope_or_scopes;
} else if (is_array($scope_or_scopes)) {
foreach ($scope_or_scopes as $scope) {
$this->addScope($scope);
}
}
}
/**
* Returns the list of scopes requested by the client
* @return array the list of scopes
*
*/
public function getScopes()
{
return $this->requestedScopes;
}
/**
* Declare whether batch calls should be used. This may increase throughput
* by making multiple requests in one connection.
*
* @param boolean $useBatch True if the batch support should
* be enabled. Defaults to False.
*/
public function setUseBatch($useBatch)
{
// This is actually an alias for setDefer.
$this->setDefer($useBatch);
}
/**
* Declare whether making API calls should make the call immediately, or
* return a request which can be called with ->execute();
*
* @param boolean $defer True if calls should not be executed right away.
*/
public function setDefer($defer)
{
$this->deferExecution = $defer;
}
/**
* Helper method to execute deferred HTTP requests.
*
* @param $request Google_Http_Request|Google_Http_Batch
* @throws Google_Exception
* @return object of the type of the expected class or array.
*/
public function execute($request)
{
if ($request instanceof Google_Http_Request) {
$request->setUserAgent(
$this->getApplicationName()
. " " . self::USER_AGENT_SUFFIX
. $this->getLibraryVersion()
);
if (!$this->getClassConfig("Google_Http_Request", "disable_gzip")) {
$request->enableGzip();
}
$request->maybeMoveParametersToBody();
return Google_Http_REST::execute($this, $request);
} else if ($request instanceof Google_Http_Batch) {
return $request->execute();
} else {
throw new Google_Exception("Do not know how to execute this type of object.");
}
}
/**
* Whether or not to return raw requests
* @return boolean
*/
public function shouldDefer()
{
return $this->deferExecution;
}
/**
* @return Google_Auth_Abstract Authentication implementation
*/
public function getAuth()
{
if (!isset($this->auth)) {
$class = $this->config->getAuthClass();
$this->auth = new $class($this);
}
return $this->auth;
}
/**
* @return Google_IO_Abstract IO implementation
*/
public function getIo()
{
if (!isset($this->io)) {
$class = $this->config->getIoClass();
$this->io = new $class($this);
}
return $this->io;
}
/**
* @return Google_Cache_Abstract Cache implementation
*/
public function getCache()
{
if (!isset($this->cache)) {
$class = $this->config->getCacheClass();
$this->cache = new $class($this);
}
return $this->cache;
}
/**
* @return Google_Logger_Abstract Logger implementation
*/
public function getLogger()
{
if (!isset($this->logger)) {
$class = $this->config->getLoggerClass();
$this->logger = new $class($this);
}
return $this->logger;
}
/**
* Retrieve custom configuration for a specific class.
* @param $class string|object - class or instance of class to retrieve
* @param $key string optional - key to retrieve
* @return array
*/
public function getClassConfig($class, $key = null)
{
if (!is_string($class)) {
$class = get_class($class);
}
return $this->config->getClassConfig($class, $key);
}
/**
* Set configuration specific to a given class.
* $config->setClassConfig('Google_Cache_File',
* array('directory' => '/tmp/cache'));
* @param $class string|object - The class name for the configuration
* @param $config string key or an array of configuration values
* @param $value string optional - if $config is a key, the value
*
*/
public function setClassConfig($class, $config, $value = null)
{
if (!is_string($class)) {
$class = get_class($class);
}
$this->config->setClassConfig($class, $config, $value);
}
/**
* @return string the base URL to use for calls to the APIs
*/
public function getBasePath()
{
return $this->config->getBasePath();
}
/**
* @return string the name of the application
*/
public function getApplicationName()
{
return $this->config->getApplicationName();
}
/**
* Are we running in Google AppEngine?
* return bool
*/
public function isAppEngine()
{
return (isset($_SERVER['SERVER_SOFTWARE']) &&
strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
}
}

123
app/api/Google/Collection.php Executable file
View file

@ -0,0 +1,123 @@
<?php
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/autoload.php';
}
/**
* Extension to the regular Google_Model that automatically
* exposes the items array for iteration, so you can just
* iterate over the object rather than a reference inside.
*/
class Google_Collection extends Google_Model implements Iterator, Countable
{
protected $collection_key = 'items';
/**
* @return void
*/
public function rewind()
{
if (isset($this->modelData[$this->collection_key])
&& is_array($this->modelData[$this->collection_key])) {
reset($this->modelData[$this->collection_key]);
}
}
/**
* @return mixed
*/
public function current()
{
$this->coerceType($this->key());
if (is_array($this->modelData[$this->collection_key])) {
return current($this->modelData[$this->collection_key]);
}
return null;
}
/**
* @return bool|float|int|string|null
*/
public function key()
{
if (isset($this->modelData[$this->collection_key])
&& is_array($this->modelData[$this->collection_key])) {
return key($this->modelData[$this->collection_key]);
}
return null;
}
/**
* @return mixed|void
*/
public function next()
{
return next($this->modelData[$this->collection_key]);
}
/**
* @return bool
*/
public function valid()
{
$key = $this->key();
return $key !== null && $key !== false;
}
/**
* @return int
*/
public function count()
{
if (!isset($this->modelData[$this->collection_key])) {
return 0;
}
return count($this->modelData[$this->collection_key]);
}
public function offsetExists($offset)
{
if (!is_numeric($offset)) {
return parent::offsetExists($offset);
}
return isset($this->modelData[$this->collection_key][$offset]);
}
public function offsetGet($offset)
{
if (!is_numeric($offset)) {
return parent::offsetGet($offset);
}
$this->coerceType($offset);
return $this->modelData[$this->collection_key][$offset];
}
public function offsetSet($offset, $value)
{
if (!is_numeric($offset)) {
return parent::offsetSet($offset, $value);
}
$this->modelData[$this->collection_key][$offset] = $value;
}
public function offsetUnset($offset)
{
if (!is_numeric($offset)) {
return parent::offsetUnset($offset);
}
unset($this->modelData[$this->collection_key][$offset]);
}
private function coerceType($offset)
{
$typeKey = $this->keyType($this->collection_key);
if (isset($this->$typeKey) && !is_object($this->modelData[$this->collection_key][$offset])) {
$type = $this->$typeKey;
$this->modelData[$this->collection_key][$offset] =
new $type($this->modelData[$this->collection_key][$offset]);
}
}
}

456
app/api/Google/Config.php Executable file
View file

@ -0,0 +1,456 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A class to contain the library configuration for the Google API client.
*/
class Google_Config
{
const GZIP_DISABLED = true;
const GZIP_ENABLED = false;
const GZIP_UPLOADS_ENABLED = true;
const GZIP_UPLOADS_DISABLED = false;
const USE_AUTO_IO_SELECTION = "auto";
const TASK_RETRY_NEVER = 0;
const TASK_RETRY_ONCE = 1;
const TASK_RETRY_ALWAYS = -1;
protected $configuration;
/**
* Create a new Google_Config. Can accept an ini file location with the
* local configuration. For example:
* application_name="My App"
*
* @param [$ini_file_location] - optional - The location of the ini file to load
*/
public function __construct($ini_file_location = null)
{
$this->configuration = array(
// The application_name is included in the User-Agent HTTP header.
'application_name' => '',
// Which Authentication, Storage and HTTP IO classes to use.
'auth_class' => 'Google_Auth_OAuth2',
'io_class' => self::USE_AUTO_IO_SELECTION,
'cache_class' => 'Google_Cache_File',
'logger_class' => 'Google_Logger_Null',
// Don't change these unless you're working against a special development
// or testing environment.
'base_path' => 'https://www.googleapis.com',
// Definition of class specific values, like file paths and so on.
'classes' => array(
'Google_IO_Abstract' => array(
'request_timeout_seconds' => 100,
),
'Google_IO_Curl' => array(
'disable_proxy_workaround' => false,
'options' => null,
),
'Google_Logger_Abstract' => array(
'level' => 'debug',
'log_format' => "[%datetime%] %level%: %message% %context%\n",
'date_format' => 'd/M/Y:H:i:s O',
'allow_newlines' => true
),
'Google_Logger_File' => array(
'file' => 'php://stdout',
'mode' => 0640,
'lock' => false,
),
'Google_Http_Request' => array(
// Disable the use of gzip on calls if set to true. Defaults to false.
'disable_gzip' => self::GZIP_ENABLED,
// We default gzip to disabled on uploads even if gzip is otherwise
// enabled, due to some issues seen with small packet sizes for uploads.
// Please test with this option before enabling gzip for uploads in
// a production environment.
'enable_gzip_for_uploads' => self::GZIP_UPLOADS_DISABLED,
),
// If you want to pass in OAuth 2.0 settings, they will need to be
// structured like this.
'Google_Auth_OAuth2' => array(
// Keys for OAuth 2.0 access, see the API console at
// https://developers.google.com/console
'client_id' => '',
'client_secret' => '',
'redirect_uri' => '',
// Simple API access key, also from the API console. Ensure you get
// a Server key, and not a Browser key.
'developer_key' => '',
// Other parameters.
'hd' => '',
'prompt' => '',
'openid.realm' => '',
'include_granted_scopes' => '',
'login_hint' => '',
'request_visible_actions' => '',
'access_type' => 'online',
'approval_prompt' => 'auto',
'federated_signon_certs_url' =>
'https://www.googleapis.com/oauth2/v1/certs',
),
'Google_Task_Runner' => array(
// Delays are specified in seconds
'initial_delay' => 1,
'max_delay' => 60,
// Base number for exponential backoff
'factor' => 2,
// A random number between -jitter and jitter will be added to the
// factor on each iteration to allow for better distribution of
// retries.
'jitter' => .5,
// Maximum number of retries allowed
'retries' => 0
),
'Google_Service_Exception' => array(
'retry_map' => array(
'500' => self::TASK_RETRY_ALWAYS,
'503' => self::TASK_RETRY_ALWAYS,
'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS
)
),
'Google_IO_Exception' => array(
'retry_map' => !extension_loaded('curl') ? array() : array(
CURLE_COULDNT_RESOLVE_HOST => self::TASK_RETRY_ALWAYS,
CURLE_COULDNT_CONNECT => self::TASK_RETRY_ALWAYS,
CURLE_OPERATION_TIMEOUTED => self::TASK_RETRY_ALWAYS,
CURLE_SSL_CONNECT_ERROR => self::TASK_RETRY_ALWAYS,
CURLE_GOT_NOTHING => self::TASK_RETRY_ALWAYS
)
),
// Set a default directory for the file cache.
'Google_Cache_File' => array(
'directory' => sys_get_temp_dir() . '/Google_Client'
)
),
);
if ($ini_file_location) {
$ini = parse_ini_file($ini_file_location, true);
if (is_array($ini) && count($ini)) {
$merged_configuration = $ini + $this->configuration;
if (isset($ini['classes']) && isset($this->configuration['classes'])) {
$merged_configuration['classes'] = $ini['classes'] + $this->configuration['classes'];
}
$this->configuration = $merged_configuration;
}
}
}
/**
* Set configuration specific to a given class.
* $config->setClassConfig('Google_Cache_File',
* array('directory' => '/tmp/cache'));
* @param $class string The class name for the configuration
* @param $config string key or an array of configuration values
* @param $value string optional - if $config is a key, the value
*/
public function setClassConfig($class, $config, $value = null)
{
if (!is_array($config)) {
if (!isset($this->configuration['classes'][$class])) {
$this->configuration['classes'][$class] = [];
}
$this->configuration['classes'][$class][$config] = $value;
} else {
$this->configuration['classes'][$class] = $config;
}
}
public function getClassConfig($class, $key = null)
{
if (!isset($this->configuration['classes'][$class])) {
return null;
}
if ($key === null) {
return $this->configuration['classes'][$class];
} else {
return $this->configuration['classes'][$class][$key];
}
}
/**
* Return the configured cache class.
* @return string
*/
public function getCacheClass()
{
return $this->configuration['cache_class'];
}
/**
* Return the configured logger class.
* @return string
*/
public function getLoggerClass()
{
return $this->configuration['logger_class'];
}
/**
* Return the configured Auth class.
* @return string
*/
public function getAuthClass()
{
return $this->configuration['auth_class'];
}
/**
* Set the auth class.
*
* @param $class string the class name to set
*/
public function setAuthClass($class)
{
$prev = $this->configuration['auth_class'];
if (!isset($this->configuration['classes'][$class]) &&
isset($this->configuration['classes'][$prev])) {
$this->configuration['classes'][$class] =
$this->configuration['classes'][$prev];
}
$this->configuration['auth_class'] = $class;
}
/**
* Set the IO class.
*
* @param $class string the class name to set
*/
public function setIoClass($class)
{
$prev = $this->configuration['io_class'];
if (!isset($this->configuration['classes'][$class]) &&
isset($this->configuration['classes'][$prev])) {
$this->configuration['classes'][$class] =
$this->configuration['classes'][$prev];
}
$this->configuration['io_class'] = $class;
}
/**
* Set the cache class.
*
* @param $class string the class name to set
*/
public function setCacheClass($class)
{
$prev = $this->configuration['cache_class'];
if (!isset($this->configuration['classes'][$class]) &&
isset($this->configuration['classes'][$prev])) {
$this->configuration['classes'][$class] =
$this->configuration['classes'][$prev];
}
$this->configuration['cache_class'] = $class;
}
/**
* Set the logger class.
*
* @param $class string the class name to set
*/
public function setLoggerClass($class)
{
$prev = $this->configuration['logger_class'];
if (!isset($this->configuration['classes'][$class]) &&
isset($this->configuration['classes'][$prev])) {
$this->configuration['classes'][$class] =
$this->configuration['classes'][$prev];
}
$this->configuration['logger_class'] = $class;
}
/**
* Return the configured IO class.
*
* @return string
*/
public function getIoClass()
{
return $this->configuration['io_class'];
}
/**
* Set the application name, this is included in the User-Agent HTTP header.
* @param string $name
*/
public function setApplicationName($name)
{
$this->configuration['application_name'] = $name;
}
/**
* @return string the name of the application
*/
public function getApplicationName()
{
return $this->configuration['application_name'];
}
/**
* Set the client ID for the auth class.
* @param $clientId string - the API console client ID
*/
public function setClientId($clientId)
{
$this->setAuthConfig('client_id', $clientId);
}
/**
* Set the client secret for the auth class.
* @param $secret string - the API console client secret
*/
public function setClientSecret($secret)
{
$this->setAuthConfig('client_secret', $secret);
}
/**
* Set the redirect uri for the auth class. Note that if using the
* Javascript based sign in flow, this should be the string 'postmessage'.
*
* @param $uri string - the URI that users should be redirected to
*/
public function setRedirectUri($uri)
{
$this->setAuthConfig('redirect_uri', $uri);
}
/**
* Set the app activities for the auth class.
* @param $rva string a space separated list of app activity types
*/
public function setRequestVisibleActions($rva)
{
$this->setAuthConfig('request_visible_actions', $rva);
}
/**
* Set the the access type requested (offline or online.)
* @param $access string - the access type
*/
public function setAccessType($access)
{
$this->setAuthConfig('access_type', $access);
}
/**
* Set when to show the approval prompt (auto or force)
* @param $approval string - the approval request
*/
public function setApprovalPrompt($approval)
{
$this->setAuthConfig('approval_prompt', $approval);
}
/**
* Set the login hint (email address or sub identifier)
* @param $hint string
*/
public function setLoginHint($hint)
{
$this->setAuthConfig('login_hint', $hint);
}
/**
* Set the developer key for the auth class. Note that this is separate value
* from the client ID - if it looks like a URL, its a client ID!
* @param $key string - the API console developer key
*/
public function setDeveloperKey($key)
{
$this->setAuthConfig('developer_key', $key);
}
/**
* Set the hd (hosted domain) parameter streamlines the login process for
* Google Apps hosted accounts. By including the domain of the user, you
* restrict sign-in to accounts at that domain.
*
* This should not be used to ensure security on your application - check
* the hd values within an id token (@see Google_Auth_LoginTicket) after sign
* in to ensure that the user is from the domain you were expecting.
*
* @param $hd string - the domain to use.
*/
public function setHostedDomain($hd)
{
$this->setAuthConfig('hd', $hd);
}
/**
* Set the prompt hint. Valid values are none, consent and select_account.
* If no value is specified and the user has not previously authorized
* access, then the user is shown a consent screen.
* @param $prompt string
*/
public function setPrompt($prompt)
{
$this->setAuthConfig('prompt', $prompt);
}
/**
* openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
* 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
* an authentication request is valid.
* @param $realm string - the URL-space to use.
*/
public function setOpenidRealm($realm)
{
$this->setAuthConfig('openid.realm', $realm);
}
/**
* If this is provided with the value true, and the authorization request is
* granted, the authorization will include any previous authorizations
* granted to this user/application combination for other scopes.
* @param $include boolean - the URL-space to use.
*/
public function setIncludeGrantedScopes($include)
{
$this->setAuthConfig(
'include_granted_scopes',
$include ? "true" : "false"
);
}
/**
* @return string the base URL to use for API calls
*/
public function getBasePath()
{
return $this->configuration['base_path'];
}
/**
* Set the auth configuration for the current auth class.
* @param $key - the key to set
* @param $value - the parameter value
*/
private function setAuthConfig($key, $value)
{
if (!isset($this->configuration['classes'][$this->getAuthClass()])) {
$this->configuration['classes'][$this->getAuthClass()] = [];
}
$this->configuration['classes'][$this->getAuthClass()][$key] = $value;
}
}

20
app/api/Google/Exception.php Executable file
View file

@ -0,0 +1,20 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Google_Exception extends Exception
{
}

145
app/api/Google/Http/Batch.php Executable file
View file

@ -0,0 +1,145 @@
<?php
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Class to handle batched requests to the Google API service.
*/
class Google_Http_Batch
{
/** @var string Multipart Boundary. */
private $boundary;
/** @var array service requests to be executed. */
private $requests = [];
/** @var Google_Client */
private $client;
private $expected_classes = [];
private $root_url;
private $batch_path;
public function __construct(Google_Client $client, $boundary = false, $rootUrl = '', $batchPath = '')
{
$this->client = $client;
$this->root_url = rtrim($rootUrl ? $rootUrl : $this->client->getBasePath(), '/');
$this->batch_path = $batchPath ? $batchPath : 'batch';
$this->expected_classes = [];
$boundary = (false == $boundary) ? mt_rand() : $boundary;
$this->boundary = str_replace('"', '', $boundary);
}
public function add(Google_Http_Request $request, $key = false)
{
if (false == $key) {
$key = mt_rand();
}
$this->requests[$key] = $request;
}
public function execute()
{
$body = '';
/** @var Google_Http_Request $req */
foreach ($this->requests as $key => $req) {
$body .= "--{$this->boundary}\n";
$body .= $req->toBatchString($key) . "\n\n";
$this->expected_classes["response-" . $key] = $req->getExpectedClass();
}
$body .= "--{$this->boundary}--";
$url = $this->root_url . '/' . $this->batch_path;
$httpRequest = new Google_Http_Request($url, 'POST');
$httpRequest->setRequestHeaders(
array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary)
);
$httpRequest->setPostBody($body);
$response = $this->client->getIo()->makeRequest($httpRequest);
return $this->parseResponse($response);
}
public function parseResponse(Google_Http_Request $response)
{
$contentType = $response->getResponseHeader('content-type');
$contentType = explode(';', $contentType);
$boundary = false;
foreach ($contentType as $part) {
$part = (explode('=', $part, 2));
if (isset($part[0]) && 'boundary' == trim($part[0])) {
$boundary = $part[1];
}
}
$body = $response->getResponseBody();
if ($body) {
$body = str_replace("--$boundary--", "--$boundary", $body);
$parts = explode("--$boundary", $body);
$responses = [];
foreach ($parts as $part) {
$part = trim($part);
if (!empty($part)) {
list($metaHeaders, $part) = explode("\r\n\r\n", $part, 2);
$metaHeaders = $this->client->getIo()->getHttpResponseHeaders($metaHeaders);
$status = substr($part, 0, strpos($part, "\n"));
$status = explode(" ", $status);
$status = $status[1];
list($partHeaders, $partBody) = $this->client->getIo()->ParseHttpResponse($part, false);
$response = new Google_Http_Request("");
$response->setResponseHttpCode($status);
$response->setResponseHeaders($partHeaders);
$response->setResponseBody($partBody);
// Need content id.
$key = $metaHeaders['content-id'];
if (isset($this->expected_classes[$key]) &&
strlen($this->expected_classes[$key]) > 0) {
$class = $this->expected_classes[$key];
$response->setExpectedClass($class);
}
try {
$response = Google_Http_REST::decodeHttpResponse($response, $this->client);
$responses[$key] = $response;
} catch (Google_Service_Exception $e) {
// Store the exception as the response, so successful responses
// can be processed.
$responses[$key] = $e;
}
}
}
return $responses;
}
return null;
}
}

View file

@ -0,0 +1,185 @@
<?php
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Implement the caching directives specified in rfc2616. This
* implementation is guided by the guidance offered in rfc2616-sec13.
*/
class Google_Http_CacheParser
{
public static $CACHEABLE_HTTP_METHODS = array('GET', 'HEAD');
public static $CACHEABLE_STATUS_CODES = array('200', '203', '300', '301');
/**
* Check if an HTTP request can be cached by a private local cache.
*
* @static
* @param Google_Http_Request $resp
* @return bool True if the request is cacheable.
* False if the request is uncacheable.
*/
public static function isRequestCacheable(Google_Http_Request $resp)
{
$method = $resp->getRequestMethod();
if (! in_array($method, self::$CACHEABLE_HTTP_METHODS)) {
return false;
}
// Don't cache authorized requests/responses.
// [rfc2616-14.8] When a shared cache receives a request containing an
// Authorization field, it MUST NOT return the corresponding response
// as a reply to any other request...
if ($resp->getRequestHeader("authorization")) {
return false;
}
return true;
}
/**
* Check if an HTTP response can be cached by a private local cache.
*
* @static
* @param Google_Http_Request $resp
* @return bool True if the response is cacheable.
* False if the response is un-cacheable.
*/
public static function isResponseCacheable(Google_Http_Request $resp)
{
// First, check if the HTTP request was cacheable before inspecting the
// HTTP response.
if (false == self::isRequestCacheable($resp)) {
return false;
}
$code = $resp->getResponseHttpCode();
if (! in_array($code, self::$CACHEABLE_STATUS_CODES)) {
return false;
}
// The resource is uncacheable if the resource is already expired and
// the resource doesn't have an ETag for revalidation.
$etag = $resp->getResponseHeader("etag");
if (self::isExpired($resp) && $etag == false) {
return false;
}
// [rfc2616-14.9.2] If [no-store is] sent in a response, a cache MUST NOT
// store any part of either this response or the request that elicited it.
$cacheControl = $resp->getParsedCacheControl();
if (isset($cacheControl['no-store'])) {
return false;
}
// Pragma: no-cache is an http request directive, but is occasionally
// used as a response header incorrectly.
$pragma = $resp->getResponseHeader('pragma');
if ($pragma == 'no-cache' || strpos($pragma, 'no-cache') !== false) {
return false;
}
// [rfc2616-14.44] Vary: * is extremely difficult to cache. "It implies that
// a cache cannot determine from the request headers of a subsequent request
// whether this response is the appropriate representation."
// Given this, we deem responses with the Vary header as uncacheable.
$vary = $resp->getResponseHeader('vary');
if ($vary) {
return false;
}
return true;
}
/**
* @static
* @param Google_Http_Request $resp
* @return bool True if the HTTP response is considered to be expired.
* False if it is considered to be fresh.
*/
public static function isExpired(Google_Http_Request $resp)
{
// HTTP/1.1 clients and caches MUST treat other invalid date formats,
// especially including the value “0”, as in the past.
$parsedExpires = false;
$responseHeaders = $resp->getResponseHeaders();
if (isset($responseHeaders['expires'])) {
$rawExpires = $responseHeaders['expires'];
// Check for a malformed expires header first.
if (empty($rawExpires) || (is_numeric($rawExpires) && $rawExpires <= 0)) {
return true;
}
// See if we can parse the expires header.
$parsedExpires = strtotime($rawExpires);
if (false == $parsedExpires || $parsedExpires <= 0) {
return true;
}
}
// Calculate the freshness of an http response.
$freshnessLifetime = false;
$cacheControl = $resp->getParsedCacheControl();
if (isset($cacheControl['max-age'])) {
$freshnessLifetime = $cacheControl['max-age'];
}
$rawDate = $resp->getResponseHeader('date');
$parsedDate = strtotime($rawDate);
if (empty($rawDate) || false == $parsedDate) {
// We can't default this to now, as that means future cache reads
// will always pass with the logic below, so we will require a
// date be injected if not supplied.
throw new Google_Exception("All cacheable requests must have creation dates.");
}
if (false == $freshnessLifetime && isset($responseHeaders['expires'])) {
$freshnessLifetime = $parsedExpires - $parsedDate;
}
if (false == $freshnessLifetime) {
return true;
}
// Calculate the age of an http response.
$age = max(0, time() - $parsedDate);
if (isset($responseHeaders['age'])) {
$age = max($age, strtotime($responseHeaders['age']));
}
return $freshnessLifetime <= $age;
}
/**
* Determine if a cache entry should be revalidated with by the origin.
*
* @param Google_Http_Request $response
* @return bool True if the entry is expired, else return false.
*/
public static function mustRevalidate(Google_Http_Request $response)
{
// [13.3] When a cache has a stale entry that it would like to use as a
// response to a client's request, it first has to check with the origin
// server to see if its cached entry is still usable.
return self::isExpired($response);
}
}

View file

@ -0,0 +1,341 @@
<?php
/**
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Manage large file uploads, which may be media but can be any type
* of sizable data.
*/
class Google_Http_MediaFileUpload
{
const UPLOAD_MEDIA_TYPE = 'media';
const UPLOAD_MULTIPART_TYPE = 'multipart';
const UPLOAD_RESUMABLE_TYPE = 'resumable';
/** @var string $mimeType */
private $mimeType;
/** @var string $data */
private $data;
/** @var bool $resumable */
private $resumable;
/** @var int $chunkSize */
private $chunkSize;
/** @var int $size */
private $size;
/** @var string $resumeUri */
private $resumeUri;
/** @var int $progress */
private $progress;
/** @var Google_Client */
private $client;
/** @var Google_Http_Request */
private $request;
/** @var string */
private $boundary;
/**
* Result code from last HTTP call
* @var int
*/
private $httpResultCode;
/**
* @param $mimeType string
* @param $data string The bytes you want to upload.
* @param $resumable bool
* @param bool $chunkSize File will be uploaded in chunks of this many bytes.
* only used if resumable=True
*/
public function __construct(
Google_Client $client,
Google_Http_Request $request,
$mimeType,
$data,
$resumable = false,
$chunkSize = false,
$boundary = false
) {
$this->client = $client;
$this->request = $request;
$this->mimeType = $mimeType;
$this->data = $data;
$this->size = strlen($this->data);
$this->resumable = $resumable;
if (!$chunkSize) {
$chunkSize = 256 * 1024;
}
$this->chunkSize = $chunkSize;
$this->progress = 0;
$this->boundary = $boundary;
// Process Media Request
$this->process();
}
/**
* Set the size of the file that is being uploaded.
* @param $size - int file size in bytes
*/
public function setFileSize($size)
{
$this->size = $size;
}
/**
* Return the progress on the upload
* @return int progress in bytes uploaded.
*/
public function getProgress()
{
return $this->progress;
}
/**
* Return the HTTP result code from the last call made.
* @return int code
*/
public function getHttpResultCode()
{
return $this->httpResultCode;
}
/**
* Sends a PUT-Request to google drive and parses the response,
* setting the appropiate variables from the response()
*
* @param Google_Http_Request $httpRequest the Reuqest which will be send
*
* @return false|mixed false when the upload is unfinished or the decoded http response
*
*/
private function makePutRequest(Google_Http_Request $httpRequest)
{
if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) {
$httpRequest->enableGzip();
} else {
$httpRequest->disableGzip();
}
$response = $this->client->getIo()->makeRequest($httpRequest);
$response->setExpectedClass($this->request->getExpectedClass());
$code = $response->getResponseHttpCode();
$this->httpResultCode = $code;
if (308 == $code) {
// Track the amount uploaded.
$range = explode('-', $response->getResponseHeader('range'));
$this->progress = $range[1] + 1;
// Allow for changing upload URLs.
$location = $response->getResponseHeader('location');
if ($location) {
$this->resumeUri = $location;
}
// No problems, but upload not complete.
return false;
} else {
return Google_Http_REST::decodeHttpResponse($response, $this->client);
}
}
/**
* Send the next part of the file to upload.
* @param [$chunk] the next set of bytes to send. If false will used $data passed
* at construct time.
*/
public function nextChunk($chunk = false)
{
if (false == $this->resumeUri) {
$this->resumeUri = $this->fetchResumeUri();
}
if (false == $chunk) {
$chunk = substr($this->data, $this->progress, $this->chunkSize);
}
$lastBytePos = $this->progress + strlen($chunk) - 1;
$headers = array(
'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
'content-type' => $this->request->getRequestHeader('content-type'),
'content-length' => $this->chunkSize,
'expect' => '',
);
$httpRequest = new Google_Http_Request(
$this->resumeUri,
'PUT',
$headers,
$chunk
);
return $this->makePutRequest($httpRequest);
}
/**
* Resume a previously unfinished upload
* @param $resumeUri the resume-URI of the unfinished, resumable upload.
*/
public function resume($resumeUri)
{
$this->resumeUri = $resumeUri;
$headers = array(
'content-range' => "bytes */$this->size",
'content-length' => 0,
);
$httpRequest = new Google_Http_Request(
$this->resumeUri,
'PUT',
$headers
);
return $this->makePutRequest($httpRequest);
}
/**
* @return array|bool
* @visible for testing
*/
private function process()
{
$postBody = false;
$contentType = false;
$meta = $this->request->getPostBody();
$meta = is_string($meta) ? json_decode($meta, true) : $meta;
$uploadType = $this->getUploadType($meta);
$this->request->setQueryParam('uploadType', $uploadType);
$this->transformToUploadUrl();
$mimeType = $this->mimeType ?
$this->mimeType :
$this->request->getRequestHeader('content-type');
if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
$contentType = $mimeType;
$postBody = is_string($meta) ? $meta : json_encode($meta);
} else if (self::UPLOAD_MEDIA_TYPE == $uploadType) {
$contentType = $mimeType;
$postBody = $this->data;
} else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
// This is a multipart/related upload.
$boundary = $this->boundary ? $this->boundary : mt_rand();
$boundary = str_replace('"', '', $boundary);
$contentType = 'multipart/related; boundary=' . $boundary;
$related = "--$boundary\r\n";
$related .= "Content-Type: application/json; charset=UTF-8\r\n";
$related .= "\r\n" . json_encode($meta) . "\r\n";
$related .= "--$boundary\r\n";
$related .= "Content-Type: $mimeType\r\n";
$related .= "Content-Transfer-Encoding: base64\r\n";
$related .= "\r\n" . base64_encode($this->data) . "\r\n";
$related .= "--$boundary--";
$postBody = $related;
}
$this->request->setPostBody($postBody);
if (isset($contentType) && $contentType) {
$contentTypeHeader['content-type'] = $contentType;
$this->request->setRequestHeaders($contentTypeHeader);
}
}
private function transformToUploadUrl()
{
$base = $this->request->getBaseComponent();
$this->request->setBaseComponent($base . '/upload');
}
/**
* Valid upload types:
* - resumable (UPLOAD_RESUMABLE_TYPE)
* - media (UPLOAD_MEDIA_TYPE)
* - multipart (UPLOAD_MULTIPART_TYPE)
* @param $meta
* @return string
* @visible for testing
*/
public function getUploadType($meta)
{
if ($this->resumable) {
return self::UPLOAD_RESUMABLE_TYPE;
}
if (false == $meta && $this->data) {
return self::UPLOAD_MEDIA_TYPE;
}
return self::UPLOAD_MULTIPART_TYPE;
}
public function getResumeUri()
{
return ( $this->resumeUri !== null ? $this->resumeUri : $this->fetchResumeUri() );
}
private function fetchResumeUri()
{
$result = null;
$body = $this->request->getPostBody();
if ($body) {
$headers = array(
'content-type' => 'application/json; charset=UTF-8',
'content-length' => Google_Utils::getStrLen($body),
'x-upload-content-type' => $this->mimeType,
'x-upload-content-length' => $this->size,
'expect' => '',
);
$this->request->setRequestHeaders($headers);
}
$response = $this->client->getIo()->makeRequest($this->request);
$location = $response->getResponseHeader('location');
$code = $response->getResponseHttpCode();
if (200 == $code && true == $location) {
return $location;
}
$message = $code;
$body = @json_decode($response->getResponseBody());
if (!empty($body->error->errors) ) {
$message .= ': ';
foreach ($body->error->errors as $error) {
$message .= "{$error->domain}, {$error->message};";
}
$message = rtrim($message, ';');
}
$error = "Failed to start the resumable upload (HTTP {$message})";
$this->client->getLogger()->error($error);
throw new Google_Exception($error);
}
public function setChunkSize($chunkSize)
{
$this->chunkSize = $chunkSize;
}
}

178
app/api/Google/Http/REST.php Executable file
View file

@ -0,0 +1,178 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* This class implements the RESTful transport of apiServiceRequest()'s
*/
class Google_Http_REST
{
/**
* Executes a Google_Http_Request and (if applicable) automatically retries
* when errors occur.
*
* @param Google_Client $client
* @param Google_Http_Request $req
* @return array decoded result
* @throws Google_Service_Exception on server side error (ie: not authenticated,
* invalid or malformed post body, invalid url)
*/
public static function execute(Google_Client $client, Google_Http_Request $req)
{
$runner = new Google_Task_Runner(
$client,
sprintf('%s %s', $req->getRequestMethod(), $req->getUrl()),
array(get_class(), 'doExecute'),
array($client, $req)
);
return $runner->run();
}
/**
* Executes a Google_Http_Request
*
* @param Google_Client $client
* @param Google_Http_Request $req
* @return array decoded result
* @throws Google_Service_Exception on server side error (ie: not authenticated,
* invalid or malformed post body, invalid url)
*/
public static function doExecute(Google_Client $client, Google_Http_Request $req)
{
$httpRequest = $client->getIo()->makeRequest($req);
$httpRequest->setExpectedClass($req->getExpectedClass());
return self::decodeHttpResponse($httpRequest, $client);
}
/**
* Decode an HTTP Response.
* @static
* @throws Google_Service_Exception
* @param Google_Http_Request $response The http response to be decoded.
* @param Google_Client $client
* @return mixed|null
*/
public static function decodeHttpResponse($response, Google_Client $client = null)
{
$code = $response->getResponseHttpCode();
$body = $response->getResponseBody();
$decoded = null;
if ((intVal($code)) >= 300) {
$decoded = json_decode($body, true);
$err = 'Error calling ' . $response->getRequestMethod() . ' ' . $response->getUrl();
if (isset($decoded['error']) &&
isset($decoded['error']['message']) &&
isset($decoded['error']['code'])) {
// if we're getting a json encoded error definition, use that instead of the raw response
// body for improved readability
$err .= ": ({$decoded['error']['code']}) {$decoded['error']['message']}";
} else {
$err .= ": ($code) $body";
}
$errors = null;
// Specific check for APIs which don't return error details, such as Blogger.
if (isset($decoded['error']) && isset($decoded['error']['errors'])) {
$errors = $decoded['error']['errors'];
}
$map = null;
if ($client) {
$client->getLogger()->error(
$err,
array('code' => $code, 'errors' => $errors)
);
$map = $client->getClassConfig(
'Google_Service_Exception',
'retry_map'
);
}
throw new Google_Service_Exception($err, $code, null, $errors, $map);
}
// Only attempt to decode the response, if the response code wasn't (204) 'no content'
if ($code != '204') {
if ($response->getExpectedRaw()) {
return $body;
}
$decoded = json_decode($body, true);
if ($decoded === null || $decoded === "") {
$error = "Invalid json in service response: $body";
if ($client) {
$client->getLogger()->error($error);
}
throw new Google_Service_Exception($error);
}
if ($response->getExpectedClass()) {
$class = $response->getExpectedClass();
$decoded = new $class($decoded);
}
}
return $decoded;
}
/**
* Parse/expand request parameters and create a fully qualified
* request uri.
* @static
* @param string $servicePath
* @param string $restPath
* @param array $params
* @return string $requestUrl
*/
public static function createRequestUri($servicePath, $restPath, $params)
{
$requestUrl = $servicePath . $restPath;
$uriTemplateVars = [];
$queryVars = [];
foreach ($params as $paramName => $paramSpec) {
if ($paramSpec['type'] == 'boolean') {
$paramSpec['value'] = ($paramSpec['value']) ? 'true' : 'false';
}
if ($paramSpec['location'] == 'path') {
$uriTemplateVars[$paramName] = $paramSpec['value'];
} else if ($paramSpec['location'] == 'query') {
if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) {
foreach ($paramSpec['value'] as $value) {
$queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value));
}
} else {
$queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value']));
}
}
}
if (count($uriTemplateVars)) {
$uriTemplateParser = new Google_Utils_URITemplate();
$requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars);
}
if (count($queryVars)) {
$requestUrl .= '?' . implode('&', $queryVars);
}
return $requestUrl;
}
}

504
app/api/Google/Http/Request.php Executable file
View file

@ -0,0 +1,504 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* HTTP Request to be executed by IO classes. Upon execution, the
* responseHttpCode, responseHeaders and responseBody will be filled in.
*
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*
*/
class Google_Http_Request
{
const GZIP_UA = " (gzip)";
private $batchHeaders = array(
'Content-Type' => 'application/http',
'Content-Transfer-Encoding' => 'binary',
'MIME-Version' => '1.0',
);
protected $queryParams;
protected $requestMethod;
protected $requestHeaders;
protected $baseComponent = null;
protected $path;
protected $postBody;
protected $userAgent = '';
protected $canGzip = null;
protected $responseHttpCode;
protected $responseHeaders;
protected $responseBody;
protected $expectedClass;
protected $expectedRaw = false;
public $accessKey;
public function __construct(
$url,
$method = 'GET',
$headers = array(),
$postBody = null
) {
$this->setUrl($url);
$this->setRequestMethod($method);
$this->setRequestHeaders($headers);
$this->setPostBody($postBody);
}
/**
* Misc function that returns the base url component of the $url
* used by the OAuth signing class to calculate the base string
* @return string The base url component of the $url.
*/
public function getBaseComponent()
{
return $this->baseComponent;
}
/**
* Set the base URL that path and query parameters will be added to.
* @param $baseComponent string
*/
public function setBaseComponent($baseComponent)
{
$this->baseComponent = rtrim($baseComponent, '/');
}
/**
* Enable support for gzipped responses with this request.
*/
public function enableGzip()
{
$this->setRequestHeaders(array("Accept-Encoding" => "gzip"));
$this->canGzip = true;
$this->setUserAgent($this->userAgent);
}
/**
* Disable support for gzip responses with this request.
*/
public function disableGzip()
{
if (
isset($this->requestHeaders['accept-encoding']) &&
$this->requestHeaders['accept-encoding'] == "gzip"
) {
unset($this->requestHeaders['accept-encoding']);
}
$this->canGzip = false;
$this->userAgent = str_replace(self::GZIP_UA, "", $this->userAgent);
}
/**
* Can this request accept a gzip response?
* @return bool
*/
public function canGzip()
{
return $this->canGzip;
}
/**
* Misc function that returns an array of the query parameters of the current
* url used by the OAuth signing class to calculate the signature
* @return array Query parameters in the query string.
*/
public function getQueryParams()
{
return $this->queryParams;
}
/**
* Set a new query parameter.
* @param $key - string to set, does not need to be URL encoded
* @param $value - string to set, does not need to be URL encoded
*/
public function setQueryParam($key, $value)
{
$this->queryParams[$key] = $value;
}
/**
* @return string HTTP Response Code.
*/
public function getResponseHttpCode()
{
return (int) $this->responseHttpCode;
}
/**
* @param int $responseHttpCode HTTP Response Code.
*/
public function setResponseHttpCode($responseHttpCode)
{
$this->responseHttpCode = $responseHttpCode;
}
/**
* @return $responseHeaders (array) HTTP Response Headers.
*/
public function getResponseHeaders()
{
return $this->responseHeaders;
}
/**
* @return string HTTP Response Body
*/
public function getResponseBody()
{
return $this->responseBody;
}
/**
* Set the class the response to this request should expect.
*
* @param $class string the class name
*/
public function setExpectedClass($class)
{
$this->expectedClass = $class;
}
/**
* Retrieve the expected class the response should expect.
* @return string class name
*/
public function getExpectedClass()
{
return $this->expectedClass;
}
/**
* Enable expected raw response
*/
public function enableExpectedRaw()
{
$this->expectedRaw = true;
}
/**
* Disable expected raw response
*/
public function disableExpectedRaw()
{
$this->expectedRaw = false;
}
/**
* Expected raw response or not.
* @return boolean expected raw response
*/
public function getExpectedRaw()
{
return $this->expectedRaw;
}
/**
* @param array $headers The HTTP response headers
* to be normalized.
*/
public function setResponseHeaders($headers)
{
$headers = Google_Utils::normalize($headers);
if ($this->responseHeaders) {
$headers = array_merge($this->responseHeaders, $headers);
}
$this->responseHeaders = $headers;
}
/**
* @param string $key
* @return array|boolean Returns the requested HTTP header or
* false if unavailable.
*/
public function getResponseHeader($key)
{
return isset($this->responseHeaders[$key])
? $this->responseHeaders[$key]
: false;
}
/**
* @param string $responseBody The HTTP response body.
*/
public function setResponseBody($responseBody)
{
$this->responseBody = $responseBody;
}
/**
* @return string $url The request URL.
*/
public function getUrl()
{
return $this->baseComponent . $this->path .
(count($this->queryParams) ?
"?" . $this->buildQuery($this->queryParams) :
'');
}
/**
* @return string $method HTTP Request Method.
*/
public function getRequestMethod()
{
return $this->requestMethod;
}
/**
* @return array $headers HTTP Request Headers.
*/
public function getRequestHeaders()
{
return $this->requestHeaders;
}
/**
* @param string $key
* @return array|boolean Returns the requested HTTP header or
* false if unavailable.
*/
public function getRequestHeader($key)
{
return isset($this->requestHeaders[$key])
? $this->requestHeaders[$key]
: false;
}
/**
* @return string $postBody HTTP Request Body.
*/
public function getPostBody()
{
return $this->postBody;
}
/**
* @param string $url the url to set
*/
public function setUrl($url)
{
if (substr($url, 0, 4) != 'http') {
// Force the path become relative.
if (substr($url, 0, 1) !== '/') {
$url = '/' . $url;
}
}
$parts = parse_url($url);
if (isset($parts['host'])) {
$this->baseComponent = sprintf(
"%s%s%s",
isset($parts['scheme']) ? $parts['scheme'] . "://" : '',
isset($parts['host']) ? $parts['host'] : '',
isset($parts['port']) ? ":" . $parts['port'] : ''
);
}
$this->path = isset($parts['path']) ? $parts['path'] : '';
$this->queryParams = [];
if (isset($parts['query'])) {
$this->queryParams = $this->parseQuery($parts['query']);
}
}
/**
* @param string $method Set he HTTP Method and normalize
* it to upper-case, as required by HTTP.
*
*/
public function setRequestMethod($method)
{
$this->requestMethod = strtoupper($method);
}
/**
* @param array $headers The HTTP request headers
* to be set and normalized.
*/
public function setRequestHeaders($headers)
{
$headers = Google_Utils::normalize($headers);
if ($this->requestHeaders) {
$headers = array_merge($this->requestHeaders, $headers);
}
$this->requestHeaders = $headers;
}
/**
* @param string $postBody the postBody to set
*/
public function setPostBody($postBody)
{
$this->postBody = $postBody;
}
/**
* Set the User-Agent Header.
* @param string $userAgent The User-Agent.
*/
public function setUserAgent($userAgent)
{
$this->userAgent = $userAgent;
if ($this->canGzip) {
$this->userAgent = $userAgent . self::GZIP_UA;
}
}
/**
* @return string The User-Agent.
*/
public function getUserAgent()
{
return $this->userAgent;
}
/**
* Returns a cache key depending on if this was an OAuth signed request
* in which case it will use the non-signed url and access key to make this
* cache key unique per authenticated user, else use the plain request url
* @return string The md5 hash of the request cache key.
*/
public function getCacheKey()
{
$key = $this->getUrl();
if (isset($this->accessKey)) {
$key .= $this->accessKey;
}
if (isset($this->requestHeaders['authorization'])) {
$key .= $this->requestHeaders['authorization'];
}
return md5($key);
}
public function getParsedCacheControl()
{
$parsed = [];
$rawCacheControl = $this->getResponseHeader('cache-control');
if ($rawCacheControl) {
$rawCacheControl = str_replace(', ', '&', $rawCacheControl);
parse_str($rawCacheControl, $parsed);
}
return $parsed;
}
/**
* @param string $id
* @return string A string representation of the HTTP Request.
*/
public function toBatchString($id)
{
$str = '';
$path = parse_url($this->getUrl(), PHP_URL_PATH) . "?" .
http_build_query($this->queryParams);
$str .= $this->getRequestMethod() . ' ' . $path . " HTTP/1.1\n";
foreach ($this->getRequestHeaders() as $key => $val) {
$str .= $key . ': ' . $val . "\n";
}
if ($this->getPostBody()) {
$str .= "\n";
$str .= $this->getPostBody();
}
$headers = '';
foreach ($this->batchHeaders as $key => $val) {
$headers .= $key . ': ' . $val . "\n";
}
$headers .= "Content-ID: $id\n";
$str = $headers . "\n" . $str;
return $str;
}
/**
* Our own version of parse_str that allows for multiple variables
* with the same name.
* @param $string - the query string to parse
*/
private function parseQuery($string)
{
$return = [];
$parts = explode("&", $string);
foreach ($parts as $part) {
list($key, $value) = explode('=', $part, 2);
$value = urldecode($value);
if (isset($return[$key])) {
if (!is_array($return[$key])) {
$return[$key] = array($return[$key]);
}
$return[$key][] = $value;
} else {
$return[$key] = $value;
}
}
return $return;
}
/**
* A version of build query that allows for multiple
* duplicate keys.
* @param $parts array of key value pairs
*/
private function buildQuery($parts)
{
$return = [];
foreach ($parts as $key => $value) {
if (is_array($value)) {
foreach ($value as $v) {
$return[] = urlencode($key) . "=" . urlencode($v);
}
} else {
$return[] = urlencode($key) . "=" . urlencode($value);
}
}
return implode('&', $return);
}
/**
* If we're POSTing and have no body to send, we can send the query
* parameters in there, which avoids length issues with longer query
* params.
*/
public function maybeMoveParametersToBody()
{
if ($this->getRequestMethod() == "POST" && empty($this->postBody)) {
$this->setRequestHeaders(
array(
"content-type" =>
"application/x-www-form-urlencoded; charset=UTF-8"
)
);
$this->setPostBody($this->buildQuery($this->queryParams));
$this->queryParams = [];
}
}
}

339
app/api/Google/IO/Abstract.php Executable file
View file

@ -0,0 +1,339 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Abstract IO base class
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
abstract class Google_IO_Abstract
{
const UNKNOWN_CODE = 0;
const FORM_URLENCODED = 'application/x-www-form-urlencoded';
private static $CONNECTION_ESTABLISHED_HEADERS = array(
"HTTP/1.0 200 Connection established\r\n\r\n",
"HTTP/1.1 200 Connection established\r\n\r\n",
);
private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
private static $HOP_BY_HOP = array(
'connection' => true,
'keep-alive' => true,
'proxy-authenticate' => true,
'proxy-authorization' => true,
'te' => true,
'trailers' => true,
'transfer-encoding' => true,
'upgrade' => true
);
/** @var Google_Client */
protected $client;
public function __construct(Google_Client $client)
{
$this->client = $client;
$timeout = $client->getClassConfig('Google_IO_Abstract', 'request_timeout_seconds');
if ($timeout > 0) {
$this->setTimeout($timeout);
}
}
/**
* Executes a Google_Http_Request
* @param Google_Http_Request $request the http request to be executed
* @return array containing response headers, body, and http code
* @throws Google_IO_Exception on curl or IO error
*/
abstract public function executeRequest(Google_Http_Request $request);
/**
* Set options that update the transport implementation's behavior.
* @param $options
*/
abstract public function setOptions($options);
/**
* Set the maximum request time in seconds.
* @param $timeout in seconds
*/
abstract public function setTimeout($timeout);
/**
* Get the maximum request time in seconds.
* @return timeout in seconds
*/
abstract public function getTimeout();
/**
* Test for the presence of a cURL header processing bug
*
* The cURL bug was present in versions prior to 7.30.0 and caused the header
* length to be miscalculated when a "Connection established" header added by
* some proxies was present.
*
* @return boolean
*/
abstract protected function needsQuirk();
/**
* @visible for testing.
* Cache the response to an HTTP request if it is cacheable.
* @param Google_Http_Request $request
* @return bool Returns true if the insertion was successful.
* Otherwise, return false.
*/
public function setCachedRequest(Google_Http_Request $request)
{
// Determine if the request is cacheable.
if (Google_Http_CacheParser::isResponseCacheable($request)) {
$this->client->getCache()->set($request->getCacheKey(), $request);
return true;
}
return false;
}
/**
* Execute an HTTP Request
*
* @param Google_Http_Request $request the http request to be executed
* @return Google_Http_Request http request with the response http code,
* response headers and response body filled in
* @throws Google_IO_Exception on curl or IO error
*/
public function makeRequest(Google_Http_Request $request)
{
// First, check to see if we have a valid cached version.
$cached = $this->getCachedRequest($request);
if ($cached !== false && $cached instanceof Google_Http_Request) {
if (!$this->checkMustRevalidateCachedRequest($cached, $request)) {
return $cached;
}
}
if (array_key_exists($request->getRequestMethod(), self::$ENTITY_HTTP_METHODS)) {
$request = $this->processEntityRequest($request);
}
list($responseData, $responseHeaders, $respHttpCode) = $this->executeRequest($request);
if ($respHttpCode == 304 && $cached) {
// If the server responded NOT_MODIFIED, return the cached request.
$this->updateCachedRequest($cached, $responseHeaders);
return $cached;
}
if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
$responseHeaders['date'] = date("r");
}
$request->setResponseHttpCode($respHttpCode);
$request->setResponseHeaders($responseHeaders);
$request->setResponseBody($responseData);
// Store the request in cache (the function checks to see if the request
// can actually be cached)
$this->setCachedRequest($request);
return $request;
}
/**
* @visible for testing.
* @param Google_Http_Request $request
* @return Google_Http_Request|bool Returns the cached object or
* false if the operation was unsuccessful.
*/
public function getCachedRequest(Google_Http_Request $request)
{
if (false === Google_Http_CacheParser::isRequestCacheable($request)) {
return false;
}
return $this->client->getCache()->get($request->getCacheKey());
}
/**
* @visible for testing
* Process an http request that contains an enclosed entity.
* @param Google_Http_Request $request
* @return Google_Http_Request Processed request with the enclosed entity.
*/
public function processEntityRequest(Google_Http_Request $request)
{
$postBody = $request->getPostBody();
$contentType = $request->getRequestHeader("content-type");
// Set the default content-type as application/x-www-form-urlencoded.
if (false == $contentType) {
$contentType = self::FORM_URLENCODED;
$request->setRequestHeaders(array('content-type' => $contentType));
}
// Force the payload to match the content-type asserted in the header.
if ($contentType == self::FORM_URLENCODED && is_array($postBody)) {
$postBody = http_build_query($postBody, '', '&');
$request->setPostBody($postBody);
}
// Make sure the content-length header is set.
if (!$postBody || is_string($postBody)) {
$postsLength = strlen($postBody);
$request->setRequestHeaders(array('content-length' => $postsLength));
}
return $request;
}
/**
* Check if an already cached request must be revalidated, and if so update
* the request with the correct ETag headers.
* @param Google_Http_Request $cached A previously cached response.
* @param Google_Http_Request $request The outbound request.
* return bool If the cached object needs to be revalidated, false if it is
* still current and can be re-used.
*/
protected function checkMustRevalidateCachedRequest($cached, $request)
{
if (Google_Http_CacheParser::mustRevalidate($cached)) {
$addHeaders = [];
if ($cached->getResponseHeader('etag')) {
// [13.3.4] If an entity tag has been provided by the origin server,
// we must use that entity tag in any cache-conditional request.
$addHeaders['If-None-Match'] = $cached->getResponseHeader('etag');
} elseif ($cached->getResponseHeader('date')) {
$addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date');
}
$request->setRequestHeaders($addHeaders);
return true;
} else {
return false;
}
}
/**
* Update a cached request, using the headers from the last response.
* @param Google_Http_Request $cached A previously cached response.
* @param mixed Associative array of response headers from the last request.
*/
protected function updateCachedRequest($cached, $responseHeaders)
{
$hopByHop = self::$HOP_BY_HOP;
if (!empty($responseHeaders['connection'])) {
$connectionHeaders = array_map(
'strtolower',
array_filter(
array_map('trim', explode(',', $responseHeaders['connection']))
)
);
$hopByHop += array_fill_keys($connectionHeaders, true);
}
$endToEnd = array_diff_key($responseHeaders, $hopByHop);
$cached->setResponseHeaders($endToEnd);
}
/**
* Used by the IO lib and also the batch processing.
*
* @param $respData
* @param $headerSize
* @return array
*/
public function parseHttpResponse($respData, $headerSize)
{
// check proxy header
foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
if (stripos($respData, $established_header) !== false) {
// existed, remove it
$respData = str_ireplace($established_header, '', $respData);
// Subtract the proxy header size unless the cURL bug prior to 7.30.0
// is present which prevented the proxy header size from being taken into
// account.
if (!$this->needsQuirk()) {
$headerSize -= strlen($established_header);
}
break;
}
}
if ($headerSize) {
$responseBody = substr($respData, $headerSize);
$responseHeaders = substr($respData, 0, $headerSize);
} else {
$responseSegments = explode("\r\n\r\n", $respData, 2);
$responseHeaders = $responseSegments[0];
$responseBody = isset($responseSegments[1]) ? $responseSegments[1] :
null;
}
$responseHeaders = $this->getHttpResponseHeaders($responseHeaders);
return array($responseHeaders, $responseBody);
}
/**
* Parse out headers from raw headers
* @param rawHeaders array or string
* @return array
*/
public function getHttpResponseHeaders($rawHeaders)
{
if (is_array($rawHeaders)) {
return $this->parseArrayHeaders($rawHeaders);
} else {
return $this->parseStringHeaders($rawHeaders);
}
}
private function parseStringHeaders($rawHeaders)
{
$headers = [];
$responseHeaderLines = explode("\r\n", $rawHeaders);
foreach ($responseHeaderLines as $headerLine) {
if ($headerLine && strpos($headerLine, ':') !== false) {
list($header, $value) = explode(': ', $headerLine, 2);
$header = strtolower($header);
if (isset($headers[$header])) {
$headers[$header] .= "\n" . $value;
} else {
$headers[$header] = $value;
}
}
}
return $headers;
}
private function parseArrayHeaders($rawHeaders)
{
$header_count = count($rawHeaders);
$headers = [];
for ($i = 0; $i < $header_count; $i++) {
$header = $rawHeaders[$i];
// Times will have colons in - so we just want the first match.
$header_parts = explode(': ', $header, 2);
if (count($header_parts) == 2) {
$headers[strtolower($header_parts[0])] = $header_parts[1];
}
}
return $headers;
}
}

194
app/api/Google/IO/Curl.php Executable file
View file

@ -0,0 +1,194 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Curl based implementation of Google_IO.
*
* @author Stuart Langley <slangley@google.com>
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_IO_Curl extends Google_IO_Abstract
{
// cURL hex representation of version 7.30.0
const NO_QUIRK_VERSION = 0x071E00;
private $options = [];
/** @var bool $disableProxyWorkaround */
private $disableProxyWorkaround;
public function __construct(Google_Client $client)
{
if (!extension_loaded('curl')) {
$error = 'The cURL IO handler requires the cURL extension to be enabled';
$client->getLogger()->critical($error);
throw new Google_IO_Exception($error);
}
parent::__construct($client);
$this->disableProxyWorkaround = $this->client->getClassConfig(
'Google_IO_Curl',
'disable_proxy_workaround'
);
}
/**
* Execute an HTTP Request
*
* @param Google_Http_Request $request the http request to be executed
* @return array containing response headers, body, and http code
* @throws Google_IO_Exception on curl or IO error
*/
public function executeRequest(Google_Http_Request $request)
{
$curl = curl_init();
if ($request->getPostBody()) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getPostBody());
}
$requestHeaders = $request->getRequestHeaders();
if ($requestHeaders && is_array($requestHeaders)) {
$curlHeaders = [];
foreach ($requestHeaders as $k => $v) {
$curlHeaders[] = "$k: $v";
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
}
curl_setopt($curl, CURLOPT_URL, $request->getUrl());
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod());
curl_setopt($curl, CURLOPT_USERAGENT, $request->getUserAgent());
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
// The SSL version will be determined by the underlying library
// @see https://github.com/google/google-api-php-client/pull/644
//curl_setopt($curl, CURLOPT_SSLVERSION, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
if ($request->canGzip()) {
curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
}
$options = $this->client->getClassConfig('Google_IO_Curl', 'options');
if (is_array($options)) {
$this->setOptions($options);
}
foreach ($this->options as $key => $var) {
curl_setopt($curl, $key, $var);
}
if (!isset($this->options[CURLOPT_CAINFO])) {
curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem');
}
$this->client->getLogger()->debug(
'cURL request',
array(
'url' => $request->getUrl(),
'method' => $request->getRequestMethod(),
'headers' => $requestHeaders,
'body' => $request->getPostBody()
)
);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
$code = curl_errno($curl);
$map = $this->client->getClassConfig('Google_IO_Exception', 'retry_map');
$this->client->getLogger()->error('cURL ' . $error);
throw new Google_IO_Exception($error, $code, null, $map);
}
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize);
$responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->client->getLogger()->debug(
'cURL response',
array(
'code' => $responseCode,
'headers' => $responseHeaders,
'body' => $responseBody,
)
);
return array($responseBody, $responseHeaders, $responseCode);
}
/**
* Set options that update the transport implementation's behavior.
* @param $options
*/
public function setOptions($options)
{
$this->options = $options + $this->options;
}
/**
* Set the maximum request time in seconds.
* @param $timeout in seconds
*/
public function setTimeout($timeout)
{
// Since this timeout is really for putting a bound on the time
// we'll set them both to the same. If you need to specify a longer
// CURLOPT_TIMEOUT, or a higher CONNECTTIMEOUT, the best thing to
// do is use the setOptions method for the values individually.
$this->options[CURLOPT_CONNECTTIMEOUT] = $timeout;
$this->options[CURLOPT_TIMEOUT] = $timeout;
}
/**
* Get the maximum request time in seconds.
* @return timeout in seconds
*/
public function getTimeout()
{
return $this->options[CURLOPT_TIMEOUT];
}
/**
* Test for the presence of a cURL header processing bug
*
* {@inheritDoc}
*
* @return boolean
*/
protected function needsQuirk()
{
if ($this->disableProxyWorkaround) {
return false;
}
$ver = curl_version();
$versionNum = $ver['version_number'];
return $versionNum < Google_IO_Curl::NO_QUIRK_VERSION;
}
}

69
app/api/Google/IO/Exception.php Executable file
View file

@ -0,0 +1,69 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_IO_Exception extends Google_Exception implements Google_Task_Retryable
{
/**
* @var array $retryMap Map of errors with retry counts.
*/
private $retryMap = [];
/**
* Creates a new IO exception with an optional retry map.
*
* @param string $message
* @param int $code
* @param Exception|null $previous
* @param array|null $retryMap Map of errors with retry counts.
*/
public function __construct(
$message,
$code = 0,
Exception $previous = null,
array $retryMap = null
) {
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
parent::__construct($message, $code, $previous);
} else {
parent::__construct($message, $code);
}
if (is_array($retryMap)) {
$this->retryMap = $retryMap;
}
}
/**
* Gets the number of times the associated task can be retried.
*
* NOTE: -1 is returned if the task can be retried indefinitely
*
* @return integer
*/
public function allowedRetries()
{
if (isset($this->retryMap[$this->code])) {
return $this->retryMap[$this->code];
}
return 0;
}
}

243
app/api/Google/IO/Stream.php Executable file
View file

@ -0,0 +1,243 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Http Streams based implementation of Google_IO.
*
* @author Stuart Langley <slangley@google.com>
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_IO_Stream extends Google_IO_Abstract
{
const TIMEOUT = "timeout";
const ZLIB = "compress.zlib://";
private $options = [];
private $trappedErrorNumber;
private $trappedErrorString;
private static $DEFAULT_HTTP_CONTEXT = array(
"follow_location" => 0,
"ignore_errors" => 1,
);
private static $DEFAULT_SSL_CONTEXT = array(
"verify_peer" => true,
);
public function __construct(Google_Client $client)
{
if (!ini_get('allow_url_fopen')) {
$error = 'The stream IO handler requires the allow_url_fopen runtime ' .
'configuration to be enabled';
$client->getLogger()->critical($error);
throw new Google_IO_Exception($error);
}
parent::__construct($client);
}
/**
* Execute an HTTP Request
*
* @param Google_Http_Request $request the http request to be executed
* @return array containing response headers, body, and http code
* @throws Google_IO_Exception on curl or IO error
*/
public function executeRequest(Google_Http_Request $request)
{
$default_options = stream_context_get_options(stream_context_get_default());
$requestHttpContext = array_key_exists('http', $default_options) ?
$default_options['http'] : [];
if ($request->getPostBody()) {
$requestHttpContext["content"] = $request->getPostBody();
}
$requestHeaders = $request->getRequestHeaders();
if ($requestHeaders && is_array($requestHeaders)) {
$headers = "";
foreach ($requestHeaders as $k => $v) {
$headers .= "$k: $v\r\n";
}
$requestHttpContext["header"] = $headers;
}
$requestHttpContext["method"] = $request->getRequestMethod();
$requestHttpContext["user_agent"] = $request->getUserAgent();
$requestSslContext = array_key_exists('ssl', $default_options) ?
$default_options['ssl'] : [];
if (!$this->client->isAppEngine() && !array_key_exists("cafile", $requestSslContext)) {
$requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
}
$options = array(
"http" => array_merge(
self::$DEFAULT_HTTP_CONTEXT,
$requestHttpContext
),
"ssl" => array_merge(
self::$DEFAULT_SSL_CONTEXT,
$requestSslContext
)
);
$context = stream_context_create($options);
$url = $request->getUrl();
if ($request->canGzip()) {
$url = self::ZLIB . $url;
}
$this->client->getLogger()->debug(
'Stream request',
array(
'url' => $url,
'method' => $request->getRequestMethod(),
'headers' => $requestHeaders,
'body' => $request->getPostBody()
)
);
// We are trapping any thrown errors in this method only and
// throwing an exception.
$this->trappedErrorNumber = null;
$this->trappedErrorString = null;
// START - error trap.
set_error_handler(array($this, 'trapError'));
$fh = fopen($url, 'r', false, $context);
restore_error_handler();
// END - error trap.
if ($this->trappedErrorNumber) {
$error = sprintf(
"HTTP Error: Unable to connect: '%s'",
$this->trappedErrorString
);
$this->client->getLogger()->error('Stream ' . $error);
throw new Google_IO_Exception($error, $this->trappedErrorNumber);
}
$response_data = false;
$respHttpCode = self::UNKNOWN_CODE;
if ($fh) {
if (isset($this->options[self::TIMEOUT])) {
stream_set_timeout($fh, $this->options[self::TIMEOUT]);
}
$response_data = stream_get_contents($fh);
fclose($fh);
$respHttpCode = $this->getHttpResponseCode($http_response_header);
}
if (false === $response_data) {
$error = sprintf(
"HTTP Error: Unable to connect: '%s'",
$respHttpCode
);
$this->client->getLogger()->error('Stream ' . $error);
throw new Google_IO_Exception($error, $respHttpCode);
}
$responseHeaders = $this->getHttpResponseHeaders($http_response_header);
$this->client->getLogger()->debug(
'Stream response',
array(
'code' => $respHttpCode,
'headers' => $responseHeaders,
'body' => $response_data,
)
);
return array($response_data, $responseHeaders, $respHttpCode);
}
/**
* Set options that update the transport implementation's behavior.
* @param $options
*/
public function setOptions($options)
{
$this->options = $options + $this->options;
}
/**
* Method to handle errors, used for error handling around
* stream connection methods.
*/
public function trapError($errno, $errstr)
{
$this->trappedErrorNumber = $errno;
$this->trappedErrorString = $errstr;
}
/**
* Set the maximum request time in seconds.
* @param $timeout in seconds
*/
public function setTimeout($timeout)
{
$this->options[self::TIMEOUT] = $timeout;
}
/**
* Get the maximum request time in seconds.
* @return timeout in seconds
*/
public function getTimeout()
{
return $this->options[self::TIMEOUT];
}
/**
* Test for the presence of a cURL header processing bug
*
* {@inheritDoc}
*
* @return boolean
*/
protected function needsQuirk()
{
return false;
}
protected function getHttpResponseCode($response_headers)
{
$header_count = count($response_headers);
for ($i = 0; $i < $header_count; $i++) {
$header = $response_headers[$i];
if (strncasecmp("HTTP", $header, strlen("HTTP")) == 0) {
$response = explode(' ', $header);
return $response[1];
}
}
return self::UNKNOWN_CODE;
}
}

2183
app/api/Google/IO/cacerts.pem Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,408 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Abstract logging class based on the PSR-3 standard.
*
* NOTE: We don't implement `Psr\Log\LoggerInterface` because we need to
* maintain PHP 5.2 support.
*
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
*/
abstract class Google_Logger_Abstract
{
/**
* Default log format
*/
const DEFAULT_LOG_FORMAT = "[%datetime%] %level%: %message% %context%\n";
/**
* Default date format
*
* Example: 16/Nov/2014:03:26:16 -0500
*/
const DEFAULT_DATE_FORMAT = 'd/M/Y:H:i:s O';
/**
* System is unusable
*/
const EMERGENCY = 'emergency';
/**
* Action must be taken immediately
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*/
const ALERT = 'alert';
/**
* Critical conditions
*
* Example: Application component unavailable, unexpected exception.
*/
const CRITICAL = 'critical';
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*/
const ERROR = 'error';
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*/
const WARNING = 'warning';
/**
* Normal but significant events.
*/
const NOTICE = 'notice';
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*/
const INFO = 'info';
/**
* Detailed debug information.
*/
const DEBUG = 'debug';
/**
* @var array $levels Logging levels
*/
protected static $levels = array(
self::EMERGENCY => 600,
self::ALERT => 550,
self::CRITICAL => 500,
self::ERROR => 400,
self::WARNING => 300,
self::NOTICE => 250,
self::INFO => 200,
self::DEBUG => 100,
);
/**
* @var integer $level The minimum logging level
*/
protected $level = self::DEBUG;
/**
* @var string $logFormat The current log format
*/
protected $logFormat = self::DEFAULT_LOG_FORMAT;
/**
* @var string $dateFormat The current date format
*/
protected $dateFormat = self::DEFAULT_DATE_FORMAT;
/**
* @var boolean $allowNewLines If newlines are allowed
*/
protected $allowNewLines = false;
/**
* @param Google_Client $client The current Google client
*/
public function __construct(Google_Client $client)
{
$this->setLevel(
$client->getClassConfig('Google_Logger_Abstract', 'level')
);
$format = $client->getClassConfig('Google_Logger_Abstract', 'log_format');
$this->logFormat = $format ? $format : self::DEFAULT_LOG_FORMAT;
$format = $client->getClassConfig('Google_Logger_Abstract', 'date_format');
$this->dateFormat = $format ? $format : self::DEFAULT_DATE_FORMAT;
$this->allowNewLines = (bool) $client->getClassConfig(
'Google_Logger_Abstract',
'allow_newlines'
);
}
/**
* Sets the minimum logging level that this logger handles.
*
* @param integer $level
*/
public function setLevel($level)
{
$this->level = $this->normalizeLevel($level);
}
/**
* Checks if the logger should handle messages at the provided level.
*
* @param integer $level
* @return boolean
*/
public function shouldHandle($level)
{
return $this->normalizeLevel($level) >= $this->level;
}
/**
* System is unusable.
*
* @param string $message The log message
* @param array $context The log context
*/
public function emergency($message, array $context = array())
{
$this->log(self::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message The log message
* @param array $context The log context
*/
public function alert($message, array $context = array())
{
$this->log(self::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message The log message
* @param array $context The log context
*/
public function critical($message, array $context = array())
{
$this->log(self::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message The log message
* @param array $context The log context
*/
public function error($message, array $context = array())
{
$this->log(self::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message The log message
* @param array $context The log context
*/
public function warning($message, array $context = array())
{
$this->log(self::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message The log message
* @param array $context The log context
*/
public function notice($message, array $context = array())
{
$this->log(self::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message The log message
* @param array $context The log context
*/
public function info($message, array $context = array())
{
$this->log(self::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message The log message
* @param array $context The log context
*/
public function debug($message, array $context = array())
{
$this->log(self::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level The log level
* @param string $message The log message
* @param array $context The log context
*/
public function log($level, $message, array $context = array())
{
if (!$this->shouldHandle($level)) {
return false;
}
$levelName = is_int($level) ? array_search($level, self::$levels) : $level;
$message = $this->interpolate(
array(
'message' => $message,
'context' => $context,
'level' => strtoupper($levelName),
'datetime' => new DateTime(),
)
);
$this->write($message);
}
/**
* Interpolates log variables into the defined log format.
*
* @param array $variables The log variables.
* @return string
*/
protected function interpolate(array $variables = array())
{
$template = $this->logFormat;
if (!$variables['context']) {
$template = str_replace('%context%', '', $template);
unset($variables['context']);
} else {
$this->reverseJsonInContext($variables['context']);
}
foreach ($variables as $key => $value) {
if (strpos($template, '%'. $key .'%') !== false) {
$template = str_replace(
'%' . $key . '%',
$this->export($value),
$template
);
}
}
return $template;
}
/**
* Reverses JSON encoded PHP arrays and objects so that they log better.
*
* @param array $context The log context
*/
protected function reverseJsonInContext(array &$context)
{
if (!$context) {
return;
}
foreach ($context as $key => $val) {
if (!$val || !is_string($val) || !($val[0] == '{' || $val[0] == '[')) {
continue;
}
$json = @json_decode($val);
if (is_object($json) || is_array($json)) {
$context[$key] = $json;
}
}
}
/**
* Exports a PHP value for logging to a string.
*
* @param mixed $value The value to
*/
protected function export($value)
{
if (is_string($value)) {
if ($this->allowNewLines) {
return $value;
}
return preg_replace('/[\r\n]+/', ' ', $value);
}
if (is_resource($value)) {
return sprintf(
'resource(%d) of type (%s)',
$value,
get_resource_type($value)
);
}
if ($value instanceof DateTime) {
return $value->format($this->dateFormat);
}
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
if ($this->allowNewLines) {
$options |= JSON_PRETTY_PRINT;
}
return @json_encode($value, $options);
}
return str_replace('\\/', '/', @json_encode($value));
}
/**
* Converts a given log level to the integer form.
*
* @param mixed $level The logging level
* @return integer $level The normalized level
* @throws Google_Logger_Exception If $level is invalid
*/
protected function normalizeLevel($level)
{
if (is_int($level) && array_search($level, self::$levels) !== false) {
return $level;
}
if (is_string($level) && isset(self::$levels[$level])) {
return self::$levels[$level];
}
throw new Google_Logger_Exception(
sprintf("Unknown LogLevel: '%s'", $level)
);
}
/**
* Writes a message to the current log implementation.
*
* @param string $message The message
*/
abstract protected function write($message);
}

View file

@ -0,0 +1,24 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Logger_Exception extends Google_Exception
{
}

158
app/api/Google/Logger/File.php Executable file
View file

@ -0,0 +1,158 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* File logging class based on the PSR-3 standard.
*
* This logger writes to a PHP stream resource.
*/
class Google_Logger_File extends Google_Logger_Abstract
{
/**
* @var string|resource $file Where logs are written
*/
private $file;
/**
* @var integer $mode The mode to use if the log file needs to be created
*/
private $mode = 0640;
/**
* @var boolean $lock If a lock should be attempted before writing to the log
*/
private $lock = false;
/**
* @var integer $trappedErrorNumber Trapped error number
*/
private $trappedErrorNumber;
/**
* @var string $trappedErrorString Trapped error string
*/
private $trappedErrorString;
/**
* {@inheritdoc}
*/
public function __construct(Google_Client $client)
{
parent::__construct($client);
$file = $client->getClassConfig('Google_Logger_File', 'file');
if (!is_string($file) && !is_resource($file)) {
throw new Google_Logger_Exception(
'File logger requires a filename or a valid file pointer'
);
}
$mode = $client->getClassConfig('Google_Logger_File', 'mode');
if (!$mode) {
$this->mode = $mode;
}
$this->lock = (bool) $client->getClassConfig('Google_Logger_File', 'lock');
$this->file = $file;
}
/**
* {@inheritdoc}
*/
protected function write($message)
{
if (is_string($this->file)) {
$this->open();
} elseif (!is_resource($this->file)) {
throw new Google_Logger_Exception('File pointer is no longer available');
}
if ($this->lock) {
flock($this->file, LOCK_EX);
}
fwrite($this->file, (string) $message);
if ($this->lock) {
flock($this->file, LOCK_UN);
}
}
/**
* Opens the log for writing.
*
* @return resource
*/
private function open()
{
// Used for trapping `fopen()` errors.
$this->trappedErrorNumber = null;
$this->trappedErrorString = null;
$old = set_error_handler(array($this, 'trapError'));
$needsChmod = !file_exists($this->file);
$fh = fopen($this->file, 'a');
restore_error_handler();
// Handles trapped `fopen()` errors.
if ($this->trappedErrorNumber) {
throw new Google_Logger_Exception(
sprintf(
"Logger Error: '%s'",
$this->trappedErrorString
),
$this->trappedErrorNumber
);
}
if ($needsChmod) {
@chmod($this->file, $this->mode & ~umask());
}
return $this->file = $fh;
}
/**
* Closes the log stream resource.
*/
private function close()
{
if (is_resource($this->file)) {
fclose($this->file);
}
}
/**
* Traps `fopen()` errors.
*
* @param integer $errno The error number
* @param string $errstr The error string
*/
private function trapError($errno, $errstr)
{
$this->trappedErrorNumber = $errno;
$this->trappedErrorString = $errstr;
}
public function __destruct()
{
$this->close();
}
}

43
app/api/Google/Logger/Null.php Executable file
View file

@ -0,0 +1,43 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Null logger based on the PSR-3 standard.
*
* This logger simply discards all messages.
*/
class Google_Logger_Null extends Google_Logger_Abstract
{
/**
* {@inheritdoc}
*/
public function shouldHandle($level)
{
return false;
}
/**
* {@inheritdoc}
*/
protected function write($message, array $context = array())
{
}
}

93
app/api/Google/Logger/Psr.php Executable file
View file

@ -0,0 +1,93 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Psr logging class based on the PSR-3 standard.
*
* This logger will delegate all logging to a PSR-3 compatible logger specified
* with the `Google_Logger_Psr::setLogger()` method.
*/
class Google_Logger_Psr extends Google_Logger_Abstract
{
/**
* @param Psr\Log\LoggerInterface $logger The PSR-3 logger
*/
private $logger;
/**
* @param Google_Client $client The current Google client
* @param Psr\Log\LoggerInterface $logger PSR-3 logger where logging will be delegated.
*/
public function __construct(Google_Client $client, /*Psr\Log\LoggerInterface*/ $logger = null)
{
parent::__construct($client);
if ($logger) {
$this->setLogger($logger);
}
}
/**
* Sets the PSR-3 logger where logging will be delegated.
*
* NOTE: The `$logger` should technically implement
* `Psr\Log\LoggerInterface`, but we don't explicitly require this so that
* we can be compatible with PHP 5.2.
*
* @param Psr\Log\LoggerInterface $logger The PSR-3 logger
*/
public function setLogger(/*Psr\Log\LoggerInterface*/ $logger)
{
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function shouldHandle($level)
{
return isset($this->logger) && parent::shouldHandle($level);
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
if (!$this->shouldHandle($level)) {
return false;
}
if ($context) {
$this->reverseJsonInContext($context);
}
$levelName = is_int($level) ? array_search($level, self::$levels) : $level;
$this->logger->log($levelName, $message, $context);
}
/**
* {@inheritdoc}
*/
protected function write($message, array $context = array())
{
}
}

312
app/api/Google/Model.php Executable file
View file

@ -0,0 +1,312 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class defines attributes, valid values, and usage which is generated
* from a given json schema.
* http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
*
*/
class Google_Model implements ArrayAccess
{
/**
* If you need to specify a NULL JSON value, use Google_Model::NULL_VALUE
* instead - it will be replaced when converting to JSON with a real null.
*/
const NULL_VALUE = "{}gapi-php-null";
protected $internal_gapi_mappings = [];
protected $modelData = [];
protected $processed = [];
/**
* Polymorphic - accepts a variable number of arguments dependent
* on the type of the model subclass.
*/
final public function __construct()
{
if (func_num_args() == 1 && is_array(func_get_arg(0))) {
// Initialize the model with the array's contents.
$array = func_get_arg(0);
$this->mapTypes($array);
}
$this->gapiInit();
}
/**
* Getter that handles passthrough access to the data array, and lazy object creation.
* @param string $key Property name.
* @return mixed The value if any, or null.
*/
public function __get($key)
{
$keyTypeName = $this->keyType($key);
$keyDataType = $this->dataType($key);
if (isset($this->$keyTypeName) && !isset($this->processed[$key])) {
if (isset($this->modelData[$key])) {
$val = $this->modelData[$key];
} else if (isset($this->$keyDataType) &&
($this->$keyDataType == 'array' || $this->$keyDataType == 'map')) {
$val = [];
} else {
$val = null;
}
if ($this->isAssociativeArray($val)) {
if (isset($this->$keyDataType) && 'map' == $this->$keyDataType) {
foreach ($val as $arrayKey => $arrayItem) {
$this->modelData[$key][$arrayKey] =
$this->createObjectFromName($keyTypeName, $arrayItem);
}
} else {
$this->modelData[$key] = $this->createObjectFromName($keyTypeName, $val);
}
} else if (is_array($val)) {
$arrayObject = [];
foreach ($val as $arrayIndex => $arrayItem) {
$arrayObject[$arrayIndex] =
$this->createObjectFromName($keyTypeName, $arrayItem);
}
$this->modelData[$key] = $arrayObject;
}
$this->processed[$key] = true;
}
return isset($this->modelData[$key]) ? $this->modelData[$key] : null;
}
/**
* Initialize this object's properties from an array.
*
* @param array $array Used to seed this object's properties.
* @return void
*/
protected function mapTypes($array)
{
// Hard initialise simple types, lazy load more complex ones.
foreach ($array as $key => $val) {
if ( !property_exists($this, $this->keyType($key)) &&
property_exists($this, $key)) {
$this->$key = $val;
unset($array[$key]);
} elseif (property_exists($this, $camelKey = Google_Utils::camelCase($key))) {
// This checks if property exists as camelCase, leaving it in array as snake_case
// in case of backwards compatibility issues.
$this->$camelKey = $val;
}
}
$this->modelData = $array;
}
/**
* Blank initialiser to be used in subclasses to do post-construction initialisation - this
* avoids the need for subclasses to have to implement the variadics handling in their
* constructors.
*/
protected function gapiInit()
{
return;
}
/**
* Create a simplified object suitable for straightforward
* conversion to JSON. This is relatively expensive
* due to the usage of reflection, but shouldn't be called
* a whole lot, and is the most straightforward way to filter.
*/
public function toSimpleObject()
{
$object = new stdClass();
// Process all other data.
foreach ($this->modelData as $key => $val) {
$result = $this->getSimpleValue($val);
if ($result !== null) {
$object->$key = $this->nullPlaceholderCheck($result);
}
}
// Process all public properties.
$reflect = new ReflectionObject($this);
$props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
foreach ($props as $member) {
$name = $member->getName();
$result = $this->getSimpleValue($this->$name);
if ($result !== null) {
$name = $this->getMappedName($name);
$object->$name = $this->nullPlaceholderCheck($result);
}
}
return $object;
}
/**
* Handle different types of values, primarily
* other objects and map and array data types.
*/
private function getSimpleValue($value)
{
if ($value instanceof Google_Model) {
return $value->toSimpleObject();
} else if (is_array($value)) {
$return = [];
foreach ($value as $key => $a_value) {
$a_value = $this->getSimpleValue($a_value);
if ($a_value !== null) {
$key = $this->getMappedName($key);
$return[$key] = $this->nullPlaceholderCheck($a_value);
}
}
return $return;
}
return $value;
}
/**
* Check whether the value is the null placeholder and return true null.
*/
private function nullPlaceholderCheck($value)
{
if ($value === self::NULL_VALUE) {
return null;
}
return $value;
}
/**
* If there is an internal name mapping, use that.
*/
private function getMappedName($key)
{
if (isset($this->internal_gapi_mappings) &&
isset($this->internal_gapi_mappings[$key])) {
$key = $this->internal_gapi_mappings[$key];
}
return $key;
}
/**
* Returns true only if the array is associative.
* @param array $array
* @return bool True if the array is associative.
*/
protected function isAssociativeArray($array)
{
if (!is_array($array)) {
return false;
}
$keys = array_keys($array);
foreach ($keys as $key) {
if (is_string($key)) {
return true;
}
}
return false;
}
/**
* Given a variable name, discover its type.
*
* @param $name
* @param $item
* @return object The object from the item.
*/
private function createObjectFromName($name, $item)
{
$type = $this->$name;
return new $type($item);
}
/**
* Verify if $obj is an array.
* @throws Google_Exception Thrown if $obj isn't an array.
* @param array $obj Items that should be validated.
* @param string $method Method expecting an array as an argument.
*/
public function assertIsArray($obj, $method)
{
if ($obj && !is_array($obj)) {
throw new Google_Exception(
"Incorrect parameter type passed to $method(). Expected an array."
);
}
}
/**
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->$offset) || isset($this->modelData[$offset]);
}
/**
* @param mixed $offset
* @return mixed
*/
public function offsetGet($offset)
{
return isset($this->$offset) ?
$this->$offset :
$this->__get($offset);
}
/**
* @param mixed $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
if (property_exists($this, $offset)) {
$this->$offset = $value;
} else {
$this->modelData[$offset] = $value;
$this->processed[$offset] = true;
}
}
/**
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->modelData[$offset]);
}
protected function keyType($key)
{
return $key . "Type";
}
protected function dataType($key)
{
return $key . "DataType";
}
public function __isset($key)
{
return isset($this->modelData[$key]);
}
public function __unset($key)
{
unset($this->modelData[$key]);
}
}

56
app/api/Google/Service.php Executable file
View file

@ -0,0 +1,56 @@
<?php
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Google_Service
{
public $batchPath;
public $rootUrl;
public $version;
public $servicePath;
public $availableScopes;
public $resource;
private $client;
public function __construct(Google_Client $client)
{
$this->client = $client;
}
/**
* Return the associated Google_Client class.
* @return Google_Client
*/
public function getClient()
{
return $this->client;
}
/**
* Create a new HTTP Batch handler for this service
*
* @return Google_Http_Batch
*/
public function createBatch()
{
return new Google_Http_Batch(
$this->client,
false,
$this->rootUrl,
$this->batchPath
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,105 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Service_Exception extends Google_Exception implements Google_Task_Retryable
{
/**
* Optional list of errors returned in a JSON body of an HTTP error response.
*/
protected $errors = [];
/**
* @var array $retryMap Map of errors with retry counts.
*/
private $retryMap = [];
/**
* Override default constructor to add the ability to set $errors and a retry
* map.
*
* @param string $message
* @param int $code
* @param Exception|null $previous
* @param [{string, string}] errors List of errors returned in an HTTP
* response. Defaults to [].
* @param array|null $retryMap Map of errors with retry counts.
*/
public function __construct(
$message,
$code = 0,
Exception $previous = null,
$errors = array(),
array $retryMap = null
) {
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
parent::__construct($message, $code, $previous);
} else {
parent::__construct($message, $code);
}
$this->errors = $errors;
if (is_array($retryMap)) {
$this->retryMap = $retryMap;
}
}
/**
* An example of the possible errors returned.
*
* {
* "domain": "global",
* "reason": "authError",
* "message": "Invalid Credentials",
* "locationType": "header",
* "location": "Authorization",
* }
*
* @return [{string, string}] List of errors return in an HTTP response or [].
*/
public function getErrors()
{
return $this->errors;
}
/**
* Gets the number of times the associated task can be retried.
*
* NOTE: -1 is returned if the task can be retried indefinitely
*
* @return integer
*/
public function allowedRetries()
{
if (isset($this->retryMap[$this->code])) {
return $this->retryMap[$this->code];
}
$errors = $this->getErrors();
if (!empty($errors) && isset($errors[0]['reason']) &&
isset($this->retryMap[$errors[0]['reason']])) {
return $this->retryMap[$errors[0]['reason']];
}
return 0;
}
}

View file

@ -0,0 +1,255 @@
<?php
/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Implements the actual methods/resources of the discovered Google API using magic function
* calling overloading (__call()), which on call will see if the method name (plus.activities.list)
* is available in this service, and if so construct an apiHttpRequest representing it.
*
*/
class Google_Service_Resource
{
// Valid query parameters that work, but don't appear in discovery.
private $stackParameters = array(
'alt' => array('type' => 'string', 'location' => 'query'),
'fields' => array('type' => 'string', 'location' => 'query'),
'trace' => array('type' => 'string', 'location' => 'query'),
'userIp' => array('type' => 'string', 'location' => 'query'),
'quotaUser' => array('type' => 'string', 'location' => 'query'),
'data' => array('type' => 'string', 'location' => 'body'),
'mimeType' => array('type' => 'string', 'location' => 'header'),
'uploadType' => array('type' => 'string', 'location' => 'query'),
'mediaUpload' => array('type' => 'complex', 'location' => 'query'),
'prettyPrint' => array('type' => 'string', 'location' => 'query'),
);
/** @var string $rootUrl */
private $rootUrl;
/** @var Google_Client $client */
private $client;
/** @var string $serviceName */
private $serviceName;
/** @var string $servicePath */
private $servicePath;
/** @var string $resourceName */
private $resourceName;
/** @var array $methods */
private $methods;
public function __construct($service, $serviceName, $resourceName, $resource)
{
$this->rootUrl = $service->rootUrl;
$this->client = $service->getClient();
$this->servicePath = $service->servicePath;
$this->serviceName = $serviceName;
$this->resourceName = $resourceName;
$this->methods = is_array($resource) && isset($resource['methods']) ?
$resource['methods'] :
array($resourceName => $resource);
}
/**
* TODO: This function needs simplifying.
* @param $name
* @param $arguments
* @param $expected_class - optional, the expected class name
* @return Google_Http_Request|expected_class
* @throws Google_Exception
*/
public function call($name, $arguments, $expected_class = null)
{
if (! isset($this->methods[$name])) {
$this->client->getLogger()->error(
'Service method unknown',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name
)
);
throw new Google_Exception(
"Unknown function: " .
"{$this->serviceName}->{$this->resourceName}->{$name}()"
);
}
$method = $this->methods[$name];
$parameters = $arguments[0];
// postBody is a special case since it's not defined in the discovery
// document as parameter, but we abuse the param entry for storing it.
$postBody = null;
if (isset($parameters['postBody'])) {
if ($parameters['postBody'] instanceof Google_Model) {
// In the cases the post body is an existing object, we want
// to use the smart method to create a simple object for
// for JSONification.
$parameters['postBody'] = $parameters['postBody']->toSimpleObject();
} else if (is_object($parameters['postBody'])) {
// If the post body is another kind of object, we will try and
// wrangle it into a sensible format.
$parameters['postBody'] =
$this->convertToArrayAndStripNulls($parameters['postBody']);
}
$postBody = json_encode($parameters['postBody']);
if ($postBody === false && $parameters['postBody'] !== false) {
throw new Google_Exception("JSON encoding failed. Ensure all strings in the request are UTF-8 encoded.");
}
unset($parameters['postBody']);
}
// TODO: optParams here probably should have been
// handled already - this may well be redundant code.
if (isset($parameters['optParams'])) {
$optParams = $parameters['optParams'];
unset($parameters['optParams']);
$parameters = array_merge($parameters, $optParams);
}
if (!isset($method['parameters'])) {
$method['parameters'] = [];
}
$method['parameters'] = array_merge(
$this->stackParameters,
$method['parameters']
);
foreach ($parameters as $key => $val) {
if ($key != 'postBody' && ! isset($method['parameters'][$key])) {
$this->client->getLogger()->error(
'Service parameter unknown',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'parameter' => $key
)
);
throw new Google_Exception("($name) unknown parameter: '$key'");
}
}
foreach ($method['parameters'] as $paramName => $paramSpec) {
if (isset($paramSpec['required']) &&
$paramSpec['required'] &&
! isset($parameters[$paramName])
) {
$this->client->getLogger()->error(
'Service parameter missing',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'parameter' => $paramName
)
);
throw new Google_Exception("($name) missing required param: '$paramName'");
}
if (isset($parameters[$paramName])) {
$value = $parameters[$paramName];
$parameters[$paramName] = $paramSpec;
$parameters[$paramName]['value'] = $value;
unset($parameters[$paramName]['required']);
} else {
// Ensure we don't pass nulls.
unset($parameters[$paramName]);
}
}
$this->client->getLogger()->info(
'Service Call',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'arguments' => $parameters,
)
);
$url = Google_Http_REST::createRequestUri(
$this->servicePath,
$method['path'],
$parameters
);
$httpRequest = new Google_Http_Request(
$url,
$method['httpMethod'],
null,
$postBody
);
if ($this->rootUrl) {
$httpRequest->setBaseComponent($this->rootUrl);
} else {
$httpRequest->setBaseComponent($this->client->getBasePath());
}
if ($postBody) {
$contentTypeHeader = [];
$contentTypeHeader['content-type'] = 'application/json; charset=UTF-8';
$httpRequest->setRequestHeaders($contentTypeHeader);
$httpRequest->setPostBody($postBody);
}
$httpRequest = $this->client->getAuth()->sign($httpRequest);
$httpRequest->setExpectedClass($expected_class);
if (isset($parameters['data']) &&
($parameters['uploadType']['value'] == 'media' || $parameters['uploadType']['value'] == 'multipart')) {
// If we are doing a simple media upload, trigger that as a convenience.
$mfu = new Google_Http_MediaFileUpload(
$this->client,
$httpRequest,
isset($parameters['mimeType']) ? $parameters['mimeType']['value'] : 'application/octet-stream',
$parameters['data']['value']
);
}
if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') {
$httpRequest->enableExpectedRaw();
}
if ($this->client->shouldDefer()) {
// If we are in batch or upload mode, return the raw request.
return $httpRequest;
}
return $this->client->execute($httpRequest);
}
protected function convertToArrayAndStripNulls($o)
{
$o = (array) $o;
foreach ($o as $k => $v) {
if ($v === null) {
unset($o[$k]);
} elseif (is_object($v) || is_array($v)) {
$o[$k] = $this->convertToArrayAndStripNulls($o[$k]);
}
}
return $o;
}
}

View file

@ -0,0 +1,29 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Signs data.
*
* @author Brian Eaton <beaton@google.com>
*/
abstract class Google_Signer_Abstract
{
/**
* Signs data, returns the signature as binary data.
*/
abstract public function sign($data);
}

94
app/api/Google/Signer/P12.php Executable file
View file

@ -0,0 +1,94 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Signs data.
*
* Only used for testing.
*
* @author Brian Eaton <beaton@google.com>
*/
class Google_Signer_P12 extends Google_Signer_Abstract
{
// OpenSSL private key resource
private $privateKey;
// Creates a new signer from a .p12 file.
public function __construct($p12, $password)
{
if (!function_exists('openssl_x509_read')) {
throw new Google_Exception(
'The Google PHP API library needs the openssl PHP extension'
);
}
// If the private key is provided directly, then this isn't in the p12
// format. Different versions of openssl support different p12 formats
// and the key from google wasn't being accepted by the version available
// at the time.
if (!$password && strpos($p12, "-----BEGIN RSA PRIVATE KEY-----") !== false) {
$this->privateKey = openssl_pkey_get_private($p12);
} elseif ($password === 'notasecret' && strpos($p12, "-----BEGIN PRIVATE KEY-----") !== false) {
$this->privateKey = openssl_pkey_get_private($p12);
} else {
// This throws on error
$certs = [];
if (!openssl_pkcs12_read($p12, $certs, $password)) {
throw new Google_Auth_Exception(
"Unable to parse the p12 file. " .
"Is this a .p12 file? Is the password correct? OpenSSL error: " .
openssl_error_string()
);
}
// TODO(beaton): is this part of the contract for the openssl_pkcs12_read
// method? What happens if there are multiple private keys? Do we care?
if (!array_key_exists("pkey", $certs) || !$certs["pkey"]) {
throw new Google_Auth_Exception("No private key found in p12 file.");
}
$this->privateKey = openssl_pkey_get_private($certs['pkey']);
}
if (!$this->privateKey) {
throw new Google_Auth_Exception("Unable to load private key");
}
}
public function __destruct()
{
if ($this->privateKey) {
openssl_pkey_free($this->privateKey);
}
}
public function sign($data)
{
if (version_compare(PHP_VERSION, '5.3.0') < 0) {
throw new Google_Auth_Exception(
"PHP 5.3.0 or higher is required to use service accounts."
);
}
$hash = defined("OPENSSL_ALGO_SHA256") ? OPENSSL_ALGO_SHA256 : "sha256";
if (!openssl_sign($data, $signature, $this->privateKey, $hash)) {
throw new Google_Auth_Exception("Unable to sign data");
}
return $signature;
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Task_Exception extends Google_Exception
{
}

View file

@ -0,0 +1,36 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Interface for checking how many times a given task can be retried following
* a failure.
*/
interface Google_Task_Retryable
{
/**
* Gets the number of times the associated task can be retried.
*
* NOTE: -1 is returned if the task can be retried indefinitely
*
* @return integer
*/
public function allowedRetries();
}

257
app/api/Google/Task/Runner.php Executable file
View file

@ -0,0 +1,257 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* A task runner with exponential backoff support.
*
* @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
*/
class Google_Task_Runner
{
/**
* @var integer $maxDelay The max time (in seconds) to wait before a retry.
*/
private $maxDelay = 60;
/**
* @var integer $delay The previous delay from which the next is calculated.
*/
private $delay = 1;
/**
* @var integer $factor The base number for the exponential back off.
*/
private $factor = 2;
/**
* @var float $jitter A random number between -$jitter and $jitter will be
* added to $factor on each iteration to allow for a better distribution of
* retries.
*/
private $jitter = 0.5;
/**
* @var integer $attempts The number of attempts that have been tried so far.
*/
private $attempts = 0;
/**
* @var integer $maxAttempts The max number of attempts allowed.
*/
private $maxAttempts = 1;
/**
* @var Google_Client $client The current API client.
*/
private $client;
/**
* @var string $name The name of the current task (used for logging).
*/
private $name;
/**
* @var callable $action The task to run and possibly retry.
*/
private $action;
/**
* @var array $arguments The task arguments.
*/
private $arguments;
/**
* Creates a new task runner with exponential backoff support.
*
* @param Google_Client $client The current API client
* @param string $name The name of the current task (used for logging)
* @param callable $action The task to run and possibly retry
* @param array $arguments The task arguments
* @throws Google_Task_Exception when misconfigured
*/
public function __construct(
Google_Client $client,
$name,
$action,
array $arguments = array()
) {
$config = (array) $client->getClassConfig('Google_Task_Runner');
if (isset($config['initial_delay'])) {
if ($config['initial_delay'] < 0) {
throw new Google_Task_Exception(
'Task configuration `initial_delay` must not be negative.'
);
}
$this->delay = $config['initial_delay'];
}
if (isset($config['max_delay'])) {
if ($config['max_delay'] <= 0) {
throw new Google_Task_Exception(
'Task configuration `max_delay` must be greater than 0.'
);
}
$this->maxDelay = $config['max_delay'];
}
if (isset($config['factor'])) {
if ($config['factor'] <= 0) {
throw new Google_Task_Exception(
'Task configuration `factor` must be greater than 0.'
);
}
$this->factor = $config['factor'];
}
if (isset($config['jitter'])) {
if ($config['jitter'] <= 0) {
throw new Google_Task_Exception(
'Task configuration `jitter` must be greater than 0.'
);
}
$this->jitter = $config['jitter'];
}
if (isset($config['retries'])) {
if ($config['retries'] < 0) {
throw new Google_Task_Exception(
'Task configuration `retries` must not be negative.'
);
}
$this->maxAttempts += $config['retries'];
}
if (!is_callable($action)) {
throw new Google_Task_Exception(
'Task argument `$action` must be a valid callable.'
);
}
$this->name = $name;
$this->client = $client;
$this->action = $action;
$this->arguments = $arguments;
}
/**
* Checks if a retry can be attempted.
*
* @return boolean
*/
public function canAttmpt()
{
return $this->attempts < $this->maxAttempts;
}
/**
* Runs the task and (if applicable) automatically retries when errors occur.
*
* @return mixed
* @throws Google_Task_Retryable on failure when no retries are available.
*/
public function run()
{
while ($this->attempt()) {
try {
return call_user_func_array($this->action, $this->arguments);
} catch (Google_Task_Retryable $exception) {
$allowedRetries = $exception->allowedRetries();
if (!$this->canAttmpt() || !$allowedRetries) {
throw $exception;
}
if ($allowedRetries > 0) {
$this->maxAttempts = min(
$this->maxAttempts,
$this->attempts + $allowedRetries
);
}
}
}
}
/**
* Runs a task once, if possible. This is useful for bypassing the `run()`
* loop.
*
* NOTE: If this is not the first attempt, this function will sleep in
* accordance to the backoff configurations before running the task.
*
* @return boolean
*/
public function attempt()
{
if (!$this->canAttmpt()) {
return false;
}
if ($this->attempts > 0) {
$this->backOff();
}
$this->attempts++;
return true;
}
/**
* Sleeps in accordance to the backoff configurations.
*/
private function backOff()
{
$delay = $this->getDelay();
$this->client->getLogger()->debug(
'Retrying task with backoff',
array(
'request' => $this->name,
'retry' => $this->attempts,
'backoff_seconds' => $delay
)
);
usleep($delay * 1000000);
}
/**
* Gets the delay (in seconds) for the current backoff period.
*
* @return float
*/
private function getDelay()
{
$jitter = $this->getJitter();
$factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
return $this->delay = min($this->maxDelay, $this->delay * $factor);
}
/**
* Gets the current jitter (random number between -$this->jitter and
* $this->jitter).
*
* @return float
*/
private function getJitter()
{
return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
}
}

133
app/api/Google/Utils.php Executable file
View file

@ -0,0 +1,133 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Collection of static utility methods used for convenience across
* the client library.
*/
class Google_Utils
{
public static function urlSafeB64Encode($data)
{
$b64 = base64_encode($data);
$b64 = str_replace(
array('+', '/', '\r', '\n', '='),
array('-', '_'),
$b64
);
return $b64;
}
public static function urlSafeB64Decode($b64)
{
$b64 = str_replace(
array('-', '_'),
array('+', '/'),
$b64
);
return base64_decode($b64);
}
/**
* Misc function used to count the number of bytes in a post body, in the
* world of multi-byte chars and the unpredictability of
* strlen/mb_strlen/sizeof, this is the only way to do that in a sane
* manner at the moment.
*
* This algorithm was originally developed for the
* Solar Framework by Paul M. Jones
*
* @link http://solarphp.com/
* @link http://svn.solarphp.com/core/trunk/Solar/Json.php
* @link http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Json/Decoder.php
* @param string $str
* @return int The number of bytes in a string.
*/
public static function getStrLen($str)
{
$strlenVar = strlen($str);
$d = $ret = 0;
for ($count = 0; $count < $strlenVar; ++ $count) {
$ordinalValue = ord($str[$ret]);
switch (true) {
case (($ordinalValue >= 0x20) && ($ordinalValue <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ret ++;
break;
case (($ordinalValue & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$ret += 2;
break;
case (($ordinalValue & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$ret += 3;
break;
case (($ordinalValue & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$ret += 4;
break;
case (($ordinalValue & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$ret += 5;
break;
case (($ordinalValue & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$ret += 6;
break;
default:
$ret ++;
}
}
return $ret;
}
/**
* Normalize all keys in an array to lower-case.
* @param array $arr
* @return array Normalized array.
*/
public static function normalize($arr)
{
if (!is_array($arr)) {
return [];
}
$normalized = [];
foreach ($arr as $key => $val) {
$normalized[strtolower($key)] = $val;
}
return $normalized;
}
/**
* Convert a string to camelCase
* @param string $value
* @return string
*/
public static function camelCase($value)
{
$value = ucwords(str_replace(array('-', '_'), ' ', $value));
$value = str_replace(' ', '', $value);
$value[0] = strtolower($value[0]);
return $value;
}
}

View file

@ -0,0 +1,333 @@
<?php
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Implementation of levels 1-3 of the URI Template spec.
* @see http://tools.ietf.org/html/rfc6570
*/
class Google_Utils_URITemplate
{
const TYPE_MAP = "1";
const TYPE_LIST = "2";
const TYPE_SCALAR = "4";
/**
* @var $operators array
* These are valid at the start of a template block to
* modify the way in which the variables inside are
* processed.
*/
private $operators = array(
"+" => "reserved",
"/" => "segments",
"." => "dotprefix",
"#" => "fragment",
";" => "semicolon",
"?" => "form",
"&" => "continuation"
);
/**
* @var reserved array
* These are the characters which should not be URL encoded in reserved
* strings.
*/
private $reserved = array(
"=", ",", "!", "@", "|", ":", "/", "?", "#",
"[", "]",'$', "&", "'", "(", ")", "*", "+", ";"
);
private $reservedEncoded = array(
"%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F",
"%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29",
"%2A", "%2B", "%3B"
);
public function parse($string, array $parameters)
{
return $this->resolveNextSection($string, $parameters);
}
/**
* This function finds the first matching {...} block and
* executes the replacement. It then calls itself to find
* subsequent blocks, if any.
*/
private function resolveNextSection($string, $parameters)
{
$start = strpos($string, "{");
if ($start === false) {
return $string;
}
$end = strpos($string, "}");
if ($end === false) {
return $string;
}
$string = $this->replace($string, $start, $end, $parameters);
return $this->resolveNextSection($string, $parameters);
}
private function replace($string, $start, $end, $parameters)
{
// We know a data block will have {} round it, so we can strip that.
$data = substr($string, $start + 1, $end - $start - 1);
// If the first character is one of the reserved operators, it effects
// the processing of the stream.
if (isset($this->operators[$data[0]])) {
$op = $this->operators[$data[0]];
$data = substr($data, 1);
$prefix = "";
$prefix_on_missing = false;
switch ($op) {
case "reserved":
// Reserved means certain characters should not be URL encoded
$data = $this->replaceVars($data, $parameters, ",", null, true);
break;
case "fragment":
// Comma separated with fragment prefix. Bare values only.
$prefix = "#";
$prefix_on_missing = true;
$data = $this->replaceVars($data, $parameters, ",", null, true);
break;
case "segments":
// Slash separated data. Bare values only.
$prefix = "/";
$data =$this->replaceVars($data, $parameters, "/");
break;
case "dotprefix":
// Dot separated data. Bare values only.
$prefix = ".";
$prefix_on_missing = true;
$data = $this->replaceVars($data, $parameters, ".");
break;
case "semicolon":
// Semicolon prefixed and separated. Uses the key name
$prefix = ";";
$data = $this->replaceVars($data, $parameters, ";", "=", false, true, false);
break;
case "form":
// Standard URL format. Uses the key name
$prefix = "?";
$data = $this->replaceVars($data, $parameters, "&", "=");
break;
case "continuation":
// Standard URL, but with leading ampersand. Uses key name.
$prefix = "&";
$data = $this->replaceVars($data, $parameters, "&", "=");
break;
}
// Add the initial prefix character if data is valid.
if ($data || ($data !== false && $prefix_on_missing)) {
$data = $prefix . $data;
}
} else {
// If no operator we replace with the defaults.
$data = $this->replaceVars($data, $parameters);
}
// This is chops out the {...} and replaces with the new section.
return substr($string, 0, $start) . $data . substr($string, $end + 1);
}
private function replaceVars(
$section,
$parameters,
$sep = ",",
$combine = null,
$reserved = false,
$tag_empty = false,
$combine_on_empty = true
) {
if (strpos($section, ",") === false) {
// If we only have a single value, we can immediately process.
return $this->combine(
$section,
$parameters,
$sep,
$combine,
$reserved,
$tag_empty,
$combine_on_empty
);
} else {
// If we have multiple values, we need to split and loop over them.
// Each is treated individually, then glued together with the
// separator character.
$vars = explode(",", $section);
return $this->combineList(
$vars,
$sep,
$parameters,
$combine,
$reserved,
false, // Never emit empty strings in multi-param replacements
$combine_on_empty
);
}
}
public function combine(
$key,
$parameters,
$sep,
$combine,
$reserved,
$tag_empty,
$combine_on_empty
) {
$length = false;
$explode = false;
$skip_final_combine = false;
$value = false;
// Check for length restriction.
if (strpos($key, ":") !== false) {
list($key, $length) = explode(":", $key);
}
// Check for explode parameter.
if ($key[strlen($key) - 1] == "*") {
$explode = true;
$key = substr($key, 0, -1);
$skip_final_combine = true;
}
// Define the list separator.
$list_sep = $explode ? $sep : ",";
if (isset($parameters[$key])) {
$data_type = $this->getDataType($parameters[$key]);
switch ($data_type) {
case self::TYPE_SCALAR:
$value = $this->getValue($parameters[$key], $length);
break;
case self::TYPE_LIST:
$values = [];
foreach ($parameters[$key] as $pkey => $pvalue) {
$pvalue = $this->getValue($pvalue, $length);
if ($combine && $explode) {
$values[$pkey] = $key . $combine . $pvalue;
} else {
$values[$pkey] = $pvalue;
}
}
$value = implode($list_sep, $values);
if ($value == '') {
return '';
}
break;
case self::TYPE_MAP:
$values = [];
foreach ($parameters[$key] as $pkey => $pvalue) {
$pvalue = $this->getValue($pvalue, $length);
if ($explode) {
$pkey = $this->getValue($pkey, $length);
$values[] = $pkey . "=" . $pvalue; // Explode triggers = combine.
} else {
$values[] = $pkey;
$values[] = $pvalue;
}
}
$value = implode($list_sep, $values);
if ($value == '') {
return false;
}
break;
}
} else if ($tag_empty) {
// If we are just indicating empty values with their key name, return that.
return $key;
} else {
// Otherwise we can skip this variable due to not being defined.
return false;
}
if ($reserved) {
$value = str_replace($this->reservedEncoded, $this->reserved, $value);
}
// If we do not need to include the key name, we just return the raw
// value.
if (!$combine || $skip_final_combine) {
return $value;
}
// Else we combine the key name: foo=bar, if value is not the empty string.
return $key . ($value != '' || $combine_on_empty ? $combine . $value : '');
}
/**
* Return the type of a passed in value
*/
private function getDataType($data)
{
if (is_array($data)) {
reset($data);
if (key($data) !== 0) {
return self::TYPE_MAP;
}
return self::TYPE_LIST;
}
return self::TYPE_SCALAR;
}
/**
* Utility function that merges multiple combine calls
* for multi-key templates.
*/
private function combineList(
$vars,
$sep,
$parameters,
$combine,
$reserved,
$tag_empty,
$combine_on_empty
) {
$ret = [];
foreach ($vars as $var) {
$response = $this->combine(
$var,
$parameters,
$sep,
$combine,
$reserved,
$tag_empty,
$combine_on_empty
);
if ($response === false) {
continue;
}
$ret[] = $response;
}
return implode($sep, $ret);
}
/**
* Utility function to encode and trim values
*/
private function getValue($value, $length)
{
if ($length) {
$value = substr($value, 0, $length);
}
$value = rawurlencode($value);
return $value;
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Verifies signatures.
*
* @author Brian Eaton <beaton@google.com>
*/
abstract class Google_Verifier_Abstract
{
/**
* Checks a signature, returns true if the signature is correct,
* false otherwise.
*/
abstract public function verify($data, $signature);
}

75
app/api/Google/Verifier/Pem.php Executable file
View file

@ -0,0 +1,75 @@
<?php
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/**
* Verifies signatures using PEM encoded certificates.
*
* @author Brian Eaton <beaton@google.com>
*/
class Google_Verifier_Pem extends Google_Verifier_Abstract
{
private $publicKey;
/**
* Constructs a verifier from the supplied PEM-encoded certificate.
*
* $pem: a PEM encoded certificate (not a file).
* @param $pem
* @throws Google_Auth_Exception
* @throws Google_Exception
*/
public function __construct($pem)
{
if (!function_exists('openssl_x509_read')) {
throw new Google_Exception('Google API PHP client needs the openssl PHP extension');
}
$this->publicKey = openssl_x509_read($pem);
if (!$this->publicKey) {
throw new Google_Auth_Exception("Unable to parse PEM: $pem");
}
}
public function __destruct()
{
if ($this->publicKey) {
openssl_x509_free($this->publicKey);
}
}
/**
* Verifies the signature on data.
*
* Returns true if the signature is valid, false otherwise.
* @param $data
* @param $signature
* @throws Google_Auth_Exception
* @return bool
*/
public function verify($data, $signature)
{
$hash = defined("OPENSSL_ALGO_SHA256") ? OPENSSL_ALGO_SHA256 : "sha256";
$status = openssl_verify($data, $signature, $this->publicKey, $hash);
if ($status === -1) {
throw new Google_Auth_Exception('Signature verification error: ' . openssl_error_string());
}
return $status === 1;
}
}

37
app/api/Google/autoload.php Executable file
View file

@ -0,0 +1,37 @@
<?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function mec_google_api_php_client_autoload($className)
{
$classPath = explode('_', $className);
if($classPath[0] != 'Google')
{
return;
}
// Drop 'Google', and maximum class file path depth in this project is 3.
$classPath = array_slice($classPath, 1, 2);
$filePath = dirname(__FILE__) . DS . implode(DS, $classPath) . '.php';
if(file_exists($filePath))
{
require_once($filePath);
}
}
spl_autoload_register('mec_google_api_php_client_autoload');

0
app/api/Google/index.html Executable file
View file

132
app/api/Meetup/meetup.php Executable file
View file

@ -0,0 +1,132 @@
<?php
class Meetup {
const BASE = 'https://api.meetup.com';
protected $_parameters = array(
'sign' => 'true',
);
public function __construct(array $parameters = array()) {
$this->_parameters = array_merge($this->_parameters, $parameters);
}
public function getEvents(array $parameters = array()) {
return $this->get('/:urlname/events', $parameters);
}
public function getEvent(array $parameters = array()) {
return $this->get('/:urlname/events/:id', $parameters);
}
public function getPhotos(array $parameters = array()) {
return $this->get('/2/photos', $parameters)->results;
}
public function getDiscussionBoards(array $parameters = array()) {
return $this->get('/:urlname/boards', $parameters);
}
public function getDiscussions(array $parameters = array()) {
return $this->get('/:urlname/boards/:bid/discussions', $parameters);
}
public function getMembers(array $parameters = array()) {
return $this->get('/2/members', $parameters);
}
public function getNext($response) {
if (!isset($response) || !isset($response->meta->next))
{
throw new Exception("Invalid response object.");
}
return $this->get_url($response->meta->next);
}
public function get($path, array $parameters = array()) {
$parameters = array_merge($this->_parameters, $parameters);
if (preg_match_all('/:([a-z]+)/', $path, $matches)) {
foreach ($matches[0] as $i => $match) {
if (isset($parameters[$matches[1][$i]])) {
$path = str_replace($match, $parameters[$matches[1][$i]], $path);
unset($parameters[$matches[1][$i]]);
} else {
throw new Exception("Missing parameter '" . $matches[1][$i] . "' for path '" . $path . "'.");
}
}
}
$url = self::BASE . $path . '?' . http_build_query($parameters);
return $this->get_url($url);
}
protected function get_url($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Accept-Charset: utf-8"));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$content = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("Failed retrieving '" . $url . "' because of ' " . $error . "'.");
}
$response = json_decode($content);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status != 200) {
if (isset($response->errors[0]->message)) {
$error = $response->errors[0]->message;
} else {
$error = 'Status ' . $status;
}
throw new Exception("Failed retrieving '" . $url . "' because of ' " . $error . "'.");
}
if (isset($response) == false) {
switch (json_last_error()) {
case JSON_ERROR_NONE:
$error = 'No errors';
break;
case JSON_ERROR_DEPTH:
$error = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$error = ' Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$error = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$error = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$error = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$error = 'Unknown error';
break;
}
throw new Exception("Cannot read response by '" . $url . "' because of: '" . $error . "'.");
}
return $response;
}
}

431
app/api/Stripe/Account.php Executable file
View file

@ -0,0 +1,431 @@
<?php
// File generated from our OpenAPI spec
namespace Stripe;
/**
* This is an object representing a Stripe account. You can retrieve it to see
* properties on the account like its current e-mail address or if the account is
* enabled yet to make live charges.
*
* Some properties, marked below, are available only to platforms that want to <a
* href="https://stripe.com/docs/connect/accounts">create and manage Express or
* Custom accounts</a>.
*
* @property string $id Unique identifier for the object.
* @property string $object String representing the object's type. Objects of the same type share the same value.
* @property null|\Stripe\StripeObject $business_profile Business information about the account.
* @property null|string $business_type The business type.
* @property \Stripe\StripeObject $capabilities
* @property bool $charges_enabled Whether the account can create live charges.
* @property \Stripe\StripeObject $company
* @property string $country The account's country.
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
* @property string $default_currency Three-letter ISO currency code representing the default currency for the account. This must be a currency that <a href="https://stripe.com/docs/payouts">Stripe supports in the account's country</a>.
* @property bool $details_submitted Whether account details have been submitted. Standard accounts cannot receive payouts before this is true.
* @property null|string $email The primary user's email address.
* @property \Stripe\Collection $external_accounts External accounts (bank accounts and debit cards) currently attached to this account
* @property \Stripe\Person $individual <p>This is an object representing a person associated with a Stripe account.</p><p>A platform cannot access a Standard or Express account's persons after the account starts onboarding, such as after generating an account link for the account. See the <a href="https://stripe.com/docs/connect/standard-accounts">Standard onboarding</a> or <a href="https://stripe.com/docs/connect/express-accounts">Express onboarding documentation</a> for information about platform pre-filling and account onboarding steps.</p><p>Related guide: <a href="https://stripe.com/docs/connect/identity-verification-api#person-information">Handling Identity Verification with the API</a>.</p>
* @property \Stripe\StripeObject $metadata Set of <a href="https://stripe.com/docs/api/metadata">key-value pairs</a> that you can attach to an object. This can be useful for storing additional information about the object in a structured format.
* @property bool $payouts_enabled Whether Stripe can send payouts to this account.
* @property \Stripe\StripeObject $requirements
* @property null|\Stripe\StripeObject $settings Options for customizing how the account functions within Stripe.
* @property \Stripe\StripeObject $tos_acceptance
* @property string $type The Stripe account type. Can be <code>standard</code>, <code>express</code>, or <code>custom</code>.
*/
class Account extends ApiResource
{
const OBJECT_NAME = 'account';
use ApiOperations\All;
use ApiOperations\Create;
use ApiOperations\Delete;
use ApiOperations\NestedResource;
use ApiOperations\Update;
const BUSINESS_TYPE_COMPANY = 'company';
const BUSINESS_TYPE_GOVERNMENT_ENTITY = 'government_entity';
const BUSINESS_TYPE_INDIVIDUAL = 'individual';
const BUSINESS_TYPE_NON_PROFIT = 'non_profit';
const CAPABILITY_CARD_PAYMENTS = 'card_payments';
const CAPABILITY_LEGACY_PAYMENTS = 'legacy_payments';
const CAPABILITY_PLATFORM_PAYMENTS = 'platform_payments';
const CAPABILITY_TRANSFERS = 'transfers';
const CAPABILITY_STATUS_ACTIVE = 'active';
const CAPABILITY_STATUS_INACTIVE = 'inactive';
const CAPABILITY_STATUS_PENDING = 'pending';
const TYPE_CUSTOM = 'custom';
const TYPE_EXPRESS = 'express';
const TYPE_STANDARD = 'standard';
use ApiOperations\Retrieve {
retrieve as protected _retrieve;
}
public static function getSavedNestedResources()
{
static $savedNestedResources = null;
if (null === $savedNestedResources) {
$savedNestedResources = new Util\Set([
'external_account',
'bank_account',
]);
}
return $savedNestedResources;
}
public function instanceUrl()
{
if (null === $this['id']) {
return '/v1/account';
}
return parent::instanceUrl();
}
public function serializeParameters($force = false)
{
$update = parent::serializeParameters($force);
if (isset($this->_values['legal_entity'])) {
$entity = $this['legal_entity'];
if (isset($entity->_values['additional_owners'])) {
$owners = $entity['additional_owners'];
$entityUpdate = isset($update['legal_entity']) ? $update['legal_entity'] : [];
$entityUpdate['additional_owners'] = $this->serializeAdditionalOwners($entity, $owners);
$update['legal_entity'] = $entityUpdate;
}
}
if (isset($this->_values['individual'])) {
$individual = $this['individual'];
if (($individual instanceof Person) && !isset($update['individual'])) {
$update['individual'] = $individual->serializeParameters($force);
}
}
return $update;
}
private function serializeAdditionalOwners($legalEntity, $additionalOwners)
{
if (isset($legalEntity->_originalValues['additional_owners'])) {
$originalValue = $legalEntity->_originalValues['additional_owners'];
} else {
$originalValue = [];
}
if (($originalValue) && (\count($originalValue) > \count($additionalOwners))) {
throw new Exception\InvalidArgumentException(
'You cannot delete an item from an array, you must instead set a new array'
);
}
$updateArr = [];
foreach ($additionalOwners as $i => $v) {
$update = ($v instanceof StripeObject) ? $v->serializeParameters() : $v;
if ([] !== $update) {
if (!$originalValue
|| !\array_key_exists($i, $originalValue)
|| ($update !== $legalEntity->serializeParamsValue($originalValue[$i], null, false, true))) {
$updateArr[$i] = $update;
}
}
}
return $updateArr;
}
/**
* @param null|array|string $id the ID of the account to retrieve, or an
* options array containing an `id` key
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Account
*/
public static function retrieve($id = null, $opts = null)
{
if (!$opts && \is_string($id) && 'sk_' === \substr($id, 0, 3)) {
$opts = $id;
$id = null;
}
return self::_retrieve($id, $opts);
}
/**
* @param null|array $clientId
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\StripeObject object containing the response from the API
*/
public function deauthorize($clientId = null, $opts = null)
{
$params = [
'client_id' => $clientId,
'stripe_user_id' => $this->id,
];
return OAuth::deauthorize($params, $opts);
}
/**
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Collection the list of persons
*/
public function persons($params = null, $opts = null)
{
$url = $this->instanceUrl() . '/persons';
list($response, $opts) = $this->_request('get', $url, $params, $opts);
$obj = Util\Util::convertToStripeObject($response, $opts);
$obj->setLastResponse($response);
return $obj;
}
/**
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return Account the rejected account
*/
public function reject($params = null, $opts = null)
{
$url = $this->instanceUrl() . '/reject';
list($response, $opts) = $this->_request('post', $url, $params, $opts);
$this->refreshFrom($response, $opts);
return $this;
}
/*
* Capabilities methods
* We can not add the capabilities() method today as the Account object already has a
* capabilities property which is a hash and not the sub-list of capabilities.
*/
const PATH_CAPABILITIES = '/capabilities';
/**
* @param string $id the ID of the account on which to retrieve the capabilities
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Collection the list of capabilities
*/
public static function allCapabilities($id, $params = null, $opts = null)
{
return self::_allNestedResources($id, static::PATH_CAPABILITIES, $params, $opts);
}
/**
* @param string $id the ID of the account to which the capability belongs
* @param string $capabilityId the ID of the capability to retrieve
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Capability
*/
public static function retrieveCapability($id, $capabilityId, $params = null, $opts = null)
{
return self::_retrieveNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts);
}
/**
* @param string $id the ID of the account to which the capability belongs
* @param string $capabilityId the ID of the capability to update
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Capability
*/
public static function updateCapability($id, $capabilityId, $params = null, $opts = null)
{
return self::_updateNestedResource($id, static::PATH_CAPABILITIES, $capabilityId, $params, $opts);
}
const PATH_EXTERNAL_ACCOUNTS = '/external_accounts';
/**
* @param string $id the ID of the account on which to retrieve the external accounts
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Collection the list of external accounts (BankAccount or Card)
*/
public static function allExternalAccounts($id, $params = null, $opts = null)
{
return self::_allNestedResources($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts);
}
/**
* @param string $id the ID of the account on which to create the external account
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\BankAccount|\Stripe\Card
*/
public static function createExternalAccount($id, $params = null, $opts = null)
{
return self::_createNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $params, $opts);
}
/**
* @param string $id the ID of the account to which the external account belongs
* @param string $externalAccountId the ID of the external account to delete
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\BankAccount|\Stripe\Card
*/
public static function deleteExternalAccount($id, $externalAccountId, $params = null, $opts = null)
{
return self::_deleteNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
}
/**
* @param string $id the ID of the account to which the external account belongs
* @param string $externalAccountId the ID of the external account to retrieve
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\BankAccount|\Stripe\Card
*/
public static function retrieveExternalAccount($id, $externalAccountId, $params = null, $opts = null)
{
return self::_retrieveNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
}
/**
* @param string $id the ID of the account to which the external account belongs
* @param string $externalAccountId the ID of the external account to update
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\BankAccount|\Stripe\Card
*/
public static function updateExternalAccount($id, $externalAccountId, $params = null, $opts = null)
{
return self::_updateNestedResource($id, static::PATH_EXTERNAL_ACCOUNTS, $externalAccountId, $params, $opts);
}
const PATH_LOGIN_LINKS = '/login_links';
/**
* @param string $id the ID of the account on which to create the login link
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\LoginLink
*/
public static function createLoginLink($id, $params = null, $opts = null)
{
return self::_createNestedResource($id, static::PATH_LOGIN_LINKS, $params, $opts);
}
const PATH_PERSONS = '/persons';
/**
* @param string $id the ID of the account on which to retrieve the persons
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Collection the list of persons
*/
public static function allPersons($id, $params = null, $opts = null)
{
return self::_allNestedResources($id, static::PATH_PERSONS, $params, $opts);
}
/**
* @param string $id the ID of the account on which to create the person
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Person
*/
public static function createPerson($id, $params = null, $opts = null)
{
return self::_createNestedResource($id, static::PATH_PERSONS, $params, $opts);
}
/**
* @param string $id the ID of the account to which the person belongs
* @param string $personId the ID of the person to delete
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Person
*/
public static function deletePerson($id, $personId, $params = null, $opts = null)
{
return self::_deleteNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
}
/**
* @param string $id the ID of the account to which the person belongs
* @param string $personId the ID of the person to retrieve
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Person
*/
public static function retrievePerson($id, $personId, $params = null, $opts = null)
{
return self::_retrieveNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
}
/**
* @param string $id the ID of the account to which the person belongs
* @param string $personId the ID of the person to update
* @param null|array $params
* @param null|array|string $opts
*
* @throws \Stripe\Exception\ApiErrorException if the request fails
*
* @return \Stripe\Person
*/
public static function updatePerson($id, $personId, $params = null, $opts = null)
{
return self::_updateNestedResource($id, static::PATH_PERSONS, $personId, $params, $opts);
}
}

26
app/api/Stripe/AccountLink.php Executable file
View file

@ -0,0 +1,26 @@
<?php
// File generated from our OpenAPI spec
namespace Stripe;
/**
* Account Links are the means by which a Connect platform grants a connected
* account permission to access Stripe-hosted applications, such as Connect
* Onboarding.
*
* Related guide: <a
* href="https://stripe.com/docs/connect/connect-onboarding">Connect
* Onboarding</a>.
*
* @property string $object String representing the object's type. Objects of the same type share the same value.
* @property int $created Time at which the object was created. Measured in seconds since the Unix epoch.
* @property int $expires_at The timestamp at which this account link will expire.
* @property string $url The URL for the account link.
*/
class AccountLink extends ApiResource
{
const OBJECT_NAME = 'account_link';
use ApiOperations\Create;
}

View file

@ -0,0 +1,75 @@
<?php
namespace Stripe;
/**
* Class AlipayAccount.
*
* @deprecated Alipay accounts are deprecated. Please use the sources API instead.
* @see https://stripe.com/docs/sources/alipay
*/
class AlipayAccount extends ApiResource
{
const OBJECT_NAME = 'alipay_account';
use ApiOperations\Delete;
use ApiOperations\Update;
/**
* @return string The instance URL for this resource. It needs to be special
* cased because it doesn't fit into the standard resource pattern.
*/
public function instanceUrl()
{
if ($this['customer']) {
$base = Customer::classUrl();
$parent = $this['customer'];
$path = 'sources';
} else {
$msg = 'Alipay accounts cannot be accessed without a customer ID.';
throw new Exception\UnexpectedValueException($msg);
}
$parentExtn = \urlencode(Util\Util::utf8($parent));
$extn = \urlencode(Util\Util::utf8($this['id']));
return "{$base}/{$parentExtn}/{$path}/{$extn}";
}
/**
* @param array|string $_id
* @param null|array|string $_opts
*
* @throws \Stripe\Exception\BadMethodCallException
*
* @deprecated Alipay accounts are deprecated. Please use the sources API instead.
* @see https://stripe.com/docs/sources/alipay
*/
public static function retrieve($_id, $_opts = null)
{
$msg = 'Alipay accounts cannot be retrieved without a customer ID. ' .
'Retrieve an Alipay account using `Customer::retrieveSource(' .
"'customer_id', 'alipay_account_id')`.";
throw new Exception\BadMethodCallException($msg);
}
/**
* @param string $_id
* @param null|array $_params
* @param null|array|string $_options
*
* @throws \Stripe\Exception\BadMethodCallException
*
* @deprecated Alipay accounts are deprecated. Please use the sources API instead.
* @see https://stripe.com/docs/sources/alipay
*/
public static function update($_id, $_params = null, $_options = null)
{
$msg = 'Alipay accounts cannot be updated without a customer ID. ' .
'Update an Alipay account using `Customer::updateSource(' .
"'customer_id', 'alipay_account_id', \$updateParams)`.";
throw new Exception\BadMethodCallException($msg);
}
}

Some files were not shown because too many files have changed in this diff Show more