├── views
├── picasas
│ ├── album.ctp
│ ├── photo.ctp
│ └── index.ctp
├── youtube_videos
│ └── index.ctp
├── analytics
│ └── index.ctp
└── helpers
│ └── picaso.php
├── gdata_app_model.php
├── gdata_app_controller.php
├── controllers
├── youtube_videos_controller.php
├── picasas_controller.php
└── analytics_controller.php
├── config
└── gdata_config.php
└── models
├── picasa.php
├── youtube_video.php
├── datasources
├── gdata
│ ├── gdata_youtube.php
│ ├── gdata_picasa.php
│ └── gdata_analytics.php
├── rest_source.php
└── gdata_source.php
└── analytic.php
/views/picasas/album.ctp:
--------------------------------------------------------------------------------
1 | render($photos); ?>
2 |
--------------------------------------------------------------------------------
/views/picasas/photo.ctp:
--------------------------------------------------------------------------------
1 | render($photo); ?>
2 |
--------------------------------------------------------------------------------
/gdata_app_model.php:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/gdata_app_controller.php:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/views/picasas/index.ctp:
--------------------------------------------------------------------------------
1 |
Demonstration of the Picaso Helper
2 | The contents below were generated using the included picaso helper. The helper is intended to be overwritten to suit your needs. Go take a look.
3 |
4 | render('albums', $albums); ?>
5 |
--------------------------------------------------------------------------------
/controllers/youtube_videos_controller.php:
--------------------------------------------------------------------------------
1 | YoutubeVideo->find('all');
8 | $this->set(compact('youtubeVideos'));
9 | }
10 |
11 | }
12 | ?>
--------------------------------------------------------------------------------
/config/gdata_config.php:
--------------------------------------------------------------------------------
1 | 'gdata',
9 | 'driver' => 'analytics',
10 | 'email' => '',
11 | 'passwd' => '',
12 | 'profileId' => '',
13 | 'source' => 'CakePHP',
14 | );
15 |
16 | var $youtube = array(
17 | 'datasource' => 'gdata',
18 | 'driver' => 'youtube',
19 | 'email' => '',
20 | 'passwd' => '',
21 | 'source' => 'CakePHP',
22 | );
23 |
24 | var $picasa = array(
25 | 'datasource' => 'gdata',
26 | 'driver' => 'picasa',
27 | 'email' => '',
28 | 'passwd' => '',
29 | 'source' => 'CakePHP',
30 | 'cache' => true,
31 | 'cacheDuration' => '+1 hours'
32 | );
33 |
34 | }
35 | ?>
36 |
--------------------------------------------------------------------------------
/models/picasa.php:
--------------------------------------------------------------------------------
1 | 'File', 'name' => 'Gdata.GDATA_CONFIG', 'file' => 'config'.DS.'gdata_config.php'));
14 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataSource', 'file' => 'models'.DS.'datasources'.DS.'gdata_source.php'));
15 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataPicasa', 'file' => 'models'.DS.'datasources'.DS.'gdata'.DS.'gdata_picasa.php'));
16 | $config =& new GDATA_CONFIG();
17 | ConnectionManager::create('picasa', $config->picasa);
18 | parent::__construct();
19 | }
20 | }
21 | ?>
22 |
--------------------------------------------------------------------------------
/models/youtube_video.php:
--------------------------------------------------------------------------------
1 | 'File', 'name' => 'Gdata.GDATA_CONFIG', 'file' => 'config'.DS.'gdata_config.php'));
14 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataSource', 'file' => 'models'.DS.'datasources'.DS.'gdata_source.php'));
15 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataYoutubeSource', 'file' => 'models'.DS.'datasources'.DS.'gdata'.DS.'gdata_youtube.php'));
16 | $config =& new GDATA_CONFIG();
17 | ConnectionManager::create('youtube', $config->youtube);
18 | parent::__construct();
19 | }
20 | }
21 |
22 | ?>
--------------------------------------------------------------------------------
/models/datasources/gdata/gdata_youtube.php:
--------------------------------------------------------------------------------
1 | 'http://gdata.youtube.com/feeds/api/users/default/uploads'
33 | );
34 |
35 | $response = parent::read($model, $request);
36 |
37 | return $response;
38 |
39 | }
40 |
41 | }
42 | ?>
--------------------------------------------------------------------------------
/controllers/picasas_controller.php:
--------------------------------------------------------------------------------
1 | Picasa->find('all');
11 | $this->set(compact('albums'));
12 | }
13 |
14 | function album($id = null) {
15 | try {
16 | if (empty($id)) {
17 | throw new Exception('No album id specified.');
18 | }
19 | $photos = $this->Picasa->find('all', array('conditions' => array('albumid' => $id)));
20 | if (empty($photos)) {
21 | throw new Exception('That album is inaccesible or does not exist.');
22 | }
23 | } catch (Exception $e) {
24 | $this->Session->setFlash($e->getMessage());
25 | $this->redirect(array('action' => 'index'));
26 | }
27 | $this->set(compact('photos'));
28 | }
29 |
30 | function photo($id = null) {
31 | try {
32 | if (empty($id)) {
33 | throw new Exception('No photo id specified.');
34 | }
35 | $photo = $this->Picasa->find('all', array('conditions' => array('photoid' => $id)));
36 | if (empty($photo)) {
37 | throw new Exception('That photo is inaccesible or does not exist.');
38 | }
39 | } catch (Exception $e) {
40 | $this->Session->setFlash($e->getMessage());
41 | $this->redirect(array('action' => 'index'));
42 | }
43 | $this->set(compact('photo'));
44 | }
45 |
46 | }
47 |
48 | ?>
49 |
--------------------------------------------------------------------------------
/views/youtube_videos/index.ctp:
--------------------------------------------------------------------------------
1 |
2 | Sorry, there are no macthing videos
3 |
4 |
5 |
6 | | Thumbnail |
7 | Title |
8 | Content |
9 | Location |
10 | Category |
11 | Keywords |
12 | Duration |
13 | Published |
14 | Updated |
15 |
16 |
17 |
18 | |
19 | image($thumbnail['url'], array(
22 | 'width' => $thumbnail['width'],
23 | 'height' => $thumbnail['height'],
24 | 'alt' => $thumbnail['time'],
25 | ));
26 | echo $html->link($image, $youtubeVideo['group']['player']['url'], array('escape' => false));
27 | ?>
28 | |
29 | link($youtubeVideo['title']['value'], $youtubeVideo['group']['player']['url']); ?> |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/controllers/analytics_controller.php:
--------------------------------------------------------------------------------
1 | data)) {
10 | $redirect = array();
11 | foreach ($this->data[$this->modelClass] as $k => $v) {
12 | if (is_array($v)) {
13 | if (strpos($k, 'date')) {
14 | $v = $v['year'].'-'.$v['month'].'-'.$v['day'];
15 | } else {
16 | $v = implode(',', $v);
17 | }
18 | }
19 | $redirect[$k] = $v;
20 | }
21 | $this->redirect($redirect);
22 | } elseif (!empty($this->passedArgs)) {
23 | // Load url params into this->data to re-populate form values
24 | $this->data[$this->modelClass] = $this->passedArgs;
25 | } else {
26 | // Set up form defaults
27 | $this->data[$this->modelClass] = array(
28 | 'start_date' => date('Y-m-d', strtotime('-1 month')),
29 | 'end_date' => date('Y-m-d'),
30 | 'metrics' => 'visitors',
31 | 'dimensions' => 'year,month',
32 | );
33 | }
34 |
35 | // Set up pagination conditions for dates
36 | $this->paginate[$this->modelClass]['conditions'] = array(
37 | 'start_date' => $this->data[$this->modelClass]['start_date'],
38 | 'end_date' => $this->data[$this->modelClass]['end_date'],
39 | );
40 |
41 | // Set the fields param as the metrics
42 | $this->paginate[$this->modelClass]['fields'] = $this->data[$this->modelClass]['metrics'];
43 |
44 | // Set the group param as the dimensions
45 | $this->paginate[$this->modelClass]['group'] = $this->data[$this->modelClass]['dimensions'];
46 |
47 | $results = $this->paginate($this->modelClass);
48 |
49 | $metrics = $dimensions = array();
50 |
51 | $metricCategories = $this->{$this->modelClass}->metrics;
52 | $dimensionCategories = $this->{$this->modelClass}->dimensions;
53 |
54 | foreach ($metricCategories as $category => $categoryMetrics) {
55 | foreach ($categoryMetrics as $metric => $description) {
56 | $metrics[$category][$metric] = ''.$metric.'';
57 | }
58 | }
59 | foreach ($dimensionCategories as $category => $categoryDimensions) {
60 | foreach ($categoryDimensions as $dimension => $description) {
61 | $dimensions[$category][$dimension] = ''.$dimension.'';
62 | }
63 | }
64 |
65 | $this->set(compact('results', 'metrics', 'dimensions'));
66 |
67 | // Separate out date components and comma separated values
68 | foreach ($this->data[$this->modelClass] as $k => $v) {
69 | if (strpos($k, 'date')) {
70 | list($this->data[$this->modelClass][$k]['year'], $this->data[$this->modelClass][$k]['month'], $this->data[$this->modelClass][$k]['day']) = explode('-', $v);
71 | } else {
72 | $this->data[$this->modelClass][$k] = explode(',', $v);
73 | }
74 | }
75 | }
76 |
77 | }
78 | ?>
--------------------------------------------------------------------------------
/models/datasources/rest_source.php:
--------------------------------------------------------------------------------
1 | Http = new HttpSocket();
24 | }
25 |
26 | /**
27 | * Sets method = POST in query data if not already set
28 | *
29 | * @param AppModel $model
30 | * @param array $queryData
31 | */
32 | public function create(&$model, $queryData = array()) {
33 | $queryData = Set::merge(array('method' => 'POST'), $queryData);
34 | $this->_request($request);
35 | }
36 |
37 | /**
38 | * Sets method = GET in query data if not already set
39 | *
40 | * @param AppModel $model
41 | * @param array $queryData
42 | */
43 | public function read(&$model, $queryData = array()) {
44 | $queryData = Set::merge(array('method' => 'GET'), $queryData);
45 | return $this->_request($queryData);
46 | }
47 |
48 | /**
49 | * Sets method = PUT in query data if not already set
50 | *
51 | * @param AppModel $model
52 | * @param array $queryData
53 | */
54 | public function update(&$model, $fields = null, $values = null) {
55 | $queryData = Set::merge(array('method' => 'PUT'), $queryData);
56 | return $this->_request($queryData);
57 | }
58 |
59 | /**
60 | * Sets method = DELETE in query data if not already set
61 | *
62 | * @param AppModel $model
63 | * @param array $queryData
64 | */
65 | public function delete(&$model, $id = null) {
66 | if ($id == null) {
67 | $id = $model->id;
68 | }
69 | $queryData[$model->primaryKey] = $id;
70 | $queryData = Set::merge(array('method' => 'DELETE'), $queryData);
71 | return $this->_request($queryData);
72 | }
73 |
74 | /**
75 | * Issues request and returns array from decoded response according to
76 | * response's content type.
77 | *
78 | * @param array $request
79 | * @return array
80 | */
81 | protected function _request($request) {
82 |
83 | // Issues request
84 | $response = $this->Http->request($request);
85 |
86 | // CHeck response code
87 | if ($this->Http->response['status']['code'] != 200) {
88 | return false;
89 | }
90 | // Get content type header
91 | $contentType = $this->Http->response['header']['Content-Type'];
92 |
93 | // Extract content type from content type header
94 | if (preg_match('/^([a-z0-9\/\+]+);\s*charset=([a-z0-9\-]+)(.*)$/i', $contentType, $matches)) {
95 | $contentType = $matches[1];
96 | $charset = $matches[2];
97 | //$extra = $matches[3];
98 | }
99 |
100 | // Decode response according to content type and return an array
101 | switch ($contentType) {
102 | case 'application/atom+xml':
103 | App::import('Core', 'Xml');
104 | $Xml = new Xml($response);
105 | return $Xml->toArray(false); // Send false to get separate elements
106 | break;
107 |
108 | default:
109 | return $response;
110 | break;
111 | }
112 |
113 | }
114 |
115 | }
116 | ?>
117 |
--------------------------------------------------------------------------------
/views/analytics/index.ctp:
--------------------------------------------------------------------------------
1 |
45 | create('Analytic', array('url' => array('action' => 'index')));
47 | echo $form->input('start_date', array('type' => 'date'));
48 | echo $form->input('end_date', array('type' => 'date'));
49 | echo $form->input('metrics', array('multiple' => 'checkbox', 'escape' => false));
50 | echo $form->input('dimensions', array('multiple' => 'checkbox', 'escape' => false));
51 | echo $form->end('Show');
52 | ?>
53 |
54 |
57 | Invalid combination, here\'s a list of valid combinations';
60 | elseif (!empty($results['entry'])) :
61 | $paginator->options(array('url' => $this->passedArgs));
62 | echo $paginator->counter(array(
63 | 'format' => __('Results %start% to %end% of %count%', true)
64 | ));
65 | ?>
66 |
67 |
68 |
69 | '.$paginator->sort(substr($dimension['name'], 3)).'';
79 | }
80 | foreach (arrayalise($results['entry'][0]['metric']) as $metric) {
81 | echo '| '.$paginator->sort(substr($metric['name'], 3)).' | ';
82 | }
83 | ?>
84 |
85 |
86 |
87 | ';
90 | foreach (arrayalise($entry['dimension']) as $dimension) {
91 | echo ''.$dimension['value'].' | ';
92 | }
93 | foreach (arrayalise($entry['metric']) as $metric) {
94 | echo ''.$metric['value'].' | ';
95 | }
96 | echo '';
97 | }
98 | ?>
99 |
100 |
101 |
102 | - prev(__('Previous', true), array(), null, array('class'=>'disabled', 'tag' => 'span'));?>
103 | numbers(array('tag' => 'li', 'separator' => ''));?>
104 |
- next(__('Next', true), array(), null, array('class'=>'disabled', 'tag' => 'span'));?>
105 |
106 |
107 | No results
108 |
--------------------------------------------------------------------------------
/models/datasources/gdata_source.php:
--------------------------------------------------------------------------------
1 | connected = false;
36 |
37 | // Construct the google login request
38 | $request = array(
39 | 'uri' => $this->_clientLoginUri,
40 | 'method' => 'POST',
41 | 'body' => array(
42 | 'accountType' => 'HOSTED_OR_GOOGLE',
43 | 'Email' => $this->config['email'],
44 | 'Passwd' => $this->config['passwd'],
45 | 'service' => $this->_service,
46 | 'source' => $this->config['source'],
47 | ),
48 | );
49 |
50 | // Issue the request using the RestSource (which is it's parent)
51 | $response = parent::_request($request);
52 |
53 | // Parse the response into an array
54 | parse_str(str_replace(array("\n", "\r\n"), '&', $response), $response);
55 |
56 | // Check we have the Auth part of the response
57 | if (!is_string($response['Auth'])) {
58 | trigger_error("Request to Google for Authentication failed.", ERROR_WARN);
59 | return false;
60 | }
61 |
62 | // Store the Auth hash returned from Google for later requests
63 | $this->_auth = $response['Auth'];
64 |
65 | $this->connected = true;
66 |
67 | return $this->connected;
68 |
69 | }
70 |
71 | /**
72 | * Required if we have a connect method
73 | *
74 | * @return boolean
75 | */
76 | public function close() {
77 | return true;
78 | }
79 |
80 | /**
81 | * Connects if not already connected to Google, then adds the Authorisation
82 | * header into the request
83 | *
84 | * @param array $request An HTTP Request as used by HttpSocket
85 | * @return array
86 | * @access protected
87 | */
88 | protected function _request($request) {
89 |
90 | // Attempts to connect to google if not connected, if fails, returns false
91 | if (!$this->connected && !$this->connect()) {
92 | return false;
93 | }
94 |
95 | // Add in authorisation header
96 | $request = Set::merge(array(
97 | 'header' => array(
98 | 'Authorization' => 'GoogleLogin auth=' . $this->_auth
99 | ),
100 | ), $request);
101 |
102 | // Get the response from calling _request on the Rest Source (it's parent)
103 | $response = parent::_request($request);
104 |
105 | // If response is false, add the body to the errors property
106 | if (!$response) {
107 | $this->_errors[] = $this->Http->response['body'];
108 | return false;
109 | }
110 |
111 | // Set the number of rows and results properties from the response
112 | $this->numRows = $response['feed']['totalResults'];
113 | $this->_result = $response['feed'];
114 |
115 | // Return the result
116 | return $this->_result;
117 |
118 | }
119 |
120 | }
121 | ?>
122 |
--------------------------------------------------------------------------------
/models/datasources/gdata/gdata_picasa.php:
--------------------------------------------------------------------------------
1 | 'default'
46 | );
47 |
48 | $queryStringParams = array();
49 |
50 | if (!empty($queryData['conditions'])) {
51 | $conditions = $queryData['conditions'];
52 |
53 | foreach ($this->_uriKeys as $key) {
54 | if (!empty($conditions[$key])) {
55 | $uriStringParams[$key] = $conditions[$key];
56 | unset($conditions[$key]);
57 | }
58 | }
59 |
60 | if (!empty($conditions['tag'])) {
61 | if (is_array($conditions['tag'])) {
62 | $conditions['tag'] = implode(',', $conditions['tag']);
63 | }
64 | if (empty($conditions['kind'])) {
65 | $conditions['kind'] = 'photo';
66 | }
67 | }
68 |
69 | $queryStringParams = $conditions;
70 |
71 | }
72 |
73 | // Compile the request
74 | $request = array('uri' => $this->_build_picasa_uri($uriStringParams, $queryStringParams));
75 |
76 | $response = parent::read($model, $request);
77 |
78 | return $response;
79 | }
80 |
81 | /**
82 | * Method to process uri and query string parameters for use as http request.
83 | *
84 | * @param array $uriStringParams Two-dimensional array of keys and avlues for appendage to base uri
85 | * @param array $queryStringParams Two-dimensional array of keys and values for use as parameters in uri
86 | * @return string
87 | */
88 | protected function _build_picasa_uri($uriStringParams = array(), $queryStringParams = array()) {
89 | $result = $this->_baseUri;
90 | if (!empty($uriStringParams)) {
91 | $_result = array();
92 | foreach ($this->_uriKeys as $key) {
93 | if (!empty($uriStringParams[$key])) {
94 | $_result[] = $key . "/" . $uriStringParams[$key];
95 | }
96 | }
97 | $result .= implode("/", $_result);
98 | }
99 | if (!empty($queryStringParams)) {
100 | $result .= '?' . http_build_query($queryStringParams);
101 | }
102 | return $result;
103 | }
104 |
105 | /**
106 | * Picasa Developer's guide suggests adding GData Version header to every request. Merging value and passing to parent
107 | * class' request method.
108 | *
109 | * @link http://code.google.com/apis/picasaweb/docs/2.0/developers_guide_protocol.html#Versioning
110 | * @param array $request An HTTP Request as used by HttpSocket
111 | * @return array
112 | */
113 | protected function _request($request) {
114 | $request = Set::merge(array(
115 | 'header' => array(
116 | 'GData-Version' => '2'
117 | )
118 | ), $request);
119 | if ($this->config['cache']) {
120 | if (!$result = $this->_getCache($request)) {
121 | $result = parent::_request($request);
122 | $this->_setCache($request, $result);
123 | }
124 | } else {
125 | $result = parent::_request($request);
126 | }
127 | return $result;
128 | }
129 |
130 | /**
131 | * Set result data as cache for a paricular request
132 | *
133 | * @param array $request Will be serialized and md5 to create cache key
134 | * @param mixed $result data to be cached
135 | * @return mixed will return $result if set, otherwise (bool) false
136 | */
137 | private function _setCache($request, $result = null) {
138 | $key = $this->_requestToCacheKey($request);
139 | Cache::set(array('duration' => $this->config['cacheDuration']));
140 | if (Cache::write($key, $result)) {
141 | return $result;
142 | } else {
143 | return false;
144 | }
145 | }
146 |
147 | private function _getCache($request) {
148 | $key = $this->_requestToCacheKey($request);
149 | $cached = Cache::read($key);
150 | if ($cached !== false) {
151 | return $cached;
152 | }
153 | return false;
154 | }
155 |
156 | private function _requestToCacheKey($request) {
157 | return 'picasa_' . md5(serialize($request));
158 | }
159 | }
160 |
161 | ?>
162 |
--------------------------------------------------------------------------------
/models/datasources/gdata/gdata_analytics.php:
--------------------------------------------------------------------------------
1 | 10000) {
72 | trigger_error(__('limit must be between 1 and 10,000', true), E_USER_WARNING);
73 | return false;
74 | }
75 | $maxResults = $queryData['limit'];
76 | } else {
77 | $maxResults = 50;
78 | }
79 |
80 | if (isset($queryData['page'])) {
81 | if (!preg_match('/^\d+$/', $queryData['page'])) {
82 | trigger_error(__('page must be an integer', true), E_USER_WARNING);
83 | return false;
84 | }
85 | if ($queryData['page'] < 1) {
86 | trigger_error(__('page must be greater than or equal to 1', true), E_USER_WARNING);
87 | return false;
88 | }
89 | $startIndex = 1 + ($queryData['page'] - 1) * $maxResults;
90 | } else {
91 | $startIndex = 1;
92 | }
93 |
94 | // An anonymous function for prefixing an arg with "ga:"
95 | $prefix = create_function('&$v', '$v = "ga:".$v;');
96 |
97 | // Prefixes all metrics with "ga:"
98 | $metrics = explode(',', $queryData['fields']);
99 | array_walk($metrics, $prefix);
100 | $metrics = implode(',', $metrics);
101 |
102 | // Prefixes all dimensions with "ga:"
103 | $dimensions = explode(',', $queryData['group']);
104 | array_walk($dimensions, $prefix);
105 | $dimensions = implode(',', $dimensions);
106 |
107 | // Initialising query string params array
108 | $queryStringParams = array(
109 | 'metrics' => $metrics,
110 | 'dimensions' => $dimensions,
111 | 'start-date' => $queryData['conditions']['start_date'],
112 | 'end-date' => $queryData['conditions']['end_date'],
113 | 'ids' => 'ga:'.$this->config['profileId'],
114 | 'start-index' => $startIndex,
115 | 'max-results' => $maxResults,
116 | 'prettyprint' => 'true',
117 | );
118 |
119 | // Add order to query string params if set
120 | $queryData['order'] = array_filter($queryData['order']);
121 | if (!empty($queryData['order'])) {
122 | $sorts = explode(',', $queryData['order'][0]);
123 | foreach ($sorts as $sort) {
124 | list($sort, $direction) = preg_split('/\s/', $sort);
125 | list($alias, $field) = explode('.', $sort);
126 | $sort = 'ga:'.$field;
127 | if (strtolower($direction) == 'desc') {
128 | $sort = '-'.$sort;
129 | }
130 | $queryStringParams['sort'][] = $sort;
131 | }
132 | $queryStringParams['sort'] = implode(',', $queryStringParams['sort']);
133 | }
134 |
135 | // Compile the request
136 | $request = array('uri' => 'https://www.google.com/analytics/feeds/data?'.http_build_query($queryStringParams));
137 |
138 | $response = parent::read($model, $request);
139 |
140 | return $response;
141 |
142 | }
143 |
144 | }
145 | ?>
--------------------------------------------------------------------------------
/views/helpers/picaso.php:
--------------------------------------------------------------------------------
1 | _analyzeDataType($data);
25 | }
26 | //debug($data);
27 | if (!empty($type) && !empty($data)) {
28 | $method = 'render'.Inflector::camelize($type);
29 | if (method_exists($this, $method)) {
30 | return call_user_func(array($this, $method), $data);
31 | } else {
32 | // This is not my beautiful house...
33 | // ... how did I get here?
34 | }
35 | }
36 | return null;
37 | }
38 |
39 | /**
40 | * Render albums with link to album photos
41 | *
42 | * @param array $data
43 | */
44 | public function renderAlbums($data = array()) {
45 | $output = '';
46 | if (!empty($data)) {
47 | $output .= 'Albums for ' . $data['author']['name'] . '
';
48 | if (!empty($data['entry'])) {
49 | if (!isset($data['entry'][0])) {
50 | $data['entry'][0] = $data['entry'];
51 | }
52 | foreach ($data['entry'] as $album) {
53 | $output .= '';
54 | $output .= '
' . $this->Html->link($album['title'], array('action' => 'album', $this->_pullIdFromHref($album['link'][0]['href']))) . '
';
55 | $output .= '

';
56 | $output .= '
' . nl2br($album['summary']) . '
';
57 | $output .= '
' . $album['numphotos'] . ife(($album['numphotos'] <= 1), ' Photo', ' Photos') . ' | Published: ' . date('m-d-Y', strtotime($album['published'])) . ' | Location(s): ' . $album['location'] . '
';
58 | if ($album['numphotos'] > 0) {
59 | // render photos?
60 | }
61 | $output .= '
';
62 | }
63 | } else {
64 | $output .= 'There are no albums available.';
65 | }
66 | }
67 | return $this->output($output);
68 | }
69 |
70 | /**
71 | * Render Photos in an album with links to photo detail
72 | *
73 | * @param array $data
74 | * @return string
75 | */
76 | public function renderPhotos($data = array()) {
77 | $output = '';
78 | if (!empty($data)) {
79 | $output = ''. $data['title'] . '
';
80 | if (!empty($data['entry'])) {
81 | if (empty($data['entry'][0])) {
82 | $data['entry'][0] = $data['entry'];
83 | }
84 | foreach ($data['entry'] as $photo) {
85 | $output .= '';
86 | $output .= $this->Html->image($photo['group']['thumbnail'][1]['url'], array('title' => $photo['title'], 'url' => array('action' => 'photo', $this->_pullIdFromHref($photo['link'][0]['href']))));
87 | $output .= '
';
88 | }
89 | } else {
90 | $output .= 'There are no available photos in this album.';
91 | }
92 | }
93 | return $this->output($output);
94 | }
95 |
96 | /**
97 | * Render Photo data and comments
98 | *
99 | * @param array $data
100 | * @return string
101 | */
102 | public function renderPhoto($data = array()) {
103 | $output = '';
104 | if (!empty($data)) {
105 | $output = '' . $data['title'] . '
';
106 | $output .= $this->Html->image($data['group']['content']['url']);
107 | }
108 | return $this->output($output);
109 | }
110 |
111 | /**
112 | * Internal method to analyze data to discover type. Primarily for use when no type is provided to base render method.
113 | *
114 | * @param array $data data from xml result
115 | * @return string (albums|photos|photo)
116 | */
117 | private function _analyzeDataType($data) {
118 | $type = null;
119 | if (!empty($data)) {
120 | if (!empty($data['category']['term'])) {
121 | switch (true) {
122 | case ($data['category']['term'] == 'http://schemas.google.com/photos/2007#user'):
123 | $type = 'albums';
124 | break;
125 | case ($data['category']['term'] == 'http://schemas.google.com/photos/2007#album'):
126 | $type = 'photos';
127 | break;
128 | case ($data['category']['term'] == 'http://schemas.google.com/photos/2007#photo'):
129 | $type = 'photo';
130 | break;
131 | }
132 | }
133 | }
134 | return $type;
135 | }
136 |
137 | /**
138 | * Quick and dirty pull from the numeric tail of the urls used as ids
139 | *
140 | * @param string href
141 | * @return string
142 | */
143 | private function _pullIdFromHref($href) {
144 | preg_match('/^.*\/([0-9]+)$/', $href, $matches);
145 | return $matches[1];
146 | }
147 |
148 | }
149 |
150 | ?>
151 |
--------------------------------------------------------------------------------
/models/analytic.php:
--------------------------------------------------------------------------------
1 | 'File', 'name' => 'Gdata.GDATA_CONFIG', 'file' => 'config'.DS.'gdata_config.php'));
14 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataSource', 'file' => 'models'.DS.'datasources'.DS.'gdata_source.php'));
15 | App::import(array('type' => 'File', 'name' => 'Gdata.GdataAnalyticsSource', 'file' => 'models'.DS.'datasources'.DS.'gdata'.DS.'gdata_analytics.php'));
16 | $config =& new GDATA_CONFIG();
17 | ConnectionManager::create('analytics', $config->analytics);
18 | parent::__construct();
19 | }
20 |
21 | /**
22 | * Custom paginate count function, issues the find('all') call which gets
23 | * passed to the datasource, and returns the number of rows found.
24 | *
25 | * NOTE: This requires a hack to CakePHP core controller.php to pass the
26 | * limit, page, fields, group and order keys to the paginate count function.
27 | *
28 | * This is because unlike SQL like pagination, which does two database queries
29 | * for the number of rows and then for the current pages rows, this issues a
30 | * single request to the API which returns both the total number of results
31 | * and the actual results for the page in one go.
32 | *
33 | * @param array $conditions
34 | * @param integer $recursive
35 | * @param array $extra
36 | * @return integer
37 | */
38 | function paginateCount($conditions, $recursive = 1, $extra = array()) {
39 |
40 | // Initial options with the conditions, then add limit, page, fields, group
41 | // and order keys
42 | $options = array(
43 | 'conditions' => $conditions,
44 | );
45 | if (isset($extra['limit'])) {
46 | $options['limit'] = $extra['limit'];
47 | }
48 | if (isset($extra['page'])) {
49 | $options['page'] = $extra['page'];
50 | }
51 | if (isset($extra['fields'])) {
52 | $options['fields'] = $extra['fields'];
53 | }
54 | if (isset($extra['group'])) {
55 | $options['group'] = $extra['group'];
56 | }
57 | if (isset($extra['order'])) {
58 | foreach ($extra['order'] as $field => $direction) {
59 | $extra['order'][$field] = $field.' '.$direction;
60 | }
61 | $options['order'] = implode(',', $extra['order']);
62 | }
63 |
64 | // Send the query
65 | $this->find('all', $options);
66 |
67 | // Return the number of results
68 | return ConnectionManager::getDataSource($this->useDbConfig)->numRows;
69 |
70 | }
71 |
72 | /**
73 | * Returns the results from the query issues in paginateCount method
74 | *
75 | * @param mixed $conditions
76 | * @param mixed $fields
77 | * @param string $order
78 | * @param integer $limit
79 | * @param integer $page
80 | * @param integer $recursive
81 | * @param array $extra
82 | * @return array
83 | */
84 | function paginate($conditions, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null, $extra = array()) {
85 | return ConnectionManager::getDataSource($this->useDbConfig)->_result;
86 | }
87 |
88 | /**
89 | * Dimensions and their descriptions, from the google anaylytics documentation
90 | * @var array
91 | */
92 | var $dimensions = array(
93 | 'Visitor' => array(
94 | 'browser' => 'The names of browsers used by visitors to your website. For example, "Internet Explorer" or "Firefox." The version of the browser is not returned in this field.',
95 | 'browserVersion' => 'The browser versions used by visitors to your site. For example, 2.0.0.14',
96 | 'city' => 'The cities of site visitors, derived from IP addresses. The city field falls in a hierarchy of geographical groupings used in Analytics, which proceeds in the following order: continent, sub-continent, country, region, sub-region, and city.',
97 | 'connectionSpeed' => 'The qualitative network connection speeds of site visitors. For example, T1, DSL, Cable, Dialup.',
98 | 'continent' => 'The continents of site visitors, derived from IP addresses.',
99 | 'countOfVisits' => 'Number of visits to your site. This is calculated by determining the number of visitor sessions. For example, if a visitor comes to your site, exits their browser, and 5 minutes later visits your site again via the same browser, that is calculated as 2 visits.',
100 | 'country' => 'The countries of site visitors, derived from IP addresses.',
101 | 'date' => 'The date of the visit. An integer in the form YYYYMMDD.',
102 | 'day' => 'The day of the month from 01 to 31.',
103 | 'daysSinceLastVisit' => 'The number of days elapsed since visitors last visited the site. Used to calculate visitor loyalty. For example, if you view this field in a report on 5/20, and some visitors last visited your site on 5/15, the value for this would be 5, reported as "5 days ago."',
104 | 'flashVersion' => 'The versions of Flash supported by visitors\' browsers, including minor versions.',
105 | 'hostname' => 'The hostnames visitors used to reach your site. In other words, if some visitors use www.googlestore.com to reach your site, this string appears as one of the hostnames used to reach your site. However, if other visitors also come to your site via googlestore.com or via an IP redirect from a search engine result (66.102.9.104), those values will also be present in this field.',
106 | 'hour' => 'A two digit hour of the day ranging from 00-23. (Google Analytics does not track visitor time more precisely than hours.) Note: Combining this dimension with ga:adContent is not currently supported.',
107 | 'javaEnabled' => 'Whether the visitor has Java support enabled on their browser. The possible values are Yes or No.',
108 | 'language' => 'This field uses the language as provided by the HTTP Request for the browser to determine the primary languages used by visitors. Values are given as an ISO-639 code (e.g. en-gb for British English).',
109 | 'latitude' => 'The approximate latitude of the visitor\'s city. Locations north of the equator are represented by positive values and locations south of the equator by negative values.',
110 | 'longitude' => 'The approximate longitude of the visitor\'s city. Locations east of the meridian are represented by positive values and locations west of the meridian by negative values.',
111 | 'month' => 'The month of the visit. A two digit integer from 01 to 12.',
112 | 'networkDomain' => 'The domain name of the ISPs used by visitors to your website.',
113 | 'networkLocation' => 'The name of service providers used to reach your site. For example, if most visitors to your site come via the major service providers for cable internet, you will see the names of those cable service providers in this element.',
114 | 'pageDepth' => 'The number of pages visited by visitors during a session (visit). The value is a histogram that counts pageviews across a range of possible values. In this calculation, all visits will have at least one pageview, and some percentage of visits will have more.',
115 | 'operatingSystem' => 'The operating system used by your visitors. For example, Windows, Linux, Macintosh, iPhone, iPod.',
116 | 'operatingSystemVersion' => 'The version of the operating system used by your visitors, such as XP for Windows or PPC for Macintosh.',
117 | 'region' => 'The region of site visitors, derived from IP addresses. In the U.S., a region is a state, such as New York.',
118 | 'screenColors' => 'The color depth of visitors\' monitors, as retrieved from the DOM of the visitor\'s browser. Values include 4-bit, 8-bit, 24-bit, or undefined-bit.',
119 | 'screenResolution' => 'The screen resolution of visitors\' monitors, as retrieved from the DOM of the visitor\'s browser. For example: 1024x738.',
120 | 'subContinent' => 'The sub-continent of site visitors, derived from IP addresses. For example, Polynesia or Northern Europe.',
121 | 'userDefinedValue' => 'The value provided when you define custom visitor segments for your site. For more information, see Creating Custom Visitor Segments.',
122 | 'visitorType' => 'A boolean indicating if visitors are new or returning. Possible values: New Visitor, Returning Visitor.',
123 | 'week' => 'The week of the visit. A two-digit number from 01 to 52.',
124 | 'year' => 'The year of the visit. A four-digit year from 2005 to the current year.',
125 | ),
126 | 'Campaign' => array(
127 | 'adContent' => 'The first line of the text for your online Ad campaign. If you are using mad libs for your AdWords content, this field displays the keywords you provided for the mad libs keyword match. Note: Combining this dimension with ga:hour is not currently supported.',
128 | 'adGroup' => 'The ad groups that you have identified for your campaign keywords. For example, you might have an ad group toys which you associate with the keywords fuzzy bear.',
129 | 'adSlot' => 'The position of the advertisement as it appears on the host page. For example, the online advertising position might be side or top.',
130 | 'adSlotPosition' => 'The order of the online advertisement as it appears along with other ads in the position on the page. For example, the ad might appear on the right side of the page and be the 3rd ad from the top.',
131 | 'campaign' => 'The name(s) of the online ad campaign that you use for your website.',
132 | 'keyword' => 'The keywords used by visitors to reach your site, via both paid ads and through search engine results.',
133 | 'medium' => 'The type of referral to your website. For example, when referring sources to your website are search engines, there are a number of possible mediums that can be used from a search engine referral: from a search result (organic) and from an online ad on the search results page (CPC, ppc, cpa, CPM, cpv, cpp).',
134 | 'referralPath' => 'The path of the referring URL. If someone places a link to your site on their website, this element contains the path of the page that contains the referring link.',
135 | 'source' => 'The domain (e.g. google.com) of the source referring the visitor to your website. The value for this dimension sometimes contains a port address as well.',
136 | ),
137 | 'Content' => array(
138 | 'exitPagePath' => 'The last page of the session (or "exit" page) for your visitors.',
139 | 'landingPagePath' => 'The path component of the URL of the entrance or "landing" page for your visitors.',
140 | 'pagePath' => 'The page on your website by path and/or query parameters.',
141 | 'pageTitle' => 'The title for the page, as specified in the element of the HTML document.',
142 | ),
143 | 'Ecommerce' => array(
144 | 'affiliation' => 'Typically used to designate a supplying company or brick and mortar location; product affiliation.',
145 | 'daysToTransaction' => 'The number of days between users\' purchases and the related campaigns that lead to the purchases.',
146 | 'productCategory' => 'Any product variations (size, color) for purchased items as supplied by your ecommerce application.',
147 | 'productName' => 'The product name for purchased items as supplied by your ecommerce tracking method.',
148 | 'productSku' => 'The product codes for purchased items as you have defined them in your ecommerce tracking application.',
149 | 'transactionId' => 'The transaction ID for the shopping cart purchase as supplied by your ecommerce tracking method.',
150 | ),
151 | 'Internal Search' => array(
152 | 'searchCategory' => 'If you have categories enabled for internal site search, this field identifies the categories used for the internal search. For example, you might have product categories for internal search, such as electronics, furniture, or clothing.',
153 | 'searchDestinationPage' => 'The page that the user visited after performing an internal site search.',
154 | 'searchKeyword' => 'Search terms used by site visitors on your internal site search.',
155 | 'searchKeywordRefinement' => 'Subsequent keyword search terms or strings entered by users after a given initial string search.',
156 | 'searchStartPage' => 'The page where the user initiated an internal site search.',
157 | 'searchUsed' => 'A boolean which separates visitor activity depending upon whether internal search activity occured or did not occur. Values are Visits With Site Search and Visits Without Site Search.',
158 | ),
159 | );
160 |
161 | /**
162 | * Metrics and their descriptions, from the google anaylytics documentation
163 | * @var array
164 | */
165 | var $metrics = array(
166 | 'Visitor' => array(
167 | 'bounces' => 'The total number of single-page visits to your site.',
168 | 'entrances' => 'The number of entrances to your site. The value will always be equal to the number of visits when aggregated over your entire website. Thus, this metric is most useful when combined with dimensions such as ga:landingPagePath, at which point entrances as a metric indicates the number of times a particular page served as an entrance to your site.',
169 | 'exits' => 'The number of exits from your site. As with entrances, it will always be equal to the number of visits when aggregated over your entire website. Use this metric in combination with content dimensions such as ga:exitPagePath in order to determine the number of times a particular page was the last one viewed by visitors.',
170 | 'newVisits' => 'The number of visitors whose visit to your site was marked as a first-time visit.',
171 | 'pageviews' => 'The total number of pageviews for your site when aggregated over the selected dimension. For example, if you select this metric together with ga:pagePath, it will return the number of page views for each URI.',
172 | 'timeOnPage' => 'How long a visitor spent on a particular page or set of pages. Calculated by subtracting the initial view time for a particular page from the initial view time for a subsequent page. Thus, this metric does not apply to exit pages for your site.',
173 | 'timeOnSite' => 'The total duration of visitor sessions over the selected dimension. For example, suppose you combine this field with a particular ad campaign. In this case, the metric will display the total duration of all visitor sessions for those visitors who came to your site via a particular ad campaign. You could then compare this metric to the duration of all visitors who came to your site through means other than the particular ad campaign. This would then give you a side-by-side comparison and a means to calculate the boost in visit duration provided by a particular campaign.',
174 | 'visitors' => 'Total number of visitors to your site for the requested time period. When requesting this metric, you can only combine it with time dimensions such as ga:hour or ga:year.',
175 | 'visits' => 'The total number of visits over the selected dimension. A visit consists of a single-user session, which times out automatically after 30 minutes unless the visitor continues activity on your site, or unless you have adjusted the user session in the ga.js tracking for your site. See Adjusting the User Session for more information.',
176 | ),
177 | 'Campaign' => array(
178 | 'adClicks' => 'The total number of times users have clicked on an ad to reach your site.',
179 | 'adCost' => 'Derived cost for the advertising campaign. The currency for this value is based on the currency that you set in your AdWords account.',
180 | 'CPC' => 'Cost to advertiser per click.',
181 | 'CPM' => 'Cost per thousand impressions.',
182 | 'CTR' => 'Click-through-rate for your ad. This is equal to the number of clicks divided by the number of impressions for your ad (e.g. how many times users clicked on one of your ads where that ad appeared).',
183 | 'impressions' => 'Total number of campaign impressions.',
184 | ),
185 | 'Content' => array(
186 | 'uniquePageviews' => 'The number of different (unique) pages within a visit, summed up across all visits',
187 | ),
188 | 'Ecommerce' => array(
189 | 'itemRevenue' => 'Total revenue from purchased product items on your site. See the tracking API reference for _addItem() for additional information.',
190 | 'itemQuantity' => 'The total number of items purchased. For example, if users purchase 2 frisbees and 5 tennis balls, 7 items have been purchased.',
191 | 'transactionRevenue' => 'The total sale revenue, including shipping and tax, if provided in the transation. See the documentation for _addTrans() in the tracking API reference for additional information.',
192 | 'transactions' => 'The total number of transactions.',
193 | 'transactionShipping' => 'The total cost of shipping.',
194 | 'transactionTax' => 'The total amount of tax.',
195 | 'uniquePurchases' => 'The number of product sets purchased. For example, if users purchase 2 frisbees and 5 tennis balls from your site, 2 product sets have been purchased.',
196 | ),
197 | 'Internal Search' => array(
198 | 'searchDepth' => 'The average number of subsequent page views made on your site after a use of your internal search feature.',
199 | 'searchDuration' => 'The visit duration to your site where a use of your internal search feature occurred.',
200 | 'searchExits' => 'The number of exits on your site that occurred following a search result from your internal search feature.',
201 | 'searchRefinements' => 'The number of refinements made on an internal search.',
202 | 'searchUniques' => 'The number of unique visitors to your site who used your internal search feature.',
203 | 'searchVisits' => 'The total number of visits to your site where a use of your internal search feature occurred.',
204 | ),
205 | 'Goals' => array(
206 | 'goal1Completions' => 'The total number of completions for goal 1.',
207 | 'goal2Completions' => 'The total number of completions for goal 2.',
208 | 'goal3Completions' => 'The total number of completions for goal 3.',
209 | 'goal4Completions' => 'The total number of completions for goal 4.',
210 | 'goalCompletionsAll' => 'The total number of completions for all goals defined for your profile.',
211 | 'goal1Starts' => 'The total number of starts for goal 1.',
212 | 'goal2Starts' => 'The total number of starts for goal 2.',
213 | 'goal3Starts' => 'The total number of starts for goal 3.',
214 | 'goal4Starts' => 'The total number of starts for goal 4.',
215 | 'goalStartsAll' => 'The total number of starts for all goals defined for your profile.',
216 | 'goal1Value' => 'The total numeric value for goal 1.',
217 | 'goal2Value' => 'The total numeric value for goal 2.',
218 | 'goal3Value' => 'The total numeric value for goal 3.',
219 | 'goal4Value' => 'The total numeric value for goal 4.',
220 | 'goalValueAll' => 'The total value for all goals defined for your profile.',
221 | ),
222 | );
223 |
224 | /**
225 | * Overwrites the hasField method in Model.
226 | * Required for pagination.
227 | *
228 | * @param string $name
229 | * @return boolean
230 | */
231 | function hasField($name) {
232 | foreach ($this->dimensions as $category => $dimensions) {
233 | if (isset($dimensions[$name])) {
234 | return true;
235 | }
236 | }
237 | foreach ($this->metrics as $category => $metrics) {
238 | if (isset($metrics[$name])) {
239 | return true;
240 | }
241 | }
242 | return false;
243 | }
244 |
245 | }
246 |
247 | ?>
--------------------------------------------------------------------------------