Gentics Portal.Node PHP API
 All Classes Namespaces Functions Variables Pages
NavigationTree.php
1 <?php
2 /**
3  * Gentics Portal.Node PHP
4  * Author & Copyright (c) by Gentics Software GmbH
5  * sales@gentics.com
6  * http://www.gentics.com
7  * Licenses can be found in the LICENSE.txt file in the root-folder of this installation
8  * You must not use this software without a valid license agreement.
9  *
10  * Class for working with Gentics Content Connector
11  */
12 class NavigationTree extends CComponent
13 {
14 
15  /**
16  * id of the root folder for current widget
17  * @var $startfolderId
18  */
19  public $startfolderId = 0;
20 
21  /**
22  * id of the root folder for current widget
23  * @var $activeElement
24  */
25  public $activeElement = 0;
26 
27  /**
28  * array that contains categories tree
29  * @var $data
30  */
31  public $data = array();
32 
33  /**
34  * array that contains Ids for active categories
35  * @var $activePath
36  */
37  public $activePath = array();
38 
39  /**
40  * @var $lang string containg current languagecode
41  */
42  public $lang;
43 
44  /**
45  * @var bool $usePersonalisation defines if personalisation should be performed
46  */
47  public $usePersonalisation = true;
48 
49  public function getTreeCacheTime()
50  {
51  return Yii::app()->getModule('navigation')->cacheTime;
52  }
53 
54  public function getLatestUpdateTimestampCacheTime()
55  {
56  return Yii::app()->getModule('navigation')->timestampCacheTime;
57  }
58 
59  /**
60  * @var array $additionalParams defines additional Parameters to request
61  */
62  public $additionalParams = array();
63 
64  /**
65  * @var string $sortby defines the field for sorting
66  */
67  public $sortby = "sortorder:asc";
68 
69  /**
70  * Construction that will load nodes tree for given startfolderId and activePath for given activeElement
71  *
72  * @param integer $startfolderId id of folder that must be treated as root
73  * @param int $activeElement id
74  *
75  * @return \NavigationTree
76  */
77  public function __construct($startfolderId = 0, $activeElement = 0, $additionalParams = array(), $sortby = null)
78  {
79  if ($startfolderId != '' && (int)$startfolderId > 0) {
80  $this->lang = substr(Yii::app()->language, 0, 2);
81 
82  $this->startfolderId = $startfolderId;
83  $this->activeElement = $activeElement;
84  $this->additionalParams = $additionalParams;
85  if ($sortby === null) {
86  $this->sortby = Yii::app()->getModule('navigation')->sortorder;
87  } else {
88  $this->sortby = $sortby;
89  }
90 
91  $this->_loadData();
92  $this->_loadActivePath();
93  } else {
94  return false;
95  }
96  }
97 
98  /**
99  * Filter tree by some schema
100  *
101  * @param integer $user_id Id of the user to filter tree for
102  * @param bool $usePersonalisation defines if personalisation of received content should be performed
103  *
104  * @return array
105  */
106  public function cleanUp($user_id, $usePersonalisation = true)
107  {
108  //get filter fields from settings
109  $filters = $this->getFiltersList();
110 
111  $this->usePersonalisation = $usePersonalisation;
112 
113  //load tree data into local array
114  $data = $this->data;
115  // apply filter here
116  if ($filters && $data) {
117  $data = $this->_unsetNodes($filters, $user_id, $data);
118  }
119 
120  return $data;
121  }
122  /**
123  * Method returns startpage url for passed contentrepository item
124  *
125  * @param array $item - contentrepository item
126  * @param string $lang- contentrepository language
127  *
128  * @return string url
129  */
130  public static function getStartpageUrl($item,$lang)
131  {
132  $defaultUrl = Yii::app()->getModule('navigation')->startpage_url;
133  $startpage_url_prefix = Yii::app()->getModule('navigation')->startpage_url_prefix;
134  $startpageUrl = '';
135  if (isset($item['attributes'][$startpage_url_prefix . $lang])
136  && !empty($item['attributes'][$startpage_url_prefix . $lang])) {
137  $startpageUrl = $item['attributes'][$startpage_url_prefix . $lang];
138  } else if (isset($item['attributes']['startpageurl']) && !empty($item['attributes']['startpageurl'])) {
139  $startpageUrl = $item['attributes']['startpageurl'];
140  } else {
141  $startpageUrl = $defaultUrl;
142  }
143  return $startpageUrl;
144  }
145  /**
146  * Method returns name for passed contentrepository item
147  *
148  * @param array $item - contentrepository item
149  * @param string $lang- contentrepository language
150  *
151  * @return string name
152  */
153  public static function getBranchName($item,$lang)
154  {
155  $defaultName = Yii::app()->getModule('navigation')->branch_name;
156  $name_prefix = Yii::app()->getModule('navigation')->name_prefix;
157  $name= '';
158  if (isset($item['attributes'][$name_prefix . $lang]) && !empty($item['attributes'][$name_prefix . $lang])) {
159  $name = $item['attributes'][$name_prefix . $lang];
160  } else if(isset($item['attributes']['name']) && !empty($item['attributes']['name'])) {
161  $name = $item['attributes']['name'];
162  } else {
163  $name = $defaultName;
164  }
165  return $name;
166  }
167 
168  /**
169  * Private method that will unset not allowed data
170  *
171  * @param array $filters contains keys that will be used as filters
172  * @param array $user_id contains user permissions
173  * @param array $data points to the array with nodes tree
174  *
175  * @return array|null
176  */
177  private function _unsetNodes($filters, $user_id, $data)
178  {
179  /* check permissions for current node */
180  $persAttributes = array();
181  foreach ($filters as $filter) {
182  if (!empty($data['attributes'][$filter])) {
183  $persAttributes = array_merge($persAttributes, $data['attributes'][$filter]);
184  }
185  }
186 
187  //check if page has allowed startpageurl and exlude the branch if it does not.
188  if ($data['contentid'] != $this->startfolderId) {
189 
190  $startpageUrl = self::getStartpageUrl($data , $this->lang);
191  $excludeURLs = Yii::app()->getModule('navigation')->excludeURLs;
192  /* if startpageurl is one those that should be excluded or empty - exclude it */
193  if (empty($startpageUrl) || in_array($startpageUrl, $excludeURLs)) {
194  return null;
195  }
196  }
197 
198  // Call function that will check permissions
199  if ($this->usePersonalisation && !Yii::app()->getModule('personalisation')->rule->checkAccess($user_id, $persAttributes)) {
200  /* if user does not have permissions - unset whole node */
201  return null;
202  } else if (isset($data['children'])) {
203  /* check every child for permission */
204  foreach ($data['children'] as $kChild => $child) {
205  $data['children'][$kChild] = $this->_unsetNodes($filters, $user_id, $child);
206  if (is_null($data['children'][$kChild])) {
207  unset($data['children'][$kChild]);
208  }
209  }
210  if (count($data['children']) == 0) {
211  unset($data['children']);
212  }
213  }
214  return $data;
215  }
216 
217  /**
218  * Filter tree by some schema
219  *
220  * @return array|bool
221  */
222  public function getFiltersList()
223  {
224  if (Yii::app()->getModule('contentSource')->contentSource->usePersonalisation) {
225  $filters = Yii::app()->getModule('contentSource')->contentSource->personalisationFields;
226  return $filters;
227  }
228 
229  return false;
230  }
231 
232  /**
233  * Load required tree data from cache. If not exists request Gentics Content Connector API for a new tree.
234  *
235  * @return array
236  */
237  private function _loadData()
238  {
239 
240  global $navigationCache;
241 
242  // only experimental
243  $compress = false;
244  //$cacheId = Yii::app()->cache->get("navigation_version").'navigation_' . $this->startfolderId;
245 
246  // reset cache data for navigation to null
247  //$cachedata = false;
248  //Yii::app()->cache->set($cacheId, $cachedata, 0);
249 
250  //$lang = substr(Yii::app()->language, 0, 2);
251 
252  $sysLangs = Yii::app()->getModule('language')->languages;
253  if (empty($sysLangs[$this->lang])) {
254  $sysLangs[$this->lang] = $this->lang;
255  }
256 
257  $requestFields = array(
258  'updatetimestamp',
259  'node_id',
260  'sortorder',
261  'navhidden',
262  'name',
263  'startpageurl'
264  );
265 
266  foreach ($sysLangs as $lang) {
267  $requestFields[] = 'name_' . $lang;
268  $requestFields[] = 'startpageurl_' . $lang;
269  }
270 
271  $requestFields = array_merge($requestFields,$this->additionalParams);
272 
273  $cacheId = 'navigation_'.$this->startfolderId.'&langs='.serialize($sysLangs);
274  $updateTimestamp = $this->_getYoungestTimestamp($this->startfolderId, $cacheId);
275  $cacheId .= $updateTimestamp;
276  $data = array();
277  $filtersList = '';
278 
279  // this is a caching for a parameter inside 1 request
280  if (isset( $navigationCache[$cacheId])) {
281  $data = $navigationCache[$cacheId];
282  } else {
283  // getting of data from cache is btwn 30-60 ms, maybe we can improve this
284  //echo (memory_get_usage()/1024/1024) . " mem<br>";
285  $data = Yii::app()->cache->get($cacheId);
286  //echo (memory_get_usage()/1024/1024) . " mem<br>";
287  }
288 
289  if ($data !== false) {
290  if ( $compress == true ) $data = unserialize( gzuncompress( $data ) );
291  // if navigation data are in variable, we write it into global variable to reduce cache requests
292  $navigationCache[$cacheId] = $data;
293  }
294 
295  //Load data
296  if ($data === false) {
297 
298  if ($filters = $this->getFiltersList()) {
299  $filtersList = implode(',', $filters);
300  }
301 
302  $data = Yii::app()->repositoryApi->requestNavigation(
303  array(
304  'rootfilter' => 'object.contentid=="'.$this->startfolderId.'"',
305  'sorting' => $this->sortby,
306  'navigation' => 'true',
307  'childfilter' => 'object.obj_type==10002 AND object.navhidden!=1',
308  'type' => 'php',
309  ),
310  CMap::mergeArray($this->getFiltersList(), $requestFields)
311  );
312 
313  if ( $compress == true )
314  $cachedata = gzcompress( $data, 9 );
315  else
316  $cachedata = unserialize( $data );
317 
318  $data = $cachedata;
319 
320  Yii::app()->cache->set($cacheId, $cachedata, $this->getTreeCacheTime());
321  }
322  $this->data = isset($data[$this->startfolderId]) ? $data[$this->startfolderId] : array();
323 
324  // removed it for smaller debug log
325  //Yii::trace(print_r($this->data, true), 'navigation tree');
326 
327  }
328 
329  private function _getYoungestTimestamp($objectId, $objectCacheId)
330  {
331  $cacheId = 'youngest_' . $objectCacheId;
332  if (($updateTimestamp = Yii::app()->cache->get($cacheId)) == false) {
333  $updateTimestamp = Yii::app()->repositoryApi->requestNavigation(
334  array(
335  "rootfilter" => 'object.contentid=="' . $objectId . '"',
336  "type" => "youngest",
337  "childfilter" => "object.obj_type==10002 AND object.navhidden!=1"
338  ),
339  array()
340  );
341  Yii::app()->cache->set($cacheId, $updateTimestamp, $this->getLatestUpdateTimestampCacheTime());
342  }
343 
344  return $updateTimestamp;
345  }
346 
347  /**
348  * Load active path for current startfolderId and activeElement. If not exists request Gentics Content Connector API.
349  *
350  * @return array
351  */
352  private function _loadActivePath()
353  {
354 
355  //$cacheId = md5('activePath_' . NavigationTree::getUpdatetimestampByContentId($this->startfolderId) . '_' . $this->startfolderId . '_' . $this->activeElement);
356  // we removed the updatetimestamp because the active path is only valid for 60 seconds
357  $cacheId = Yii::app()->cache->get("navigation_version").'activePath_' . $this->startfolderId . '_' . $this->activeElement;
358 
359  //Load data
360  if (($data = Yii::app()->cache->get($cacheId)) === false) {
361  if (!empty($this->activeElement)) {
362  $data = Yii::app()->repositoryApi->requestActivePath(
363  array(
364  'root' => $this->startfolderId,
365  'filter' => 'object.contentid=="'.$this->activeElement.'"',
366  'type' => 'php',
367  )
368  );
369 
370  $data = unserialize($data);
371  unset($data['status']);
372  $data = array_keys($data);
373  } else {
374  $data = array(0);
375  }
376 
377  Yii::app()->cache->set($cacheId, $data, $this->getTreeCacheTime());
378  Yii::trace('active path for element ' . $this->activeElement . ' putted to cache', 'test');
379  } else {
380  Yii::trace('active path for element ' . $this->activeElement . ' taken from cache', 'test');
381  }
382 
383  $this->activePath = $data;
384  }
385 
386  /**
387  * returnes array with active path for given $startfolderId
388  *
389  * @param varchar $startfolderId contentId to check timestamp
390  *
391  * @return string|bool
392  */
394  {
395  $cacheId = 'navigation_' . $startfolderId;
396 
397  //if (($data = Yii::app()->cache->get($cacheId)) === false) {
398  // data of navigation are now in a global variable
399  if ( isset( $navigationCache[$cacheId] ) ) {
400  $data = $navigationCache[$cacheId];
401  }
402  if ( !isset( $data ) || is_array( $data ) ) {
403  //if ( ($data = $navigationCache[$cacheId]) === false ) {
404  return false;
405  } else {
406  return $data[(string)$startfolderId]['attributes']['updatetimestamp'];
407  }
408  }
409 
410  /**
411  * Returnes branch with given id if exists
412  *
413  * @param array $node contains navigation tree
414  * @param string $contentId contains id of content to search
415  *
416  * @return array|bool searched node
417  */
418  public static function extractBranch($node = array(), $contentId = '')
419  {
420  if ($node['contentid'] == $contentId) {
421  return $node;
422  } elseif (!empty($node['children'])) {
423  foreach ($node['children'] as $childNode) {
424  if ($searchedNode = self::extractBranch($childNode, $contentId)) {
425  return $searchedNode;
426  }
427  }
428  }
429 
430  return false;
431  }
432 }