Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
asset_manager.inc
1 <?php
18 require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
19 
20 
32 {
33 
39  var $_asset_types;
40 
47  var $_assets = Array();
48 
56  var $_asset_cache;
57 
63  var $_system_assetids = Array();
64 
70  var $_attributes = Array();
71 
77  var $_get_asset_history = Array();
78 
79 
84  function Asset_Manager()
85  {
86  $this->MySource_Object();
87  $this->_loadAssetTypes();
88 
89  require_once SQ_INCLUDE_PATH.'/asset_cache.inc';
90  $this->_asset_cache = new Asset_Cache();
91  $this->_asset_cache->setSizeRules((php_sapi_name() == 'cli') ? SQ_CONF_ASSET_CACHE_SIZE_CLI : SQ_CONF_ASSET_CACHE_SIZE_WEB);
92 
93  }//end constructor
94 
95 
96 //-- ASSET TYPES --//
97 
98 
105  function _loadAssetTypes()
106  {
107  $this->_asset_types = Array();
108 
109  if (is_file(SQ_DATA_PATH.'/private/db/asset_types.inc')) {
110  include(SQ_DATA_PATH.'/private/db/asset_types.inc');
111  $this->_asset_types = $asset_types;
112  } else {
113  // if the table columns have not been cached, the database
114  // install has not been completed, so we cant get the types from the DB
115  if (!is_file(SQ_DATA_PATH.'/private/db/table_columns.inc')) {
116  return;
117  }
118 
119  // load the asset types from the DB, which will also cache
120  // them to the asset_types.inc file
121  $this->_asset_types = $this->getAssetTypes();
122  }
123 
124  }//end _loadAssetTypes()
125 
126 
136  function getAssetTypes($instantiable=NULL, $non_system_access=FALSE)
137  {
138  $asset_types = Array();
139 
140  $db = MatrixDAL::getDb();
141  $sql = 'SELECT type_code, version, name, instantiable, allowed_access, parent_type, dir, customisation, description, lvl
142  FROM sq_ast_typ';
143 
144  $where = '';
145  if (!is_null($instantiable)) {
146  $where .= ' WHERE instantiable = '.MatrixDAL::quote((int)$instantiable);
147  }
148  if ($non_system_access) {
149  $where .= (($where) ? ' AND' : ' WHERE').' allowed_access != '.MatrixDAL::quote('system');
150  }
151 
152  $rows = MatrixDAL::executeSqlAssoc($sql.$where);
153  foreach ($rows as $row) {
154  $asset_types[$row['type_code']] = $row;
155  }
156 
157  return $asset_types;
158 
159  }//end getAssetTypes()
160 
161 
172  function refreshAssetType($type_code)
173  {
174  $db = MatrixDAL::getDb();
175  $sql = 'SELECT type_code, version, name, instantiable, allowed_access, parent_type, dir, customisation, description, lvl
176  FROM sq_ast_typ
177  WHERE type_code = :type_code';
178 
179  try {
180  $query = MatrixDAL::preparePdoQuery($sql);
181  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
182  $result = MatrixDAL::executePdoAll($query);
183  } catch (Exception $e) {
184  throw new Exception('Unable to get information for type code "'.$type_code.'" due to database error: '.$e->getMessage());
185  }
186 
187  if (empty($result)) {
188  trigger_localised_error('SYS0085', E_USER_WARNING, $type_code);
189  return;
190  } else {
191  $this->_asset_types[$result[0]['type_code']] = $result[0];
192  }
193 
194  $parents = Array($type_code);
195  $tmp_type_code = $type_code;
196  while ($this->_asset_types[$tmp_type_code]['parent_type'] != 'asset') {
197  // this should NEVER happen, if it does DIE
198  assert_isset($this->_asset_types[$this->_asset_types[$tmp_type_code]['parent_type']], 'Unable to get the parent of asset type "'.$this->_asset_types[$tmp_type_code]['parent_type'].'" as this asset is not installed');
199 
200  $tmp_type_code = $this->_asset_types[$tmp_type_code]['parent_type'];
201  $parents[] = $tmp_type_code;
202  }
203  $parents[] = 'asset';
204 
205  $sql = 'SELECT inhd_type_code
206  FROM sq_ast_typ_inhd
207  WHERE type_code = :type_code';
208  try {
209  $query = MatrixDAL::preparePdoQuery($sql);
210  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
211  $db_parents = MatrixDAL::executePdoAssoc($query, 0);
212  } catch (Exception $e) {
213  throw new Exception('Unable to get inheritance information for type code "'.$type_code.'" due to database error: '.$e->getMessage());
214  }
215 
216  $inserts = array_diff($parents, $db_parents);
217  $deletes = array_diff($db_parents, $parents);
218  $updates = array_intersect($parents, $db_parents);
219 
220  $type_code_level = (int) $this->getTypeInfo($type_code, 'lvl');
221 
222  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
223  $db = MatrixDAL::getDb();
224  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
225 
226  foreach ($inserts as $inherited_type_code) {
227  $inherited_type_code_level = ($inherited_type_code == 'asset') ? 0 : (int) $this->getTypeInfo($inherited_type_code, 'lvl');
228  $sql = 'INSERT INTO sq_ast_typ_inhd
229  (inhd_type_code, type_code, inhd_type_code_lvl, type_code_lvl)
230  VALUES
231  (:inhd_type_code, :type_code, :inhd_lvl, :lvl)';
232 
233  try {
234  $query = MatrixDAL::preparePdoQuery($sql);
235  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
236  MatrixDAL::bindValueToPdo($query, 'inhd_type_code', $inherited_type_code, PDO::PARAM_STR);
237  MatrixDAL::bindValueToPdo($query, 'lvl', $type_code_level, PDO::PARAM_INT);
238  MatrixDAL::bindValueToPdo($query, 'inhd_lvl', $inherited_type_code_level, PDO::PARAM_INT);
239  MatrixDAL::execPdoQuery($query);
240  } catch (Exception $e) {
241  throw new Exception('Unable to insert inheritance information for type code "'.$type_code.'" due to database error: '.$e->getMessage());
242  }
243  }
244 
245  foreach ($deletes as $inherited_type_code) {
246  $sql = 'DELETE FROM
247  sq_ast_typ_inhd
248  WHERE
249  inhd_type_code = :inhd_type_code
250  AND type_code = :type_code';
251 
252  try {
253  $query = MatrixDAL::preparePdoQuery($sql);
254  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
255  MatrixDAL::bindValueToPdo($query, 'inhd_type_code', $inherited_type_code, PDO::PARAM_STR);
256  MatrixDAL::execPdoQuery($query);
257  } catch (Exception $e) {
258  throw new Exception('Unable to delete inheritance information for type code "'.$type_code.'" due to database error: '.$e->getMessage());
259  }
260  }
261 
262  foreach ($updates as $inherited_type_code) {
263  $inherited_type_code_level = ($inherited_type_code == 'asset') ? 0 : (int) $this->getTypeInfo($inherited_type_code, 'lvl');
264  $sql = 'UPDATE
265  sq_ast_typ_inhd
266  SET
267  inhd_type_code_lvl = :inhd_lvl,
268  type_code_lvl = :lvl
269  WHERE
270  inhd_type_code = :inhd_type_code
271  AND type_code = :type_code';
272 
273  try {
274  $query = MatrixDAL::preparePdoQuery($sql);
275  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
276  MatrixDAL::bindValueToPdo($query, 'inhd_type_code', $inherited_type_code, PDO::PARAM_STR);
277  MatrixDAL::bindValueToPdo($query, 'lvl', $type_code_level, PDO::PARAM_INT);
278  MatrixDAL::bindValueToPdo($query, 'inhd_lvl', $inherited_type_code_level, PDO::PARAM_INT);
279  MatrixDAL::execPdoQuery($query);
280  } catch (Exception $e) {
281  throw new Exception('Unable to update inheritance information for type code "'.$type_code.'" due to database error: '.$e->getMessage());
282  }
283  }
284 
285  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
286  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
287 
288  }//end refreshAssetType()
289 
290 
299  function installed($type_code)
300  {
301  return isset($this->_asset_types[$type_code]);
302 
303  }//end installed()
304 
305 
312  function getTypeList()
313  {
314  return array_keys($this->_asset_types);
315 
316  }//end getTypeList()
317 
318 
331  function getTypeInfo($type_code, $field='')
332  {
333  $field = trim($field);
334 
335  // if the file does not exist, then we must be installing so get the info from the database
336  if (!is_file(SQ_DATA_PATH.'/private/db/asset_types.inc')) {
337 
338  if (empty($type_code)) {
339  // No type code specified
340  if ($field == '') {
341  return Array();
342  } else {
343  return NULL;
344  }
345  }
346 
347  $db = MatrixDAL::getDb();
348 
349  if (is_array($type_code)) {
350  foreach ($type_code as $key => $asset_type) {
351  $type_code[$key] = MatrixDAL::quote($type_code[$key]);
352  }
353  $type_code_cond = 'IN ('.implode(', ', $type_code).')';
354  } else {
355  $type_code_cond = ' = '.MatrixDAL::quote($type_code);
356  }
357 
358  // if they have not specified any fields, then we want to select *
359  if ($field == '') {
360  $field_list = 'type_code, version, name, description, instantiable, allowed_access, parent_type, lvl, dir, customisation';
361  } else {
362  $field_list = 'type_code, '.$field;
363  }
364 
365  $sql = 'SELECT
366  '.$field_list.'
367  FROM
368  sq_ast_typ
369  WHERE
370  type_code '.$type_code_cond;
371 
372  try {
373  $query = MatrixDAL::preparePdoQuery($sql);
374  $result = MatrixDAL::executePdoGroupedAssoc($query);
375  } catch (Exception $e) {
376  if (is_array($type_code)) {
377  throw new Exception('Unable to get type info for multiple type codes due to database error: '.$e->getMessage());
378  } else {
379  throw new Exception('Unable to get type info for type code "'.$type_code.'" due to database error: '.$e->getMessage());
380  }
381  }
382 
383  if (!is_array($type_code) && !empty($field)) {
384  return $result[$type_code][0][$field];
385  }
386 
387  if (is_array($type_code)) {
388  return $result;
389  } else {
390  return $result[$type_code][0];
391  }
392 
393  } else {
394  // else we should have our own store of asset types
395  if (is_array($type_code)) {
396  $result = Array();
397  foreach ($type_code as $type) {
398  if (!isset($this->_asset_types[$type])) {
399  trigger_localised_error('SYS0091', E_USER_WARNING, $type);
400  continue;
401  }
402  if (empty($field)) {
403  $result[$type] = $this->_asset_types[$type];
404  } else {
405  if (!isset($this->_asset_types[$type][$field])) {
406  trigger_localised_error('SYS0185', E_USER_WARNING, $field);
407  continue;
408  }
409  $result[$type] = $this->_asset_types[$type][$field];
410  }
411  }
412  return $result;
413  } else {
414  if (!isset($this->_asset_types[$type_code])) {
415  trigger_localised_error('SYS0091', E_USER_WARNING, $type_code);
416  return (empty($field)) ? Array() : NULL;
417  }
418  if (empty($field)) {
419  return $this->_asset_types[$type_code];
420  } else {
421  if (!isset($this->_asset_types[$type_code][$field])) {
422  trigger_localised_error('SYS0185', E_USER_WARNING, $field);
423  return NULL;
424  }
425  return $this->_asset_types[$type_code][$field];
426  }
427  }
428  }//end else
429 
430  }//end getTypeInfo()
431 
432 
443  function getAssetTypeHierarchy($base_type_code='', $allowed_access='')
444  {
445  if (empty($base_type_code)) $base_type_code = 'asset';
446 
447  $type_codes = Array();
448  $allowed_access = trim($allowed_access);
449  if (!empty($allowed_access)) {
450  try {
451  $bind_vars = Array (
452  'allowed_access' => $allowed_access,
453  );
454  $type_codes = MatrixDAL::executeAssoc('core', 'getTypeCodeWithAllowedAccess', 0, $bind_vars);
455  } catch (Exception $e) {
456  throw new Exception('Failed to get type code with allowed access due to database error: '. $e->getMessage());
457  }
458  } else {
459  $type_codes = array_keys($this->_asset_types);
460  }
461 
462  $offspring = Array();
463  foreach ($type_codes as $type_code) {
464  if (is_array($type_code)) {
465  $type_code = $type_code['type_code'];
466  }
467 
468  $parent = $this->_asset_types[$type_code]['parent_type'];
469  if (!isset($offspring[$parent])) {
470  $offspring[$parent] = Array();
471  }
472  $offspring[$parent][] = $type_code;
473  }
474 
475  $hier = $this->_recurseGetAssetTypeHierarchy($offspring, $base_type_code);
476 
477  return $hier;
478 
479  }//end getAssetTypeHierarchy()
480 
481 
491  function _recurseGetAssetTypeHierarchy($offspring, $base_type_code)
492  {
493  if (empty($offspring[$base_type_code])) return Array();
494  $arr = Array();
495  for ($i = 0; $i < count($offspring[$base_type_code]); $i++) {
496  $type = $offspring[$base_type_code][$i];
497  $arr[$type] = Array(
498  'name' => $this->_asset_types[$type]['name'],
499  'subs' => $this->_recurseGetAssetTypeHierarchy($offspring, $type),
500  );
501  }
502  return $arr;
503 
504  }//end _recurseGetAssetTypeHierarchy()
505 
506 
517  function getAssetTypeInfo($assetids, $type_code=Array(), $strict_type_code=TRUE)
518  {
519  if (!is_array($assetids)) {
520  $assetids = Array($assetids);
521  }
522  assert_type($type_code, 'array');
523  if (empty($assetids)) return Array();
524 
525  $db = MatrixDAL::getDb();
526  $where = '';
527 
528  $shadowids = Array();
529  $normalids = Array();
530  $shadow_results = Array();
531 
532  // create a list of (hopefully) separated assetids and shadow assetids
533  for ($i = 0; $i < count($assetids); $i++) {
534  $id_parts = explode(':', $assetids[$i]);
535 
536  if (isset($id_parts[1])) {
537  $shadowids[$id_parts[0]][] = $assetids[$i];
538  } else {
539  $normalids[] = $assetids[$i];
540  }
541  }
542 
543  // set it to the array minus the shadowids
544  $assetids = $normalids;
545 
546  // we have picked up some shadow assets
547  if (!empty($shadowids)) {
548  $shadow_result = Array();
549 
550  // make each bridge get its info
551  foreach ($shadowids as $assetid => $shadows) {
552  $asset = $this->getAsset($assetid);
553  if (method_exists($asset, 'getAssetInfo')) {
554  $shadow_results += $asset->getAssetTypeInfo($shadows, $type_code, $strict_type_code);
555  }
556  }
557  }
558 
559  // breakout if nothing left to do
560  if (empty($assetids)) return $shadow_results;
561 
562  for (reset($assetids); NULL !== ($k = key($assetids)); next($assetids)) {
563  $assetids[$k] = MatrixDAL::quote( (string) $assetids[$k]);
564  }
565  $where .= 'a.assetid IN ('.implode(', ', $assetids).')';
566 
567  if (!empty($type_code)) {
568 
569  $type_code_cond = '';
570  if (is_array($type_code)) {
571  for ($i = 0; $i < count($type_code); $i++) {
572  $type_code[$i] = MatrixDAL::quote($type_code[$i]);
573  }
574  $type_code_cond = 'IN ('.implode(', ', $type_code).')';
575  } else {
576  $type_code_cond = '= '.MatrixDAL::quote($type_code);
577  }
578 
579  if ($strict_type_code) {
580  $where .= ' AND a.type_code '.$type_code_cond;
581  } else {
582  $where .= ' AND a.type_code IN (
583  SELECT type_code
584  FROM sq_ast_typ_inhd
585  WHERE inhd_type_code '.$type_code_cond.'
586  )';
587  }
588  }//end if
589 
590  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
591  $sql = 'SELECT a.assetid, at.inhd_type_code
592  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast a INNER JOIN sq_ast_typ_inhd at ON a.type_code = at.type_code
593  '.$where.' ORDER BY a.assetid ASC, at.inhd_type_code_lvl DESC';
594 
595  try {
596  $result = MatrixDAL::executeSqlGrouped($sql);
597  $result += $shadow_results;
598 
599  } catch (Exception $e) {
600  throw new Exception('Unable to get asset type info due to database error: '.$e->getMessage());
601  }
602 
603  return $result;
604 
605  }//end getAssetTypeInfo()
606 
607 
617  function includeAsset($type_code, $with_edit_fns=FALSE)
618  {
619  $error_msg = 'Asset "'.$type_code.'" is not installed on the system, unable to include its source file';
620  $type_code = strtolower($type_code);
621  assert_isset_array_index($this->_asset_types, $type_code, $error_msg);
622 
623  // we only need to require the type_code and its asset string once
624  if (isset($this->_asset_types[$type_code]['included']) && $this->_asset_types[$type_code]['included']) {
625  return;
626  }
627 
628  require_once SQ_SYSTEM_ROOT.'/'.$this->_asset_types[$type_code]['dir'].'/'.$type_code.'.inc';
629  if ($with_edit_fns) {
630  require_once SQ_SYSTEM_ROOT.'/'.$this->_asset_types[$type_code]['dir'].'/'.$type_code.'_edit_fns.inc';
631  }
632 
633  // include the language strings
634  $GLOBALS['SQ_SYSTEM']->lm->includeAssetStrings($type_code);
635  foreach ($this->getTypeAncestors($type_code) as $type_parent) {
636  $GLOBALS['SQ_SYSTEM']->lm->includeAssetStrings($type_parent);
637  }
638 
639  $this->_asset_types[$type_code]['included'] = TRUE;
640 
641  }//end includeAsset()
642 
643 
656  function getTypeAncestors($type_code, $include_asset=TRUE, $query_db=FALSE)
657  {
658  if ($type_code == 'asset') return Array();
659  assert_isset_array_index($this->_asset_types, $type_code, 'Asset Type "'.$type_code.'" is not installed on the system');
660 
661  if ($query_db || !isset($this->_asset_types[$type_code]['ancestor_types'])) {
662 
663  $db = MatrixDAL::getDb();
664  $sql = 'SELECT
665  inhd_type_code
666  FROM
667  sq_ast_typ_inhd
668  WHERE
669  type_code = :type_code
670  AND inhd_type_code <> type_code
671  '.(($include_asset) ? '' : ' AND inhd_type_code <> :asset_type_code').'
672  ORDER BY inhd_type_code_lvl DESC';
673 
674  try {
675  $query = MatrixDAL::preparePdoQuery($sql);
676  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
677  if (!$include_asset) {
678  MatrixDAL::bindValueToPdo($query, 'asset_type_code', 'asset', PDO::PARAM_STR);
679  }
680  $result = MatrixDAL::executePdoAssoc($query, 0);
681  } catch (Exception $e) {
682  throw new Exception('Unable to load type descendants of asset type "'.$type_code.'" due to database error: '.$e->getMessage());
683  }
684 
685  return $result;
686 
687  } else {
688  $res = $this->_asset_types[$type_code]['ancestor_types'];
689  if ($include_asset) $res[] = 'asset';
690  return $res;
691 
692  }
693 
694  }//end getTypeAncestors()
695 
696 
707  function getTypeDescendants($type_code, $include_passed=FALSE)
708  {
709  if (!is_array($type_code)) {
710  $type_code = Array($type_code);
711  }
712 
713  for (reset($type_code); NULL !== ($i = key($type_code)); next($type_code)) {
714  if ($type_code[$i] != 'asset') {
715  assert_isset_array_index($this->_asset_types, $type_code[$i], 'Asset Type "'.$type_code[$i].'" is not installed on the system');
716  }
717  }
718 
719  try {
720  $bind_vars = Array(
721  'type_codes' => $type_code,
722  'include_passed' => $include_passed,
723  );
724  $result = MatrixDAL::executeAssoc('core', 'getTypeDescendants', 0, $bind_vars);
725  //$result = MatrixDAL::executeSqlAssoc($sql, 0);
726  } catch (Exception $e) {
727  throw new Exception('Unable to load type descendants of asset type "'.$type_code.'" due to database error: '.$e->getMessage());
728  }
729 
730  return $result;
731 
732  }//end getTypeDescendants()
733 
734 
744  function isTypeDecendant($type_code1, $type_code2)
745  {
746  // get_class in PHP5 preserves capitalisation unlike PHP4
747  $decendants = $this->getTypeDescendants(strtolower($type_code2), TRUE);
748  return in_array(strtolower($type_code1), $decendants);
749 
750  }//end isTypeDecendant()
751 
752 
762  function isTypeAncestor($type_code1, $type_code2)
763  {
764  $ancestors = $this->getTypeAncestors($type_code2, TRUE);
765  return in_array($type_code1, $ancestors);
766 
767  }//end isTypeAncestor()
768 
769 
778  function validAttributeType($attr_type)
779  {
780  $file = SQ_ATTRIBUTES_PATH.'/'.$attr_type.'/'.$attr_type.'.inc';
781  if (!file_exists($file)) return FALSE;
782  require_once($file);
783  return class_exists('Asset_Attribute_'.$attr_type);
784 
785  }//end validAttributeType()
786 
787 
788 //-- ATTRIBUTES --//
789 
790 
799  function getAttributeInfo($attrids)
800  {
801  assert_type($attrids, 'array');
802  if (empty($attrids)) return Array();
803 
804  $db = MatrixDAL::getDb();
805 
806  for ($i = 0; $i < count($attrids); $i++) {
807  $attrids[$i] = MatrixDAL::quote($attrids[$i]);
808  }
809  $where = 'a.attrid IN ('.implode(', ', $attrids).')';
810  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
811 
812  $sql = 'SELECT * FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr a '.$where.' ORDER BY a.attrid';
813 
814  try {
815  // cannot bind multiple values to a single named parameter, that's why we don't use bind vars here
816  $result = MatrixDAL::executeSqlAssoc($sql);
817  } catch (Exception $e) {
818  throw new Exception('Unable to fetch attribute info due to database error: '.$e->getMessage());
819  }
820 
821  $return_result = Array();
822  foreach (array_values($result) as $row) {
823  foreach ($row as $name => $value) {
824  $return_result[$row['attrid']][$name] = $value;
825  }
826  }
827 
828  return $return_result;
829 
830  }//end getAttributeInfo()
831 
832 
845  function getAttribute($attrid, $value=NULL, $mute_errors=FALSE)
846  {
847  if (!isset($this->_attributes[$attrid]) || !is_object($this->_attributes[$attrid]['object'])) {
848 
849  $this->_attributes[$attrid] = Array();
850  $this->_attributes[$attrid]['object'] = NULL;
851  $this->_attributes[$attrid]['count'] = 0;
852 
853  include_once SQ_INCLUDE_PATH.'/asset_attribute.inc';
854  $base_attr = new Asset_Attribute();
855  $this->_attributes[$attrid]['object'] = $base_attr->loadAttribute($attrid);
856  unset($base_attr);
857  if (empty($this->_attributes[$attrid]['object']->id)) {
858  $this->_attributes[$attrid]['object'] = NULL;
859  }
860 
861  }
862 
863  $this->_attributes[$attrid]['count']++;
864  return $this->_attributes[$attrid]['object'];
865 
866  }//end getAttribute()
867 
868 
878  function getAssetTypeAttributes($type_code, $details=Array('name', 'type'))
879  {
880  if (!is_string($type_code)) return Array();
881 
882  $db = MatrixDAL::getDb();
883  require_once SQ_INCLUDE_PATH.'/asset_attribute.inc';
884 
885  $sql = 'SELECT '.implode(', ', $details).' FROM sq_ast_attr
886  WHERE type_code = :type_code';
887 
888  try {
889  $query = MatrixDAL::preparePdoQuery($sql);
890  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code);
891 
892  if (count($details) > 1) {
893  $result_x = MatrixDAL::executePdoGroupedAssoc($query);
894  $result = Array();
895  foreach ($result_x as $key => $row) {
896  $result[$key] = $row[0];
897  }
898  } else {
899  $result = MatrixDAL::executePdoAssoc($query, 0);
900  }
901  } catch (Exception $e) {
902  throw new Exception('Unable to get asset info due to database error: '.$e->getMessage());
903  }
904 
905  return $result;
906 
907  }//end getAssetTypeAttributes()
908 
909 
932  function getAttributeValuesByName($attr_name, $asset_type, $assetids, $contextid=NULL)
933  {
934  if (empty($assetids)) return Array();
935 
936  // Create a list of separated assetids and shadow assetids
937  $shadowids = Array();
938  $normalids = Array();
939  foreach ($assetids as $one_id) {
940  $id_parts = explode(':', $one_id);
941  if (isset($id_parts[1])) {
942  $shadowids[$id_parts[0]][] = $one_id;
943  } else {
944  $normalids[] = $one_id;
945  }
946  }//end foreach
947 
948  // Set it to the array minus the shadowids
949  $assetids = $normalids;
950 
951  // If we have picked up some shadow assets, make each bridge get its info
952  $shadow_results = Array();
953  foreach ($shadowids as $assetid => $shadows) {
954  $asset = $this->getAsset($assetid, '', TRUE);
955  if (!is_null($asset) && method_exists($asset, 'getAttributeValuesByName')) {
956  $shadow_results += $asset->getAttributeValuesByName($attr_name, $asset_type, $shadows, $contextid);
957  }//end if
958  }//end foreach
959 
960  // breakout if nothing left to do
961  if (empty($assetids)) return $shadow_results;
962 
963  if ($contextid === NULL) {
964  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
965  }
966 
967  // IN clause fix - assetids were not being treated as strings
968  $in = 'assetid IN (';
969 
970  for ($i = 0; $i < count($assetids); $i++) {
971  $in .= MatrixDAL::quote( (string) $assetids[$i] ).(($i == count($assetids) - 1) ? ')' : ',');
972  }
973 
974  // First get the default values
975  $sql = 'SELECT a.assetid, at.default_val
976  FROM sq_ast a, sq_ast_attr at
977  WHERE a.type_code = at.type_code
978  AND at.name = :attr_name
979  AND ((at.type_code = :asset_type) OR (at.owning_type_code = :asset_type))
980  AND a.'.$in;
981  try {
982  $query = MatrixDAL::preparePdoQuery($sql);
983  MatrixDAL::bindValueToPdo($query, 'attr_name', $attr_name);
984  MatrixDAL::bindValueToPdo($query, 'asset_type', $asset_type);
985  $res = DAL::executePdoGroupedAssoc($query);
986  } catch (Exception $e) {
987  throw new Exception('Unable to get default attribute values due to database error: '.$e->getMessage());
988  }
989 
990  // We have to convert it here to the assetid=>value format
991  foreach ($res as $assetid => $val_info) {
992  if (isset($val_info[0]['default_val'])) {
993  $res[$assetid] = $val_info[0]['default_val'];
994  }// oracle fix, converting null to empty string. in case isset fail on null value for array entry
995  else if (isset($val_info[0]) && is_array($val_info[0]) && array_key_exists('default_val',$val_info[0])){
996  $res[$assetid] = '';
997  }
998  }//end foreach
999 
1000  // Now get customised values where applicable
1001  $sub_sql = 'SELECT attrid FROM sq_ast_attr WHERE name = :attr_name';
1002  if ($asset_type != 'asset') {
1003  $sub_sql .= ' AND (type_code = :asset_type OR owning_type_code = :asset_type_1)';
1004  }
1005 
1006  $dbtype = MatrixDAL::getDbType();
1007  $bind_vars = Array();
1008  if ($dbtype === 'pgsql') {
1009  if (count($assetids) <= 10) {
1013  $sql = 'SELECT assetid, custom_val FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val';
1014  $where = 'attrid IN ('.$sub_sql.') AND '.$in;
1015  $where .= ' AND contextid = :contextid';
1016  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
1017  $sql .= $where;
1018  $bind_vars['contextid'] = $contextid;
1019  }
1020  else {
1034  $sql1 = 'SELECT assetid, custom_val
1035  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val';
1036  $where1 = 'attrid IN ('.$sub_sql.') ';
1037  $where1 .= ' AND contextid = :contextid_1';
1038  $where1 = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where1);
1039 
1040  $sql2 = 'SELECT assetid, custom_val
1041  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val';
1042  $where2 = $in;
1043  $where2 .= ' AND contextid = :contextid_2';
1044  $where2 = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where2);
1045 
1046  $sql = $sql1 . $where1 . ' INTERSECT ' . $sql2 . $where2;
1047  $bind_vars['contextid_1'] = $contextid;
1048  $bind_vars['contextid_2'] = $contextid;
1049  }
1050  }
1051 
1061  if ($dbtype === 'oci') {
1062  $sql = 'SELECT assetid, custom_val FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val';
1063  $where = 'attrid IN ('.$sub_sql.') AND '.$in;
1064  $where .= ' AND contextid = :contextid';
1065  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
1066  $sql .= $where;
1067  $bind_vars['contextid'] = $contextid;
1068  }
1069 
1070  $bind_vars['attr_name'] = $attr_name;
1071  if ($asset_type !== 'asset') {
1072  $bind_vars['asset_type'] = $asset_type;
1073  $bind_vars['asset_type_1'] = $asset_type;
1074  }
1075 
1076  try {
1077  $query = MatrixDAL::preparePdoQuery($sql);
1078  foreach ($bind_vars as $bind_var => $bind_value) {
1079  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
1080  }
1081  $custom_vals = MatrixDAL::executePdoGroupedAssoc($query);
1082  } catch (Exception $e) {
1083  throw new Exception('Unable to get customised attribute values for attr "'.$attr_name.'" and type "'.$asset_type.'" due to database error: '.$e->getMessage());
1084  }
1085 
1086  // Traverse through the custom val array, if it these assets have custom val for theirs attribute, return it
1087  // If the assets do not have custom val, the array is probably empty and thus it wont go into the loop
1088  foreach ($custom_vals as $assetid => $rowinfo) {
1089  if (isset($rowinfo[0]['custom_val'])) {
1090  $res[$assetid] = $rowinfo[0]['custom_val'];
1091  } // oracle fix, converting null to empty string. in case isset fail on null value for array entry
1092  else if (isset($rowinfo[0]) && is_array($rowinfo[0]) && array_key_exists('custom_val',$rowinfo[0])){
1093  $res[$assetid] = '';
1094  }
1095  }//end foreach
1096 
1097  return $res;
1098 
1099  }//end getAttributeValuesByName()
1100 
1101 
1119  function getAllAttributeValues($assetid='', $type_code, $contextid=NULL)
1120  {
1121 
1122  if ($contextid === NULL) {
1123  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1124  }
1125 
1126  // Right, now we need to get any values that this asset has customised
1127  $vars = Array();
1128 
1129  $sql = '';
1130  // if we have an id then we need to load with current vars
1131  if ($assetid) {
1132  // PURPOSLY DONT ADD EXTRA CLAUSES FOR ASSET_ATTRIBUTE_VALUE BECAUSE WE WONT GET
1133  // DEFAULT VALUES IF WE DO
1134  $sql = 'SELECT atr.name, atr.attrid, atr.type, COALESCE(v.custom_val, atr.default_val) AS value, atr.is_contextable, v.use_default
1135  FROM (sq_ast_attr atr
1136  LEFT OUTER JOIN (SELECT * FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val WHERE contextid = :contextid) v
1137  ON (atr.attrid = v.attrid AND v.assetid = :assetid'
1138  .$GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause('', 'v', 'AND').'))
1139  WHERE atr.type_code = :type_code';
1140  } else {
1141  // else just load all defaults
1142  $sql = 'SELECT atr.name, atr.attrid, atr.type, atr.default_val AS value, atr.is_contextable, \'1\' as use_default
1143  FROM sq_ast_attr atr
1144  WHERE atr.type_code = :type_code';
1145  }// end if
1146 
1147  try {
1148  $query = MatrixDAL::preparePdoQuery($sql);
1149  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code, PDO::PARAM_STR);
1150  // Only bind it if we are using the first query, not binding when going in the else above
1151  if ($assetid) {
1152  MatrixDAL::bindValueToPdo($query, 'contextid', $contextid, PDO::PARAM_INT);
1153  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid, PDO::PARAM_STR);
1154  }//end if
1155  $result = MatrixDAL::executePdoGroupedAssoc($query);
1156 
1157  if (empty($result)) {
1158  $vars = Array();
1159  } else {
1160  $vars = $result;
1161  foreach (array_keys($vars) as $name) {
1162  $vars[$name] = $vars[$name][0];
1163  unset($vars[$name][0]);
1164  }
1165  }
1166 
1167  unset($result);
1168  } catch (Exception $e) {
1169  throw new Exception('Unable to load variables of asset #'.$assetid.' ('.$type_code.') due to database error: '.$e->getMessage());
1170  }
1171 
1172  return $vars;
1173 
1174  }//end getAllAttributeValues()
1175 
1176 //-- ASSETS --//
1177 
1178 
1192  function getAsset($assetid, $type_code='', $mute_errors=FALSE)
1193  {
1194 
1195  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1196 
1197  // This function is supposed to take in an assetid of type int or string only, array of assetids should not be accepted.
1198  if (is_array($assetid)) {
1199  $msg = translate('assert_assetid', gettype($assetid), '');
1200  trigger_exception($msg, FALSE, TRUE);
1201  $asset = NULL;
1202  return $asset;
1203  }//end if
1204 
1205  assert_valid_assetid($assetid);
1206  $asset = $this->_asset_cache->get($contextid.'\\'.$assetid);
1207 
1208  if (empty($asset)) {
1209  // Try Deja vu
1210  $deja_vu = $GLOBALS['SQ_SYSTEM']->getDejaVu();
1211  if ($deja_vu) {
1212  $asset = $deja_vu->recall(SQ_DEJA_VU_ASSET, $assetid);
1213  if (!empty($asset)) {
1214  if ($asset->status & SQ_SC_STATUS_SAFE_EDITING) {
1215  $asset = NULL;
1216  }
1217  else {
1218  // We still want asset cache to do its thing.
1219  $this->_asset_cache->add($asset, $contextid.'\\'.$asset->id);
1220  }
1221  }
1222  }
1223  }
1224 
1228  if (!empty($asset)) {
1233  if (!empty($type_code) && $asset->type() != $type_code) {
1234  if (!$mute_errors) {
1235  trigger_localised_error('SYS0089', E_USER_WARNING, $assetid, $type_code);
1236  }
1237  $asset = NULL;
1238  }
1239  return $asset;
1240  }
1241 
1242  // check if we are getting a shadow asset, and palm the request off to the
1243  // handler of the shadow asset if we are
1244  $id_parts = explode(':', $assetid);
1245  if (isset($id_parts[1])) {
1246  $real_assetid = $id_parts[0];
1247  $bridge = $this->getAsset($real_assetid, '', TRUE);
1248  if (is_null($bridge)) {
1249  // bridge is unknown, we cannot return anything from it
1250  $asset = NULL;
1251  return $asset;
1252  } else if (!method_exists($bridge, 'getAsset')) {
1253  trigger_localised_error('SYS0203', E_USER_WARNING, $bridge->name);
1254  } else {
1255  $asset = $bridge->getAsset($assetid, '', $mute_errors);
1256  }
1257  $this->forgetAsset($bridge);
1258 
1259  } else {
1260 
1261  if (empty($type_code)) {
1262  $sql = 'SELECT type_code FROM '.SQ_TABLE_RUNNING_PREFIX.'ast ';
1263  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause('assetid = :assetid');
1264  $query = MatrixDAL::preparePdoQuery($sql.$where);
1265  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
1266  $type_code = MatrixDAL::executePdoOne($query);
1267  }
1268 
1269  if (empty($type_code)) {
1270  if (!$mute_errors) {
1271  trigger_localised_error('SYS0087', E_USER_WARNING, $assetid);
1272  }
1273  } else {
1274  if (!isset($this->_asset_types[$type_code])) {
1275  if (!$mute_errors) {
1276  trigger_localised_error('SYS0091', E_USER_WARNING, $type_code);
1277  }
1278  } else {
1279  $this->includeAsset($type_code);
1280  if ($mute_errors) {
1281  $asset = @new $type_code($assetid);
1282  } else {
1283  $asset = new $type_code($assetid);
1284  }
1285  if (empty($asset->id)) {
1286  $asset = NULL;
1287  }
1288  }
1289  }
1290 
1291  }//end if
1292 
1293  if (isset($asset)) {
1294  $this->_get_asset_history[] = $assetid;
1295  $this->_asset_cache->add($asset, $contextid.'\\'.$asset->id);
1296 
1297  $deja_vu = $GLOBALS['SQ_SYSTEM']->getDejaVu();
1298  if ($deja_vu && !($asset->status & SQ_SC_STATUS_SAFE_EDITING)) {
1299  $deja_vu->remember(SQ_DEJA_VU_ASSET, $asset->id, $asset);
1300  }
1301  }
1302 
1303  return $asset;
1304 
1305  }//end getAsset()
1306 
1307 
1319  function assetExists($assetids)
1320  {
1321  $shadows = Array();
1322  $shadow_asset_ids = Array();
1323 
1324  // number of assetids
1325  $count = count($assetids);
1326 
1327  $is_array = is_array($assetids);
1328  $is_empty = empty($assetids);
1329 
1330  // assert assetid format
1331  if ($is_array && !$is_empty) {
1332  foreach ($assetids as $key => $value) {
1333  assert_valid_assetid($value);
1334 
1335  // catch the shadow assets
1336  $id_parts = explode(':', $value);
1337  if (isset($id_parts[1])) {
1338  $shadows[$id_parts[0]][] = $value;
1339  }
1340  }
1341  } else if (!$is_empty) {
1342  assert_valid_assetid($assetids);
1343  // catch the shadow assets
1344  $id_parts = explode(':', $assetids);
1345  if (isset($id_parts[1])) {
1346  $shadows[$id_parts[0]][] = $id_parts[0].':'.$id_parts[1];
1347  }
1348  }
1349 
1350  // pipe it off to the real asset
1351  if (!empty($shadows)) {
1352  foreach ($shadows as $shadow_id => $shadow) {
1353  $asset = $this->getAsset($shadow_id);
1354  if (method_exists($asset,'assetExists')) {
1355  $ret_val = $asset->assetExists($shadow);
1356  if (is_array($ret_val)) {
1357  $shadow_asset_ids += $ret_val;
1358  } else if ($ret_val) {
1359  $shadow_asset_ids[] = $shadow;
1360  }
1361  }
1362  $this->forgetAsset($asset);
1363  }
1364  }
1365 
1366  $db = MatrixDAL::getDb();
1367  $sql = 'SELECT assetid
1368  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast ';
1369 
1370  // single asset id
1371  if (!$is_array) {
1372  if ($is_empty) return FALSE;
1373  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause('assetid = :assetids');
1374  try {
1375  $query = MatrixDAL::preparePdoQuery($sql.$where);
1376  MatrixDAL::bindValueToPdo($query, 'assetids', $assetids);
1377  $db_assetids = MatrixDAL::executePdoOne($query);
1378  } catch (Exception $e) {
1379  throw new Exception('Unable to determine if asset with assetid: '.$assetids.' exists due to database error: '.$e->getMessage());
1380  }
1381 
1382  if (!empty($shadow_asset_ids)) {
1383  $shadow_asset_ids[] = $db_assetids;
1384  } else {
1385  return ($db_assetids == $assetids);
1386  }
1387  }
1388 
1389  // array of asset ids
1390  $existing_asset_ids = Array();
1391  if (!$is_empty) {
1392  $in = 'assetid IN (';
1393 
1394  for ($i=0; $i<count($assetids); $i++) {
1395  $in .= MatrixDAL::quote( (string) $assetids[$i] ).(($i == count($assetids) - 1) ? ')' : ',');
1396  }
1397 
1398  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($in);
1399  try {
1400  $db_assetids = MatrixDAL::executeSqlAll($sql.$where);
1401  } catch (Exception $e) {
1402  throw new Exception('Unable to determine if multipled assets exists due to database error: '.$e->getMessage());
1403  }
1404 
1405  foreach ($db_assetids as $db_id) {
1406  $existing_asset_ids[] = $db_id['assetid'];
1407  }
1408  }
1409 
1410  for ($i = 0; $i < count($shadow_asset_ids); $i++) {
1411  $existing_asset_ids[] = $shadow_asset_ids[$i];
1412  }
1413 
1414  return $existing_asset_ids;
1415 
1416  }//end assetExists()
1417 
1418 
1429  function rememberAsset(&$obj)
1430  {
1431  // wrong class or no id, cya
1432  if (!is_object($obj) || !($obj instanceof Asset) || !$obj->id) {
1433  return;
1434  }
1435 
1436  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1437 
1438  if (!$this->_asset_cache->add($obj, $contextid.'\\'.$obj->id)) {
1439  trigger_localised_error('SYS0305', E_USER_ERROR, $obj->id);
1440  }
1441 
1442  }//end rememberAsset()
1443 
1444 
1458  function forgetAsset(&$obj, $force_clean=FALSE)
1459  {
1460  // wrong class or no id, cya
1461  if (!is_object($obj) || !($obj instanceof Asset) || !$obj->id) {
1462  return;
1463  }
1464 
1465  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1466 
1467  $this->_asset_cache->release($contextid.'\\'.$obj->id);
1468  if ($force_clean) {
1469  if (!$this->_asset_cache->remove($contextid.'\\'.$obj->id, TRUE)) {
1470  trigger_localised_error('SYS0306', E_USER_WARNING, $contextid.'\\'.$obj->id);
1471  } else {
1472  if (isset($obj->_tmp)) unset($obj->_tmp);
1473  }
1474  }
1475 
1476  }//end forgetAsset()
1477 
1478 
1487  function getSystemAssetid($name)
1488  {
1489  if (!is_string($name)) {
1490  trigger_localised_error('SYS0190', E_USER_WARNING);
1491  return FALSE;
1492  }
1493 
1494  if (empty($this->_system_assetids)) {
1495  $this->_reloadSystemAssetList();
1496  }
1497 
1498  if (isset($this->_system_assetids[$name])) {
1499  return $this->_system_assetids[$name];
1500  } else {
1501  trigger_localised_error('SYS0209', E_USER_WARNING, $name);
1502  return FALSE;
1503  }
1504 
1505  }//end getSystemAssetid()
1506 
1507 
1515  {
1516  require_once SQ_INCLUDE_PATH.'/system_asset_config.inc';
1517  $sys_asset_cfg = new System_Asset_Config();
1518  // include the system asset config file
1519  if (!file_exists($sys_asset_cfg->config_file)) {
1520  // we called this too early
1521  $system_assets = FALSE;
1522  } else {
1523  require $sys_asset_cfg->config_file;
1524  }
1525 
1526  $this->_system_assetids = $system_assets;
1527 
1528  }//end _reloadSystemAssetList()
1529 
1530 
1538  {
1539  $this->_system_assetids = Array();
1540 
1541  }//end resetSystemAssetList()
1542 
1543 
1555  function isSystemAssetType($asset_type)
1556  {
1557  if (empty($this->_system_assetids)) {
1558  $this->_reloadSystemAssetList();
1559  }
1560 
1561  return isset($this->_system_assetids[$asset_type]);
1562 
1563  }//end isSystemAssetType()
1564 
1565 
1575  function registerSystemAsset($asset_type=NULL, $assetid=NULL)
1576  {
1577  if (is_null($asset_type) || is_null($assetid)) {
1578  return FALSE;
1579  }
1580 
1581  $this->_system_assetids[$asset_type] = $assetid;
1582  return TRUE;
1583 
1584  }//end registerSystemAsset()
1585 
1586 
1597  function getSystemAsset($name, $mute_errors=FALSE)
1598  {
1599  $assetid = ($mute_errors) ? @$this->getSystemAssetid($name) : $this->getSystemAssetid($name);
1600 
1601  if ($assetid !== FALSE) {
1602  $asset = $this->getAsset($assetid, $name, $mute_errors);
1603  return $asset;
1604  } else {
1605  $null = NULL;
1606  return $null;
1607  }
1608 
1609  }//end getSystemAsset()
1610 
1611 
1622  function canPurgeAsset(&$asset)
1623  {
1624  // check if we are trying to purge a system asset
1625  require SQ_DATA_PATH.'/private/conf/system_assets.inc';
1626  $system_asset_ids = array_values($system_assets);
1627  if (in_array($asset->id, $system_asset_ids)) {
1628  return FALSE;
1629  }
1630 
1631  // if we get to here, it is not one of the
1632  // system assets, but it may be dependently linked to one
1633  // so lets check for that
1634  $parents = $this->getDependantParents($asset->id);
1635  foreach ($parents as $parentid) {
1636  if (in_array($parentid, $system_asset_ids)) {
1637  return FALSE;
1638  }
1639  }
1640 
1641  return TRUE;
1642 
1643  }//end canPurgeAsset()
1644 
1645 
1660  function getAssetInfo($assetids, $type_code=Array(), $strict_type_code=TRUE, $field='')
1661  {
1662  $shadow_results = Array();
1663  if (!is_array($assetids)) {
1664  $assetids = Array($assetids);
1665  }
1666  if (empty($assetids)) return Array();
1667 
1668  $bind_vars = Array();
1669  $db = MatrixDAL::getDb();
1670 
1671  $shadowids = Array();
1672  $normalids = Array();
1673  $shadow_results = Array();
1674 
1675  // create a list of (hopefully) separated assetids and shadow assetids
1676  foreach ($assetids as $one_id) {
1677  $id_parts = explode(':', $one_id);
1678 
1679  if (isset($id_parts[1])) {
1680  $shadowids[$id_parts[0]][] = $one_id;
1681  } else {
1682  $normalids[] = $one_id;
1683  }
1684  }
1685 
1686  // set it to the array minus the shadowids
1687  $assetids = $normalids;
1688 
1689  // we have picked up some shadow assets
1690  if (!empty($shadowids)) {
1691  $shadow_result = Array();
1692 
1693  // make each bridge get its info
1694  foreach ($shadowids as $assetid => $shadows) {
1695  $asset = $this->getAsset($assetid, '', TRUE);
1696  if ($asset && method_exists($asset, 'getAssetInfo')) {
1697  $shadow_results += $asset->getAssetInfo($shadows, $type_code, $strict_type_code, $field);
1698  }
1699  }
1700  }
1701 
1702  // breakout if nothing left to do
1703  if (empty($assetids)) return $shadow_results;
1704 
1705  // need to do some checking on the field to make sure it's in the table
1706  // but only do it if the field is specified
1707  if (!empty($field)) {
1708  if (empty($this->_tmp['sq_tables'])) {
1709  require SQ_DATA_PATH.'/private/db/table_columns.inc';
1710  $this->_tmp['sq_tables'] = $tables;
1711  unset($tables);
1712  }
1713 
1714  // field not found
1715  if (!in_array($field, $this->_tmp['sq_tables']['ast']['columns'])) {
1716  trigger_localised_error('SYS0185', E_USER_WARNING, $field);
1717  return Array();
1718  }
1719  }
1720 
1721  for ($i = 0; $i < count($assetids); $i++) {
1722  $assetids[$i] = MatrixDAL::quote($assetids[$i]);
1723  }
1724 
1725  // break up the assets into chunks of 1000 so that oracle does not complain
1726  $in_clauses = Array();
1727  foreach (array_chunk($assetids, 999) as $chunk) {
1728  $in_clauses[] = ' a.assetid IN ('.implode(', ', $chunk).')';
1729  }
1730  $where = '('.implode(' OR ', $in_clauses).')';
1731 
1732  if (!empty($type_code)) {
1733  $type_code_cond = '';
1734  $type_query_str = Array();
1735  if (is_array($type_code)) {
1736  for ($i = 0; $i < count($type_code); $i++) {
1737  $bind_vars['type_code_'.$i] = $type_code[$i];
1738  $type_query_str[] = ':type_code_'.$i;
1739  }
1740  $type_code_cond = 'IN ('.implode(', ', $type_query_str).')';
1741  } else {
1742  $type_code_cond = '= :type_code';
1743  $bind_vars['type_code'] = $type_code;
1744  }
1745 
1746  if ($strict_type_code) {
1747  $where .= ' AND a.type_code '.$type_code_cond;
1748  } else {
1749  $where .= ' AND a.type_code IN (
1750  SELECT type_code
1751  FROM sq_ast_typ_inhd
1752  WHERE inhd_type_code '.$type_code_cond.'
1753  )';
1754  }
1755  }//end if
1756 
1757  if (empty($field)) {
1758  // if we want all the column info, get a list of columns
1759  // and append them after the assetid so we can use getAssoc
1760  // to produce assetid => Array(info)
1761  if (empty($this->_tmp['sq_tables'])) {
1762  require SQ_DATA_PATH.'/private/db/table_columns.inc';
1763  $this->_tmp['sq_tables'] = $tables;
1764  } else {
1765  $tables = $this->_tmp['sq_tables'];
1766  }
1767  unset($tables['ast']['columns'][array_search('assetid', $tables['ast']['columns'])]);
1768  $col_string = implode(', ', $tables['ast']['columns']);
1769 
1770  }
1771 
1772  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
1773  $sql = 'SELECT assetid, '.((empty($field)) ? $col_string : $field).'
1774  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast a
1775  '.$where.'
1776  ORDER BY a.assetid';
1777 
1778  try {
1779  $query = MatrixDAL::preparePdoQuery($sql);
1780  foreach ($bind_vars as $bind_var => $bind_value) {
1781  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
1782  }
1783  $result_x = MatrixDAL::executePdoGroupedAssoc($query);
1784 
1785  $result = Array();
1786  foreach ($result_x as $key => $row) {
1787  // if the field is equal to asset id we will have to behave slightly differently
1788  // because when the field is asset id the result_x has the following format:
1789  // 56 => array (
1790  // 0 => array (
1791  // [Empty]
1792  // )
1793  // ),
1794  if (!empty($field) && (strcmp($field, 'assetid') == 0)) {
1795  $result[$key] = $key;
1796  } else {
1797  // if we are only after a single field
1798  $result[$key] = (empty($field)) ? $row[0] : $row[0][$field];
1799  }
1800  }
1801 
1802  unset($result_x);
1803  } catch (Exception $e) {
1804  throw new Exception('Unable to get asset info due to database error: '.$e->getMessage());
1805  }
1806 
1807  // add the shadow assets info
1808  $result += $shadow_results;
1809 
1810  return $result;
1811 
1812  }//end getAssetInfo()
1813 
1814 
1822  {
1823  if (!isset($this->_tmp['asset_info_fields'])) {
1824  require SQ_DATA_PATH.'/private/db/table_columns.inc';
1825  $this->_tmp['asset_info_fields'] = Array();
1826  foreach ($tables['ast']['columns'] as $col) {
1827  $this->_tmp['asset_info_fields'][$col] = translate('asset_field_'.$col);
1828  }
1829  unset($tables);
1830  }
1831  return $this->_tmp['asset_info_fields'];
1832 
1833  }//end getAssetInfoFields()
1834 
1835 
1849  function getTypeAssetids($type_code, $strict=TRUE, $include_type=FALSE)
1850  {
1851  if (!is_string($type_code)) return Array();
1852  $db = MatrixDAL::getDb();
1853 
1854  if ($strict) {
1855  $where = 'type_code = :type_code';
1856  } else {
1857  $where = 'type_code IN (
1858  SELECT type_code
1859  FROM sq_ast_typ_inhd
1860  WHERE inhd_type_code = :type_code
1861  )';
1862  }
1863 
1864  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
1865  $sql = 'SELECT assetid'.(($include_type) ? ', type_code' : '').'
1866  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast '.$where;
1867 
1868  try {
1869  $query = MatrixDAL::preparePdoQuery($sql);
1870  MatrixDAL::bindValueToPdo($query, 'type_code', $type_code);
1871  if ($include_type) {
1872  $result_x = MatrixDAL::executePdoGroupedAssoc($query);
1873  $result = Array();
1874  foreach ($result_x as $key => $row) {
1875  $result[$key] = $row[0];
1876  }
1877  } else {
1878  $result = MatrixDAL::executePdoAssoc($query, 0);
1879  }
1880  } catch (Exception $e) {
1881  throw $e;
1882  }
1883 
1884  return $result;
1885 
1886  }//end getTypeAssetids()
1887 
1888 
1889 //-- CONTENT --//
1890 
1891 
1901  function getEditableContents($assetid)
1902  {
1903  if (!$assetid || !$this->assetExists($assetid)) {
1904  return FALSE;
1905  }
1906 
1907  $children = array_keys($this->getDependantChildren($assetid));
1908  $contents = Array();
1909 
1910  foreach ($children as $child_id) {
1911  $child = $this->getAsset($child_id);
1912  $dependants_children = array_keys($this->getDependantChildren($child_id));
1913 
1914  // find out if our dependent children are in the children list, otherwise
1915  // we'll get the same content twice
1916  // we have to do in array for each key, an array needle gets us nowhere
1917  for ($i = 0; $i < count($dependants_children); $i++) {
1918  if (in_array($dependants_children[$i], $children)) {
1919  continue 2;
1920  }
1921  }
1922 
1923  $child_content = $child->getContent();
1924 
1925  if (!empty($child_content)) {
1926  $contents[$child_id] = $child->getContent();
1927  }
1928 
1929  $this->forgetAsset($child);
1930  }
1931 
1932  return empty($contents) ? FALSE : $contents;
1933 
1934  }//end getEditableContents()
1935 
1936 
1947  function setEditableContents($assetid, $content)
1948  {
1949  $asset = $this->getAsset($assetid);
1950 
1951  if (is_null($asset)) return FALSE;
1952 
1953  $asset->setContent($content);
1954 
1955  return TRUE;
1956 
1957  }//end setEditableContents()
1958 
1959 
1960 //-- CLONING --//
1961 
1962 
2004  function _cloneAsset(&$source, &$link, &$clone_map, $components, $cloning_dependent=FALSE)
2005  {
2006  if (!empty($link)) {
2007  // make sure the initial link information is passed in
2008  assert_isset_array_index($link, 'asset', 'Cannot clone asset without an asset to link to');
2009  assert_isset_array_index($link, 'link_type', 'Cannot clone asset without a link type');
2010  assert_not_empty(($link['link_type'] & SQ_SC_LINK_SIGNIFICANT), 'Cannot clone asset with an insignificant link type');
2011  }
2012 
2013  $null = NULL; // needed because we return by reference
2014  if (!$source->id) return $null;
2015  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
2016  $lock = $this->getLockInfo($source->id, 'all');
2017  foreach ($lock as $lock_type => $lock_info) {
2018  if (empty($lock_info)) {
2019  trigger_localised_error('SYS0266', E_USER_WARNING, $source->id, $lock_type);
2020  return $null;
2021  }
2022  }
2023  }
2024  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2025  $db = MatrixDAL::getDb();
2026  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2027 
2028  $assetid = MatrixDAL::executeOne('core', 'seqNextVal', Array('seqName' => 'sq_ast_seq'));
2029 
2030  $now = time();
2031  $userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
2032  // version number starts at 0.0.0 again because the clone is a brand new asset
2033  $initial_version = '0.0.0';
2034 
2035  try {
2036  $bind_vars = Array(
2037  'date_value' => ts_iso8601(time()),
2038  );
2039  $last_updated = MatrixDAL::executeOne('core', 'toDate', $bind_vars);
2040  $bind_vars = Array(
2041  'assetid' => $assetid,
2042  'version' => $initial_version,
2043  'type_code' => $source->type(),
2044  'name' => $source->name.' - clone',
2045  'short_name' => $source->short_name.' - clone',
2046  'status' => SQ_STATUS_UNDER_CONSTRUCTION,
2047  'created' => $last_updated,
2048  'created_userid' => $userid,
2049  'published' => NULL,
2050  'published_userid' => NULL,
2051  'updated' => $last_updated,
2052  'updated_userid' => $userid,
2053  'status_changed' => NULL,
2054  'status_changed_userid' => NULL,
2055  );
2056  MatrixDAL::executeQuery('core', 'createAsset', $bind_vars);
2057  } catch (Exception $e) {
2058  throw new Exception('Failed to create cloned asset due to database error: '.$e->getMessage());
2059  }
2060 
2061  $clone = $this->getAsset($assetid, $source->type());
2062  if (is_null($clone)) {
2063  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2064  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2065  return $null;
2066  }
2067 
2068  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
2069  // lock the new clone in the same chain as we are locked
2070  if (!$this->acquireLock($clone->id, 'all', $source->id)) {
2071  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2072  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2073  return $null;
2074  }
2075  }
2076 
2077  $parent_components = Array(
2078  'permissions',
2079  'metadata_schemas',
2080  'workflow',
2081  'roles',
2082  );
2083  // make an exception for tags - tags never cascade to dependent assets, so leave
2084  // them out if we're cloning a dependent asset
2085  if (!$cloning_dependent) {
2086  $parent_components[] = 'content_tags';
2087  }
2088 
2089 
2091 
2092  // cascade various components from parent asset to new child
2093  // unless the parent is the root folder or system management folder;
2094  // components are cascaded from the new parent regardless of what permissions the
2095  // current user has (to match creation and moving assets)
2096 
2097  // If no $link is specified, just don't clone anything from the parent.
2098  if (!empty($link)) {
2099  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_INTEGRITY)) {
2100  $this->_tmp[__CLASS__.'_in_create_cascading'] = TRUE;
2101  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() & SQ_RUN_LEVEL_FORCED);
2102 
2103  // remove content_tags from the array, if present; munge array, then give it to cloneComponents
2104  $nonlink_parent_components = $parent_components;
2105  foreach ($nonlink_parent_components as $idx => $component) {
2106  if ($component == 'content_tags') {
2107  unset($nonlink_parent_components[$idx]);
2108  }
2109  }
2110 
2111  if (!$link['asset']->cloneComponents($clone, $nonlink_parent_components)) {
2112  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2113  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2114 
2115  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
2116 
2117  return $null;
2118  }
2119  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
2120  unset($this->_tmp[__CLASS__.'_in_create_cascading']);
2121  }
2122  }
2123 
2124  // now that we (might) have permission, copy all the schemas, attributes,
2125  // etc. from the original asset
2126  // (will throw errors but return TRUE if the user doesn't have sufficient permissions
2127  // to cascade everything)
2128  $nonlink_components = $components;
2129  foreach ($nonlink_components as $idx => $component) {
2130  if ($component == 'content_tags') {
2131  unset($nonlink_components[$idx]);
2132  }
2133  }
2134  if (!$source->cloneComponents($clone, $nonlink_components, TRUE)) {
2135  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2136  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2137  return $null;
2138  }
2139 
2141 
2142  // we've created the asset, now link it to its new parent
2143  $clone = $this->_cloneLink($clone, $link, $lock, $source);
2144  if (is_null($clone)) {
2145  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2146  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2147  return $clone_result;
2148  }
2149 
2150  // enhancement #5988 Cloning an asset loses it "Force Secure" option
2151  // set the required properties of the cloned asset same as the source
2152  $clone->setForceSecure($source->force_secure);
2153 
2154  // clone the other links
2155  if (!$source->cloneLinks($clone)) {
2156  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2157  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2158  return $null;
2159  }
2160 
2161 
2163 
2164  // as per above, cascade components from parent asset to new
2165  // child, regardless of permission
2166 
2167  // If no $link is specified, just don't clone anything from the parent.
2168  if (!empty($link)) {
2169  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_INTEGRITY)) {
2170  $this->_tmp[__CLASS__.'_in_create_cascading'] = TRUE;
2171  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() & SQ_RUN_LEVEL_FORCED);
2172 
2173  // only clone content_tags this time (only link-based component so far)
2174  $link_parent_components = (in_array('content_tags', $parent_components) ? Array('content_tags') : Array());
2175 
2176  if (!empty($link_parent_components) && !$link['asset']->cloneComponents($clone, $link_parent_components)) {
2177  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2178  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2179 
2180  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
2181 
2182  return $null;
2183  }
2184  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
2185  unset($this->_tmp[__CLASS__.'_in_create_cascading']);
2186  }
2187  }
2188 
2189  // copy all link-based components from the original asset
2190  $link_components = $components;
2191  foreach ($link_components as $idx => $component) {
2192  if ($component != 'content_tags') {
2193  unset($link_components[$idx]);
2194  }
2195  }
2196  if (!empty($link_components) && !$source->cloneComponents($clone, $link_components, TRUE)) {
2197  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2198  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2199  return $null;
2200  }
2201 
2202 
2203  // if we got this far all is well,
2204  // so add to the map and return the clone
2205  $clone_map[$source->id] = $clone->id;
2206  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2207  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2208 
2209  return $clone;
2210 
2211  }//end _cloneAsset()
2212 
2213 
2228  function &_cloneLink(&$clone, &$link, &$lock, &$source)
2229  {
2230  if (!empty($link)) {
2231  // create the initial link
2232  if (!isset($link['value'])) $link['value'] = '';
2233  if (!isset($link['sort_order'])) {
2234  $link['sort_order'] = -1;
2235  }
2236  if (!isset($link['is_dependant'])) {
2237  $link['is_dependant'] = 0;
2238  }
2239  if (!isset($link['is_exclusive'])) {
2240  $link['is_exclusive'] = 0;
2241  }
2242 
2243  $linkid = $link['asset']->createLink($clone, $link['link_type'], $link['value'], $link['sort_order'], $link['is_dependant'], $link['is_exclusive']);
2244  if (empty($linkid)) return $null;
2245 
2246  }//end if !empty(link)
2247 
2248  return $clone;
2249 
2250  }//end _cloneLink()
2251 
2252 
2297  function &cloneAsset(&$source, &$link, &$clone_map, $components, $clone_dependents=TRUE, $cloning_dependent=FALSE)
2298  {
2299  $null = NULL;
2300  // if the source asset is actually a shadow asset, skip it
2301  $id_parts = explode(':', $source->id);
2302  if (isset($id_parts[1])) return $null;
2303 
2304  // make sure this type of asset can be cloned
2305  if (!$source->canClone()) {
2306  trigger_localised_error('SYS0070', E_USER_WARNING, $source->type());
2307  return $null;
2308  }
2309  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2310  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2311 
2312  $clone = $this->_cloneAsset($source, $link, $clone_map, $components, $cloning_dependent);
2313  if (is_null($clone)) {
2314  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2315  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2316  return $null;
2317  }
2318 
2319  // OK now that we are linked up let's clone our dependents
2320  if ($clone_dependents) {
2321 
2322  $dependent_links = $this->getLinks($source->id, SQ_SC_LINK_SIGNIFICANT, '', TRUE, 'major', NULL, 1);
2323 
2324  if (!empty($dependent_links)) {
2325  $create_link = Array(
2326  'asset' => &$clone,
2327  'link_type' => NULL,
2328  'value' => NULL,
2329  'sort_order' => -1,
2330  'is_dependant' => '1',
2331  'is_exclusive' => '0',
2332  );
2333 
2334  foreach ($dependent_links as $data) {
2335  // If this asset has already been cloned in this duplication run
2336  // then just link it to the new clone of ourselves
2337  if (isset($clone_map[$data['minorid']])) {
2338 
2339  $cloned_child = $this->getAsset($clone_map[$data['minorid']], $data['minor_type_code']);
2340  if (is_null($cloned_child)) {
2341  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2342  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2343  return $null;
2344  }
2345 
2346  $linkid = $clone->createLink($cloned_child, $data['link_type'], $data['value'], $data['sort_order'], '1', $data['is_exclusive']);
2347  if (!$linkid) {
2348  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2349  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2350  return $null;
2351  }
2352 
2353  // otherwise clone and link
2354  } else {
2355  $child = $this->getAsset($data['minorid'], $data['minor_type_code']);
2356  if (is_null($child)) continue;
2357 
2358  // skip shadow assets
2359  $id_parts = explode(':', $child->id);
2360  if (isset($id_parts[1])) continue;
2361 
2362  $create_link['link_type'] = $data['link_type'];
2363  $create_link['value'] = $data['value'];
2364  $create_link['sort_order'] = $data['sort_order'];
2365  $create_link['is_exclusive'] = $data['is_exclusive'];
2366  $new_component = Array();
2367 
2368  $cloned_child = $this->cloneAsset($child, $create_link, $clone_map, $components, $clone_dependents, TRUE);
2369 
2370  if (is_null($cloned_child)) {
2371  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2372  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2373  return $null;
2374  }
2375  }//end if
2376 
2377  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
2378  // lock the new clone in the same chain as we are locked
2379  $lock = $this->getLockInfo($cloned_child->id, 'all');
2380  if (!empty($lock)) {
2381  if (!$this->releaseLock($cloned_child->id, 'all')) {
2382  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2383  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2384  return $null;
2385  }//end if
2386  }//end if not empty lock
2387  }//end if
2388  }//end foreach
2389  }//end if empty
2390  }//end if clone dependents
2391 
2392  // Now all the dependents are clones. cloneComponentsAdditional()
2393  // will process extra processing required for completing cloning
2394  if (!$source->cloneComponentsAdditional($clone, Array('all'))) {
2395  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2396  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2397  return $null;
2398  }
2399 
2400  // contents and some other components are not index for this cloned asset because the
2401  // asset link wasnt created to its parent.
2402  // Bug Fix - #4615 Cloning an Asset doesnt re-index all the components
2403  $sm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('search_manager');
2404  if(!is_null($sm)) {
2405  $sm->reindexAsset($clone, Array('all'));
2406  $sm->reindexContents($clone, Array());
2407  }
2408 
2409  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2410  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2411 
2412  // if all went well then broadcast the event telling system a asset has been cloned
2413  $parameter = Array();
2414  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_asset_cloned', $clone, $parameter);
2415  return $clone;
2416 
2417  }//end cloneAsset()
2418 
2419 
2420 //-- LOCKING --//
2421 
2422 
2444  function acquireLock($assetid, $lock_type, $source_assetid=0, $force=FALSE, $expires=0)
2445  {
2446  // type-check lock type BEFORE we get asset, otherwise we're just wasting time allocating it
2447  if (empty($lock_type) || !is_string($lock_type)) {
2448  trigger_localised_error('SYS0074', E_USER_WARNING, $assetid);
2449  return FALSE;
2450  }
2451 
2452  $asset = $this->getAsset($assetid);
2453  // Instead of throwing a fatal error, we just return false here, getAsset has already given a warning if the asset does not exists.
2454  if (is_null($asset)) return FALSE;
2455 
2456  // because lock_type can be expanded, we need to keep the original type
2457  // to check if we can force getting it.
2458  $orig_lock_type = $lock_type;
2459 
2460  $current_locks = $this->getLockInfo($assetid, $lock_type, TRUE, TRUE);
2461  if (empty($current_locks)) return FALSE;
2462 
2463  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db3');
2464  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2465 
2466  $success = TRUE;
2467 
2468  // Set this to unknown for now.
2469  // This is a fairly expensive operation. we only need this info if
2470  // a lock is held by someone other than ourselves.
2471  // On the other hand, if we're not trying to force acquiring a lock,
2472  // we can immediately set this to FALSE.
2473  $can_force = NULL;
2474  if ($force === FALSE) {
2475  $can_force = FALSE;
2476  }
2477 
2478  $lock_updated = FALSE;
2479  foreach ($current_locks as $lock_type => $lock) {
2480 
2481  if (!empty($lock) && $lock['userid'] == $GLOBALS['SQ_SYSTEM']->currentUserid()) {
2482  // the user is asking to acquire a lock they already had
2483  // so just update the lock expiry date
2484  if ($this->updateLock($assetid, $lock_type, $expires)) {
2485  $lock_updated = TRUE;
2486  continue;
2487  } else {
2488  $success = FALSE;
2489  break;
2490  }
2491  }
2492 
2493  if ($can_force === NULL) {
2494  $can_force = $asset->canForceablyAcquireLock($orig_lock_type);
2495  }
2496 
2497  // special case where the current user is actually
2498  // removing the current lock and taking it for themselves
2499  if (!empty($lock) && $can_force) {
2500 
2501  // attempt to remove the lock
2502  if (!$this->releaseLock($assetid, $lock_type)) {
2503  $success = FALSE;
2504  break;
2505  }
2506 
2507  // send an internal message
2508  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
2509  $ms->openQueue();
2510  $ms->openLog();
2511 
2512  $user = $this->getAsset($lock['userid']);
2513 
2514  $locked_assetids = Array((int) $assetid);
2515  foreach ($lock['chained_assets'] as $row) {
2516  $locked_assetids[] = $row['assetid'];
2517  }
2518 
2519  foreach ($locked_assetids as $locked_assetid) {
2520  $locked_asset = $this->getAsset($locked_assetid);
2521 
2522  // create a new message object and populate it, before adding to
2523  // the message queue in the internal messaging system to sending later
2524  $msg_reps = Array(
2525  'user_name' => $GLOBALS['SQ_SYSTEM']->user->name,
2526  'type_code' => $this->getTypeInfo($locked_asset->type(), 'name'),
2527  'lock_type' => ucwords($lock_type),
2528  'asset_name' => $locked_asset->name,
2529  'old_user_name' => $user->name,
2530  );
2531  $log = $ms->newMessage(Array(), 'asset.locking.forced', $msg_reps);
2532  $log->parameters['assetid'] = $locked_asset->id;
2533  $log->parameters['former_userid'] = $user->id;
2534  $ms->logMessage($log);
2535 
2536  $this->forgetAsset($locked_asset);
2537  }
2538 
2539  // close the queue of messages we opened, which sends all the messages in the queue
2540  $ms->closeQueue();
2541  $ms->closeLog();
2542 
2543  // refresh the lock info, just in case someone has sneaked in while we are sending the message
2544  $lock = $this->getLockInfo($assetid, $lock_type, FALSE, TRUE);
2545 
2546  }//end if
2547 
2548  // is this asset already locked
2549  if (!empty($lock)) {
2550  $user = $this->getAsset($lock['userid']);
2551  trigger_localised_error('SYS0101', E_USER_NOTICE, $lock_type, $asset->name, $user->name);
2552  $success = FALSE;
2553  break;
2554  }
2555 
2556  $lockid = 'asset.'.$assetid.'.'.$lock_type;
2557  $source_lockid = ($source_assetid) ? 'asset.'.$source_assetid.'.'.$lock_type : '';
2558  if (TRUE !== ($err_msg = $GLOBALS['SQ_SYSTEM']->acquireLock($lockid, $source_lockid, $expires))) {
2559  trigger_localised_error('SYS0100', E_USER_NOTICE, $lock_type, $asset->name, $err_msg);
2560  $success = FALSE;
2561  break;
2562  }
2563 
2564  }//end foreach
2565 
2566  $GLOBALS['SQ_SYSTEM']->doTransaction(($success) ? 'COMMIT' : 'ROLLBACK');
2567  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2568 
2569  // return 2 for expiry updated only, 1 for success and 0 for failure
2570  return ($success) ? (($lock_updated) ? 2 : 1) : 0;
2571 
2572  }//end acquireLock()
2573 
2574 
2587  function releaseLock($assetid, $lock_type)
2588  {
2589  // type-check lock type BEFORE we get asset, otherwise we're just wasting time allocating it
2590  if (empty($lock_type) || !is_string($lock_type)) {
2591  trigger_localised_error('SYS0075', E_USER_WARNING, $assetid);
2592  return FALSE;
2593  }
2594 
2595  // because lock_type can be expanded, we need to keep the original type
2596  // to check if we can force getting it.
2597  $orig_lock_type = $lock_type;
2598 
2599  $current_locks = $this->getLockInfo($assetid, $lock_type, TRUE, FALSE);
2600  if (empty($current_locks)) return TRUE;
2601 
2602  $asset = $this->getAsset($assetid);
2603  assert_not_null($asset);
2604 
2605  $success = TRUE;
2606 
2607  // Set this to unknown for now.
2608  // This is a fairly expensive operation. we only need this info if
2609  // a lock is held by someone other than ourselves.
2610  $can_force = NULL;
2611 
2612  foreach ($current_locks as $lock_type => $lock) {
2613  if (empty($lock)) continue;
2614 
2615  if ((int) $lock['userid'] != $GLOBALS['SQ_SYSTEM']->currentUserid()) {
2616  if ($can_force === NULL) {
2617  $can_force = $asset->canForceablyAcquireLock($orig_lock_type);
2618  }
2619 
2620  // is this asset already locked by someone else and we can't forceably acquire it, error
2621  if (!$can_force) {
2622  $user = $this->getAsset($lock['userid']);
2623  trigger_localised_error('SYS0264', E_USER_WARNING, $lock_type, $asset->name, $user->name);
2624  $success = FALSE;
2625  continue;
2626  }
2627  }
2628 
2629  if (TRUE !== ($err_msg = $GLOBALS['SQ_SYSTEM']->releaseLock('asset.'.$assetid.'.'.$lock_type))) {
2630  trigger_localised_error('SYS0109', E_USER_NOTICE, $lock_type, $asset->name, $err_msg);
2631  $success = FALSE;
2632  continue;
2633  }
2634 
2635  }//end foreach
2636 
2637  return $success;
2638 
2639  }//end releaseLock()
2640 
2641 
2652  function updateLock($assetid, $lock_type, $expires=0)
2653  {
2654  $current_locks = $this->getLockInfo($assetid, $lock_type, TRUE, TRUE, TRUE, TRUE);
2655 
2656  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db3');
2657  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2658 
2659  $success = TRUE;
2660 
2661  foreach (array_keys($current_locks) as $lock_type) {
2662  if (!empty($lock_type)) {
2663  if (TRUE !== ($err_msg = $GLOBALS['SQ_SYSTEM']->updateLock('asset.'.$assetid.'.'.$lock_type, $expires))) {
2664  trigger_localised_error('SYS0122', E_USER_NOTICE, $assetid, $err_msg);
2665  $success = FALSE;
2666  break;
2667  }//end if
2668  }//end if
2669  }//end foreach
2670 
2671  if ($success) {
2672  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2673  } else {
2674  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2675  }
2676 
2677  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2678  return $success;
2679 
2680  }//end updateLock()
2681 
2682 
2696  function getLockInfo($assetid, $lock_type, $force_array=FALSE, $full_chain=FALSE, $check_expires=TRUE, $allow_only_one=FALSE)
2697  {
2698  $lock_types = $this->getLockTypes($assetid, $lock_type);
2699  $locks = Array();
2700  $lockids = Array();
2701  $count = 0;
2702  foreach ($lock_types as $each_lock_type) {
2703  $lockids[] = 'asset.'.$assetid.'.'.$each_lock_type;
2704  $last_lock = 'asset.'.$assetid.'.'.$each_lock_type;
2705  $count++;
2706  }
2707 
2708  if (!empty($lockids)) {
2709  if ($count > 1) {
2710  $lock = $GLOBALS['SQ_SYSTEM']->getLockInfo($lockids, $full_chain, $check_expires, $allow_only_one);
2711  } else {
2712  $lock = $GLOBALS['SQ_SYSTEM']->getLockInfo($last_lock, $full_chain, $check_expires, $allow_only_one);
2713  }
2714 
2715  $all_locks = Array();
2716  if ($allow_only_one) {
2717  $all_locks[] = $lock;
2718  } else {
2719  $all_locks = $lock;
2720  }
2721 
2722  foreach ($lock_types as $each_lock_type) {
2723  $current_lock_id = 'asset.'.$assetid.'.'.$each_lock_type;
2724  $locks[$each_lock_type] = Array();
2725  foreach ($all_locks as $each_key => $each_lock) {
2726  if (isset($each_lock['lockid']) && ($current_lock_id == $each_lock['lockid'])) {
2727  $each_lock['lock_type'] = $lock_type;
2728  $each_lock['source_assetid'] = preg_replace('|^asset\.(.*)\.[\w]+$|', '\1', $each_lock['source_lockid']);
2729  if ($full_chain) {
2730  for (reset($each_lock['chained_assets']); NULL !== ($k = key($each_lock['chained_assets'])); next($each_lock['chained_assets'])) {
2731  $each_lock['chained_assets'][$k]['assetid'] = preg_replace('/^asset\.([0-9]+(:.+)?)\..*$/', '\1', $each_lock['chained_assets'][$k]['lockid']);
2732  $each_lock['chained_assets'][$k]['source_assetid'] = preg_replace('/^asset\.([0-9]+(:.+)?)\..*$/', '\1', $each_lock['chained_assets'][$k]['source_lockid']);
2733  }
2734  }
2735  $locks[$each_lock_type] = $each_lock;
2736  }
2737  }
2738  }
2739  unset($lock);
2740  }//end if
2741 
2742  if (!$force_array && count($locks) == 1) {
2743  return reset($locks);
2744  } else {
2745  return $locks;
2746  }
2747 
2748  }//end getLockInfo()
2749 
2750 
2761  function getLockTypes($assetid, $lock_type)
2762  {
2763 
2764  // If the lock_type is not a string (invalid), just return an empty array.
2765  if (!is_string($lock_type) || empty($lock_type)) {
2766  return Array();
2767  }//end if
2768 
2769  if (!isset($this->_tmp['lock_types'][$assetid])) {
2770  $asset = $this->getAsset($assetid);
2771  // If the asset is null, just return an empty array, throwing fatal error is a bit over done.
2772  if (is_null($asset)) return Array();
2773  $this->_tmp['lock_types'][$assetid] = $asset->lockTypes();
2774  }
2775 
2776  if ($lock_type !== 'all') {
2777  if (!isset($this->_tmp['lock_types'][$assetid][$lock_type])) {
2778  return Array();
2779  }
2780  $bits = bit_elements($this->_tmp['lock_types'][$assetid][$lock_type]);
2781  $lock_types = Array();
2782  foreach ($bits as $bit) {
2783  if (FALSE !== ($k = array_search($bit, $this->_tmp['lock_types'][$assetid]))) {
2784  $lock_types[] = $k;
2785  }
2786  }
2787  return $lock_types;
2788  } else {
2789  $lock_types = Array();
2790  foreach ($this->_tmp['lock_types'][$assetid] as $lock_type => $bit) {
2791  if (preg_match('/^0*10*$/', decbin($bit))) {
2792  $lock_types[] = $lock_type;
2793  }
2794  }
2795  return $lock_types;
2796  }//end if
2797 
2798  }//end getLockTypes()
2799 
2800 
2801 //-- LINKING --//
2802 
2803 
2825  function getLink($assetid, $link_type=NULL, $type_code='', $strict_type_code=TRUE, $value=NULL, $side_of_link='major', $exclusive=NULL)
2826  {
2827  assert_valid_assetid($assetid);
2828  $bind_vars = Array();
2829 
2830  // if its a shadow assetid pipe it off to bridge
2831  $id_parts = explode(':', $assetid);
2832  if (isset($id_parts[1])) {
2833  $asset = $this->getAsset($id_parts[0]);
2834  $link = $asset->getLink($assetid, $link_type, $type_code, $strict_type_code, $value, $side_of_link, $exclusive);
2835  $this->forgetAsset($asset);
2836  return $link;
2837  }
2838 
2839  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
2840 
2841  $other_side = ($side_of_link == 'major') ? 'minor' : 'major';
2842 
2843  $db = MatrixDAL::getDb();
2844  $sql = 'SELECT l.linkid, l.'.$other_side.'id, l.value, l.link_type,
2845  l.type_code as '.$other_side.'_type_code, l.sort_order, l.is_dependant, l.is_exclusive, l.locked
2846  FROM '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_lnk_'.$other_side.' l';
2847 
2848  $where = 'l.'.$side_of_link.'id = :assetid';
2849  $bind_vars['assetid'] = $assetid;
2850 
2851  if (!is_null($link_type)) {
2852  $where .= '
2853  AND l.link_type = :link_type';
2854  $bind_vars['link_type'] = $link_type;
2855  }
2856 
2857  if (!is_null($exclusive)) {
2858  $where .= ' AND l.is_exclusive = :is_exclusive';
2859  $bind_vars['is_exclusive'] = ($exclusive) ? '1' : '0';
2860  }
2861 
2862  if ($type_code) {
2863 
2864  $type_code_cond = '';
2865  if (is_array($type_code)) {
2866  $type_query_str = Array();
2867  for (reset($type_code); NULL !== ($i = key($type_code)); next($type_code)) {
2868  $type_query_str[$i] = ':type_code_'.$i;
2869  $bind_vars['type_code_'.$i] = $type_code[$i];
2870  }
2871  $type_code_cond = 'IN ('.implode(', ', $type_query_str).')';
2872  } else {
2873  $type_code_cond = '= :type_code';
2874  $bind_vars['type_code'] = $type_code;
2875  }
2876 
2877  if ($strict_type_code) {
2878  $where .= ' AND l.type_code '.$type_code_cond;
2879  } else {
2880  $where .= ' AND l.type_code IN (
2881  SELECT type_code
2882  FROM sq_ast_typ_inhd
2883  WHERE inhd_type_code '.$type_code_cond.'
2884  )';
2885  }
2886  }
2887 
2888  if (!is_null($value)) {
2889  $where .= ' AND (l.value = :link_value';
2890  if ($value == '') $where .= ' OR l.value IS NULL';
2891  $where .= ')';
2892 
2893  $bind_vars['link_value'] = $value;
2894  }
2895 
2896  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lnk_', 'WHERE', FALSE);
2897  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'ast_', 'WHERE', FALSE);
2898 
2899  $sql .= $where.' ORDER BY l.sort_order';
2900 
2901  try {
2902  $query = MatrixDAL::preparePdoQuery($sql);
2903  foreach ($bind_vars as $bind_var => $bind_value) {
2904  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
2905  }
2906  $result = MatrixDAL::executePdoAssoc($query);
2907  if (!empty($result)) $result = $result[0];
2908  } catch (Exception $e) {
2909  throw new Exception('Unable to get link for asset with assetid: '.$assetid.' due to database error: '.$e->getMessage());
2910  }
2911 
2912  return $result;
2913 
2914  }//end getLink()
2915 
2916 
2930  function getShadowLinkByAsset($assetid, $other_assetid=NULL, $link_types=NULL, $value=NULL, $side_of_link='major', $force_array=FALSE)
2931  {
2932  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
2933 
2934  assert_valid_assetid($assetid);
2935  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
2936 
2937  if (!is_null($other_assetid)) {
2938  assert_valid_assetid($other_assetid);
2939 
2940  if (!$other_assetid || is_object($other_assetid)) {
2941  trigger_localised_error('SYS0108', E_USER_WARNING);
2942  return Array();
2943  }
2944  }
2945 
2946  $other_side = ($side_of_link == 'major') ? 'minor' : 'major';
2947 
2948  $db = MatrixDAL::getDb();
2949  $sql = 'SELECT l.linkid, l.'.$other_side.'id, l.value, l.link_type
2950  FROM '.SQ_TABLE_RUNNING_PREFIX.'shdw_ast_lnk l';
2951 
2952  $where = 'l.'.$side_of_link.'id = :assetid';
2953 
2954  if (!is_null($other_assetid)) {
2955  $where .= ' AND l.'.$other_side.'id = '.MatrixDAL::quote($other_assetid);
2956  }
2957 
2958  if (!is_null($link_types)) {
2959  $where .= ' AND '.db_extras_bitand(MatrixDAL::getDbType(), 'l.link_type', $link_types).' > 0 ';
2960  }
2961 
2962  if (!is_null($value)) {
2963  $where .= ' AND l.value = '.MatrixDAL::quote($value);
2964  }
2965 
2966  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lnk_', 'WHERE', FALSE);
2967  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'ast_', 'WHERE', FALSE);
2968 
2969  try {
2970  $query = MatrixDAL::preparePdoQuery($sql.$where);
2971  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
2972  $result = MatrixDAL::executePdoAssoc($query);
2973  } catch (Exception $e) {
2974  throw new Exception('Unable to get shadow link for this assetid: '.$assetid.' due to database error: '.$e->getMessage());
2975  }
2976 
2977  if (!$force_array && count($result) == 1) {
2978  return $result[0];
2979  } else {
2980  return $result;
2981  }
2982 
2983  }//end getShadowLinkByAsset()
2984 
2985 
2997  function getLinkById($linkid, $assetid=0, $side_of_link='major')
2998  {
2999  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
3000 
3001  $other_side = ($side_of_link == 'major') ? 'minor' : 'major';
3002 
3003  $id_parts = explode(':', $linkid);
3004  if (isset($id_parts[1])) {
3005  $bridge = $this->getAsset($id_parts[0]);
3006  $link = $bridge->getLinkById($linkid, $assetid, $side_of_link);
3007  $this->forgetAsset($bridge);
3008  return $link;
3009  }
3010 
3011  $db = MatrixDAL::getDb();
3012 
3013  $sql = 'SELECT l.linkid, l.value, l.link_type, l.sort_order, l.is_dependant, l.is_exclusive, l.locked, ';
3014  $where = 'l.linkid = :linkid';
3015 
3016  // if they only want one side of the link, we can do less work
3017  if ($assetid !== 0) {
3018  $sql .= 'l.'.$other_side.'id, l.type_code as '.$other_side.'_type_code
3019  FROM '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_lnk_'.$other_side.' l';
3020  $where .= ' AND l.'.$side_of_link.'id = :assetid';
3021  } else {
3022  $sql .= 'l.majorid, l.type_code as minor_type_code, l.minorid, a.type_code as major_type_code
3023  FROM '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_lnk_minor l
3024  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON l.majorid = a.assetid';
3025 
3026  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
3027 
3028  }//end if
3029 
3030  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lnk_', 'WHERE', FALSE);
3031  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'ast_', 'WHERE', FALSE);
3032 
3033  try {
3034  $query = MatrixDAL::preparePdoQuery($sql.$where);
3035  MatrixDAL::bindValueToPdo($query, 'linkid', (int) $linkid);
3036  if ($assetid !== 0) {
3037  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
3038  }
3039  $result = MatrixDAL::executePdoAssoc($query);
3040  if (empty($result)) {
3041  $result = NULL;
3042  } else if (isset($result[0])) {
3043  $result = $result[0];
3044  }
3045  } catch (Exception $e) {
3046  throw new Exception('Unable to get link details for asset with assetid: '.$assetid.' due to database error: '.$e->getMessage());
3047  }
3048 
3049  return $result;
3050 
3051  }//end getLinkById()
3052 
3053 
3077  function getLinkByAsset($assetid, $other_assetid, $link_types=NULL, $value=NULL, $side_of_link='major', $force_array=FALSE, $dependant=NULL, $exclusive=NULL)
3078  {
3079  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
3080 
3081  assert_valid_assetid($assetid);
3082  assert_valid_assetid($other_assetid);
3083  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
3084 
3085  if (!$other_assetid || is_object($other_assetid)) {
3086  trigger_localised_error('SYS0108', E_USER_WARNING);
3087  return Array();
3088  }
3089 
3090  if (!is_array($other_assetid)) {
3091  $other_assetid = Array($other_assetid);
3092  }
3093 
3094  // handle shadow assets
3095  $other_shadow_assetids = Array();
3096  $shadow_links = Array();
3097 
3098  // If the main asset ID is a shadow asset, then we need to run through
3099  // ALL of the assets with it, as all combinations will have a shadow
3100  // component. If it's not a shadow asset, then we need to find which
3101  // other asset IDs are shadow, extract them, and then run the rest
3102  // through the sq_ast_lnk table as normal.
3103  $assetid_shadow_parts = explode(':', $assetid);
3104  $assetid_is_shadow = (count($assetid_shadow_parts) > 1);
3105 
3106  if ($assetid_is_shadow) {
3107  $other_shadow_assetids = $other_assetid;
3108  $other_assetid = Array();
3109  } else {
3110  foreach ($other_assetid as $key => $other_assetid_value) {
3111  $shadow_parts = explode(':', $other_assetid_value);
3112 
3113  if (count($shadow_parts) > 1) {
3114  $other_shadow_assetids[] = $other_assetid_value;
3115  unset($other_assetid[$key]);
3116  }
3117  }
3118  }
3119 
3120  // If there are no shadow assets then don't bother looking at this
3121  if (!empty($other_shadow_assetids)) {
3122 
3123  // Assign major and minor asset IDs according to side of link -
3124  // making the main asset ID a single-element array
3125  if ($side_of_link == 'major') {
3126  $majorids = Array($assetid);
3127  $minorids = $other_shadow_assetids;
3128  } else {
3129  $majorids = $other_shadow_assetids;
3130  $minorids = Array($assetid);
3131 
3132  // the site_of_link also should be swapped
3133  $side_of_link = 'major';
3134  }
3135 
3136  // Loop through each of these and get each possible combination of
3137  // minor/major asset ID. (This isn't as bad as it sounds because
3138  // one of these WILL be a single asset ID)
3139  foreach ($majorids as $majorid) {
3140  $major_shadow_parts = FALSE;
3141  $id_parts = explode(':', $majorid);
3142  if (isset($id_parts[1])) {
3143  $major_shadow_parts = Array(
3144  'bridgeid' => $id_parts[0],
3145  'shadowid' => $id_parts[1],
3146  );
3147  }
3148 
3149  foreach ($minorids as $minorid) {
3150  $minor_shadow_parts = FALSE;
3151  $id_parts = explode(':', $minorid);
3152  if (isset($id_parts[1])) {
3153  $minor_shadow_parts = Array(
3154  'bridgeid' => $id_parts[0],
3155  'shadowid' => $id_parts[1],
3156  );
3157  }
3158 
3159  if (!empty($major_shadow_parts)) {
3160  // major is shadow asset, use bridge
3161  $link = Array();
3162  $asset = $this->getAsset($major_shadow_parts['bridgeid']);
3163  if (!is_null($asset)) {
3164  // Always force an array in this case, and merge it in
3165  $link = $asset->getLinkByAsset($majorid, $minorid, $link_types, $value, $side_of_link, TRUE, $dependant, $exclusive);
3166  $this->forgetAsset($asset);
3167  }
3168  $shadow_links = array_merge($shadow_links, $link);
3169  } else {
3170  if (!empty($minor_shadow_parts)) {
3171  // minor is shadow asset, look at the shadow link table
3172  // instead of the regular link table
3173  // Always force an array, and merge it in
3174  $link = $this->getShadowLinkByAsset($majorid, $minorid, $link_types, $value, $side_of_link, TRUE, $dependant, $exclusive);
3175  $shadow_links = array_merge($shadow_links, $link);
3176  }
3177  }
3178 
3179  }//end foreach minor asset ID
3180 
3181  }//end foreach major asset ID
3182 
3183  }//end if there are shadow assets
3184 
3185  // If there are no more assets to run through, return what shadow links
3186  // we have only
3187  if (!empty($other_assetid)) {
3188  $db = MatrixDAL::getDb();
3189  $bind_vars = Array();
3190 
3191  $prepared_otherids = Array();
3192  foreach ($other_assetid as $id) {
3193  $prepared_otherids[] = '\''.$id.'\'';
3194  }
3195 
3196  $other_side = ($side_of_link == 'major') ? 'minor' : 'major';
3197 
3198  $sql = 'SELECT l.linkid, l.'.$other_side.'id, l.value, l.link_type,
3199  l.type_code as '.$other_side.'_type_code, l.sort_order, l.is_dependant, l.is_exclusive, l.locked
3200  FROM '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_lnk_'.$other_side.' l';
3201 
3202  $where = 'l.'.$side_of_link.'id = :assetid
3203  AND l.'.$other_side.'id IN ('.implode(',', $prepared_otherids).')';
3204  $bind_vars['assetid'] = $assetid;
3205 
3206  if (!is_null($link_types)) {
3207  $where .= ' AND '.db_extras_bitand(MatrixDAL::getDbType(), 'l.link_type', $link_types).' > 0 ';
3208  }
3209  if (!is_null($value)) {
3210  if (empty($value)) {
3211  $where .= ' AND (l.value = :value OR l.value IS NULL)';
3212  } else {
3213  $where .= ' AND l.value = :value';
3214  }
3215  $bind_vars['value'] = $value;
3216  }
3217  if (!is_null($dependant)) {
3218  $where .= ' AND l.is_dependant = :is_dependant';
3219  $bind_vars['is_dependant'] = ($dependant) ? '1' : '0';
3220  }
3221  if (!is_null($exclusive)) {
3222  $where .= ' AND l.is_exclusive = :is_exclusive';
3223  $bind_vars['is_exclusive'] = ($exclusive) ? '1' : '0';
3224  }
3225 
3226  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lnk_', 'WHERE', FALSE);
3227  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'ast_', 'WHERE', FALSE);
3228 
3229  $sql .= $where.'
3230  ORDER BY l.sort_order';
3231 
3232  try {
3233  $query = MatrixDAL::preparePdoQuery($sql);
3234  foreach ($bind_vars as $bind_var => $bind_value) {
3235  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
3236  }
3237  $result = MatrixDAL::executePdoAssoc($query);
3238  } catch (Exception $e) {
3239  throw new Exception('Unable to get link for assetid: '.$assetid.' due to database error: '.$e->getMessage());
3240  }
3241 
3242  } else {
3243  $result = Array();
3244  }
3245 
3246  // Add shadow links from above to the result
3247  $result += $shadow_links;
3248 
3249  if (!$force_array && count($result) == 1) {
3250  return $result[0];
3251  } else {
3252  return $result;
3253  }
3254 
3255  }//end getLinkByAsset()
3256 
3257 
3270  function arrayMerge($array1,$array2)
3271  {
3272  $common_keys = Array();
3273  $merged_array = Array();
3274  if (!is_array($array1)) $array1 = Array();
3275  if (!is_array($array2)) $array2 = Array();
3276 
3277  // add all elements which is not common to both array
3278  foreach ($array1 as $key => $value) {
3279  if (array_key_exists($key, $array2)) {
3280  $common_keys = array_merge($common_keys, Array($key));
3281  continue;
3282  }
3283  // if the key does not exist in the other array
3284  $merged_array[$key] = $array1[$key];
3285  }
3286 
3287  foreach ($array2 as $key => $value) {
3288  if (array_key_exists($key, $array1)) continue;
3289 
3290  // if the key does not exist in the other array
3291  $merged_array[$key] = $array2[$key];
3292  }
3293 
3294  foreach ($common_keys as $key) {
3295  $merged_array[$key] = array_merge($array1[$key], $array2[$key]);
3296  }
3297  return $merged_array;
3298 
3299  }//end arrayMerge()
3300 
3301 
3334  function getLinks($assetid, $link_types, $type_code='', $strict_type_code=TRUE, $side_of_link='major', $value=NULL, $dependant=NULL, $exclusive=NULL, $sort_by=NULL, $access=NULL, $effective=TRUE)
3335  {
3336  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
3337  $force_array = FALSE;
3338 
3339  if (!is_array($assetid)) {
3340  $assetids = Array($assetid);
3341  } else {
3342  $force_array = TRUE;
3343  $assetids = $assetid;
3344  }
3345 
3346  $links = Array();
3347  $query_assetids = Array();
3348  foreach ($assetids as $assetid) {
3349  assert_valid_assetid($assetid);
3350 
3351  // check if we are getting links for a shadow asset, and palm the request off to the
3352  // handler of the shadow asset if we are
3353  $id_parts = explode(':', $assetid);
3354  if (isset($id_parts[1])) {
3355  $real_assetid = $id_parts[0];
3356  $asset = $this->getAsset($real_assetid);
3357  if (is_null($asset)) continue;
3358 
3359  $links[$assetid] = $asset->getLinks($assetid, $link_types, $type_code, $strict_type_code, $side_of_link, $sort_by, $dependant, $exclusive);
3360  $this->forgetAsset($asset);
3361 
3362  // we also need the links to real assets for this shadow asset
3363  // if we are looking up the tree
3364  if (($side_of_link == 'minor') && !$exclusive && !$dependant) {
3365  $db = MatrixDAL::getDb();
3366  $sql = 'SELECT
3367  l.linkid,
3368  l.majorid,
3369  l.minorid,
3370  l.value,
3371  l.link_type,
3372  l.locked,
3373  a.type_code AS major_type_code
3374  FROM
3375  '.SQ_TABLE_RUNNING_PREFIX.'shdw_ast_lnk l
3376  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a on l.majorid = a.assetid';
3377 
3378  $where = 'minorid = '.MatrixDAL::quote($assetid);
3379  if(!is_null($link_types)){
3380  $where .= ' AND '.db_extras_bitand(MatrixDAL::GetDbType(), 'l.link_type', MatrixDAL::quote($link_types)).' > 0 ';
3381  }
3382  if ($type_code) {
3383  if ($strict_type_code) {
3384  $where .= ' AND a.type_code IN (';
3385  if (is_array($type_code)) {
3386  foreach ($type_code as $index => $typ_cd) {
3387  $type_code[$index] = MatrixDAL::quote($typ_cd);
3388  }
3389  $where .= implode(', ', $type_code);
3390  } else {
3391  $where .= MatrixDAL::quote($type_code);
3392  }
3393  $where .= ') ';
3394  } else {
3395  $type_ancestors = $this->getTypeDescendants($type_code);
3396  foreach ($type_ancestors as $i => $v) {
3397  $type_ancestors[$i] = MatrixDAL::quote($v);
3398  }
3399  if (is_array($type_code)) {
3400  foreach ($type_code as $index => $typ_cd) {
3401  $type_ancestors[] = MatrixDAL::quote($typ_cd);
3402  }
3403  } else {
3404  $type_ancestors[] = MatrixDAL::quote($type_code);
3405  }
3406  $where .= ' AND a.type_code IN ('.implode(', ', $type_ancestors).')';
3407  }
3408  }
3409  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
3410  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
3411 
3412  $result = NULL;
3413  $query = MatrixDAL::preparePdoQuery($sql.$where);
3414  try {
3415  $result = MatrixDAL::executePdoAll($query);
3416  } catch (Exception $e) {
3417  throw new Exception('Could not get shadow links of asset ID #'.$assetid.' due to database error: '.$e->getMessage());
3418  }//end
3419 
3420  $shdw_links = Array();
3421  foreach ($result as $shdw_link) {
3422  $shdw_links[] = Array(
3423  'linkid' => $shdw_link['linkid'],
3424  'majorid' => $shdw_link['majorid'],
3425  'major_type_code' => $shdw_link['major_type_code'],
3426  'minorid' => $shdw_link['minorid'],
3427  'value' => $shdw_link['value'],
3428  'link_type' => $shdw_link['link_type'],
3429  'is_dependant' => '0',
3430  'is_exclusive' => '0',
3431  'sort_order' => 0,
3432  'locked' => $shdw_link['locked'],
3433  );
3434 
3435  }
3436  // in some cases the links[$assetid] value is null. If this is the case return an empty array
3437  // Now php5 complains if the parameters of the array_merge function is not an array
3438  $_links_to_merge = is_null($links[$assetid]) ? Array() : $links[$assetid];
3439  $links[$assetid] = array_merge($_links_to_merge, $shdw_links);
3440 
3441  }//end if side_of_link is major
3442 
3443  continue;
3444 
3445  }//end if shadow asset
3446 
3447  // we are not getting links for a shadow asset, so if we are getting child links of
3448  // an asset that handles shadow assets, palm the request off to it
3449  if ($side_of_link == 'major') {
3450  $asset = $this->getAsset($assetid);
3451  if (is_null($asset)) return Array();
3452  if (implements_interface($asset, 'bridge')) {
3453  $links[$assetid] = $asset->getLinks($assetid, $link_types, $type_code, $strict_type_code, $side_of_link, $sort_by, $dependant, $exclusive);
3454  // still need the asset manager to get asset that are not managed by the bridge
3455  }
3456  $this->forgetAsset($asset);
3457  }
3458 
3459  // we now have an assetid that still requires a query, so we'll add it to its own list
3460  $query_assetids[] = $assetid;
3461 
3462  }//end foreach
3463 
3464  if (!empty($query_assetids)) {
3465  // if value is not null and not array, convert it to array
3466  if (!is_null($value) && !is_array($value)) {
3467  $value = Array(
3468  'link_value' => Array($value),
3469  'equal' => TRUE,
3470  );
3471  }
3472  // we couldnt palm the request off, so we better do it ourselves
3473  $links_query = $this->generateGetLinksQuery($query_assetids, $link_types, $type_code, $strict_type_code, $side_of_link, $value, $dependant, $exclusive, $sort_by, $access, $effective);
3474 
3475  if (!empty($links_query)) {
3476  $sql = implode(' ', $links_query['sql_array']);
3477  $query = MatrixDAL::preparePdoQuery($sql);
3478 
3479  foreach ($links_query['bind_vars'] as $bind_var => $bind_value) {
3480  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
3481  }
3482 
3483  $result = MatrixDAL::executePdoGroupedAssoc($query);
3484  $links = $this->arrayMerge($result,$links);
3485  }
3486  }
3487 
3488  if (count($links) == 1 && !$force_array) {
3489  $keys = array_keys($links);
3490  $links = $links[$keys[0]];
3491  }
3492 
3493  return $links;
3494 
3495  }//end getLinks()
3496 
3497 
3514  function getAllChildLinks($assetid, $link_type=0, $access=NULL, $effective=TRUE, $bind_prefix='gc_', $depth=0)
3515  {
3516  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
3517 
3518  if (!isset($this->_tmp['child_links'][$assetid])) {
3519  $id_parts = explode(':', $assetid);
3520  if (isset($id_parts[1])) {
3521  $asset = $this->getAsset($id_parts[0]);
3522  $links = $asset->getAllChildLinks($assetid, $link_type);
3523  $this->forgetAsset($asset);
3524  return $links;
3525  }
3526 
3527  // So it is not a shadow asset, but it could be a bridge
3528  $asset = $this->getAsset($assetid);
3529  $shadow_links = Array();
3530  if (implements_interface($asset, 'bridge')) {
3531  $shadow_links = $asset->getAllChildLinks($assetid, $link_type);
3532  }
3533 
3534  $db = MatrixDAL::getDb();
3535 
3536  $where = 'l.minorid = :assetid';
3537  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
3538  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
3539  $sql = 'SELECT t.treeid
3540  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l ON t.linkid = l.linkid'.$where;
3541  $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), 1);
3542 
3543  try {
3544  $query = MatrixDAL::preparePdoQuery($sql);
3545  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
3546  $treeid = MatrixDAL::executePdoOne($query);
3547  } catch (Exception $e) {
3548  throw new Exception('Unable to get treeid for asset: '.$assetid.' due to database error: '.$e->getMessage());
3549  }
3550 
3551  $current_level = strlen($treeid) / SQ_CONF_ASSET_TREE_SIZE;
3552 
3553  $where = 't.treeid LIKE :'.$bind_prefix.'treeid_like
3554  AND t.treeid > :'.$bind_prefix.'treeid';
3555  if ($depth != 0) {
3556  $where .=' AND ((LENGTH(t.treeid) / '.SQ_CONF_ASSET_TREE_SIZE.') '.(($current_level) ? ' - '.$current_level : '').') <= :gc_max_tree_depth';
3557  }
3558 
3559  // only do a link comparison if they have specified a link type
3560  if ($link_type != 0) {
3561  $where .= ' AND '.db_extras_bitand(MatrixDAL::getDbType(), 'l.link_type', $link_type).' > 0';
3562  }
3563 
3564  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
3565  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
3566  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
3567 
3568  $select = 'SELECT SUBSTR(t.treeid, '.MatrixDAL::quote((int) strlen($treeid) + 1).') as treeid,
3569  ((LENGTH(t.treeid) / '.SQ_CONF_ASSET_TREE_SIZE.') '.(($current_level) ? ' - '.$current_level : '').') as lvl,
3570  l.linkid, a.assetid, a.short_name, a.type_code, l.link_type, l.sort_order, l.value, l.is_dependant, l.is_exclusive';
3571  $from = 'FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t
3572  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l ON t.linkid = l.linkid
3573  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON l.minorid = a.assetid';
3574  $order_by = 'ORDER BY t.treeid';
3575  $having = '';
3576  $group_by = '';
3577 
3578  $bind_vars[$bind_prefix.'treeid_like'] = $treeid.'%';
3579  $bind_vars[$bind_prefix.'treeid'] = $treeid;
3580  if ($depth != 0) $bind_vars[$bind_prefix.'max_tree_depth'] = $depth;
3581 
3582  if (!is_null($access)) {
3583  $access = (String)$access;
3584  $userid_cond = '';
3585  $group_by = 'GROUP BY t.treeid, l.linkid, l.minorid, l.link_type, l.sort_order, l.value, l.is_dependant, l.is_exclusive, a.assetid, a.short_name, a.type_code, a.name';
3586  if (!$GLOBALS['SQ_SYSTEM']->userRoot() && !$GLOBALS['SQ_SYSTEM']->userSystemAdmin()) {
3587  $from .= ' INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_perm p ON p.assetid = a.assetid ';
3588  $from .= ' LEFT JOIN '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_role r ON (p.userid = r.roleid AND r.assetid = a.assetid) ';
3589 
3590  $current_user = $this->getAsset($GLOBALS['SQ_SYSTEM']->user->id);
3591  $userids = $current_user->getUserGroups();
3592  $this->forgetAsset($current_user);
3593  $userids[] = (String)$GLOBALS['SQ_SYSTEM']->user->id;
3594  $public_userid = (String)$this->getSystemAssetid('public_user');
3595  $userids[] = $public_userid;
3596 
3597  $p_userids_str = Array();
3598  $r_userids_str = Array();
3599  for (reset($userids); NULL !== ($i = key($userids)); next($userids)) {
3600  $p_userids_str[$i] = ':'.$bind_prefix.'p_userids_'.$i;
3601  $r_userids_str[$i] = ':'.$bind_prefix.'r_userids_'.$i;
3602  $bind_vars[$bind_prefix.'p_userids_'.$i] = (string)$userids[$i];
3603  $bind_vars[$bind_prefix.'r_userids_'.$i] = (string)$userids[$i];
3604  }
3605 
3606  $p_userids_str = implode(',', $p_userids_str);
3607  $r_userids_str = implode(',', $r_userids_str);
3608  $userid_cond = ' (p.userid IN ('.$p_userids_str.') OR r.userid IN ('.$r_userids_str.'))';
3609  $where .= ' AND '.$userid_cond.'
3610  AND (
3611  (p.permission = :'.$bind_prefix.'_access AND (
3612  p.userid <> :'.$bind_prefix.'_public_userid
3613  OR r.userid <> :'.$bind_prefix.'_public_userid_1
3614  OR (p.userid = :'.$bind_prefix.'_public_userid_2 AND p.granted = \'1\')
3615  OR (r.userid = :'.$bind_prefix.'_public_userid_3 AND p.granted = \'1\')
3616  )
3617  )';
3618 
3619  $bind_vars[$bind_prefix.'_access'] = $access;
3620  $bind_vars[$bind_prefix.'_public_userid'] = $public_userid;
3621  $bind_vars[$bind_prefix.'_public_userid_1'] = $public_userid;
3622  $bind_vars[$bind_prefix.'_public_userid_2'] = $public_userid;
3623  $bind_vars[$bind_prefix.'_public_userid_3'] = $public_userid;
3624 
3625  if ($effective) {
3626  $where .= ' OR (p.permission > :'.$bind_prefix.'_access_effective AND p.granted = \'1\')';
3627  $bind_vars[$bind_prefix.'_access_effective'] = $access;
3628  }
3629  $where .= ') ';
3630  $having = 'HAVING MIN(p.granted) <> \'0\'';
3631  $group_by .= ', p.assetid';
3632  }
3633  }
3634 
3635  $sql_array = Array(
3636  'select' => '('.$select,
3637  'from' => $from,
3638  'where' => $where,
3639  'group_by' => $group_by,
3640  'having' => $having,
3641  'order_by' => ')'.$order_by,
3642  );
3643 
3644  try {
3645  $query = MatrixDAL::preparePdoQuery(implode(' ', $sql_array));
3646  foreach ($bind_vars as $bind_var => $bind_value) {
3647  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
3648  }
3649  $links = MatrixDAL::executePdoGroupedAssoc($query);
3650 
3651  $child_links = Array();
3652  foreach ($links as $id => $info) {
3653  $child_links[$id] = $info[0];
3654  }
3655  } catch (Exception $e) {
3656  throw new Exception('Unable to get child links for tree id: '.$treeid.' due to database error: '.$e->getMessage());
3657  }
3658 
3659  // OK, what we are going to do is set up the effective dependant treeid for each tree link
3660  for (reset($child_links); NULL !== ($treeid = key($child_links)); next($child_links)) {
3661  if ($child_links[$treeid]['is_dependant']) {
3662  $parent_treeid = substr($treeid, 0, -SQ_CONF_ASSET_TREE_SIZE);
3663  if ($parent_treeid == '') {
3664  $child_links[$treeid]['dependant_treeid'] = '';
3665  } else {
3666  if (!isset($child_links[$parent_treeid])) continue;
3667  $child_links[$treeid]['dependant_treeid'] = $child_links[$parent_treeid]['dependant_treeid'];
3668  }
3669  } else {
3670  $child_links[$treeid]['dependant_treeid'] = $treeid;
3671  }
3672  }//end for
3673 
3674  $this->_tmp['child_links'][$assetid] = $child_links + $shadow_links;
3675 
3676  }//end if
3677 
3678  return $this->_tmp['child_links'][$assetid];
3679 
3680  }//end getAllChildLinks()
3681 
3682 
3698  function getLinkLineages($assetid, $result_limit=0, $from_treeid=NULL, $fields='name', $only_significant=FALSE)
3699  {
3700  assert_valid_assetid($assetid);
3701 
3702  $link_lineages = Array();
3703  $assetids = Array();
3704  $db = MatrixDAL::getDb();
3705  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
3706 
3707  if (is_array($fields)) {
3708  $fields_str = 'a.'.implode(', a.', $fields);
3709  } else {
3710  $fields_str = 'a.'.$fields;
3711  }
3712 
3713  $id_parts = explode(':', $assetid);
3714  if (isset($id_parts[1])) {
3715  // if this asset is a shadow asset the we want the get the lineages of the
3716  // bridge, and also the lineages of and real assets that this asset is linked under
3717  $asset_field = is_array($fields) ? 'name' : $fields;
3718  $bridge_info = $this->getAssetInfo(Array($id_parts[0]), '', TRUE, $asset_field);
3719  $link_lineages = $this->getLinkLineages($id_parts[0]);
3720 
3721  // add the lineage of the actual bridge to the bridge's lineages
3722  for (reset($link_lineages); NULL !== ($key = key($link_lineages)); next($link_lineages)) {
3723  $link_lineages[$key]['lineage'][$id_parts[0]] = $bridge_info[$id_parts[0]];
3724  }
3725  $sql = 'SELECT
3726  l.majorid, '.$fields_str.'
3727  FROM
3728  '.SQ_TABLE_RUNNING_PREFIX.'shdw_ast_lnk l
3729  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON l.majorid = a.assetid';
3730 
3731  $where = 'l.minorid = '.MatrixDAL::quote($assetid);
3732  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
3733  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
3734 
3735  try {
3736  $result = NULL;
3737  $query = MatrixDAL::preparePdoQuery($sql.$where);
3738  $result = MatrixDAL::executePdoGrouped($query);
3739  } catch (DALException $e) {
3740  throw new Exception('Could not get shadow links of minor asset ID '.$assetid.' due to database error: '.$e->getMessage());
3741  }
3742 
3743  // get the lineage of the real assets that this shadow asset is linked under
3744  // and then append the lineage of that real asset to its lineages
3745  foreach ($result as $assetid => $name) {
3746  $lineages = $this->getLinkLineages($assetid);
3747  for (reset($lineages); NULL !== ($key = key($lineages)); next($lineages)) {
3748  $lineages[$key]['lineage'][$assetid] = $name;
3749  }
3750  $link_lineages = array_merge($link_lineages, $lineages);
3751  if ($result_limit != 0 && count($link_lineages) >= $result_limit) {
3752  break;
3753  }
3754  }
3755 
3756  if ($result_limit != 0 && count($link_lineages) > $result_limit) {
3757  $link_lineages = array_slice($link_lineages, 0, $result_limit);
3758  }
3759 
3760  // Clean up link_linkages Array as shadow assets cause a complex array return (as per Bug #2977)
3761  foreach ($link_lineages as $key => $value) {
3762  foreach ($value['lineage'] as $lineage_key => $lineage_value) {
3763  if (is_array($lineage_value)) {
3764  $link_lineages[$key]['lineage'][$lineage_key] = $lineage_value[0][0];
3765  }
3766  }
3767  }
3768 
3769  return $link_lineages;
3770 
3771  }//end if shadow asset
3772 
3773  // Bug Fix #3535 - Binocular icon disappears if NOTICE links overcrowd other links
3774  if ($only_significant) {
3775  $link_types = SQ_SC_LINK_SIGNIFICANT;
3776  } else {
3777  $link_types = SQ_SC_LINK_ALL;
3778  }//end if
3779 
3780  // we want to get all the parent links of this assetid and use these
3781  // as the minorid in the query, effectively getting the lineages of the
3782  // asset's immediate parents.
3783  $bind_vars = Array();
3784 
3785  $sub_sql = 'SELECT l.majorid FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l';
3786  $sub_where = 'l.minorid = :minorid';
3787  $sub_where .= ' and l.link_type < :link_types';
3788  $bind_vars['minorid'] = (string)$assetid;
3789  $bind_vars['link_types'] = $link_types;
3790  $sub_where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($sub_where, 'l');
3791  $sub_sql .= $sub_where;
3792 
3793  $sql = 'SELECT ct.treeid as our_treeid, cl.minorid, pt.treeid as parent_treeid, a.assetid, '.$fields_str.'
3794  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk cl
3795  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree ct ON cl.linkid = ct.linkid
3796  INNER JOIN ('.$sub_sql.') x ON cl.minorid = x.majorid,
3797  '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk pl
3798  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree pt ON pl.linkid = pt.linkid
3799  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON a.assetid = pl.minorid
3800  ';
3801 
3802  $where = 'ct.treeid LIKE pt.treeid || '.'\''.'%'.'\''.'
3803  AND pt.treeid <= ct.treeid';
3804 
3805  if (!is_null($from_treeid)) {
3806  if (is_array($from_treeid)) {
3807  $treeid_bits = Array();
3808  foreach ($from_treeid as $ftids) {
3809  $ftid = $ftids[0];
3810  $treeid_bits[] = '(pt.treeid LIKE '.'\''.($ftid.'%').'\''.')';
3811  }
3812  $where .= ' AND ('.implode(' OR ', $treeid_bits).')';
3813  } else {
3814  $where .= ' AND pt.treeid LIKE :from_treeid';
3815  $bind_vars['from_treeid'] = $from_treeid.'%';
3816  }
3817  }
3818 
3830  $in_sql = SQ_TABLE_RUNNING_PREFIX.'get_lineage_treeids(:assetid, :tree_size, :rb_date, :link_types)';
3831 
3832  $treeids = array();
3833  if (MatrixDAL::getDbType() === 'oci') {
3834  $treeids_query = "SELECT column_value AS treeid FROM TABLE(" . $in_sql . ")";
3835  } else {
3836  $treeids_query = "SELECT " . SQ_TABLE_RUNNING_PREFIX . "get_lineage_treeids AS treeid FROM " . $in_sql;
3837  }
3838  $treeid_bind_vars['assetid'] = (string) $assetid;
3839  $treeid_bind_vars['tree_size'] = (int) SQ_CONF_ASSET_TREE_SIZE;
3840  $treeid_bind_vars['rb_date'] = NULL;
3841  $treeid_bind_vars['link_types'] = (int) $link_types;
3842 
3843  try {
3844  $tree_query = MatrixDAL::preparePdoQuery($treeids_query);
3845  foreach ($treeid_bind_vars as $bind_var => $bind_value) {
3846  MatrixDAL::bindValueToPdo($tree_query, $bind_var, $bind_value);
3847  }
3848  $tree_result = MatrixDAL::executePdoAssoc($tree_query);
3849 
3850  } catch (Exception $e) {
3851  throw $e;
3852  }
3853 
3858  if (!empty($tree_result)) {
3859  foreach ($tree_result as $row => $info) {
3867  $quoted_treeid = MatrixDAL::quote($info['treeid']);
3868  if (!in_array($quoted_treeid, $treeids)) {
3869  $treeids[] = $quoted_treeid;
3870  }
3871  }
3872 
3873  // Break up the treeids into chunks of 1000 so that oracle does not complain
3874  $in_clauses = Array();
3875  foreach (array_chunk($treeids, 999) as $chunk) {
3876  $in_clauses[] = ' pt.treeid IN ('.implode(', ', $chunk).')';
3877  }
3878  $where .= ' AND ('.implode(' OR ', $in_clauses).')';
3879  unset($in_clauses);
3880  unset($treeids);
3881 
3882  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'cl');
3883  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'ct');
3884  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'pl');
3885  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'pt');
3886  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
3887 
3888  $sql .= $where.'
3889  ORDER BY cl.linkid, ct.treeid, pt.treeid';
3890 
3891  try {
3892  $query = MatrixDAL::preparePdoQuery($sql);
3893  foreach ($bind_vars as $bind_var => $bind_value) {
3894  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
3895  }
3896  $result = MatrixDAL::executePdoGroupedAssoc($query);
3897  } catch (Exception $e) {
3898  throw $e;
3899  }
3900  } else {
3905  $result = array();
3906  }
3907 
3908  $asset_links = $this->getLinks($assetid, $link_types, '', TRUE, 'minor');
3909 
3910  $link_info = Array();
3911  foreach ($asset_links as $link_data) {
3912  $link_info[$link_data['majorid']][] = $link_data;
3913  }
3914 
3915  $result_count = 0;
3916  foreach (array_values($result) as $link_tree) {
3917  // work out some values we are going to need
3918  $parent_data = $link_tree[count($link_tree) -1];
3919  $majorid = $parent_data['assetid'];
3920 
3921  if (!isset($link_info[$majorid])) continue;
3922 
3923  foreach ($link_info[$majorid] as $link_data) {
3924  $link_type = $link_data['link_type'];
3925  $linkid = $link_data['linkid'];
3926  $link_value = $link_data['value'];
3927 
3928  $asset_lineage = Array();
3929  foreach ($link_tree as $tree_data) {
3930  if ($tree_data['assetid'] == $majorid) continue;
3931  if (is_array($fields)) {
3932  $asset_lineage[$tree_data['assetid']] = Array();
3933  foreach ($fields as $field_name) {
3934  $asset_lineage[$tree_data['assetid']][$field_name] = $tree_data[$field_name];
3935  }
3936  } else {
3937  $asset_lineage[$tree_data['assetid']] = $tree_data[$fields];
3938  }
3939  }
3940 
3941  if (is_array($fields)) {
3942  $asset_lineage[$tree_data['assetid']] = Array();
3943  foreach ($fields as $field_name) {
3944  $asset_lineage[$parent_data['assetid']][$field_name] = $parent_data[$field_name];
3945  }
3946  } else {
3947  $asset_lineage[$parent_data['assetid']] = $parent_data[$fields];
3948  }
3949 
3950  $link_lineages[] = Array(
3951  'linkid' => $linkid,
3952  'link_type' => $link_type,
3953  'link_value'=> $link_value,
3954  'lineage' => $asset_lineage,
3955  );
3956  }//end foreach
3957 
3958  if ($result_limit != 0 && $result_count == $result_limit) {
3959  break;
3960  }
3961  $result_count++;
3962  }//end foreach result
3963 
3964  return $link_lineages;
3965 
3966  }//end getLinkLineages()
3967 
3968 
3999  function getParents($assetid, $type_code='', $strict_type_code=TRUE, $sort_by=NULL, $access=NULL, $effective=TRUE, $min_height=NULL, $max_height=NULL, $ignore_bridge=FALSE, $link_value_wanted=NULL, $bind_var_prefix='gp_')
4000  {
4001  // check if we are getting parents for a shadow asset, and palm the request off to the
4002  // handler of the shadow asset if we are
4003  $id_parts = explode(':', $assetid);
4004  if (isset($id_parts[1])) {
4005  $real_assetid = $id_parts[0];
4006  $asset = $this->getAsset($real_assetid);
4007 
4008  $parents = Array();
4009  if (!$ignore_bridge) {
4010  $parents = $asset->getParents($assetid, $type_code, $strict_type_code);
4011  }
4012 
4013  $bind_vars = array();
4014  $db = MatrixDAL::getDb();
4015  $sql = 'SELECT
4016  sl.majorid
4017  FROM
4018  '.SQ_TABLE_RUNNING_PREFIX.'shdw_ast_lnk sl';
4019 
4020  $where = 'sl.minorid = :shadow_assetid AND sl.link_type != :link_notice';
4021  $bind_vars['shadow_assetid'] = $assetid;
4022  $bind_vars['link_notice'] = SQ_LINK_NOTICE;
4023 
4024  if (!empty($type_code)) {
4025  $sql .= ' INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a
4026  ON sl.majorid = a.assetid ';
4027  $type_codes_quoted = Array();
4028  $all_type_codes = is_array($type_code) ? $type_code : Array($type_code);
4029  foreach ($all_type_codes as $tc) {
4030  $type_codes_quoted[] = MatrixDAL::quote($tc);
4031  }
4032  if (!$strict_type_code) {
4033  foreach ($all_type_codes as $tc) {
4034  $ancestors = $this->getTypeDescendants($tc);
4035  foreach ($ancestors as $ancestor) {
4036  $type_codes_quoted[] = MatrixDAL::quote($ancestor);
4037  }
4038  }
4039  }
4040  if (count($type_codes_quoted) == 1) {
4041  $where .= ' AND a.type_code = '.reset($type_codes_quoted);
4042  } else {
4043  $where .= ' AND a.type_code IN ('.implode(',', $type_codes_quoted).')';
4044  }
4045 
4046  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
4047  }
4048 
4049  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'sl');
4050 
4051  $query = MatrixDAL::preparePdoQuery($sql.$where);
4052  foreach ($bind_vars as $bind_var => $bind_value) {
4053  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
4054  }
4055 
4056  try {
4057  $shdw_parentids = MatrixDAL::executePdoAssoc($query, 0);
4058  } catch (Exception $e) {
4059  throw new Exception('Unable to get parents from due to the following database error:'.$e->getMessage());
4060  }//end try catch
4061 
4062  $shdw_parents = $this->getAssetInfo($shdw_parentids, Array(), TRUE, 'type_code');
4063 
4064  $real_parents = Array();
4065  // we also need the parents of the majorid from the shadow asset link table
4066  foreach ($shdw_parentids as $parentid) {
4067  $real_parents += $this->getParents($parentid, $type_code, $strict_type_code, $sort_by, $access);
4068  }
4069 
4070  $parents = $parents + $shdw_parents + $real_parents;
4071 
4072  $this->forgetAsset($asset);
4073  return $parents;
4074  }//end if shadow asset
4075 
4076  // if value is not null and not array, convert it to array
4077  if (!is_null($link_value_wanted) && !is_array($link_value_wanted)) {
4078  $link_value_wanted = Array(
4079  'link_value' => Array($link_value_wanted),
4080  'equal' => TRUE,
4081  );
4082  }
4083 
4084  $ret_val = $this->generateGetParentsQuery($assetid, $type_code, $strict_type_code, $sort_by, $access, $effective, $min_height, $max_height, $link_value_wanted);
4085  if (empty($ret_val)) return Array();
4086 
4087  $query = MatrixDAL::preparePdoQuery(implode(' ', $ret_val['sql_array']));
4088  foreach ($ret_val['bind_vars'] as $bind_var => $bind_value) {
4089  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
4090  }
4091 
4092  $result = MatrixDAL::executePdoGroupedAssoc($query);
4093  $parents = Array();
4094  foreach ($result as $assetid => $row) {
4095  $parents[$assetid] = $row[0]['type_code'];
4096  }
4097  unset($result);
4098 
4099  return $parents;
4100 
4101  }//end getParents()
4102 
4103 
4146  function getChildren($assetid, $type_code='', $strict_type_code=TRUE, $dependant=NULL, $sort_by=NULL, $access=NULL, $effective=TRUE, $min_depth=NULL, $max_depth=NULL, $direct_shadows_only=TRUE, $link_value_wanted=NULL, Array $link_types_wanted=Array(), $expand_shadows=FALSE)
4147  {
4148  assert_valid_assetid($assetid);
4149  // check if we are getting children for a shadow asset, and palm the request off to the
4150  // handler of the shadow asset if we are
4151  $id_parts = explode(':', $assetid);
4152  if (isset($id_parts[1])) {
4153  $children = Array();
4154  $real_assetid = $id_parts[0];
4155  $asset = $this->getAsset($real_assetid);
4156  if (!is_null($asset)) {
4157  if (!method_exists($asset, 'getChildren')) {
4158  trigger_localised_error('SYS0204', E_USER_WARNING, $asset->name);
4159  } else {
4160  $children = $asset->getChildren($assetid, $type_code, $strict_type_code, $dependant, $sort_by);
4161  }
4162  $this->forgetAsset($asset);
4163  } else {
4164  trigger_localised_error('SYS0206', E_USER_WARNING, $real_assetid);
4165  }
4166  return $children;
4167  }
4168  // we are not getting children for a shadow asset, so if we are getting children of
4169  // an asset that handles shadow assets, palm the request off to it
4170  $asset = $this->getAsset($assetid);
4171  if (is_null($asset)) return Array();
4172  if (implements_interface($asset, 'bridge')) {
4173  $children = $asset->getChildren($assetid, $type_code, $strict_type_code, $dependant, $sort_by);
4174  $this->forgetAsset($asset);
4175  return $children;
4176  }
4177 
4178  // if value is not null and not array, convert it to array
4179  if (!is_null($link_value_wanted) && !is_array($link_value_wanted)) {
4180  $link_value_wanted = Array(
4181  'link_value' => Array($link_value_wanted),
4182  'equal' => TRUE,
4183  );
4184  }
4185 
4186  $ret_val = $this->generateGetChildrenQuery($asset, $type_code, $strict_type_code, $dependant, $sort_by, $access, $effective, TRUE, $min_depth, $max_depth, $direct_shadows_only, $link_value_wanted, 'gc_', $link_types_wanted);
4187 
4188  $this->forgetAsset($asset);
4189  unset($asset);
4190 
4191  if (empty($ret_val)) return Array();
4192  try {
4193  $sql_array = $ret_val['sql_array'];
4194  $bind_vars = $ret_val['bind_vars'];
4195  $query = MatrixDAL::preparePdoQuery(implode(' ', $sql_array));
4196  foreach ($bind_vars as $bind_var => $bind_value) {
4197  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
4198  }
4199  $result = MatrixDAL::executePdoGroupedAssoc($query);
4200  } catch (Exception $e) {
4201  throw new Exception('Unable to get children for asset: '.$assetid.' due to database error: '.$e->getMessage());
4202  }
4203 
4204  if ($expand_shadows){
4205  foreach (array_keys($result) as $child_id){
4206  $id_parts = explode(':', $child_id);
4207  if (isset($id_parts[1])) {
4208  $children = $this->getChildren($child_id, $type_code, $strict_type_code, $dependant, $sort_by);
4209  $result += $children;
4210  }
4211  }
4212  }
4213 
4214  if (!is_null($sort_by)) {
4215  $children = Array();
4216  foreach ($result as $assetid => $asset_info) {
4217  $children[$assetid] = $asset_info[0]['type_code'];
4218  }
4219  return $children;
4220  } else {
4221  return $result;
4222  }
4223 
4224  }//end getChildren()
4225 
4226 
4243  function getDependantChildren($assetid, $type_code='', $strict_type_code=TRUE)
4244  {
4245  $children = Array();
4246 
4247  $asset = $this->getAsset($assetid);
4248  if (!is_null($asset)) {
4249 
4250  $dependant_links = $this->getLinks($asset->id, SQ_SC_LINK_SIGNIFICANT, $type_code, $strict_type_code, 'major', NULL, 1);
4251  for (reset($dependant_links); NULL !== ($k = key($dependant_links)); next($dependant_links)) {
4252  if (!$dependant_links[$k]['is_dependant']) continue;
4253  $children[$dependant_links[$k]['minorid']] = Array (
4254  0 => Array (
4255  'type_code' => $dependant_links[$k]['minor_type_code'],
4256  ),
4257  );
4258  }
4259 
4260  // get ALL kids, regardless of type code, so we can chase them down
4261  // for the type code we want
4262  $all_links = $this->getLinks($asset->id, SQ_SC_LINK_SIGNIFICANT, '', FALSE, 'major', NULL, 1);
4263  for (reset($all_links); NULL !== ($k = key($all_links)); next($all_links)) {
4264  // must still be dependent
4265  if (!$all_links[$k]['is_dependant']) continue;
4266  $children = $children + $this->getDependantChildren($all_links[$k]['minorid'],$type_code, $strict_type_code);
4267  }
4268 
4269  $this->forgetAsset($asset);
4270  }
4271 
4272  unset($asset);
4273  return $children;
4274 
4275  }//end getDependantChildren()
4276 
4277 
4295  function getDependantParents($assetid, $type_code='', $strict_type_code=TRUE, $include_all_dependants=TRUE)
4296  {
4297  $parents = Array();
4298 
4299  $asset = $this->getAsset($assetid);
4300  if (!is_null($asset)) {
4301 
4302  $dependant_links = $this->getLinks($asset->id, SQ_SC_LINK_SIGNIFICANT, $type_code, $strict_type_code, 'minor', NULL, 1);
4303  if (empty($dependant_links) && !$include_all_dependants) {
4304  $parents[] = $assetid;
4305  }
4306  for (reset($dependant_links); NULL !== ($k = key($dependant_links)); next($dependant_links)) {
4307  $parentid = $dependant_links[$k]['majorid'];
4308  if ($include_all_dependants) $parents[] = $parentid;
4309  $parents = array_merge($parents, $this->getDependantParents($parentid, $type_code, $strict_type_code, $include_all_dependants));
4310  }
4311 
4312  $this->forgetAsset($asset);
4313  }
4314 
4315  unset($asset);
4316  return $parents;
4317 
4318  }//end getDependantParents()
4319 
4320 
4360  function getAssetTree($majorids, $levels=NULL, $exclude_list=Array(), $link_type=SQ_SC_LINK_FRONTEND_NAV, $include_type_list=Array(), $include_dependants=TRUE)
4361  {
4362  $tree_data = Array();
4363  if (!is_array($majorids)) $majorids = Array($majorids);
4364  $this->_getAssetTree($majorids, $tree_data, $levels, $exclude_list, $link_type, $include_type_list, $include_dependants);
4365  return $tree_data;
4366 
4367  }//end getAssetTree()
4368 
4369 
4396  function _getAssetTree($majorids, &$tree_data, $levels, $exclude_list, $link_type, $include_type_list, $include_dependants)
4397  {
4398  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
4399 
4400  // Stuff that is only needed once per design file
4401  $db = MatrixDAL::getDb();
4402 
4403  $assetids = Array();
4404  $majorids_str = '';
4405 
4406  foreach ($majorids as $id) {
4407  if (empty($tree_data[$id])) {
4408  $majorids_str .= MatrixDAL::quote((string) $id).',';
4409  } else {
4410  foreach ($tree_data[$id] as $row) {
4411  $assetids[] = (int) $row['assetid'];
4412  }
4413  }
4414  }
4415 
4416  // remove the last reailing comma from the string
4417  $majorids_str = rtrim($majorids_str, ',');
4418 
4419  if (empty($majorids_str)) return $assetids;
4420 
4421  $exclude_list_before_quote = $exclude_list;
4422  for (reset($exclude_list); NULL !== ($k = key($exclude_list)); next($exclude_list)) {
4423  $exclude_list[$k] = MatrixDAL::quote((string) $exclude_list[$k]);
4424  }
4425 
4426  $exclude_str = implode(',', $exclude_list);
4427 
4428  static $USERIDS_COND = NULL;
4429 
4430  if (is_null($USERIDS_COND)) {
4431  if ($GLOBALS['SQ_SYSTEM']->userRoot() || $GLOBALS['SQ_SYSTEM']->userSystemAdmin()) {
4432  $USERIDS_COND = '';
4433  } else {
4434  $current_user = $this->getAsset($GLOBALS['SQ_SYSTEM']->user->id);
4435  $userids = $current_user->getUserGroups();
4436  $this->forgetAsset($current_user);
4437  $userids[] = $GLOBALS['SQ_SYSTEM']->user->id;
4438  $userids[] = $this->getSystemAssetid('public_user');
4439  for (reset($userids); NULL !== ($i = key($userids)); next($userids)) {
4440  $userids[$i] = MatrixDAL::quote((string)$userids[$i]);
4441  }
4442  $USERIDS_COND = 'AND p.userid IN ('.implode(',', $userids).')';
4443  }
4444 
4445  if (!empty($USERIDS_COND)) {
4446  $USERIDS_COND .= '
4447  GROUP BY a.assetid, l.majorid, a.type_code, a.status, a.name, a.short_name, pt.path, l.sort_order, p.granted
4448  HAVING MIN(p.granted) = \'1\'';
4449  }
4450  }
4451 
4452 
4453  $include_types_str = '';
4454  if (isset($include_type_list['type_code'])) {
4455  $include_types = Array();
4456  $include_inherit = Array();
4457 
4458  if (isset($include_type_list['inherit'])) {
4459  $include_inherit = $include_type_list['inherit'];
4460  }
4461 
4462  foreach ($include_type_list['type_code'] as $include_type) {
4463  if (!empty($include_type)) {
4464  if (isset($include_inherit) && !empty($include_inherit) && array_shift($include_inherit)) {
4465  $include_types = array_merge($this->getTypeDescendants($include_type, TRUE), $include_types);
4466  } else {
4467  $include_types[] = $include_type;
4468  }
4469  }
4470  }
4471 
4472  if (!empty($include_types)) {
4473  $include_types_str = '\''.implode('\',\'', $include_types).'\'';
4474  }
4475  }
4476 
4477 
4478  $sql = 'SELECT DISTINCT a.assetid, l.majorid, a.type_code, a.status, a.name, a.short_name, pt.path, l.sort_order
4479  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast a
4480  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l ON a.assetid = l.minorid
4481  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_path pt ON a.assetid = pt.assetid ';
4482  if (!empty($USERIDS_COND)) {
4483  $sql .= '
4484  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'vw_ast_perm p ON a.assetid = p.assetid ';
4485  }
4486 
4487  $majorids_array = explode(',', $majorids_str);
4488  $in_clause = Array();
4489 
4490  foreach (array_chunk($majorids_array, 999) as $chunk) {
4491  $in_clause[] = ' (l.majorid IN ('.implode(', ', $chunk).'))';
4492  }
4493 
4494 
4495  $where = ' ('.implode(' OR ',$in_clause).') AND ('.db_extras_bitand(MatrixDAL::getDbType(), 'l.link_type', $link_type).' > 0) ';
4496 
4497  if (!empty($exclude_list)) {
4498  $where .= 'AND a.assetid NOT IN ('.$exclude_str.')';
4499  }
4500 
4501  if (!empty($include_types_str)) {
4502  $where .= ' AND a.type_code IN ('.$include_types_str.')';
4503  }
4504 
4505  if (!$include_dependants) {
4506  $where .= ' AND l.is_dependant = \'0\'';
4507  }
4508 
4509  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
4510  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
4511  if (!empty($USERIDS_COND)) {
4512  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'p');
4513  }
4514  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'pt');
4515  $where .= ' '.$USERIDS_COND.'
4516  ORDER BY l.majorid, l.sort_order';
4517 
4518  try {
4519  $result = MatrixDAL::executeSqlAssoc($sql.$where);
4520  } catch (Exception $e) {
4521  throw new Exception('Unable to get order of assets as they propogate from passed majorid(s): '.array_contents($majorids).' due to database error: '.$e->getMessage());
4522  }
4523 
4524 
4525  $assetids = Array();
4526  foreach ($result as $row) {
4527  if (!($row['status'] & (SQ_STATUS_LIVE | SQ_STATUS_LIVE_APPROVAL))) {
4528  $asset = $this->getAsset($row['assetid']);
4529  $read_access = $asset->readAccess();
4530 
4531  $row['name'] = $asset->name;
4532  $row['short_name'] = $asset->short_name;
4533 
4534  if ($row['status'] & SQ_SC_STATUS_NOT_LIVE) {
4535  // somewhere between under construction and live so we show this by altering the name
4536  $row['name'] = '(( '.$row['name'].' ))';
4537  $row['short_name'] = '(( '.$row['short_name'].' ))';
4538  }
4539  $this->forgetAsset($asset);
4540  unset($asset);
4541  if (!$read_access) continue;
4542  }
4543  $assetids[] = (int) $row['assetid'];
4544  if (!isset($tree_data[$row['majorid']])) {
4545  $tree_data[$row['majorid']] = Array();
4546  }
4547  $tree_data[$row['majorid']][$row['assetid']] = $row;
4548 
4549  }
4550 
4551  if (!empty($assetids) && ($levels > 1 || is_null($levels))) {
4552  $levels--;
4553  $this->_getAssetTree($assetids, $tree_data, $levels, $exclude_list_before_quote, $link_type, $include_type_list, $include_dependants);
4554  }
4555  return TRUE;
4556 
4557  }//end _getAssetTree()
4558 
4559 
4573  function getAssetTreeids($assetid, $link_type=NULL)
4574  {
4575  $db = MatrixDAL::getDb();
4576  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
4577  if (is_array($assetid)) {
4578  $assetids_set = Array();
4579  foreach ($assetid as $this_assetid) {
4580  assert_valid_assetid($this_assetid);
4581  $assetids_set[] = MatrixDAL::quote($this_assetid);
4582  }
4583  } else {
4584  assert_valid_assetid($assetid);
4585  }
4586 
4587  if (empty($link_type)) {
4588  $link_type = SQ_SC_LINK_SIGNIFICANT;
4589  }
4590 
4591  if (is_array($assetid)) {
4592  $sql = 'SELECT l.minorid, t.treeid
4593  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l
4594  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t ON l.linkid = t.linkid';
4595  $where = 'WHERE l.minorid IN ('.implode(', ', $assetids_set).')';
4596  } else {
4597  $sql = 'SELECT t.treeid
4598  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l
4599  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t ON l.linkid = t.linkid';
4600  $where = 'WHERE l.minorid = '.MatrixDAL::quote($assetid);
4601  }
4602 
4603  $where .= ' AND '.db_extras_bitand(MatrixDAL::getDbType(), 'l.link_type', $link_type).' > 0';
4604 
4605  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
4606  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
4607 
4608  try {
4609  $query = MatrixDAL::preparePdoQuery($sql.' '.$where);
4610  if (is_array($assetid)) {
4611  $result = MatrixDAL::executePdoGrouped($query);
4612  } else {
4613  $result = MatrixDAL::executePdoAssoc($query, 0);
4614  }
4615  } catch (Exception $e) {
4616  throw new Exception('Unable to get tree ID for asset: '.$e->getMessage());
4617  }
4618 
4619  return $result;
4620 
4621  }//end getAssetTreeids()
4622 
4623 
4634  function getLinkTreeid($linkid)
4635  {
4636  if (!is_array($linkid)) {
4637  $linkid = Array($linkid);
4638  }
4639 
4640  $db = MatrixDAL::getDb();
4641  try {
4642  $bind_vars['linkid'] = $linkid;
4643  $result = MatrixDAL::executeGrouped('core', 'getLinkTreeid', $bind_vars);
4644  } catch (Exception $e) {
4645  throw new Exception('Failed to get link tree id: '.$e->getMessage());
4646  }
4647 
4648  return $result;
4649 
4650  }//end getLinkTreeid()
4651 
4652 
4667  function assetInTrash($assetid, $exclusively=FALSE)
4668  {
4669  if (FALSE !== strpos($assetid, ':')) {
4670  return $this->assetInTrash(strtok($assetid, ':'), $exclusively);
4671  }
4672  $trash = $this->getSystemAsset('trash_folder');
4673 
4674  // if we are being asked "Is the trash in the trash"
4675  // lets say "Are you kidding?"
4676  if ($assetid == $trash->id) return FALSE;
4677 
4678  $db = MatrixDAL::getDb();
4679 
4680  // we need to work out the treeid of the trash
4681  $sub_sql = 'SELECT (t.treeid || \'%\')
4682  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t
4683  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l ON t.linkid = l.linkid ';
4684  $sub_where = 'l.minorid = :trashid';
4685  $sub_where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($sub_where, 't');
4686  $sub_where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($sub_where, 'l');
4687  $sub_sql .= $sub_where;
4688  $sub_sql = db_extras_modify_limit_clause($sub_sql, MatrixDAL::getDbType(), 1);
4689 
4690  $sql = 'SELECT COUNT(*)
4691  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk_tree t INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l ON t.linkid = l.linkid';
4692  $where = 'l.minorid = :assetid
4693  AND t.treeid '.(($exclusively) ? 'NOT ' : '').'LIKE ('.$sub_sql.')';
4694  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 't');
4695  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
4696  $query = MatrixDAL::preparePdoQuery($sql.$where);
4697  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
4698  MatrixDAL::bindValueToPdo($query, 'trashid', $trash->id);
4699  $result = MatrixDAL::executePdoOne($query);
4700 
4701  if ($exclusively) {
4702  return ($result == 0);
4703  } else {
4704  return ($result > 0);
4705  }
4706 
4707  }//end assetInTrash()
4708 
4709 
4731  function countLinks($assetid, $side_of_link='major', $link_types=0, $type_code='', $strict_type_code=TRUE, $ignore_linkid=0)
4732  {
4733  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
4734 
4735  assert_valid_assetid($assetid);
4736  assert_false($side_of_link != 'major' && $side_of_link != 'minor', 'Unknown Side of Link "'.$side_of_link.'"');
4737 
4738  $id_parts = explode(':', $assetid);
4739 
4740  // shadow asset found, pipe the request off to the bridge
4741  if (isset($id_parts[1])) {
4742  $real_assetid = $id_parts[0];
4743  $asset = $this->getAsset($real_assetid);
4744  $results = $asset->countLinks($assetid, $side_of_link, $link_types, $type_code, $strict_type_code, $ignore_linkid);
4745 
4746  $this->forgetAsset($asset);
4747  return $results;
4748  }
4749 
4750  $bind_vars = Array();
4751 
4752  $extra_table = '';
4753  $where = 'l.'.$side_of_link.'id = :assetid';
4754  $bind_vars['assetid'] = $assetid;
4755  if ($link_types) {
4756  $where .= ' AND '.db_extras_bitand(MatrixDAL::getDbType(), 'link_type', $link_types).' > 0';
4757  }
4758  if ($type_code) {
4759  $extra_table .= ', '.SQ_TABLE_RUNNING_PREFIX.'ast a';
4760  $where .= ' AND l.minorid = a.assetid ';
4761  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
4762 
4763  $type_code_cond = '';
4764  if (is_array($type_code)) {
4765  for ($i = 0; $i < count($type_code); $i++) {
4766  $type_code[$i] = MatrixDAL::quote($type_code[$i]);
4767  }
4768  $type_code_cond = 'IN ('.implode(', ', $type_code).')';
4769  } else {
4770  $type_code_cond = '= '.MatrixDAL::quote($type_code);
4771  }
4772 
4773  if ($strict_type_code) {
4774  $where .= ' AND a.type_code '.$type_code_cond;
4775  } else {
4776  $where .= ' AND a.type_code IN (
4777  SELECT type_code
4778  FROM sq_ast_typ_inhd
4779  WHERE inhd_type_code '.$type_code_cond.'
4780  )';
4781  }
4782  }//end if
4783 
4784  if ($ignore_linkid) {
4785  $where .= ' AND l.linkid <> :ignore_linkid';
4786  $bind_vars['ignore_linkid'] = $ignore_linkid;
4787  }
4788 
4789  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
4790  $sql = 'SELECT COUNT(*)
4791  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk l '.$extra_table.'
4792  '.$where;
4793 
4794  try {
4795  $query = MatrixDAL::preparePdoQuery($sql);
4796  foreach ($bind_vars as $bind_var => $bind_value) {
4797  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
4798  }
4799  $result = MatrixDAL::executePdoOne($query);
4800  } catch (DALException $e) {
4801  throw new Exception ('Unable to count '.$link_type.' links from asset ID #'.$assetid.' due to database error: '.$e->getMessage());
4802  }
4803 
4804  return $result;
4805 
4806  }//end countLinks()
4807 
4808 
4828  function createAssetLink(Asset $major, Asset $minor, $link_type, $value='', $sort_order=NULL, $dependant='0', $exclusive='0', $moving=FALSE, $locked='0')
4829  {
4830  // Initial Checks
4831  if (!$major->id) return 0;
4832  assert_is_a($minor, 'asset');
4833  if (!is_null($sort_order)) {
4834  $sort_order = (int) $sort_order;
4835  }
4836 
4837  assert_false(!($link_type & SQ_SC_LINK_SIGNIFICANT) && $dependant, 'In order for a link to be dependant it must also be a significant link');
4838  assert_false(!($link_type & SQ_SC_LINK_SIGNIFICANT) && ($locked == '1'), 'In order for a link to be locked it must also be a significant link');
4839 
4840  $original_link_type = $link_type;
4841  $link_type = (int) $link_type;
4842  if ($link_type != $original_link_type) {
4843  trigger_localised_error('SYS0241', E_USER_WARNING);
4844  return 0;
4845  }
4846 
4847  // Handle shadow assets
4848  $majorid_parts = explode(':', $major->id);
4849  $minorid_parts = explode(':', $minor->id);
4850  if (isset($minorid_parts[1])) {
4851  // minor is a shadow
4852  if (isset($majorid_parts[1]) || ($majorid_parts[0] == $minorid_parts[0])) {
4853  // major is a shadow or the minor's bridge, so get the bridge to handle the linking
4854  $bridge = $this->getAsset($majorid_parts[0]);
4855  $linkid = $bridge->createAssetLink($major, $minor, $link_type, $value, $sort_order, $dependant, $exclusive, $moving);
4856  $this->forgetAsset($bridge);
4857  } else {
4858  // we are linking a shadow asset under a normal asset
4859  $linkid = $this->createShadowAssetLink($major, $minor, $link_type, $value);
4860  }
4861  return $linkid;
4862  }//end if shadow asset
4863 
4864 
4865  // Use Master DB since we are going to change stuff
4866  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
4867  $db = MatrixDAL::getDb();
4868  $is_oci = (MatrixDAL::getDbType() === 'oci') ? TRUE : FALSE;
4869 
4870  // Can't link to the new parent if it is only in the trash
4871  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LINK_INTEGRITY)) {
4872  if ($this->assetInTrash($major->id, TRUE)) {
4873  trigger_localised_error('SYS0223', E_USER_WARNING, $major->id);
4874  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
4875  return 0;
4876  }
4877  }
4878 
4879  // prepare the link - do the minor first because the major gets the final say in linking
4880  $minor->prepareLink($major, 'minor', $link_type, $value, $sort_order, $dependant, $exclusive);
4881  $major->prepareLink($minor, 'major', $link_type, $value, $sort_order, $dependant, $exclusive);
4882 
4883  // First, we should check that we don't already have a link of this type
4884  $current_link = $this->getLinkByAsset($major->id, $minor->id, $link_type, $value);
4885  if (empty($current_link) === FALSE) {
4886  // The link we are creating already exists, so throw an error
4887  // unless the major is the trash, in which case we just pretend we linked ok
4888  $trash_folder_id = $this->getSystemAssetid('trash_folder');
4889  if ($trash_folder_id != $major->id) {
4890  trigger_localised_error('SYS0192', E_USER_WARNING, "$major->name (#$major->id)", "$minor->name (#$minor->id)");
4891  }
4892  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
4893  return $current_link['linkid'];
4894  }
4895 
4896  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LINK_INTEGRITY)) {
4897  // Check if we are allowed to link to these type of assets - if we
4898  // are moving though, we need a different check which is done in moveLink()
4899  if (!$moving) {
4900  if (($err_msg = $major->canCreateLink($minor, $link_type, $exclusive)) !== TRUE) {
4901  trigger_localised_error('SYS0301', E_USER_WARNING, $err_msg);
4902  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
4903  return 0;
4904  }
4905  }
4906  }
4907 
4908  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
4909  // Acquire lock if necessary, but remember its previous state so we can leave it as we found it
4910  $lock_info = @$this->getLockInfo($major->id, 'links');
4911  $parent_was_locked = !empty($lock_info);
4912  if (!$this->acquireLock($major->id, 'links')) {
4913  trigger_localised_error('CORE0012', E_USER_WARNING, $major->name);
4914  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
4915  return 0;
4916  }
4917  }
4918 
4919  // Change to DB3 to deal with the link tables
4920  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db3');
4921  $db = MatrixDAL::getDB();
4922  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
4923 
4924  if ($link_type & SQ_SC_LINK_SIGNIFICANT) {
4925 
4926  // This is a significant link
4927 
4928  // Check we aren't moving the minor asset under itself
4929  $sql = 'SELECT
4930  COUNT(DISTINCT ct.linkid)
4931  FROM sq_ast_lnk_tree pt,
4932  sq_ast_lnk_tree ct INNER JOIN sq_ast_lnk cl ON ct.linkid = cl.linkid
4933  WHERE
4934  ct.treeid LIKE (pt.treeid || \'%\')
4935  AND ct.treeid >= pt.treeid
4936  AND pt.linkid IN (SELECT linkid
4937  FROM sq_ast_lnk
4938  WHERE minorid = :minorid)
4939  AND cl.minorid = :majorid';
4940 
4941  try {
4942  $query = MatrixDAL::preparePdoQuery($sql);
4943  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
4944  MatrixDAL::bindValueToPdo($query, 'minorid', $minor->id);
4945  $moving_under = MatrixDAL::executePdoOne($query);
4946  } catch (Exception $e) {
4947  throw new Exception('Unable to determine if minor asset "'.$minor->name.'" (#'.$minor->id.') in new link is being linked underneath itself, due to database error: '.$e->getMessage());
4948  }
4949 
4950  if ($moving_under) {
4951  trigger_localised_error('CORE0114', E_USER_WARNING, $minor->name, $minor->id, $major->name, $major->id);
4952  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); //end db3
4953  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); //end db2
4954  return 0;
4955  }
4956 
4957  // Check that the major is linked somewhere
4958  try {
4959  $bind_vars = Array(
4960  'minorid' => $major->id,
4961  );
4962  $existing_treeid = MatrixDAL::executeOne('core', 'getTreeIdsByMinorid', $bind_vars);
4963  } catch (Exception $e) {
4964  throw new Exception('Unable to determine if major asset "'.$major->name.'" (#'.$major->id.') in new link is linked somewhere else, due to database error: '.$e->getMessage());
4965  }
4966 
4967  $existing_treeid = (string) $existing_treeid;
4968 
4969  // if we aren't linked anywhere we can't be linked to, sorry
4970  // NOTE: exception to rule is root folder
4971  if ((string) $existing_treeid == '-' && get_class($major) != 'Root_Folder') {
4972  trigger_localised_error('SYS0240', E_USER_WARNING, $major->name, $major->id);
4973  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); //end db3
4974  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); //end db2
4975  return 0;
4976  }
4977 
4978  }//end if significant link
4979 
4980  // Make sure the sort order is in a valid range
4981  $sql = 'SELECT
4982  COUNT(*) as count, MAX(sort_order) as max
4983  FROM
4984  sq_ast_lnk
4985  WHERE
4986  majorid = :majorid';
4987 
4988  try {
4989  $query = MatrixDAL::preparePdoQuery($sql);
4990  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
4991  $result = MatrixDAL::executePdoAll($query);
4992  $row = $result[0];
4993  unset($result);
4994  } catch (Exception $e) {
4995  throw new Exception('Unable to determine sort order range for new link between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
4996  }
4997 
4998  $max = ($row['count'] > 0) ? (int) $row['max'] + 1 : 0;
4999  if (is_null($sort_order) || (int) $sort_order > (int) $max || (int) $sort_order < 0) {
5000  $sort_order = (int) $max;
5001  }
5002 
5003  // Get Link ID
5004  $linkid = MatrixDAL::executeOne('core', 'seqNextVal', Array('seqName' => 'sq_ast_lnk_seq'));
5005 
5006  // Update sort order for other children of the new parent
5007  $sql = 'UPDATE
5008  sq_ast_lnk
5009  SET
5010  sort_order = sort_order + 1
5011  WHERE
5012  majorid = :majorid
5013  AND sort_order >= :sort_order';
5014 
5015  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5016 
5017  try {
5018  $query = MatrixDAL::preparePdoQuery($sql);
5019  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
5020  MatrixDAL::bindValueToPdo($query, 'sort_order', $sort_order);
5021  MatrixDAL::execPdoQuery($query);
5022  } catch (Exception $e) {
5023  throw new Exception('Unable to shift sort orders of children of "'.$major->name.'" (#'.$major->id.') up one, due to database error: '.$e->getMessage());
5024  }
5025 
5026  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5027 
5028  // We insert into the link table first to preserve referential integrity
5029  $sql = 'INSERT INTO
5030  sq_ast_lnk
5031  (
5032  linkid,
5033  majorid,
5034  minorid,
5035  link_type,
5036  value,
5037  sort_order,
5038  is_dependant,
5039  is_exclusive,
5040  updated,
5041  updated_userid,
5042  locked
5043  )
5044  VALUES
5045  (
5046  :linkid,
5047  :majorid,
5048  :minorid,
5049  :link_type,
5050  :value,
5051  :sort_order,
5052  :is_dependant,
5053  :is_exclusive,
5054  :updated,
5055  :updated_userid,
5056  :link_locked
5057  )';
5058 
5059  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5060 
5061  try {
5062  $query = MatrixDAL::preparePdoQuery($sql);
5063  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5064  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
5065  MatrixDAL::bindValueToPdo($query, 'minorid', $minor->id);
5066  MatrixDAL::bindValueToPdo($query, 'link_type', $link_type);
5067  MatrixDAL::bindValueToPdo($query, 'value', $value);
5068  MatrixDAL::bindValueToPdo($query, 'sort_order', $sort_order);
5069  MatrixDAL::bindValueToPdo($query, 'is_dependant', (($dependant) ? '1' : '0'));
5070  MatrixDAL::bindValueToPdo($query, 'is_exclusive', (($exclusive) ? '1' : '0'));
5071  MatrixDAL::bindValueToPdo($query, 'updated', ts_iso8601(time()));
5072  MatrixDAL::bindValueToPdo($query, 'updated_userid', $GLOBALS['SQ_SYSTEM']->currentUserId());
5073  MatrixDAL::bindValueToPdo($query, 'link_locked', (($locked == '1' && $link_type <= 2) ? '1' : '0'));
5074  MatrixDAL::execPdoQuery($query);
5075  } catch (Exception $e) {
5076  throw new Exception('Unable to add new link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5077  }
5078 
5079  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5080 
5081  if ($link_type & SQ_SC_LINK_SIGNIFICANT) {
5082  // This is a significant link, so do stuff with the tree
5083  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5084 
5085  // OK, what we are going to do is get a treeid and then do a "INSERT INTO ... SELECT FROM"
5086  // into the tree table of all the links that are under the minor asset
5087  $sql = 'SELECT
5088  t.treeid, t.num_kids
5089  FROM
5090  sq_ast_lnk_tree t
5091  INNER JOIN sq_ast_lnk l ON t.linkid = l.linkid
5092  WHERE
5093  l.minorid = :minorid';
5094 
5095  try {
5096  $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), 1);
5097  $query = MatrixDAL::preparePdoQuery($sql);
5098  MatrixDAL::bindValueToPdo($query, 'minorid', $minor->id);
5099  $result = MatrixDAL::executePdoAll($query);
5100  if (empty($result)) {
5101  $minor_tree = Array();
5102  } else {
5103  $minor_tree = $result[0];
5104  }
5105  unset($result);
5106  } catch (Exception $e) {
5107  throw new Exception('Unable to get existing child tree information of "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5108  }
5109 
5110  if (empty($minor_tree)) {
5111  $minor_tree = Array('treeid' => '', 'num_kids' => 0);
5112  } else {
5113  $minor_tree['treeid'] = (string) $minor_tree['treeid'];
5114  $minor_tree['num_kids'] = (int) $minor_tree['num_kids'];
5115  }
5116 
5117  $i = 0;
5118  $requires_update = FALSE;
5119 
5120  // Yes, this is a do-while loop. Live with it. The following code is to stop
5121  // constraint violations which may occur if two processes are trying to
5122  // create a link under the same branch, simultaneously
5123  do {
5124  // try to find the last added treeid on the branch that existing_treeid is the parent of
5125  $sql = 'SELECT
5126  SUBSTR(ct.treeid, (LENGTH(ct.treeid) + 1) - '.SQ_CONF_ASSET_TREE_SIZE.') AS treeid
5127  FROM
5128  sq_ast_lnk_tree ct';
5129 
5130  if ($existing_treeid != '-') {
5131 
5132  $sql .= ' WHERE
5133  ct.treeid LIKE :existing_treeid_wildcard';
5134  if (!empty($existing_treeid)) {
5135  $sql .= ' AND ct.treeid > :existing_treeid';
5136  }
5137  $sql .= ' AND LENGTH(ct.treeid) = :length';
5138  } else {
5139  $sql .= ' WHERE LENGTH(ct.treeid) = :length';
5140  }
5141  $sql .= ' AND linkid > 0 ORDER BY treeid DESC';
5142 
5143  try {
5144  $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), 1);
5145 
5146  $bind_vars = Array();
5147  if ($existing_treeid != '-') {
5148  if (!$is_oci) {
5149  $sql = str_replace(':existing_treeid_wildcard', MatrixDAL::quote($existing_treeid.'%'), $sql);
5150  } else {
5151  $bind_vars['existing_treeid_wildcard'] = $existing_treeid.'%';
5152  }
5153  if (!empty($existing_treeid)) {
5154  $bind_vars['existing_treeid'] = $existing_treeid;
5155  }
5156  $bind_vars['length'] = strlen($existing_treeid) + SQ_CONF_ASSET_TREE_SIZE;
5157  } else {
5158  $bind_vars['length'] = SQ_CONF_ASSET_TREE_SIZE;
5159  }
5160  $query = MatrixDAL::preparePdoQuery($sql);
5161  foreach ($bind_vars as $bind_name => $bind_value) {
5162  MatrixDAL::bindValueToPdo($query, $bind_name, $bind_value);
5163  }
5164  $last_treeid = MatrixDAL::executePdoOne($query);
5165  //if (!empty($last_treeid)) {
5166  //$last_treeid = $last_treeid;
5167  //}
5168  } catch (Exception $e) {
5169  throw new Exception('Unable to get tree data due to database error: '.$e->getMessage());
5170  }
5171 
5172  $treeid = ($existing_treeid == '-') ? '' : $existing_treeid;
5173 
5174  // if we couldn't find any treeids on this branch, then we can just
5175  // do a straight inert and break the loop as we are done
5176  if (empty($last_treeid)) {
5177  $sql = 'asset_link_treeid_convert(\'0\', \'1\', '.SQ_CONF_ASSET_TREE_BASE.', '.SQ_CONF_ASSET_TREE_SIZE.')';
5178  $free_treeid = db_get_function_result(MatrixDAL::getDbType(), $sql);
5179 
5180  $new_treeid = ($existing_treeid == '-') ? $free_treeid : $existing_treeid.$free_treeid;
5181 
5182  $sql = 'INSERT INTO
5183  sq_ast_lnk_tree
5184  (
5185  treeid,
5186  linkid,
5187  num_kids
5188  )
5189  VALUES
5190  (
5191  :treeid,
5192  :linkid,
5193  :num_kids
5194  )';
5195  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5196 
5197  try {
5198  $query = MatrixDAL::preparePdoQuery($sql);
5199  MatrixDAL::bindValueToPdo($query, 'treeid', $new_treeid);
5200  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5201  MatrixDAL::bindValueToPdo($query, 'num_kids', 0);
5202  MatrixDAL::execPdoQuery($query);
5203  } catch (Exception $e) {
5204  throw new Exception('Unable to add new tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5205  }
5206 
5207  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5208 
5209  break;
5210 
5211  }//end no existing treeid
5212 
5213  // otherwise we could find a treeid on this branch, so increment it
5214  // to get a free childid that we can use for our new link
5215  $sql = 'asset_link_treeid_convert(:treeid, :encode, :tree_base, :tree_size)';
5216  $bind_vars = Array(
5217  'treeid' => $last_treeid,
5218  'encode' => '0',
5219  'tree_base' => SQ_CONF_ASSET_TREE_BASE,
5220  'tree_size' => SQ_CONF_ASSET_TREE_SIZE,
5221  );
5222  $child_num = db_get_function_result(MatrixDAL::getDbType(), $sql, $bind_vars);
5223 
5224  $bind_vars = Array(
5225  'treeid' => (string) ($child_num + 1),
5226  'encode' => '1',
5227  'tree_base' => SQ_CONF_ASSET_TREE_BASE,
5228  'tree_size' => SQ_CONF_ASSET_TREE_SIZE,
5229  );
5230  $free_treeid = db_get_function_result(MatrixDAL::getDbType(), $sql, $bind_vars);
5231 
5232  // we want to try and reserve a treeid for us that we have just converted
5233  // from the last treeid on the branch. So what we are doing here is inserting
5234  // an entry into the tree table with our linkid appended to the end.
5235  // We need to do this because someone might have already inserted into the
5236  // tree table between the time that we selected the last treeid on the branch
5237  // and this point here
5238 
5239  $sql = 'INSERT INTO
5240  sq_ast_lnk_tree
5241  (
5242  treeid,
5243  linkid,
5244  num_kids
5245  )
5246  VALUES
5247  (
5248  :treeid,
5249  :linkid,
5250  :num_kids
5251  )';
5252 
5253  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5254 
5255  try {
5256  $query = MatrixDAL::preparePdoQuery($sql);
5257  MatrixDAL::bindValueToPdo($query, 'treeid', $treeid.$free_treeid.':'.$linkid);
5258  MatrixDAL::bindValueToPdo($query, 'linkid', 0);
5259  MatrixDAL::bindValueToPdo($query, 'num_kids', 0);
5260  MatrixDAL::execPdoQuery($query);
5261  } catch (Exception $e) {
5262  throw new Exception('Unable to reserve new tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5263  }
5264 
5265  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5266 
5267  // now try to select the last treeid from this branch , ordering
5268  // by treeid. If a process got in before us, we will select it as
5269  // the order by will cause it to come first as its linkid will be smaller
5270 
5271  $sql = 'SELECT
5272  treeid
5273  FROM
5274  sq_ast_lnk_tree
5275  WHERE
5276  (treeid LIKE :treeid_wildcard OR treeid = :treeid)
5277  AND num_kids = 0 ORDER BY treeid DESC';
5278 
5279  try {
5280  $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), 1);
5281 
5282  $bind_vars = Array();
5283  if (!$is_oci) {
5284  $sql = str_replace(':treeid_wildcard', MatrixDAL::quote($treeid.$free_treeid.':%'), $sql);
5285  } else {
5286  $bind_vars['treeid_wildcard'] = $treeid.$free_treeid.':%';
5287  }
5288  $bind_vars['treeid'] = $treeid.$free_treeid;
5289  $query = MatrixDAL::preparePdoQuery($sql);
5290  foreach ($bind_vars as $bind_name => $bind_value) {
5291  MatrixDAL::bindValueToPdo($query, $bind_name, $bind_value);
5292  }
5293  $found_treeid = MatrixDAL::executePdoOne($query);
5294  } catch (Exception $e) {
5295  throw new Exception('Unable to get tree data due to database error: '.$e->getMessage());
5296  }
5297 
5298  if ($found_treeid == $treeid.$free_treeid.':'.$linkid) {
5299 
5300  // if the found treeid was like the one that we just inserted, then
5301  // we MIGHT have a winner. The reason that I say might is because
5302  // someone else might have thought that they have won between the
5303  // time of the last select to now
5304 
5305  $sql = 'UPDATE
5306  sq_ast_lnk_tree
5307  SET
5308  linkid = :linkid
5309  WHERE
5310  treeid = :treeid_found
5311  AND
5312  linkid = 0
5313  AND NOT EXISTS(
5314  SELECT
5315  1
5316  FROM
5317  sq_ast_lnk_tree
5318  WHERE
5319  (treeid LIKE :treeid_wildcard
5320  OR treeid = :treeid)
5321  AND linkid <> 0)';
5322 
5323  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5324 
5325  try {
5326  $bind_vars = Array();
5327  if (!$is_oci) {
5328  $sql = str_replace(':treeid_wildcard', MatrixDAL::quote($treeid.$free_treeid.':%'), $sql);
5329  } else {
5330  $bind_vars['treeid_wildcard'] = $treeid.$free_treeid.':%';
5331  }
5332  $bind_vars['linkid'] = -$linkid;
5333  $bind_vars['treeid'] = $treeid.$free_treeid;
5334  $bind_vars['treeid_found'] = $found_treeid;
5335  $query = MatrixDAL::preparePdoQuery($sql);
5336  foreach ($bind_vars as $bind_name => $bind_value) {
5337  MatrixDAL::bindValueToPdo($query, $bind_name, $bind_value);
5338  }
5339  $aff_rows = MatrixDAL::execPdoQuery($query);
5340  } catch (Exception $e) {
5341  throw new Exception('Unable to attempt to win tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5342  }
5343 
5344  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5345 
5346  // Get the number of affected rows - if the update to our
5347  // link ID worked, then we've won this tree ID
5348  if ($aff_rows != 0) {
5349  // before we declare won tree ID, we have to double check if other processes also declare won tree ID
5350  // this could happen because above "UPDATE .. AND NOT EXISTS" query is not granular enough.
5351  // every concurrent processes may think others NOT EXIST, and therefore all UPDATE at the same time.
5352  $sql = 'SELECT count(1) FROM sq_ast_lnk_tree
5353  WHERE
5354  (
5355  treeid LIKE :treeid_wildcard
5356  OR treeid = :treeid
5357  )
5358  AND linkid <> 0
5359  ';
5360  $bind_vars = Array();
5361  $bind_vars['treeid'] = $treeid.$free_treeid;
5362  if (!$is_oci) {
5363  $sql = str_replace(':treeid_wildcard', MatrixDAL::quote($treeid.$free_treeid.':%'), $sql);
5364  } else {
5365  $bind_vars['treeid_wildcard'] = $treeid.$free_treeid.':%';
5366  }
5367  $query = MatrixDAL::preparePdoQuery($sql);
5368  foreach ($bind_vars as $bind_name => $bind_value) {
5369  MatrixDAL::bindValueToPdo($query, $bind_name, $bind_value);
5370  }
5371  $result = MatrixDAL::executePdoOne($query);
5372  if($result <=1) {
5373  $requires_update = TRUE;
5374  break;
5375  }
5376  }
5377  }//end if
5378 
5379  // otherwise we lost this treeid and will have to do another
5380  // iterator to try and win a different one
5381  $sql = 'DELETE FROM
5382  sq_ast_lnk_tree
5383  WHERE
5384  treeid = :treeid';
5385 
5386  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5387 
5388  try {
5389  $query = MatrixDAL::preparePdoQuery($sql);
5390  MatrixDAL::bindValueToPdo($query, 'treeid', $treeid.$free_treeid.':'.$linkid);
5391  MatrixDAL::execPdoQuery($query);
5392  } catch (Exception $e) {
5393  throw new Exception('Unable to attempt to drop lost tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5394  }
5395 
5396  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5397 
5398  $i++;
5399  // only try to do this 200 times. more tries, better chance to avoid concurrency deadlock.
5400  // but too many tries could freeze the process by exceeding max execution time
5401  } while ($i < 200);
5402 
5403  if ($i >= 200) {
5404  trigger_localised_error('SYS0310', E_USER_ERROR);
5405  }
5406 
5407  if ($requires_update) {
5408 
5409  $sql = 'UPDATE
5410  sq_ast_lnk_tree
5411  SET
5412  treeid = :treeid,
5413  linkid = :linkid
5414  WHERE
5415  treeid = :old_treeid';
5416 
5417  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5418 
5419  try {
5420  $query = MatrixDAL::preparePdoQuery($sql);
5421  // The negative link ID is correct; we will replace with
5422  // a positive version if we've drawn a winning tree ID
5423  MatrixDAL::bindValueToPdo($query, 'treeid', $treeid.$free_treeid);
5424  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5425  MatrixDAL::bindValueToPdo($query, 'old_treeid', $treeid.$free_treeid.':'.$linkid);
5426  MatrixDAL::execPdoQuery($query);
5427  } catch (Exception $e) {
5428  throw new Exception('Unable to attempt to update won tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5429  }
5430 
5431  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5432  }
5433 
5434  // if we don't have any existing tree entries then
5435  // we are the root folder, so do a simple insert
5436  if ((string) $existing_treeid == '-') {
5437  $sql = 'UPDATE
5438  sq_ast_lnk_tree
5439  SET
5440  linkid = :linkid,
5441  num_kids = :num_kids
5442  WHERE
5443  treeid = :treeid';
5444 
5445  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5446 
5447  try {
5448  $query = MatrixDAL::preparePdoQuery($sql);
5449  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5450  MatrixDAL::bindValueToPdo($query, 'num_kids', $minor_tree['num_kids']);
5451  MatrixDAL::bindValueToPdo($query, 'treeid', $free_treeid);
5452  MatrixDAL::execPdoQuery($query);
5453  } catch (Exception $e) {
5454  throw new Exception('Unable to update top-level tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5455  }
5456 
5457  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5458 
5459  } else {
5460  $sql = 'UPDATE
5461  sq_ast_lnk_tree
5462  SET
5463  linkid = :linkid,
5464  num_kids = :num_kids
5465  WHERE
5466  treeid = :treeid';
5467 
5468  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5469 
5470  try {
5471  $query = MatrixDAL::preparePdoQuery($sql);
5472  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5473  MatrixDAL::bindValueToPdo($query, 'num_kids', $minor_tree['num_kids']);
5474  MatrixDAL::bindValueToPdo($query, 'treeid', $existing_treeid.$free_treeid);
5475  MatrixDAL::execPdoQuery($query);
5476  } catch (Exception $e) {
5477  throw new Exception('Unable to update tree entry for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5478  }
5479 
5480  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5481 
5482  // we have existing tree entries, do a insert..select to create entries for them all
5483 
5484  $sql = 'INSERT INTO
5485  sq_ast_lnk_tree
5486  (
5487  treeid,
5488  linkid,
5489  num_kids
5490  )
5491  SELECT
5492  (t.treeid || :free_treeid),
5493  :linkid,
5494  :num_kids
5495  FROM
5496  sq_ast_lnk_tree t
5497  INNER JOIN sq_ast_lnk l ON t.linkid = l.linkid
5498  WHERE
5499  l.minorid = :minorid
5500  AND treeid || :free_treeid_1 <> :treeid';
5501 
5502  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5503 
5504  try {
5505  $query = MatrixDAL::preparePdoQuery($sql);
5506  MatrixDAL::bindValueToPdo($query, 'free_treeid', $free_treeid);
5507  MatrixDAL::bindValueToPdo($query, 'free_treeid_1', $free_treeid);
5508  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5509  MatrixDAL::bindValueToPdo($query, 'minorid', $major->id);
5510  MatrixDAL::bindValueToPdo($query, 'num_kids', $minor_tree['num_kids']);
5511  MatrixDAL::bindValueToPdo($query, 'treeid', $existing_treeid.$free_treeid);
5512  MatrixDAL::execPdoQuery($query);
5513  } catch (Exception $e) {
5514  throw new Exception('Unable to insert child tree entries for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5515  }
5516 
5517  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5518 
5519  }//end else
5520 
5521  $sql = 'UPDATE
5522  sq_ast_lnk_tree
5523  SET
5524  num_kids = num_kids + 1
5525  WHERE
5526  treeid IN
5527  (
5528  SELECT
5529  CASE WHEN
5530  LENGTH(SUBSTR(t.treeid, 1, (LENGTH(t.treeid) - :tree_size))) != 0
5531  THEN
5532  SUBSTR(t.treeid, 1, (LENGTH(t.treeid) - :tree_size_1))
5533  ELSE
5534  :treeid
5535  END
5536  FROM
5537  sq_ast_lnk_tree t
5538  WHERE
5539  t.linkid = :linkid
5540  )';
5541 
5542  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5543 
5544  try {
5545  $query = MatrixDAL::preparePdoQuery($sql);
5546  MatrixDAL::bindValueToPdo($query, 'tree_size', SQ_CONF_ASSET_TREE_SIZE);
5547  MatrixDAL::bindValueToPdo($query, 'tree_size_1', SQ_CONF_ASSET_TREE_SIZE);
5548  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5549  MatrixDAL::bindValueToPdo($query, 'treeid', '-');
5550  MatrixDAL::execPdoQuery($query);
5551  } catch (Exception $e) {
5552  throw new Exception('Unable to insert child tree entries for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5553  }
5554 
5555  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5556 
5557  // if this minor has already been linked so do a select into
5558  if ($minor_tree['treeid'] != '') {
5559  $case = "CASE pt.treeid
5560  WHEN '-' THEN ''
5561  ELSE pt.treeid
5562  END";
5563 
5564  $sql = 'INSERT INTO
5565  sq_ast_lnk_tree
5566  (
5567  treeid,
5568  linkid,
5569  num_kids
5570  )
5571  SELECT
5572  ('.$case.') || :treeid || SUBSTR(ct.treeid, :minor_treeid),
5573  ct.linkid,
5574  ct.num_kids
5575  FROM
5576  sq_ast_lnk_tree pt
5577  INNER JOIN sq_ast_lnk pl ON pt.linkid = pl.linkid,
5578  sq_ast_lnk_tree ct
5579  WHERE
5580  pl.minorid = :minorid
5581  AND ct.treeid LIKE :minor_treeid_wildcard
5582  AND ct.treeid > :minor_treeid_1';
5583 
5584  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5585 
5586  try {
5587  $query = MatrixDAL::preparePdoQuery($sql);
5588  MatrixDAL::bindValueToPdo($query, 'treeid', $free_treeid);
5589  MatrixDAL::bindValueToPdo($query, 'minorid', $major->id);
5590  MatrixDAL::bindValueToPdo($query, 'minor_treeid_wildcard', $minor_tree['treeid'].'%');
5591  MatrixDAL::bindValueToPdo($query, 'minor_treeid', strlen($minor_tree['treeid'])+1);
5592  MatrixDAL::bindValueToPdo($query, 'minor_treeid_1', $minor_tree['treeid']);
5593  MatrixDAL::execPdoQuery($query);
5594  } catch (Exception $e) {
5595  throw new Exception('Unable to insert child tree entries for link #'.$linkid.', between "'.$major->name.'" (#'.$major->id.') and "'.$minor->name.'" (#'.$minor->id.'), due to database error: '.$e->getMessage());
5596  }
5597 
5598  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5599  }//end if
5600 
5601 
5602  }//end if significant link
5603 
5604  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); /******** End DB3 ************/
5605 
5606  // only call the linksUpdated() method on the parent if this link is dependent
5607  if ($dependant) $major->linksUpdated();
5608  $minor->linksUpdated();
5609 
5610  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
5611  // Release locks if we acquired them earlier
5612  if (!$parent_was_locked) {
5613  $this->releaseLock($major->id, 'links'); // don't care if this fails, too late to revert now
5614  }
5615  }
5616 
5617  // Send Internal Message
5618  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
5619  $msg_reps = Array(
5620  'major_name' => $major->name,
5621  'minor_name' => $minor->name,
5622  );
5623  $message = $ms->newMessage(Array(), 'asset.linking.create', $msg_reps);
5624  $message->parameters['majorid'] = $major->id;
5625  $message->parameters['minorid'] = $minor->id;
5626  $message->parameters['linkid'] = $linkid;
5627  $message->send();
5628 
5629  // Broadcast Event
5630  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
5631  $em->broadcastEvent($major, 'CreateLink', Array('linkid' => $linkid));
5632 
5633  // Fire Triggers
5634  if ($major->type() != 'trash_folder' && $minor->type() != 'trash_folder') {
5635  // fire the 'Link Created' events, one for the major asset, one for the minor
5636 
5637  $link_info['majorid'] = $major->id;
5638  $link_info['minorid'] = $minor->id;
5639  $link_info['linkid'] = $linkid;
5640  $link_info['value'] = $value;
5641  $link_info['link_type'] = $link_type;
5642  $link_info['minor_type_code'] = $minor->type();
5643  $link_info['major_type_code'] = $major->type();
5644  $link_info['sort_order'] = $sort_order;
5645  $link_info['is_dependant'] = $dependant;
5646  $link_info['is_exclusive'] = $exclusive;
5647  $link_info['locked'] = $locked;
5648 
5649  $event_data = Array('link_info' => $link_info, 'linkid' => $linkid);
5650 
5651  if ($major->type() != 'root_folder') {
5652  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_link_created', $major, $event_data);
5653  }
5654  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_link_created', $minor, $event_data);
5655  }
5656 
5657  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db2
5658 
5659  return $linkid;
5660 
5661  }//end createAssetLink()
5662 
5663 
5677  function createShadowAssetLink(Asset $major, Asset $minor, $link_type, $value='')
5678  {
5679  // we are linking a shadow asset under a normal asset
5680  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
5681 
5682  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
5683  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5684  $db = MatrixDAL::getDb();
5685  // use the sq_ast_lnk sequence so that when we query any link view later
5686  // on we don't have any conflicting linkids
5687 
5688  $linkid = MatrixDAL::executeOne('core', 'seqNextVal', Array('seqName' => 'sq_ast_lnk_seq'));
5689  $now = time();
5690  $ts_fragment = MatrixDAL::executeOne('core', 'toDate', Array('date_value' => ts_iso8601($now)));
5691  $sql = 'INSERT INTO
5692  sq_shdw_ast_lnk
5693  (
5694  linkid,
5695  majorid,
5696  minorid,
5697  link_type,
5698  value,
5699  updated,
5700  updated_userid
5701  )
5702  VALUES
5703  (
5704  :linkid,
5705  :majorid,
5706  :minorid,
5707  :link_type,
5708  :value,
5709  :updated,
5710  :currentUserId
5711  )';
5712 
5713  $result = NULL;
5714  try {
5715  $query = MatrixDAL::preparePdoQuery($sql);
5716  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
5717  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
5718  MatrixDAL::bindValueToPdo($query, 'minorid', $minor->id);
5719  MatrixDAL::bindValueToPdo($query, 'link_type', $link_type);
5720  MatrixDAL::bindValueToPdo($query, 'updated', $ts_fragment);
5721  MatrixDAL::bindValueToPdo($query, 'value', $value);
5722  MatrixDAL::bindValueToPdo($query, 'currentUserId', $GLOBALS['SQ_SYSTEM']->currentUserid());
5723  MatrixDAL::execPdoQuery($query);
5724  } catch (Exception $e) {
5725  throw new Exception('cannot create Shadow Asset Links due to database error: '.$e->getMessage());
5726  }
5727 
5728  $this->forgetAsset($major);
5729  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5730  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5731 
5732  return $linkid;
5733 
5734  }//end createShadowAssetLink()
5735 
5736 
5749  function canCreateLink(Asset $major, Asset $minor, $link_type, $exclusive)
5750  {
5751  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
5752  if (!$major->id) return FALSE;
5753 
5754  if (!($minor instanceof Asset)) {
5755  return translate('minor_is_not_asset');
5756  }
5757 
5758  // check if we are allowed to link to these type of assets
5759  if (($err_msg = $this->canLinkToType($major, $minor->type(), $link_type, 0, $exclusive)) !== TRUE) {
5760  return $err_msg;
5761  }
5762 
5763  // Check write access as appropriate
5764  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
5765  if ($link_type == SQ_LINK_NOTICE) {
5766  // To create a notice link you need effective write access
5767  // to the major, but you don't need any access to the minor
5768  if (($major->status == SQ_STATUS_ARCHIVED) || (!$major->writeAccess(''))) {
5769  return translate('cannot_create_notice_link_permission_denied', $major->name, $major->id, $minor->name, $minor->id);
5770  }
5771  } else {
5772  // To create a significant link you need write permissions
5773  // (but not effective write access) to both assets
5774  $majwa = $major->writeAccess('');
5775  $minwa = $minor->writeAccess('');
5776  if (!($majwa || $minwa)) {
5777  return translate('cannot_create_sig_link_no_perm_either', $major->name, $major->id, $minor->name, $minor->id);
5778  } else if (!$majwa) {
5779  return translate('cannot_create_sig_link_no_perm_major', $major->name, $major->id, $minor->name, $minor->id);
5780  } else if (!$minwa) {
5781  return translate('cannot_create_sig_link_no_perm_minor', $major->name, $major->id, $minor->name, $minor->id);
5782  }
5783  }
5784  }
5785 
5786  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db3');
5787  $db = MatrixDAL::getDb();
5788 
5789  // check for web path conflicts with the new parents children
5790  if ($link_type & SQ_SC_LINK_WEB_PATHS) {
5791  $paths = $minor->getWebPaths();
5792  $bad_paths = $this->webPathsInUse($major, $paths, $minor->id);
5793  if (!empty($bad_paths)) {
5794  return translate('cannot_create_link_paths_in_use', $major->name, $major->id, $minor->name, $minor->id, '"'.implode('", "', $bad_paths).'"');
5795  }
5796  }
5797 
5798  // if this link is a significant link, then we need to make sure that this
5799  // minor asset doesn't already have an exclusive link from anything
5800  // and that we aren't moving under ourselves
5801  if ($link_type & SQ_SC_LINK_SIGNIFICANT) {
5803  $sql = 'SELECT majorid
5804  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lnk ';
5805  $where = 'minorid = :minor_id
5806  AND '.db_extras_bitand(MatrixDAL::getDbType(), 'link_type', SQ_SC_LINK_SIGNIFICANT).' > 0';
5807  if (!$exclusive) {
5808  $where .= ' AND is_exclusive = :is_exclusive';
5809  }
5810  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
5811  $sql .= $where;
5812 
5813  try {
5814  $query = MatrixDAL::preparePdoQuery($sql);
5815  MatrixDAL::bindValueToPdo($query, 'minor_id', $minor->id);
5816  if (!$exclusive) {
5817  MatrixDAL::bindValueToPdo($query, 'is_exclusive', 1);
5818  }
5819  $current_majorid = MatrixDAL::executePdoOne($query);
5820  } catch (Exception $e) {
5821  throw new Exception('Unable to check that minor asset doesnt already have an exclusive link due to database error: '.$e->getMessage());
5822  }
5823 
5824  if ($current_majorid) {
5825  $current_major = $this->getAsset($current_majorid);
5826  return translate('cannot_create_link_exclusive_link', $minor->name, $minor->id, $major->name, $major->id, $current_major->name, $current_majorid);
5827  }
5828 
5829  }//end if
5830 
5831  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5832 
5833  return TRUE;
5834 
5835  }//end canCreateLink()
5836 
5837 
5852  function canLinkToType(Asset $major, $type_code, $link_type, $ignore_linkid=0, $exclusive=0)
5853  {
5854  // type checking
5855  if (!($major instanceof Asset)) {
5856  return translate('major_is_not_asset');
5857  }
5858 
5859  if (!is_string($type_code)) {
5860  return translate('type_code_is_not_string');
5861  }
5862 
5863  if (!is_numeric($link_type)) {
5864  return translate('link_type_is_not_integer');
5865  }
5866 
5867  // get the minor assets parents and add it's type to the front of the indexed array
5868  $types = $this->getTypeAncestors($type_code);
5869  array_unshift($types, $type_code);
5870 
5871  $allowed_links = $major->_getAllowedLinks();
5872 
5873  // we will be ascending up the parent tree from the current asset type.
5874  // that way the major asset can have specific cardinality for different assets types
5875  // EG, assets => 'M', user => '1' -> this means that many assets can be linked to this asset
5876  // and only one user, but because 'user' is an 'asset' we need to check for any 'user' references
5877  // before we check for any 'asset' references
5878  $type = '';
5879  for ($i = 0; $i < count($types); $i++) {
5880  $type = $types[$i];
5881  if (!empty($allowed_links[$link_type][$type])) break;
5882  }
5883 
5884  if (empty($allowed_links[$link_type][$type])) {
5885  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5886  return translate('asset_type_cannot_be_linktype_linked_to_type', $type_code, link_type_name($link_type), $major->type());
5887  }
5888 
5889  if (!($link_type & SQ_SC_LINK_SIGNIFICANT) && $exclusive) {
5890  return translate('exclusive_links_must_be_significant');
5891  }
5892 
5893  if (!$exclusive && !empty($allowed_links[$link_type][$type]['exclusive'])) {
5894  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5895  return translate('asset_type_must_be_linktype_exclusively_linked_to_type', $type_code, link_type_name($link_type), $major->type());
5896  }
5897 
5899 
5900  // if we are only allowed up to a certain number of these links
5901  if ($allowed_links[$link_type][$type]['card'] != 'M' && $major->id) {
5902  $num_curr_links = $this->countLinks($major->id, 'major', $link_type, $type, TRUE, $ignore_linkid);
5903  // and we already have our quota of links
5904  if ($num_curr_links >= (int) $allowed_links[$link_type][$type]['card']) {
5905  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5906  return translate('asset_type_can_have_only_linktypes_links_to_type', $this->getTypeInfo($major->type(), 'name'), ((int) $allowed_links[$link_type][$type]['card']), link_type_name($link_type), $type);
5907  }
5908  }
5909 
5910  // if we get this far all is OK
5911  return TRUE;
5912 
5913  }//end canLinkToType()
5914 
5915 
5928  function moveLink($linkid, $to_parentid, $link_type, $to_parent_pos, $link_value='')
5929  {
5930  // parent position type must be numeric
5931  if (!is_numeric($to_parent_pos)) {
5932  trigger_localised_error('SYS0227', E_USER_WARNING);
5933  return 0;
5934  }
5935 
5936  // parent ID must be a valid assetid
5937  if (!assert_valid_assetid($to_parentid, '', FALSE, FALSE)) {
5938  trigger_localised_error('SYS0225', E_USER_WARNING);
5939  return 0;
5940  }
5941 
5942  // link type must be numeric (ie. an int, or a numeric string)
5943  if (!is_numeric($link_type)) {
5944  trigger_localised_error('SYS0221', E_USER_WARNING);
5945  return 0;
5946  }
5947 
5948  // get the link, and the old parent
5949  $link = $this->getLinkById($linkid);
5950 
5951  if (empty($link)) {
5952  trigger_localised_error('SYS0138', E_USER_WARNING, $linkid);
5953  return 0;
5954  }
5955 
5956  if (isset($link['locked']) && $link['locked'] == '1') {
5957  trigger_localised_error('SYS0333', E_USER_WARNING, $linkid);
5958  return 0;
5959  }
5960 
5961  $old_parent = $this->getAsset($link['majorid'], $link['major_type_code']);
5962  if (is_null($old_parent)) {
5963  trigger_localised_error('SYS0226', E_USER_WARNING, $link['majorid']);
5964  return 0;
5965  }
5966 
5967  // if you dont have write access to the old parent asset you cant delete any links
5968  if (!$old_parent->writeAccess('')) {
5969  trigger_localised_error('SYS0229', E_USER_WARNING, $old_parent->name);
5970  $this->forgetAsset($old_parent);
5971  return 0;
5972  }
5973 
5974  // now the new parent
5975  $new_parent = $this->getAsset($to_parentid, '', TRUE);
5976  if (is_null($new_parent)) {
5977  trigger_localised_error('SYS0224', E_USER_WARNING, $to_parentid);
5978  $this->forgetAsset($old_parent);
5979  return 0;
5980  }
5981 
5982  // if you dont have write access to the new parent asset you cant create any links
5983  if (!$new_parent->writeAccess('')) {
5984  trigger_localised_error('SYS0228', E_USER_WARNING, $new_parent->name);
5985  $this->forgetAsset($old_parent);
5986  $this->forgetAsset($new_parent);
5987  return 0;
5988  }
5989 
5990  // this is the asset we are moving
5991  $minor = $this->getAsset($link['minorid'], $link['minor_type_code']);
5992  if (!$minor->id) {
5993  trigger_localised_error('SYS0222', E_USER_WARNING, $link['minorid']);
5994  $this->forgetAsset($old_parent);
5995  $this->forgetAsset($new_parent);
5996  return 0;
5997  }
5998 
5999  // are we allowed to move this link?
6000  if (TRUE !== ($error_msg = $new_parent->canMoveLink($minor, $old_parent, $link_type))) {
6001  trigger_error($error_msg, E_USER_WARNING);
6002  $this->forgetAsset($old_parent);
6003  $this->forgetAsset($new_parent);
6004  $this->forgetAsset($minor);
6005  return 0;
6006  }
6007 
6008  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6009  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6010  $success = TRUE;
6011 
6012  // now create the new link, it's non-exclusive + non-dependent, skip canCreateLink() checks
6013  $new_linkid = $new_parent->createLink($minor, $link_type, $link_value, $to_parent_pos, '0', '0', TRUE);
6014  if (!$new_linkid) $success = FALSE;
6015 
6016  // let's try and delete the old link
6017  if ($success) {
6018  $success = $old_parent->deleteLink($linkid);
6019  }
6020 
6021  if ($success) {
6022  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6023  } else {
6024  if ($new_linkid != 0) $link = $GLOBALS['SQ_SYSTEM']->am->getLinkById($new_linkid);
6025  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
6026  //Bug Fix #3886 if we rolling back, delete the link we had already created
6027  if ($new_linkid != 0) $this->deleteAssetLinkByLink($link);
6028  $new_linkid = 0;
6029  }
6030 
6031  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6032 
6033  $this->forgetAsset($old_parent);
6034  $this->forgetAsset($new_parent);
6035  $this->forgetAsset($minor);
6036 
6037  return $new_linkid;
6038 
6039  }//end moveLink()
6040 
6041 
6052  function moveLinkPos($linkid, $sort_order=-1)
6053  {
6054  return $this->updateLink($linkid, NULL, NULL, $sort_order);
6055 
6056  }//end moveLinkPos()
6057 
6058 
6074  function updateLink($linkid, $link_type=NULL, $value=NULL, $sort_order=NULL, $locked=NULL)
6075  {
6076  // First, we should try and find the link
6077  $link = $this->getLinkById($linkid);
6078  if (empty($link)) {
6079  trigger_localised_error('SYS0139', E_USER_WARNING, $linkid);
6080  return FALSE;
6081  }
6082 
6083  $id_parts = explode(':', $linkid);
6084  $minorid_parts = explode(':', $link['minorid']);
6085  // shadow asset found, pipe the request of to the bridge
6086  if (isset($id_parts[1])) {
6087 
6088  $bridge = $this->getAsset($id_parts[0]);
6089  $result = $bridge->updateLink($linkid, $link_type, $value, $sort_order);
6090  $this->forgetAsset($bridge);
6091  return $result;
6092  }
6093 
6094 
6095  $db = MatrixDAL::getDb();
6096 
6097  $set_clauses = Array();
6098 
6099  // Do not allow link types of type 3 and notice to be locked
6100  if ($link_type == SQ_LINK_TYPE_3 || $link_type == SQ_LINK_NOTICE) {
6101  if (!is_null($locked)) $locked = '0';
6102  }//end if
6103 
6104  $link_type_changed = (!is_null($link_type) && $link['link_type'] != $link_type );
6105  $value_changed = (!is_null($value) && $link['value'] != $value );
6106  $sort_order_changed = (!is_null($sort_order) && $link['sort_order'] != $sort_order );
6107  $link_locked_changed = (!is_null($locked) && $link['locked'] != $locked );
6108 
6109 
6110  $link_type = (int) $link_type;
6111  $sort_order = (int) $sort_order;
6112  $locked = ($locked == '1') ? '1' : '0';
6113 
6114  $major = $this->getAsset((int) $link['majorid'], $link['major_type_code']);
6115  $minor = $this->getAsset((int) $link['minorid'], $link['minor_type_code']);
6116 
6117  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
6118  $ms->openLog();
6119 
6120  if ($link_type_changed) {
6121 
6122  // We need to make sure that we aren't going to have to be stuffing
6123  // about with tree to get the update working
6124  // so because the tree only contains significant links if the
6125  // significant state has changed... error
6126  $current_is_sig = (bool) ((int) $link['link_type'] & SQ_SC_LINK_SIGNIFICANT);
6127  $new_is_sig = (bool) ($link_type & SQ_SC_LINK_SIGNIFICANT);
6128  if ($current_is_sig !== $new_is_sig) {
6129  trigger_localised_error('SYS0256', E_USER_WARNING, $linkid);
6130  $ms->abortLog();
6131  return FALSE;
6132  }
6133 
6134  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LINK_INTEGRITY)) {
6135  if (($err_msg = $this->canLinkToType($major, $minor->type(), $link_type)) !== TRUE) {
6136  trigger_localised_error('SYS0255', E_USER_WARNING, $linkid, $err_msg);
6137  $ms->abortLog();
6138  return FALSE;
6139  }
6140  }
6141 
6142  // check for web path conflicts with the new parents children
6143  // if the old link is not a web path link but the new one is
6144  if (!($link['link_type'] & SQ_SC_LINK_WEB_PATHS) && ($link_type & SQ_SC_LINK_WEB_PATHS)) {
6145  $paths = $minor->getWebPaths();
6146  $bad_paths = $this->webPathsInUse($major, $paths, $minor->id);
6147  if (!empty($bad_paths)) {
6148  trigger_localised_error('SYS0121', E_USER_WARNING, $linkid, implode('", "', $bad_paths), $major->name);
6149  $ms->abortLog();
6150  return FALSE;
6151  }
6152  }
6153 
6154  $set_clauses['link_type'] = MatrixDAL::quote($link_type);
6155 
6156  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
6157  $msg_reps = Array(
6158  'linkid' => $linkid,
6159  'major_name' => $major->name,
6160  'minor_name' => $minor->name,
6161  'old_link_type' => link_type_name($link['link_type']),
6162  'new_link_type' => link_type_name($link_type),
6163  );
6164  $message = $ms->newMessage(Array(), 'asset.linking.type', $msg_reps);
6165  $message->parameters['majorid'] = $major->id;
6166  $message->parameters['minorid'] = $minor->id;
6167  $message->parameters['linkid'] = $linkid;
6168  $ms->logMessage($message);
6169 
6170  }//end if link_type_changed
6171 
6172  if ($value_changed) {
6173 
6174  $set_clauses['value'] = MatrixDAL::quote($value);
6175 
6176  $msg_reps = Array(
6177  'linkid' => $linkid,
6178  'major_name' => $major->name,
6179  'minor_name' => $minor->name,
6180  'old_link_value' => $link['value'],
6181  'new_link_value' => $value,
6182  );
6183  $message = $ms->newMessage(Array(), 'asset.linking.value', $msg_reps);
6184  $message->parameters['majorid'] = $major->id;
6185  $message->parameters['minorid'] = $minor->id;
6186  $message->parameters['linkid'] = $linkid;
6187  $ms->logMessage($message);
6188 
6189  }//end if value_changed
6190 
6191  if ($link_locked_changed) {
6192 
6193  $set_clauses['locked'] = MatrixDAL::quote($locked);
6194 
6195  $msg_reps = Array(
6196  'linkid' => $linkid,
6197  'major_name' => $major->name,
6198  'minor_name' => $minor->name,
6199  'link_locked_value' => (($locked == '1') ? translate('locked') : translate('unlocked')),
6200  );
6201  $message = $ms->newMessage(Array(), 'asset.linking.locked', $msg_reps);
6202  $message->parameters['majorid'] = $major->id;
6203  $message->parameters['minorid'] = $minor->id;
6204  $message->parameters['linkid'] = $linkid;
6205  $ms->logMessage($message);
6206 
6207  }//end if link_locked_changed
6208 
6209  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
6210  // If the minor is a shadow asset, we need to update the shadow link table instead
6211  // There's no order field in the shadow table, so we branch before we add order to the set clause
6212  if (isset($minorid_parts[1])) {
6213 
6214  if (empty($set_clauses)) {
6215  $ms->abortLog();
6216  return TRUE;
6217  }
6218 
6219  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6220  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6221  $db = MatrixDAL::getDb();
6222 
6223  $set_clauses['updated'] = MatrixDAL::quote(MatrixDAL::executeOne('core', 'toDate', Array('date_value' => ts_iso8601(time()))));
6224 
6225  $set_array = Array();
6226 
6227  foreach ($set_clauses as $key => $value) {
6228  $set_array[] = $key.'='.$value;
6229  }
6230 
6231  $set_string = implode(',', $set_array);
6232 
6233  $sql = 'UPDATE
6234  sq_shdw_ast_lnk
6235  SET
6236  updated_userid = :updated_userid,
6237  '.$set_string.'
6238  WHERE
6239  linkid = :linkid
6240  AND majorid = :majorid';
6241 
6242  try {
6243  $query = MatrixDAL::preparePdoQuery($sql);
6244  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6245  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6246  MatrixDAL::bindValueToPdo($query, 'updated_userid', $GLOBALS['SQ_SYSTEM']->currentUserId());
6247  MatrixDAL::execPdoQuery($query);
6248  } catch (Exception $e) {
6249  throw new Exception('Unable to update the link due to database error: '.$e->getMessage());
6250  }
6251 
6252  $this->forgetAsset($major);
6253 
6254  } else {
6255 
6256  if ($sort_order_changed) {
6257 
6258  $row = NULL;
6259  try {
6260  $sql = 'SELECT COUNT(*) AS count, MAX(sort_order) AS max
6261  FROM
6262  sq_ast_lnk
6263  WHERE
6264  majorid = :majorid';
6265 
6266  $query = MatrixDAL::preparePdoQuery($sql);
6267  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6268  $result = MatrixDAL::executePdoAssoc($query);
6269  $row = $result[0];
6270 
6271  } catch (Exception $e) {
6272  throw new Exception('Unable to get execute the query: '.$e->getMessage());
6273  }
6274 
6275  $max = ($row['count'] > 0) ? (int) $row['max'] : 0;
6276  if ($sort_order > $max || $sort_order < 0) {
6277  $sort_order = $max;
6278  }
6279 
6280  $set_clauses['sort_order'] = MatrixDAL::quote($sort_order);
6281 
6282  $msg_reps = Array(
6283  'linkid' => $linkid,
6284  'major_name' => $major->name,
6285  'minor_name' => $minor->name,
6286  'old_sort_order' => $link['sort_order'],
6287  'new_sort_order' => $sort_order,
6288  );
6289  $message = $ms->newMessage(Array(), 'asset.linking.order', $msg_reps);
6290  $message->parameters['majorid'] = $major->id;
6291  $message->parameters['minorid'] = $minor->id;
6292  $message->parameters['linkid'] = $linkid;
6293  $ms->logMessage($message);
6294  }
6295 
6296  if (empty($set_clauses)) {
6297  $ms->abortLog();
6298  return TRUE;
6299  }
6300  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6301  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6302  $db = MatrixDAL::getDb();
6303 
6304  $set_clauses['updated'] = MatrixDAL::quote(MatrixDAL::executeOne('core', 'toDate', Array('date_value' => ts_iso8601(time()))));
6305 
6306  $set_array = Array();
6307 
6308  foreach ($set_clauses as $key => $value) {
6309  $set_array[] = $key.'='.$value;
6310  }
6311 
6312  $set_string = implode(',', $set_array);
6313 
6314  $sql = 'UPDATE
6315  sq_ast_lnk
6316  SET
6317  updated_userid = :updated_userid,
6318  '.$set_string.'
6319  WHERE
6320  linkid = :linkid
6321  AND majorid = :majorid';
6322  try {
6323  $query = MatrixDAL::preparePdoQuery($sql);
6324  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6325  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6326  MatrixDAL::bindValueToPdo($query, 'updated_userid', $GLOBALS['SQ_SYSTEM']->currentUserId());
6327  MatrixDAL::execPdoQuery($query);
6328  } catch (Exception $e) {
6329  throw new Exception('Unable to update the link due to database error: '.$e->getMessage());
6330  }
6331 
6332  if ($sort_order_changed) {
6333  // move 'em up, higher
6334  if ($link['sort_order'] > $sort_order) {
6335 
6336  $sql = 'UPDATE
6337  sq_ast_lnk
6338  SET
6339  sort_order = sort_order + 1
6340  WHERE
6341  majorid = :majorid
6342  AND linkid <> :linkid
6343  AND sort_order >= :sort_order_1
6344  AND sort_order <= :sort_order_2';
6345 
6346  try {
6347  $query = MatrixDAL::preparePdoQuery($sql);
6348  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6349  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6350  MatrixDAL::bindValueToPdo($query, 'sort_order_1', $sort_order);
6351  MatrixDAL::bindValueToPdo($query, 'sort_order_2', $link['sort_order']);
6352  MatrixDAL::execPdoQuery($query);
6353  } catch (Exception $e) {
6354  throw new Exception('Unable to update the link due to database error: '.$e->getMessage());
6355  }
6356 
6357  } else {
6358 
6359  $sql = 'UPDATE
6360  sq_ast_lnk
6361  SET
6362  sort_order = sort_order - 1
6363  WHERE
6364  majorid = :majorid
6365  AND linkid <> :linkid
6366  AND sort_order >= :sort_order_1
6367  AND sort_order <= :sort_order_2';
6368 
6369  try {
6370  $query = MatrixDAL::preparePdoQuery($sql);
6371  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6372  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6373  MatrixDAL::bindValueToPdo($query, 'sort_order_1', $link['sort_order']);
6374  MatrixDAL::bindValueToPdo($query, 'sort_order_2', $sort_order);
6375  MatrixDAL::execPdoQuery($query);
6376  } catch (Exception $e) {
6377  throw new Exception('Unable to update the link due to database error: '.$e->getMessage());
6378  }
6379  }
6380 
6381  }//end if
6382  }//end else
6383 
6384 
6385  if ($link_type_changed) {
6386  // if this is a web path link or if the old link type was,
6387  // then we need to inform the asset to update it's lookups
6388  if (($link_type & SQ_SC_LINK_WEB_PATHS) || ((int) $link['link_type'] & SQ_SC_LINK_WEB_PATHS)) {
6389  if (!$minor->updateLookups()) {
6390  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
6391  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6392  $ms->abortLog();
6393  return FALSE;
6394  }
6395  }
6396  }
6397 
6398 
6399 
6400  // tell, the asset it has updated
6401  $major->linksUpdated();
6402  $minor->linksUpdated();
6403 
6404  $ms->closeLog();
6405  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6406  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6407  return TRUE;
6408 
6409  }//end updateLink()
6410 
6411 
6421  function deleteAssetLink($linkid, $check_locked=TRUE)
6422  {
6423  if ($linkid == 0) return FALSE;
6424  // before anything else is done, check if this is a shadow link, i.e. a link that is managed by a bridge
6425  $id_parts = explode(':', $linkid);
6426  if (isset($id_parts[1])) {
6427  $bridge = $this->getAsset($id_parts[0]);
6428  $result = $bridge->deleteAssetLink($linkid);
6429  $this->forgetAsset($bridge);
6430  return $result;
6431  }
6432 
6433  // Use the master DB since we are changing things
6434  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6435 
6436  // First, we should try and find the link
6437  $link = $this->getLinkById($linkid);
6438  if (empty($link)) {
6439  trigger_localised_error('SYS0137', E_USER_WARNING, $linkid);
6440  return FALSE;
6441  }
6442 
6443  return $this->deleteAssetLinkByLink($link, $check_locked);
6444 
6445  }//end deleteAssetLink()
6446 
6447 
6462  function deleteAssetLinkByLink(Array $link, $check_locked=TRUE, $aborting=FALSE)
6463  {
6464  if (empty($link)) {
6465  return FALSE;
6466  }
6467  $linkid = $link['linkid'];
6468 
6469  // Check if link is locked and cannot be deleted
6470  if ($check_locked && $link['locked'] == '1') {
6471  trigger_localised_error('SYS0334', E_USER_WARNING, $linkid);
6472  return FALSE;
6473  }//end if
6474 
6475  $major = $this->getAsset($link['majorid'], $link['major_type_code']);
6476  $minor = $this->getAsset($link['minorid'], $link['minor_type_code']);
6477  if (is_null($major) || is_null($minor)) {
6478  return FALSE;
6479  }
6480 
6481  $asset_was_locked = FALSE;
6482  $parent_was_locked = FALSE;
6483 
6484  if (!$aborting) {
6485  // Acquire some locks if necessary
6486  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
6487  // Keep a record of the locking state beforehand so we can leave it as we found it
6488  $lock_info = @$this->getLockInfo($major->id, 'links');
6489  $parent_was_locked = !empty($lock_info);
6490  if (!$this->acquireLock($major->id, 'links')) {
6491  trigger_localised_error('SYS0126', E_USER_WARNING, 'major', $major->name);
6492  return FALSE;
6493  }
6494  // To check if we can delete this link, we need to lock current asset
6495  $minor_lock_info = @$this->getLockInfo($minor->id, 'links');
6496  $asset_was_locked = !empty($minor_lock_info);
6497  if (!$this->acquireLock($minor->id, 'links')) {
6498  trigger_localised_error('SYS0126', E_USER_WARNING, 'minor', $minor->name);
6499  return FALSE;
6500  }
6501  }
6502 
6503  // Check permissions if necessary
6504  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LINK_INTEGRITY)) {
6505  if ($link['link_type'] == SQ_LINK_NOTICE) {
6506  // to delete a notice link we need effective write access to the major asset
6507  if (!($major->writeAccess() && $major->accessEffective()) && empty($GLOBALS['SQ_PURGING_TRASH'])) {
6508  trigger_localised_error('SYS0319', E_USER_WARNING, $linkid, $major->id, $major->name);
6509  return FALSE;
6510  }
6511  } else {
6512  // to delete a significant link we need write permission (but not
6513  // necessarily effective write access) to both assets
6514  if (($err_msg = $minor->canDeleteLink($linkid)) !== TRUE) {
6515  trigger_localised_error('SYS0318', E_USER_WARNING, $linkid, $minor->id, $minor->name, $err_msg);
6516  return FALSE;
6517  }
6518  if (($err_msg = $major->canDeleteLink($linkid)) !== TRUE) {
6519  trigger_localised_error('SYS0302', E_USER_WARNING, $linkid, $major->id, $major->name, $err_msg);
6520  return FALSE;
6521  }
6522  }
6523  }
6524  }
6525  $minorid_parts = explode(':', $link['minorid']);
6526  if (isset($minorid_parts[1])) {
6527  // Shadow asset found
6528  if (implements_interface($major, 'bridge')) {
6529  // Let the bridge handle it
6530  $result = $major->deleteAssetLink($linkid);
6531  $this->forgetAsset($asset);
6532  if ($result != FALSE) return $result;
6533  }
6534  // Link between a normal asset and a shadow asset
6535  $this->deleteShadowAssetLink($linkid);
6536  return TRUE;
6537 
6538  }//end if shadow asset
6539  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
6540  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db3');
6541  $db = MatrixDAL::getDb();
6542 
6543  if ($link['link_type'] & SQ_SC_LINK_SIGNIFICANT) {
6544  // This is a significant link
6545 
6546  $num_other_links = $this->countLinks($minor->id, 'minor', SQ_SC_LINK_SIGNIFICANT, '', TRUE, $linkid);
6547 
6548  if (!$GLOBALS['SQ_PURGING_TRASH'] && ($num_other_links <= 1)) {
6549  $linked_outside_trash = FALSE;
6550  if ($num_other_links == 1) {
6551  // we need to see if the other remaining link is to the trash
6552  $trash_links = $this->getLinks($minor->id, SQ_SC_LINK_SIGNIFICANT, 'trash_folder', TRUE, 'minor');
6553  if (empty($trash_links)) {
6554  // the other link must be to something else
6555  $linked_outside_trash = TRUE;
6556  }
6557  }
6558  if (!$linked_outside_trash) {
6559  // it's about to become linked to the trash only, so run the trigger
6560  if (!$GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_before_asset_deleted', $minor, Array())) {
6561  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db3
6562  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db2
6563  return FALSE;
6564  }
6565  }
6566  }
6567  if (!$aborting) {
6568  // we create a new link to the trash if this is the last significant link
6569  // being deleted and if we are not purging the trash. We check the moving
6570  // flag so that if we are the last significant link, we dont create a link
6571  // to the trash as the move operation will re-create the link elsewhere
6572  if (!$num_other_links && !$GLOBALS['SQ_PURGING_TRASH']) {
6573 
6574  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LINK_INTEGRITY)) {
6575 
6576  // some assets may not be able to have their last significant link
6577  // deleted, so lets check first before going ahead
6578  if (!$minor->canDelete()) {
6579  trigger_localised_error('SYS0069', E_USER_WARNING, $minor->name);
6580  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db3
6581  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db2
6582  return FALSE;
6583  }
6584 
6585  $trash_folder = $this->getSystemAsset('trash_folder');
6586  if (is_null($trash_folder)) {
6587  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db3
6588  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db2
6589  return FALSE;
6590  }
6591 
6592  // this asset is destined for the trash, so we need to set its permissions to
6593  // whatever it is inheriting right now so it keeps them while in the trash
6594  $perms = Array(SQ_PERMISSION_ADMIN, SQ_PERMISSION_WRITE, SQ_PERMISSION_READ);
6595  foreach ($perms as $perm) {
6596  $all_permissions = $this->getPermission($minor->id, $perm, NULL, FALSE, FALSE, TRUE);
6597  foreach ($all_permissions as $userid => $granted) {
6598  // we are deliberatly not checking for the return value here
6599  // because if for some reason the permission can't be set that is really just
6600  // bad luck
6601  @$this->setPermission($minor->id, $userid, $perm, $granted);
6602  }
6603  }
6604 
6605  if (!$trash_folder->createLink($minor, SQ_LINK_TYPE_2)) {
6606  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db3
6607  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // end db2
6608  return FALSE;
6609  }
6610  }//end if
6611  }//end if last significant link
6612  }
6613 
6615 
6616  // update the parents to tell them that they are going to be one kid less
6617  $sql = 'UPDATE
6618  sq_ast_lnk_tree
6619  SET
6620  num_kids = num_kids - 1
6621  WHERE
6622  treeid IN
6623  (
6624  SELECT
6625  CASE WHEN
6626  LENGTH(SUBSTR(t.treeid, 1, LENGTH(t.treeid) - '.SQ_CONF_ASSET_TREE_SIZE.')) != 0
6627  THEN
6628  SUBSTR(t.treeid, 1, LENGTH(t.treeid) - '.SQ_CONF_ASSET_TREE_SIZE.')
6629  ELSE
6630  \'-\'
6631  END
6632  FROM
6633  sq_ast_lnk_tree t
6634  WHERE
6635  t.linkid = :linkid
6636  )';
6637 
6638  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6639  try {
6640  $query = MatrixDAL::preparePdoQuery($sql);
6641  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6642  MatrixDAL::execPdoQuery($query);
6643  } catch (Exception $e) {
6644  throw new Exception('Unable to update the link tree for linkid: '.$linkid.' due to database error: '.$e->getMessage());
6645  }
6646  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6647 
6648  // we can delete all the links under these nodes because it will be a clean start
6649  // when we insert into the gap's we create below
6650 
6651  $sql = 'DELETE FROM
6652  sq_ast_lnk_tree
6653  WHERE
6654  treeid in
6655  (
6656  SELECT
6657  ct.treeid
6658  FROM
6659  sq_ast_lnk_tree pt, sq_ast_lnk_tree ct
6660  WHERE
6661  pt.linkid = :linkid
6662  AND (ct.treeid LIKE pt.treeid || '.'\''.'%'.'\''.'
6663  OR ct.treeid = pt.treeid)
6664  )';
6665 
6666  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6667  try {
6668  $query = MatrixDAL::preparePdoQuery($sql);
6669  MatrixDAL::bindValueToPdo($query, 'linkid', $linkid);
6670  MatrixDAL::execPdoQuery($query);
6671  } catch (Exception $e) {
6672  throw new Exception('Unable to delete tree links for linkid: '.$linkid.' due to database error: '.$e->getMessage());
6673  }
6674  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6675 
6676  }//end if significant link
6677 
6678  // Update sort orders of other children of this parent
6679  $sql = 'UPDATE
6680  sq_ast_lnk
6681  SET
6682  sort_order = sort_order - 1
6683  WHERE
6684  majorid = :majorid
6685  AND sort_order > :sort_order';
6686 
6687  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6688  try {
6689  $query = MatrixDAL::preparePdoQuery($sql);
6690  MatrixDAL::bindValueToPdo($query, 'majorid', $major->id);
6691  MatrixDAL::bindValueToPdo($query, 'sort_order', $link['sort_order']);
6692  MatrixDAL::execPdoQuery($query);
6693  } catch (Exception $e) {
6694  throw new Exception('Unable to update sort orders for majorid: '.$major->id.' due to database error: '.$e->getMessage());
6695  }
6696  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6697 
6698  // Delete from the link table
6699  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6700  try {
6701  $bind_vars = Array (
6702  'linkid' => $linkid,
6703  'majorid' => $major->id,
6704  );
6705  MatrixDAL::executeQuery('core', 'deleteLink', $bind_vars);
6706  } catch (Exception $e) {
6707  throw new Exception('Unable to delete link with linkid: '.$linkid.' due to database error: '.$e->getMessage());
6708  }
6709  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6710 
6711  // Tell the assets that they have been updated
6712  $major->linksUpdated();
6713  $minor->linksUpdated();
6714 
6715  // Release locks on parent and/or asset if we acquired them
6716  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
6717  if (!$parent_was_locked) {
6718  $this->releaseLock($major->id, 'links'); // we don't care if it fails, it's too late to reverse anything
6719  }
6720  if (!$asset_was_locked) {
6721  $this->releaseLock($minor->id, 'links');
6722  }
6723  }
6724  if (!$aborting) {
6725  // Send Internal Message
6726  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
6727  $msg_reps = Array(
6728  'major_name' => $major->name,
6729  'minor_name' => $minor->name,
6730  );
6731  $message = $ms->newMessage(Array(), 'asset.linking.delete', $msg_reps);
6732  $message->parameters['majorid'] = $major->id;
6733  $message->parameters['minorid'] = $minor->id;
6734  $message->parameters['linkid'] = $linkid;
6735  $message->send();
6736 
6737  // Broadcast Event
6738  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
6739  $em->broadcastEvent($major, 'DeleteLink', Array('linkid' => $linkid));
6740 
6741  // Fire triggers
6742  if ($major->type() != 'trash_folder' && $minor->type() != 'trash_folder') {
6743  $event_data = Array('link_info' => $link, 'linkid' => $linkid);
6744  if (!$this->assetInTrash($minor->id, TRUE)) {
6745  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_link_deleted', $minor, $event_data);
6746  }
6747  if (!$this->assetInTrash($major->id, TRUE)) {
6748  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_link_deleted', $major, $event_data);
6749  }
6750  }
6751  }
6752 
6753  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection(); // finished with db2
6754 
6755  return TRUE;
6756 
6757  }//end deleteAssetLinkByLink()
6758 
6759 
6768  function deleteShadowAssetLink($linkid)
6769  {
6770  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6771  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6772 
6773  $db = MatrixDAL::getDb();
6774  try {
6775  $bind_vars = Array('linkid' => $linkid);
6776  $result = MatrixDAL::executeQuery('core', 'deleteShadowAssetLink', $bind_vars);
6777  } catch (Exception $e) {
6778  throw new Exception('Unable to delete shadow asset link with linkid: '.$linkid.' due to database error: '.$e->getMessage());
6779  }
6780 
6781  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6782  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6783 
6784  return TRUE;
6785 
6786  }//end deleteShadowAssetLink()
6787 
6788 
6799  function couldTrashAsset($assetid, $check_locked=TRUE)
6800  {
6801  if (!$assetid) return FALSE;
6802  // We can trash the asset if we can delete all the parent links
6803  $msgs = Array();
6804  $parent_links = $this->getLinks($assetid, SQ_SC_LINK_ALL, '', TRUE, 'minor');
6805  foreach ($parent_links as $id => $link_details) {
6806  $asset = $this->getAsset($link_details['majorid']);
6807  $msg = $asset->canDeleteLink($link_details['linkid']);
6808  if (($check_locked && $link_details['locked'] == '1') || TRUE !== $msg) {
6809  $msgs[$link_details['linkid']] = $msg;
6810  }
6811  }
6812  return empty($msgs) ? TRUE : $msgs;
6813 
6814  }//end couldTrashAsset()
6815 
6816 
6833  function canSafeTrashAsset($assetid, $ignore_linkid=0, $ignore_other_links=TRUE)
6834  {
6835  $trash_errors = Array();
6836 
6837  // if its a shadow asset, this check is irrelevant
6838  if (strpos($assetid, ':') !== FALSE) {
6839  return $trash_errors;
6840  }
6841 
6842  $asset = $this->getAsset($assetid);
6843 
6844  // If we are ignoring other backend links (so we can provide a status),
6845  // then skip this part and always run the links check. Otherwise, we
6846  // don't consider links a problem if we are not the last link going into
6847  // the trash, and therefore only look at links if there are no more
6848  // links outside the trash
6849 
6850  if (!$ignore_other_links) {
6851  $trash_assetid = $this->getSystemAssetid('trash_folder');
6852  $trash_link = $this->getLinkByAsset($trash_assetid, $asset->id, SQ_LINK_TYPE_1 | SQ_LINK_TYPE_2);
6853  $num_other_links = $this->countLinks($asset->id, 'minor', SQ_LINK_TYPE_1 + SQ_LINK_TYPE_2, '', TRUE, $ignore_linkid);
6854 
6855  if (!empty($trash_link)) $num_other_links--;
6856  } else {
6857  $num_other_links = 0;
6858  }
6859 
6860  if (!$num_other_links) {
6861  // Get all the TYPE_3|NOTICE links this asset has in the system and
6862  // display them to the user
6863  // note that we are also going to ask the major asset in the link
6864  // to describe it so it makes sense to the user looking at it
6865  // NB: if there is a Safe Trash cron job attached to this, don't
6866  // count that towards this
6867  $affected_links = $this->getLinks($asset->id, SQ_LINK_TYPE_3 | SQ_LINK_NOTICE, '', TRUE, 'minor');
6868  $safe_trash_cron_job = $this->getLinks($asset->id, SQ_LINK_TYPE_3 | SQ_LINK_NOTICE, 'cron_job_attempt_safe_trash', TRUE, 'minor');
6869 
6870  // We can't use array_diff() because it does a string comparison
6871  // ("Array" == "Array".... ooh nasty). So we will trawl through the
6872  // array to find the cron job and pluck it out
6873  if (!empty($safe_trash_cron_job)) {
6874  foreach (array_keys($affected_links) as $affected_link_key) {
6875  if ($affected_links[$affected_link_key]['linkid'] == $safe_trash_cron_job[0]['linkid']) {
6876  unset($affected_links[$affected_link_key]);
6877  break;
6878  }
6879  }
6880  }
6881 
6882  if (!empty($affected_links)) {
6883  // if type3 or notice linked to other asset, it is not safe to be trashed
6884  $trash_errors['links'] = $affected_links;
6885  }
6886  }
6887 
6888  // if the status is not under construction and not archived, it is not safe to be trashed
6889  if ($asset->status >= SQ_STATUS_LIVE) {
6890  $trash_errors['status'] = $asset->status;
6891  }
6892 
6893  // check the same thing for all children
6894  $children = $this->getChildren($assetid, '', TRUE, FALSE);
6895 
6896  if (!empty($children)) {
6897  try {
6898  $children_ids = array_keys($children);
6899  $bind_vars = Array(
6900  'link_type' => Array(SQ_LINK_TYPE_3, SQ_LINK_NOTICE),
6901  'status' => SQ_STATUS_LIVE,
6902  'assetid' => $children_ids,
6903  );
6904  $result = MatrixDAL::executeAssoc('core', 'canSafeTrashAsset', $bind_vars);
6905  } catch (Exception $e) {
6906  throw new Exception('Unable to check safe trash condition: '.$e->getMessage());
6907  }
6908 
6909  if (!empty($result)) {
6910  $used_minorids = Array();
6911  // Reduce the children array so there is only one entry for
6912  // each violating asset
6913  foreach (array_keys($result) as $result_key) {
6914  $minorid = $result[$result_key]['minorid'];
6915  if (in_array($minorid, $used_minorids)) {
6916  unset($result[$result_key]);
6917  } else {
6918  $used_minorids[] = $minorid;
6919  }//end if
6920  }
6921 
6922  $trash_errors['children'] = $result;
6923  }//end if
6924  }//end if
6925 
6926  return $trash_errors;
6927 
6928  }//end canSafeTrashAsset()
6929 
6930 
6942  function trashAsset($assetid, $force_trash=FALSE)
6943  {
6944  if ($this->assetInTrash($assetid, TRUE)) {
6945  // already in trash exclusively
6946  return TRUE;
6947  }
6948 
6949  // trash requests are charged at $4.95 a minute
6950  // please ask your parents for permission before trashing
6951  $parent_links = $this->getLinks($assetid, SQ_SC_LINK_ALL, '', TRUE, 'minor');
6952 
6953  $locked = Array();
6954  foreach ($parent_links as $index => $link_details) {
6955  $asset = $this->getAsset($link_details['majorid']);
6956  // try to acquire the lock
6957  if ($this->acquireLock($link_details['majorid'], 'links')) {
6958  $locked[] = $link_details['majorid'];
6959  } else {
6960  trigger_localised_error('SYS0119', E_USER_WARNING, $assetid, $link_details['majorid']);
6961  if ($force_trash) {
6962  unset($parent_links[$index]);
6963  continue;
6964  } else {
6965  foreach ($locked as $aid) {
6966  $this->releaseLock($aid, 'links');
6967  }
6968  return FALSE;
6969  }
6970  }
6971  if (TRUE !== ($msg = $asset->canDeleteLink($link_details['linkid']))) {
6972  trigger_localised_error('SYS0120', E_USER_WARNING, $assetid, $link_details['majorid'], $msg);
6973  if ($force_trash) {
6974  unset($parent_links[$index]);
6975  continue;
6976  } else {
6977  foreach ($locked as $aid) {
6978  $this->releaseLock($aid, 'links');
6979  }
6980  return FALSE;
6981  }
6982  }
6983  }//end foreach
6984 
6985  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6986  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6987  $trashid = $this->getSystemAssetid('trash_folder');
6988 
6989  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() | SQ_SECURITY_LINK_INTEGRITY);
6990  foreach ($parent_links as $link_details) {
6991  // don't delete the link to the trash, we don't want to end up with an orphan asset
6992  if ($trashid == $link_details['majorid']) continue;
6993 
6994  // deleteAssetLink will create the trash link when no other links remain
6995  if (!$this->deleteAssetLink($link_details['linkid'])) {
6996  trigger_localised_error('SYS0152', E_USER_WARNING, $link_details['linkid']);
6997  foreach ($locked as $aid) {
6998  $this->releaseLock($aid, 'links');
6999  }
7000  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
7001  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7002  return FALSE;
7003  }
7004  }
7005  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
7006 
7007  foreach ($locked as $aid) {
7008  $this->releaseLock($aid, 'links');
7009  }
7010  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
7011  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7012  return TRUE;
7013 
7014  }//end trashAsset()
7015 
7016 
7017 //-- PERMISSIONS --//
7018 
7019 
7042  function getPermission($assetid, $permission, $granted=NULL, $and_greater=TRUE, $expand_groups=FALSE, $all_info=FALSE, $collapse_roles=FALSE)
7043  {
7044  $permission = (int) $permission;
7045  if (!is_null($granted)) $granted = (bool) $granted;
7046 
7047  // check if we are getting permissions for a shadow asset, and palm the request off to the
7048  // handler of the shadow asset if we are
7049  $id_parts = explode(':', $assetid);
7050 
7051  if (isset($id_parts[1])) {
7052  $real_assetid = $id_parts[0];
7053  $asset = $this->getAsset($real_assetid);
7054 
7055  if (!is_null($asset)) {
7056  if (method_exists($asset, 'getPermission')) {
7057  $ret_val = $asset->getPermission($assetid, $permission, $granted, $and_greater, $expand_groups, $all_info);
7058  } else {
7059  $ret_val = $GLOBALS['SQ_SYSTEM']->am->getPermission($real_assetid, $permission, $granted, $and_greater, $expand_groups, $all_info);
7060  }
7061  $this->forgetAsset($asset);
7062 
7063  return $ret_val;
7064  }
7065  }
7066 
7067  if (($and_greater || $expand_groups) && $all_info) {
7068  trigger_localised_error('SYS0273', E_USER_NOTICE, __CLASS__, __FUNCTION__);
7069  $all_info = FALSE;
7070  }
7071 
7072  if (!isset($this->_tmp['permission_cache'])) {
7073  $this->_tmp['permission_cache'] = Array();
7074  }
7075  if (!isset($this->_tmp['permission_cache'][$assetid])) {
7076  $this->_tmp['permission_cache'][$assetid] = Array();
7077  }
7078 
7079  // return cached version if we can
7080  $suffix = $collapse_roles ? '1' : '0';
7081  $storage_name =(($and_greater) ? 'effective_' : '').'permission_'.$permission.'_'.$suffix;
7082  if (!isset($this->_tmp['permission_cache'][$assetid][$storage_name])) {
7083 
7084  $db = MatrixDAL::getDb();
7085 
7086  $table = ($collapse_roles) ? 'ast_perm' : 'vw_ast_perm';
7087  $sql = ' SELECT DISTINCT assetid, userid, granted
7088  FROM '.SQ_TABLE_RUNNING_PREFIX.$table.' ';
7089  $where = 'assetid = :assetid
7090  AND permission '.(($and_greater) ? '>= ' : '= ').':permission';
7091  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
7092 
7093 
7094  try {
7095  $query = MatrixDAL::preparePdoQuery($sql.$where);
7096  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid, PDO::PARAM_STR);
7097  MatrixDAL::bindValueToPdo($query, 'permission', $permission, PDO::PARAM_INT);
7098  $result = MatrixDAL::executePdoAll($query);
7099  } catch (Exception $e) {
7100  throw new Exception('Unable to get permissions of asset ID #'.$assetid.' due to database error: '.$e->getMessage());
7101  }
7102 
7103  // cache the result for next time
7104  $this->_tmp['permission_cache'][$assetid][$storage_name] = $result;
7105 
7106  }//end if
7107 
7108  $ret_val = Array();
7109  foreach ($this->_tmp['permission_cache'][$assetid][$storage_name] as $data) {
7110 
7111  if ($granted === FALSE && $data['granted'] != '0') {
7112  continue;
7113  } else if ($granted === TRUE && $data['granted'] != '1') {
7114  continue;
7115  }
7116 
7117  // return all info
7118  if ($all_info) {
7119  $ret_val[$data['userid']] = $data['granted'];
7120 
7121  // else we just want user ids
7122  } else {
7123 
7124  // if we are expanding user groups and we aren't public access
7125  if ($expand_groups && $data['userid']) {
7126  $user = $this->getAsset($data['userid']);
7127  if (!is_null($user)) {
7128  if ($user instanceof User_Group) {
7129  $ret_val = array_merge($ret_val, array_keys($this->getChildren($user->id, Array('user'), FALSE)));
7130  }
7131  // always include the current user/group id, even if it is a user group
7132  $ret_val[] = $user->id;
7133  }
7134  } else {
7135  $ret_val[] = $data['userid'];
7136  }
7137 
7138  }//end if all info
7139 
7140  }//end foreach
7141 
7142  if ($all_info) {
7143  return $ret_val;
7144  } else {
7145  return array_unique($ret_val);
7146  }
7147 
7148  }//end getPermission()
7149 
7150 
7157  function getAssetPermissionByCascade($assetid, $permission, $userid=NULL, $cascades=NULL)
7158  {
7159  $sql = ' SELECT DISTINCT userid, granted, cascades
7160  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_perm ';
7161  $where = 'assetid = :assetid AND permission = :permission';
7162  if (!is_null($cascades)) {
7163  $where .= ' AND cascades = :cascades';
7164  }
7165  if (!is_null($userid)) {
7166  $where .= ' AND userid = :userid';
7167  }
7168  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
7169 
7170  try {
7171  $query = MatrixDAL::preparePdoQuery($sql.$where);
7172  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid, PDO::PARAM_STR);
7173  MatrixDAL::bindValueToPdo($query, 'permission', $permission, PDO::PARAM_STR);
7174  if (!is_null($cascades)) {
7175  MatrixDAL::bindValueToPdo($query, 'cascades', $cascades ? '1' : '0', PDO::PARAM_STR);
7176  }
7177  if (!is_null($userid)) {
7178  MatrixDAL::bindValueToPdo($query, 'userid', $userid, PDO::PARAM_STR);
7179  }
7180  $result = MatrixDAL::executePdoAll($query);
7181  } catch (Exception $e) {
7182  throw new Exception('Unable to get set permissions of asset ID #'.$assetid.' due to database error: '.$e->getMessage());
7183  }
7184 
7185  return $result;
7186 
7187  }//end getAssetPermissionByCascade()
7188 
7189 
7203  function setPermission($assetid, $userid, $permission, $granted, $cascades=TRUE, $force_set=FALSE)
7204  {
7205  $permission = (int) $permission;
7206  $granted = (bool) $granted;
7207  $db_action = 'insert';
7208 
7209  // check if we are getting links for a shadow asset, and palm the request off to the
7210  // handler of the shadow asset if we are
7211  $id_parts = explode(':', $assetid);
7212 
7213  if (isset($id_parts[1])) {
7214  $real_assetid = $id_parts[0];
7215  $asset = $this->getAsset($real_assetid);
7216 
7217  $ret_val = NULL;
7218  if (is_null($asset)) continue;
7219  if (method_exists($asset, 'setPermission')) {
7220  $ret_val = $asset->setPermission($assetid, $userid, $permission, $granted);
7221  $this->forgetAsset($asset);
7222  }
7223 
7224  return $ret_val;
7225  }
7226 
7227  $asset = $this->getAsset($assetid);
7228  if (is_null($asset)) return FALSE;
7229  if (!$asset->adminAccess('permissions')) {
7230  trigger_localised_error('SYS0111', E_USER_WARNING, $asset->name);
7231  return FALSE;
7232  }
7233 
7234  // check if this permission is already set
7235  $current = $this->getPermission($assetid, $permission, $granted, FALSE, FALSE, FALSE, TRUE);
7236  if (in_array($userid, array_values($current))) {
7237  // Check whether the cascade setting is also matched
7238  $perm_info = $this->getAssetPermissionByCascade($assetid, $permission, $userid, $cascades);
7239  if (!empty($perm_info)) {
7240  return TRUE;
7241  } else {
7242  // We are merely changing the cascade setting, so
7243  // set it to update
7244  $db_action = 'update';
7245  }
7246  }
7247 
7248  // if we dont have an userid, we are granting public access
7249  if (!empty($userid)) {
7250  // check that the passed userid is a user or user_group
7251  $user = $this->getAsset($userid, '', TRUE);
7252  if (!$user->id) {
7253  trigger_localised_error('SYS0112', E_USER_WARNING);
7254  return FALSE;
7255  } else if (!($user instanceof User) && !($user instanceof User_Group)) {
7256  trigger_localised_error('SYS0113', E_USER_WARNING, $user->type());
7257  return FALSE;
7258  }
7259  $user_name = $user->name;
7260  } else {
7261  $user_name = 'General Public';
7262  }
7263  // check if the reverse access of this permission is set
7264  $current = $this->getPermission($assetid, $permission, !$granted, FALSE, FALSE, FALSE, TRUE);
7265  if (in_array($userid, array_values($current))) {
7266  if ($force_set) {
7267  $db_action = 'update';
7268  } else {
7269  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
7270  $perm_name = permission_type_name($permission);
7271 
7272  $new_access = ($granted) ? 'grant' : 'revoke';
7273  $current_access = ($granted) ? 'revoked' : 'granted';
7274  trigger_localised_error('SYS0123', E_USER_WARNING, $new_access, $perm_name, $user_name, $asset->name, $current_access);
7275  return FALSE;
7276  }
7277  }
7278 
7279  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
7280  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
7281 
7282  $db = MatrixDAL::getDb();
7283 
7284  switch ($db_action) {
7285  case 'insert':
7286  $sql = 'INSERT INTO
7287  sq_ast_perm
7288  (
7289  assetid,
7290  userid,
7291  permission,
7292  granted,
7293  cascades
7294  )
7295  VALUES
7296  (
7297  :assetid,
7298  :userid,
7299  :permission,
7300  :granted,
7301  :cascades
7302  )';
7303 
7304  break;
7305  case 'update':
7306  $sql = 'UPDATE
7307  sq_ast_perm
7308  SET
7309  granted = :granted,
7310  cascades = :cascades
7311  WHERE
7312  assetid = :assetid
7313  AND userid = :userid
7314  AND permission = :permission';
7315 
7316  break;
7317  }//end switch
7318 
7319  try {
7320  $query = MatrixDAL::preparePdoQuery($sql);
7321  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
7322  MatrixDAL::bindValueToPdo($query, 'userid', $userid);
7323  MatrixDAL::bindValueToPdo($query, 'permission', $permission);
7324  MatrixDAL::bindValueToPdo($query, 'granted', $granted ? '1' : '0', PDO::PARAM_STR);
7325  MatrixDAL::bindValueToPdo($query, 'cascades', $cascades ? '1' : '0', PDO::PARAM_STR);
7326  MatrixDAL::execPdoQuery($query);
7327  } catch (Exception $e) {
7328  throw new Exception('Unable to '.$db_action.' permissions for asset "'.$asset->name.'" (#'.$asset->id.') due to database error: '.$e->getMessage());
7329  }
7330 
7331  if (!$asset->permissionsUpdated()) {
7332  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
7333  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7334  return FALSE;
7335  }
7336 
7337  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
7338  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7339 
7340  // clear the permission cache
7341  if (isset($this->_tmp['permission_cache'][$assetid])) {
7342  unset($this->_tmp['permission_cache'][$assetid]);
7343  }
7344 
7345  // log the action
7346  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
7347  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
7348 
7349  $msg_reps = Array(
7350  'perm_name' => permission_type_name($permission),
7351  'asset_name' => $asset->name,
7352  'user_name' => $user_name,
7353  );
7354  $message = $ms->newMessage(Array(), 'asset.permissions.'.($granted ? 'grant' : 'deny'), $msg_reps);
7355  $message->parameters['assetid'] = $asset->id;
7356  $message->send();
7357 
7358  // notify anyone interested that permission changed
7359  $changed_array = Array(
7360  'perm_name' => permission_type_name($permission),
7361  'asset_name' => $asset->name,
7362  'user_name' => $user_name,
7363  );
7364  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
7365  $em->broadcastEvent($asset, 'PermissionChange', $changed_array);
7366 
7367  return TRUE;
7368 
7369  }//end setPermission()
7370 
7371 
7382  function deletePermission($assetid, $userid, $permission)
7383  {
7384  $permission = (int) $permission;
7385 
7386  // check if we are getting links for a shadow asset, and palm the request off to the
7387  // handler of the shadow asset if we are
7388  $id_parts = explode(':', $assetid);
7389 
7390  if (isset($id_parts[1])) {
7391  $real_assetid = $id_parts[0];
7392  $asset = $this->getAsset($real_assetid);
7393 
7394  if (is_null($asset)) continue;
7395 
7396  $ret_val = FALSE;
7397  if (method_exists($asset, 'deletePermission')) {
7398  $ret_val = $asset->deletePermission($assetid, $userid, $permission);
7399  $this->forgetAsset($asset);
7400  }
7401 
7402  return $ret_val;
7403  }
7404 
7405  $asset = $this->getAsset($assetid);
7406  if (is_null($asset)) return FALSE;
7407  if (!$asset->adminAccess('permissions')) {
7408  trigger_localised_error('SYS0104', E_USER_WARNING, $asset->name);
7409  return FALSE;
7410  }
7411 
7412  // if we dont have an userid, we are deleting public access
7413  if (!empty($userid)) {
7414  // if the permission is in the database then we know that it must have
7415  // been set up correctly, and if an user or LDAP user's asset has been deleted then we
7416  // know we want it out. So we'll use getAssetInfo() instead which does not throw an
7417  // assertion, just instead just gives an empty array
7418  $user_info = $this->getAssetInfo(Array($userid), 'user', FALSE);
7419 
7420  // check that the passed userid is a user or user_group
7421  if (empty($user_info)) {
7422  $user_name = 'Unknown User';
7423  } else {
7424  $user_name = $user_info[$userid]['name'];
7425  }
7426  } else {
7427  $user_name = 'General Public';
7428  }
7429 
7430  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
7431  $db = MatrixDAL::getDb();
7432 
7433  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
7434 
7435  try {
7436  $bind_vars = Array (
7437  'userid' => $userid,
7438  'permission' => $permission,
7439  'assetid' => $assetid,
7440  );
7441  $result = MatrixDAL::executeQuery('core', 'deletePermissionForUserOnAsset', $bind_vars);
7442  } catch (Exception $e) {
7443  throw new Exception('Unable to delete permission '.$permission.' of userid #'.$userid.' on assetid #'.$assetid .' due to the following database error.'.$e->getMessage());
7444  }//end try catch
7445 
7446  if (!$asset->permissionsUpdated()) {
7447  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
7448  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7449  return FALSE;
7450  }
7451 
7452  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
7453  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
7454 
7455  // clear the permission cache
7456  if (isset($this->_tmp['permission_cache'][$assetid])) {
7457  unset($this->_tmp['permission_cache'][$assetid]);
7458  }
7459 
7460  // log the action
7461  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
7462  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
7463 
7464  $msg_reps = Array(
7465  'perm_name' => permission_type_name($permission),
7466  'asset_name' => $asset->name,
7467  'user_name' => $user_name,
7468  );
7469  $message = $ms->newMessage(Array(), 'asset.permissions.delete', $msg_reps);
7470  $message->parameters['assetid'] = $asset->id;
7471  $message->send();
7472 
7473  // notify anyone interested that permission changed
7474  $changed_array = Array(
7475  'perm_name' => permission_type_name($permission),
7476  'asset_name' => $asset->name,
7477  'user_name' => $user_name,
7478  );
7479  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
7480  $em->broadcastEvent($asset, 'PermissionChange', $changed_array);
7481 
7482  return TRUE;
7483 
7484  }//end deletePermission()
7485 
7486 
7487 //-- ROLES --//
7488 
7489 
7510  function getRole($assetid=NULL, $roleid=NULL, $userid=NULL, $include_assetid=FALSE, $include_globals=FALSE, $expand_groups=FALSE, $inc_dependants=TRUE, $include_parents=FALSE, $type_codes=Array(), $strict_type_code=TRUE)
7511  {
7512  if (SQ_CONF_ENABLE_ROLES_PERM_SYSTEM == '0' && SQ_CONF_ENABLE_ROLES_WF_SYSTEM == '0') return Array();
7513 
7514  $db = MatrixDAL::getDb();
7515 
7516  $where = Array();
7517  if (!is_null($assetid)) {
7518  $where[] = 'r.assetid = :assetid';
7519  }
7520 
7521  if (!is_null($roleid)) {
7522  $where[] = 'r.roleid = :roleid';
7523  }
7524 
7525  if (!is_null($userid)) {
7526  // find all parent user groups of user id to search roles
7527  if($include_parents && !empty($userid)) {
7528  $parents = $GLOBALS['SQ_SYSTEM']->am->getParents($userid, 'user_group', FALSE);
7529  foreach ($parents as $usergroupid => $type_code) {
7530