├── 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 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
ThumbnailTitleContentLocationCategoryKeywordsDurationPublishedUpdated
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 | link($youtubeVideo['title']['value'], $youtubeVideo['group']['player']['url']); ?>
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 ''; 82 | } 83 | ?> 84 | 85 | 86 | 87 | '; 90 | foreach (arrayalise($entry['dimension']) as $dimension) { 91 | echo ''; 92 | } 93 | foreach (arrayalise($entry['metric']) as $metric) { 94 | echo ''; 95 | } 96 | echo ''; 97 | } 98 | ?> 99 | 100 |
'.$paginator->sort(substr($metric['name'], 3)).'
'.$dimension['value'].''.$metric['value'].'
101 | 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 | ?> --------------------------------------------------------------------------------