Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
asset.inc
1 <?php
28 class Asset extends MySource_Object
29 {
30 
35  public $id = 0;
36 
41  public $version = '';
42 
47  public $name = '';
48 
53  public $short_name = '';
54 
59  public $status;
60 
65  public $languages = '';
66 
71  public $charset = '';
72 
80  public $force_secure = '0';
81 
86  public $created;
87 
92  public $created_userid;
93 
98  public $updated;
99 
104  public $updated_userid;
105 
110  public $published;
111 
116  public $published_userid;
117 
122  public $status_changed;
123 
128  public $status_changed_userid;
129 
134  public $_is_cacheable = FALSE;
135 
140  public $vars = Array();
141 
146  public $_available_keywords = Array();
147 
153  public $data_path_suffix = '';
154 
162  public $data_path = '';
163 
171  public $data_path_public = '';
172 
179  public $_ser_attrs = FALSE;
180 
181 
190  function Asset($assetid=0)
191  {
192  $this->MySource_Object();
193 
194  if ($assetid) {
195  $this->load($assetid);
196  } else {
197  $this->_loadVars();
198  }
199 
200  }//end constructor
201 
202 
227  public function create(Array &$link)
228  {
229  if ($this->id) {
230  trigger_localised_error('CORE0268', E_USER_WARNING, $this->type());
231  return;
232  }
233 
234  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
235  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
236  $db = MatrixDAL::getDb();
237 
238  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_DATA_VALIDATION)) {
239  // perform any validation before starting the create process
240  if (!$this->_preCreateCheck($link)) {
241  return $this->_abortCreate();
242  }
243 
244  if (!empty($link)) {
245  // make sure the initial link information is passed in
246  assert_isset_array_index($link, 'asset', 'Cannot create asset without an asset to link to');
247  assert_isset_array_index($link, 'link_type', 'Cannot create asset without a link type');
248  assert_not_empty(($link['link_type'] & SQ_SC_LINK_SIGNIFICANT), 'Cannot create asset with an insignificant link type');
249  }
250  }
251 
252  // disable the triggering system. we do not want to broadcast any nested events, only our own
253  $trigger_level_changed = FALSE;
254  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_TRIGGERS)) {
255  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() - SQ_SECURITY_TRIGGERS);
256  $trigger_level_changed = TRUE;
257  }
258 
259  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
260 
261  // let the system know that we are creating this asset so we dont
262  // do any updating of versions etc. until we are done
263  $this->_tmp['__creating__'] = TRUE;
264 
265  $assetid = MatrixDAL::executeOne('core', 'seqNextVal', Array('seqName' => 'sq_ast_seq'));
266  $name = ucwords(str_replace('_', ' ', $this->type())).' #'.$assetid;
267  $now = time();
268  $userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
269  $initial_version = '0.0.1';
270  $initial_status = SQ_STATUS_UNDER_CONSTRUCTION;
271  if (array_get_index($link, 'is_dependant') && ($link['asset']->status != SQ_STATUS_UNDER_CONSTRUCTION)) {
272  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
273  if (0 == count($wfm->getSchemas($link['asset']->id, TRUE))) {
274  // no workflow for parent
275  $initial_status = $link['asset']->status;
276  }
277  }
278 
279  try {
280  $now_iso = ts_iso8601($now);
281  $bind_vars = Array(
282  'assetid' => $assetid,
283  'version' => $initial_version,
284  'type_code' => $this->type(),
285  'name' => $name,
286  'short_name' => $name,
287  'status' => $initial_status,
288  'created' => $now_iso,
289  'created_userid' => $userid,
290  'updated' => $now_iso,
291  'updated_userid' => $userid,
292  'published' => NULL,
293  'published_userid' => NULL,
294  'status_changed' => $now_iso,
295  'status_changed_userid' => $userid,
296  );
297  MatrixDAL::executeQuery('core', 'createAsset', $bind_vars);
298  } catch (Exception $e) {
299  throw new Exception('Unable to create new asset "'.$name.'" (# "'.$assetid.'") due to database error: '.$e->getMessage());
300  }
301 
302  $this->id = $assetid;
303  $this->version = $initial_version;
304  $this->name = $name;
305  $this->short_name = $name;
306  $this->status = $initial_status;
307  $this->created = $now;
308  $this->created_userid = $userid;
309  $this->updated = $now;
310  $this->updated_userid = $userid;
311  $this->published = NULL;
312  $this->published_userid = NULL;
313  $this->status_changed = $now;
314  $this->status_changed_userid = $userid;
315 
316  // Get ready to log what happens
317  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
318  $ms->openLog();
319 
320  // Register ourselves with asset mgr so getAsset doesn't load a us again from the DB
321  $GLOBALS['SQ_SYSTEM']->am->rememberAsset($this);
322 
323  // Acquire permissions lock on ourselves so permissions can be cascaded
324  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
325  if (!$GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'permissions')) {
326  return $this->_abortCreate($trigger_level_changed);
327  }
328  }
329 
330  // Send a message to tell everyone the good news
331  $message_body = 'New '.$this->type().' "'.$this->name.'" created';
332  $msg_reps = Array(
333  'type_code' => $this->type(),
334  'asset_name' => $this->name,
335  );
336  $message = $ms->newMessage(Array(), 'asset', $msg_reps);
337  $message->parameters['assetid'] = $this->id;
338  $message->send();
339 
340  // Save the attribute values that have been stored temporarily
341  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() & SQ_RUN_LEVEL_FORCED);
342  $ok = $this->saveAttributes(TRUE);
343  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
344  if (!$ok) {
345  return $this->_abortCreate($trigger_level_changed);
346  }
347 
348  $this->_loadVars();
349 
350  if (!empty($link)) {
351  if (!isset($link['value'])) $link['value'] = '';
352  if (!isset($link['sort_order'])) {
353  $link['sort_order'] = -1;
354  }
355  if (!isset($link['is_dependant'])) {
356  $link['is_dependant'] = 0;
357  }
358  if (!isset($link['is_exclusive'])) {
359  $link['is_exclusive'] = 0;
360  }
361  if (!isset($link['is_locked'])) {
362  $link['is_locked'] = 0;
363  }
364 
365  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
366  // Acquire the links lock on the new parent
367  $lock_result = $GLOBALS['SQ_SYSTEM']->am->acquireLock($link['asset']->id, 'links');
368  if (!$lock_result) {
369  return $this->_abortCreate($trigger_level_changed);
370  }
371  $parent_was_locked = ($lock_result == 2);
372 
373  // Acquire links lock on ourselves
374  // If we are creating a dependant link, we need to make sure this new
375  // asset is locked in the same chain as the parent
376  // (ie. has the same source_assetid)
377  $source_assetid = 0;
378  if ($link['is_dependant']) {
379  $lock = $GLOBALS['SQ_SYSTEM']->am->getLockInfo($link['asset']->id, 'links');
380  $source_assetid = $lock['source_assetid'];
381  }
382  if (!$GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'links', $source_assetid)) {
383  return $this->_abortCreate($trigger_level_changed);
384  }
385  }
386 
387  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_INTEGRITY)) {
388  // Cascade various components from parent asset to new child
389  // unless the parent is the root folder or system management folder
390  $this->_tmp[__CLASS__.'_in_create_cascading'] = TRUE;
391  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() & SQ_RUN_LEVEL_FORCED);
392 
393  if (!$link['asset']->cloneComponents($this, Array('permissions', 'metadata_schemas', 'workflow', 'content_tags', 'roles'))) {
394  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
395  return $this->_abortCreate($trigger_level_changed);
396  }
397 
398  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
399  unset($this->_tmp[__CLASS__.'_in_create_cascading']);
400  }
401 
402  // Create the link to the parent asset
403  $linkid = $link['asset']->createLink($this, $link['link_type'], $link['value'], $link['sort_order'], $link['is_dependant'], $link['is_exclusive'], FALSE, $link['is_locked']);
404  if (!$linkid) {
405  if (!$parent_was_locked) {
406  $GLOBALS['SQ_SYSTEM']->am->releaseLock($link['asset']->id, 'links');
407  }
408  return $this->_abortCreate($trigger_level_changed);
409  }
410  // bug fix #3877 Matrix will create and retain links in DB if asset fails to get created
411  // by this point ^ we have linkid available since the link has been created.
412  // any further calls to _abortCreate() should include linkid so the we are not left
413  // with unwanted rubissh in sq_ast_lnk and sq_ast_lnk_tree tables
414 
415  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
416 
417  if (!$GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'permissions')) {
418  return $this->_abortCreate($trigger_level_changed, $linkid);
419  }
420 
421  // Get the link so we can find out if it was dependent
422  // (link details may have changed during createLink)
423  $new_link = $GLOBALS['SQ_SYSTEM']->am->getLinkById($linkid, $this->id, 'minor');
424 
425  // Release locks, unless the parent was locked before we were created
426  if (!$parent_was_locked) {
427  if (!$GLOBALS['SQ_SYSTEM']->am->releaseLock($link['asset']->id, 'links')) {
428  return $this->_abortCreate($trigger_level_changed, $linkid);
429  }
430  } else if ($new_link['is_dependant']) {
431  // The parent was locked before we created this asset and the new asset
432  // is dependantly linked, so make sure we have all the locks of our parent
433  $parent_lock_info = $GLOBALS['SQ_SYSTEM']->am->getLockInfo($link['asset']->id, 'all');
434  foreach (array_values($parent_lock_info) as $lock_details) {
435  if (empty($lock_details)) continue;
436  if (!$GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, $lock_details['lock_type'], $lock_details['source_assetid'])) {
437  return $this->_abortCreate($trigger_level_changed, $linkid);
438  }
439  }
440  }
441  if (!$new_link['is_dependant']) {
442  if (!$GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'links')) {
443  return $this->_abortCreate($trigger_level_changed, $linkid);
444  }
445  }
446 
447  }//end if locking subsystem enabled
448 
449  }//end if creating link
450 
451  // Re-enable the triggers, if they were disabled here
452  if ($trigger_level_changed) {
453  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
454  }
455 
456  // Before firing the "Before Asset Created" event, turn off locking checks
457  $modified_runlevel = FALSE;
458  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
459  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() - SQ_SECURITY_LOCKING);
460  $modified_runlevel = TRUE;
461  }
462 
463  // Fire the event, abort if the event fails
464  $event_result = $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_before_asset_created', $this);
465 
466  if ($modified_runlevel) {
467  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
468  }
469 
470  if (!$event_result) return $this->_abortCreate(FALSE, $linkid);
471 
472 
473  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_INTEGRITY)) {
474  // Perform any additional processing required, such as creating other assets
475  if (!$this->_createAdditional($link)) {
476  return $this->_abortCreate(FALSE, $linkid);
477  }
478  }
479 
480  // If for some unknown reason they don't have write access to the asset
481  // from the cascaded permissions of the parent (goodness knows why - you
482  // shouldn't be here in the first place!), add an explicit permission
483  if (!$this->writeAccess()) {
484  // note, if the user is unknown (NULL or 0), the permissions WILL NOT BE SET, and the creation will not fail
485  $current_user = $GLOBALS['SQ_SYSTEM']->currentUserId();
486  if (!empty($current_user) && !$GLOBALS['SQ_SYSTEM']->am->setPermission($this->id, $current_user, SQ_PERMISSION_WRITE, TRUE)) {
487  return $this->_abortCreate(FALSE, $linkid);
488  }
489  }
490 
491  unset($this->_tmp['__creating__']);
492  if (!$this->_updated()) return $this->_abortCreate(FALSE, $linkid);
493  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
494  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
495 
496  // because we are inheriting statuses, perhaps we are in safe edit now?
497  if ($this->status == SQ_STATUS_EDITING) {
498  $this->saveSystemVersion();
499  }
500 
501  // fire the 'Asset Created' event
502  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_asset_created', $this);
503  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
504 
505  // create a list of asset vars that have changed
506  // only published and published_userid is
507  $vars = Array(
508  'assetid',
509  'version',
510  'type_code',
511  'name',
512  'short_name',
513  'status',
514  'created',
515  'created_userid',
516  'updated',
517  'updated_userid',
518  'status_changed',
519  'status_changed_userid',
520  );
521 
522  $em->broadcastEvent($this, 'AssetCreate', $vars);
523  $ms->closeLog();
524 
525  if (!empty($link)) {
526  return (int) $linkid;
527  } else {
528  return TRUE;
529  }
530 
531  }//end create()
532 
533 
554  protected function _preCreateCheck(Array &$link)
555  {
556  return TRUE;
557 
558  }//end _preCreateCheck()
559 
560 
581  protected function _createAdditional(Array &$link)
582  {
583  return TRUE;
584 
585  }//end _createAdditional()
586 
587 
598  protected function _abortCreate($trigger_level_changed=FALSE, $linkid = 0)
599  {
600  if ($trigger_level_changed) {
601  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
602  }
603 
604  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
605  $ms->abortLog();
606  if ($linkid != 0) {
607  // Get the asset's link and its immediate children info before we rollback
608  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkById($linkid);
609 
610  $children_assets = Array();
611  if (isset($link['minorid'])) {
612  $children_assets = $GLOBALS['SQ_SYSTEM']->am->getChildren($link['minorid'], '', TRUE, NULL, NULL, NULL, TRUE, NULL, 1);
613  }
614  }
615 
616  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
617  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
618 
619  if (!empty($link) && isset($link['linkid']) && isset($link['majorid'])) {
620  // Clear the asset's link left on the link table
621  $GLOBALS['SQ_SYSTEM']->am->deleteAssetLinkByLink($link, TRUE, TRUE);
622 
623  // If there were children created by this asset successfully (that didn't got aborted),
624  // even though those childern will be rolledback when parent asset aborts itself,
625  // however those children will leave links in link table
626  foreach($children_assets as $child_assetid => $val) {
627  $child_link = $GLOBALS['SQ_SYSTEM']->am->getLinkByAsset($link['minorid'], $child_assetid);
628 
629  if (isset($child_link['linkid'])) {
630  $child_link['majorid'] = $link['minorid'];
631  $child_link['major_type_code'] = $this->type();
632  $GLOBALS['SQ_SYSTEM']->am->deleteAssetLinkByLink($child_link, TRUE, TRUE);
633  }
634  }//end foreach
635 
636  }//end if
637 
638  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($this);
639  unset($this->_tmp['__creating__']);
640  if ($this->id) {
641  @$GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'all');
642  }
643  $this->id = 0;
644  return FALSE;
645 
646  }//end _abortCreate()
647 
648 
657  public function load($assetid)
658  {
659  $db = MatrixDAL::getDb();
660 
661  // OK, the first thing to do is check we actually exist :)
662  $sql = 'SELECT assetid, type_code, version, name, short_name, status, languages,
663  charset, force_secure, created, created_userid, updated, updated_userid,
664  published, published_userid, status_changed, status_changed_userid
665  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast ';
666  $where = 'assetid = :assetid';
667  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
668 
669  try {
670  $query = MatrixDAL::preparePdoQuery($sql.$where);
671  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
672  $result = MatrixDAL::executePdoAssoc($query);
673  if (!empty($result)) {
674  $result = $result[0];
675  }
676  } catch (Exception $e) {
677  throw new Exception('Unable to get asset info for asset ID #'.$assetid.' due to database error: '.$e->getMessage());
678  }
679 
680  if (empty($result)) {
681  trigger_localised_error('SYS0087', E_USER_WARNING, $assetid);
682  return;
683  }
684 
685  $this->id = $result['assetid'];
686  $type_code = $result['type_code'];
687  $this->version = $result['version'];
688  $this->name = $result['name'];
689  $this->short_name = $result['short_name'];
690  $this->status = $result['status'];
691  $this->languages = $result['languages'];
692  $this->charset = $result['charset'];
693  $this->force_secure = $result['force_secure'];
694  $this->created = iso8601_ts($result['created']);
695  $this->created_userid = $result['created_userid'];
696  $this->updated = iso8601_ts($result['updated']);
697  $this->updated_userid = $result['updated_userid'];
698  $this->published = $result['published'];
699  $this->published_userid = $result['published_userid'];
700  $this->status_changed = $result['status_changed'];
701  $this->status_changed_userid = $result['status_changed_userid'];
702 
703  if (!is_null($this->status_changed)) {
704  $this->status_changed = iso8601_ts($this->status_changed);
705  }
706  if (!is_null($this->published)) {
707  $this->published = iso8601_ts($this->published);
708  }
709  unset($result);
710 
711  // make sure the asset we are loading is of the same type as our class
712  if ($type_code != $this->type()) {
713  trigger_localised_error('SYS0089', E_USER_WARNING, $assetid, $this->type());
714  $this->id = 0;
715  $this->created = NULL;
716  $this->created_userid = NULL;
717  $this->updated = NULL;
718  $this->updated_userid = NULL;
719  $this->published = NULL;
720  $this->published_userid = NULL;
721  $this->status_changed = NULL;
722  $this->status_changed_userid = NULL;
723  return;
724  }
725 
726  if ($this->useSystemVersion()) {
727  $this->_loadDataPaths();
728  if (!$this->loadSystemVersion()) {
729  trigger_localised_error('SYS0088', E_USER_WARNING, $assetid);
730  return;
731  }
732  $this->_loadDataPaths();
733  } else {
734  $this->_loadVars();
735  }
736 
737  }//end load()
738 
739 
748  public function _loadDataPaths()
749  {
750  $this->data_path_suffix = asset_data_path_suffix($this->type(), $this->id);
751  $this->data_path = SQ_DATA_PATH.'/private/'.$this->data_path_suffix;
752  $this->data_path_public = SQ_DATA_PATH.'/public/'.$this->data_path_suffix;
753  if ($this->useSystemVersion()) {
754  $this->data_path .= '/.sq_system';
755  }
756 
757  }//end _loadDataPaths()
758 
759 
768  protected function _loadVars()
769  {
770  // let's setup the data path
771  $this->_loadDataPaths();
772 
773  // Right, now we need to get any values that this asset has customised
774  $this->vars = Array();
775 
776  $sql = '';
777  // if we have an id then we need to load with current vars
778  if ($this->id) {
779  // PURPOSLY DONT ADD EXTRA CLAUSES FOR ASSET_ATTRIBUTE_VALUE BECAUSE WE WONT GET
780  // DEFAULT VALUES IF WE DO
781  $sql = 'SELECT atr.name, atr.attrid, atr.type, COALESCE(v.custom_val, atr.default_val) AS value, atr.is_contextable, v.use_default
782  FROM (sq_ast_attr atr
783  LEFT OUTER JOIN (SELECT * FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val WHERE contextid = :contextid) v
784  ON (atr.attrid = v.attrid AND v.assetid = :assetid'
785  .$GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause('', 'v', 'AND').'))
786  WHERE atr.type_code = :type_code';
787  } else {
788  // else just load all defaults
789  $sql = 'SELECT atr.name, atr.attrid, atr.type, atr.default_val AS value, atr.is_contextable, \'1\' as use_default
790  FROM sq_ast_attr atr
791  WHERE atr.type_code = :type_code';
792  }// end if
793 
794  try {
795  $query = MatrixDAL::preparePdoQuery($sql);
796  MatrixDAL::bindValueToPdo($query, 'type_code', $this->type(), PDO::PARAM_STR);
797  // Only bind it if we are using the first query, not binding when going in the else above
798  if ($this->id) {
799  MatrixDAL::bindValueToPdo($query, 'contextid', $GLOBALS['SQ_SYSTEM']->getContextId(), PDO::PARAM_INT);
800  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id, PDO::PARAM_STR);
801  }//end if
802  $result = MatrixDAL::executePdoGroupedAssoc($query);
803 
804  if (empty($result)) {
805  $this->vars = Array();
806  } else {
807  $this->vars = $result;
808  foreach (array_keys($this->vars) as $name) {
809  $this->vars[$name] = $this->vars[$name][0];
810  unset($this->vars[$name][0]);
811  }
812  }
813 
814  unset($result);
815  } catch (Exception $e) {
816  throw new Exception('Unable to load variables of asset #'.$this->id.' ('.$this->type().') due to database error: '.$e->getMessage());
817  }
818 
819  if ($this->_ser_attrs && $this->vars) {
820  for (reset($this->vars); NULL !== ($name = key($this->vars)); next($this->vars)) {
821  if ($this->vars[$name]['type'] != 'serialise') {
822  continue;
823  }
824  $this->vars[$name]['value'] = @unserialize($this->vars[$name]['value']);
825  }
826  }
827 
828  }//end _loadVars()
829 
830 
837  public function reload()
838  {
839  $this->_tmp = Array();
840  $this->load($this->id);
841 
842  }//end reload()
843 
844 
853  public function canDelete()
854  {
855  return TRUE;
856 
857  }//end canDelete()
858 
859 
871  public function delete($release_lock=TRUE, $check_locked=TRUE)
872  {
873  // check that we are in the trash
874  if (!$GLOBALS['SQ_SYSTEM']->am->assetInTrash($this->id)) {
875  trigger_localised_error('SYS0103', E_USER_WARNING, $this->name, $this->id);
876  return FALSE;
877  }
878 
879  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
880  $db = MatrixDAL::getDb();
881  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
882 
883  $links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_ALL, '', TRUE, 'minor');
884  foreach ($links as $link) {
885  $major = $GLOBALS['SQ_SYSTEM']->am->getAsset($link['majorid'], $link['major_type_code']);
886  if (!is_null($major)) {
887  $major->deleteLink($link['linkid'], $check_locked);
888  } else {
889  trigger_localised_error('SYS0128', E_USER_WARNING, $link['majorid']);
890  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
891  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
892  return FALSE;
893  }
894  }
895 
896  if ($release_lock) {
897  // we are about to go to the big asset manager in the sky,
898  // so we wont be needing worldly things such as locks
899  if (!$GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'all')) {
900  trigger_localised_error('SYS0131', E_USER_WARNING, $this->name);
901  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
902  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
903  return FALSE;
904  }
905  }
906 
907  // remove web paths
908  if (!$this->saveWebPaths(Array())) {
909  trigger_localised_error('SYS0133', E_USER_WARNING, $this->name);
910  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
911  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
912  return FALSE;
913  }
914 
915  // update the lookups to clear them; figures that if there are no web paths there cannot
916  // be any lookups
917  if (!$this->updateLookups()) {
918  trigger_localised_error('SYS0132', E_USER_WARNING, $this->name);
919  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
920  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
921  return FALSE;
922  }
923 
924  $bind_vars['assetid'] = $this->id;
925  try {
926  // delete related asset data from the DB tables
927  $result = MatrixDAL::executeQuery('core', 'assetDeleteAsset', $bind_vars);
928  $result = MatrixDAL::executeQuery('core', 'assetDeleteAssetAttrVal', $bind_vars);
929  $result = MatrixDAL::executeQuery('core', 'assetDeleteAssetAttrUniqVal', $bind_vars);
930  $result = MatrixDAL::executeQuery('core', 'assetDeleteAssetPerm', $bind_vars);
931  $result = MatrixDAL::executeQuery('core', 'assetDeleteAssetRole', $bind_vars);
932  } catch (Exception $e) {
933  throw new Exception("Failed to delete asset (#$this->id): ".$e->getMessage());
934  }
935 
936  // delete any metadata values this asset might have
937  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
938  if (!$mm->purgeMetadata($this->id)) {
939  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
940  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
941  return FALSE;
942  }
943 
944  // delete any workflow values that this asset might have
945  $wm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
946  if (!$wm->purgeWorkflow($this->id)) {
947  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
948  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
949  return FALSE;
950  }
951 
952  // now try and clear the data directories for this asset
953  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
954  if (is_dir($this->data_path)) {
955  if (!delete_directory($this->data_path)) {
956  trigger_localised_error('SYS0151', E_USER_WARNING, $this->name);
957  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
958  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
959  return FALSE;
960  }
961  }
962 
963  if (is_dir($this->data_path_public)) {
964  if (!delete_directory($this->data_path_public)) {
965  trigger_localised_error('SYS0151', E_USER_WARNING, $this->name);
966  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
967  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
968  return FALSE;
969  }
970  }
971 
972  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
973  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
974 
975  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
976  $em->broadcastEvent($this, 'assetDeleted', Array());
977  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($this, TRUE);
978 
979  return TRUE;
980 
981  }//end delete()
982 
983 
993  public function useSystemVersion()
994  {
995  if (!isset($this->_tmp['use_system_version'])) {
996  if (!($this->status & SQ_SC_STATUS_SAFE_EDITING)) {
997  $this->_tmp['use_system_version'] = FALSE;
998  } else {
999  if ($GLOBALS['SQ_SYSTEM']->getGlobalDefine('force_system_version')) {
1000  $this->_tmp['use_system_version'] = TRUE;
1001  } else {
1002  if (SQ_ROLLBACK_VIEW) {
1003  $usv = (!$this->checkAccess(SQ_PERMISSION_WRITE, ''));
1004  } else {
1005  $usv = (!$this->writeAccess(''));
1006  }//end if
1007  if ($usv && !$GLOBALS['SQ_SYSTEM']->userPublic()) {
1008  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
1009  $publishers = $wfm->whoCanPublish($this->id);
1010  if (!empty($publishers) && in_array($GLOBALS['SQ_SYSTEM']->currentUserId(), $publishers)) {
1011  $usv = FALSE;
1012  }
1013  }
1014  $this->_tmp['use_system_version'] = $usv;
1015  }
1016  }
1017  }
1018  return $this->_tmp['use_system_version'];
1019 
1020  }//end useSystemVersion()
1021 
1022 
1031  public function loadSystemVersion()
1032  {
1033  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1034 
1035  // make sure our data directory exists
1036  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1037  $filename = '.object_data'.(($contextid === 0) ? '' : '.'.$contextid);
1038  if (file_exists($this->data_path.'/'.$filename) === TRUE) {
1039  $use_filename = $this->data_path.'/'.$filename;
1040  } else if (file_exists($this->data_path.'/.object_data') === TRUE) {
1041  // The context mustn't have existed when we saved the system
1042  // version, so use the default context version instead...?
1043  $use_filename = $this->data_path.'/.object_data';
1044  } else {
1045  trigger_localised_error('SYS0159', E_USER_WARNING, $this->id, $this->data_path);
1046  return FALSE;
1047  }
1048 
1049  $real_status = $this->status;
1050 
1051  foreach (get_object_vars(unserialize(file_to_string($use_filename))) as $key => $value) {
1052  $this->$key = $value;
1053  }
1054  $this->status = $real_status;
1055  return TRUE;
1056 
1057  }//end loadSystemVersion()
1058 
1059 
1069  {
1070  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1071 
1072  // make sure our data directories exists
1073  if (!create_directory($this->data_path)) {
1074  trigger_localised_error('CORE0049', E_USER_WARNING, $this->name);
1075  return FALSE;
1076  }
1077 
1078  // make sure our system directories exists
1079  if (!create_directory($this->data_path.'/.sq_system')) {
1080  trigger_localised_error('CORE0050', E_USER_WARNING, $this->name);
1081  return FALSE;
1082  }
1083 
1084  // make sure there is nothing in the system directories
1085  if (!clear_directory($this->data_path.'/.sq_system')) {
1086  trigger_localised_error('CORE0050', E_USER_WARNING, $this->name);
1087  return FALSE;
1088  }
1089 
1090  // save the object for later (in the restricted directory)
1091  // The only thing that varies at the moment is attributes, so we
1092  // can reload the vars in each context instead of completely reloading
1093  // the asset
1094  $current_vars = $this->vars;
1095  $contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
1096  foreach ($contextids as $contextid) {
1097  // Reload the vars for this context
1098  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
1099  $this->_loadVars();
1100 
1101  $filename = '.object_data'.(($contextid === 0) ? '' : '.'.$contextid);
1102  if (!string_to_file(serialize($this), $this->data_path.'/.sq_system/'.$filename)) {
1103  trigger_localised_error('CORE0051', E_USER_WARNING, $this->name);
1104  return FALSE;
1105  }
1106 
1107  $GLOBALS['SQ_SYSTEM']->restoreContext();
1108  }//end foreach
1109 
1110  // Put it back how it was
1111  $this->vars = $current_vars;
1112 
1113  // move all the other files in our data directories
1114  // into the new system directories for later use
1115  $files_to_copy = list_files($this->data_path);
1116  foreach ($files_to_copy as $filename) {
1117  if (!copy_file($this->data_path.'/'.$filename, $this->data_path.'/.sq_system/'.$filename)) {
1118  trigger_localised_error('SYS0166', E_USER_WARNING, $this->name, $filename);
1119  return FALSE;
1120  }
1121  }
1122 
1123  // we need to save the current notice links
1124  $replace_assetids = Array();
1125  $notice_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_NOTICE);
1126 
1127  // preserve the nest content related notice links so that we can recreate the links when reverting to system version
1128  $links_to_preserve = Array(
1129  'nested_asset',
1130  'paint_with_layout',
1131  'root',
1132  'redirect_asset',
1133  'thumbnail',
1134  );
1135  foreach ($notice_links as $link) {
1136  if (in_array($link['value'], $links_to_preserve)) {
1137  $replace_assetids[] = $link;
1138  }
1139  }
1140 
1141  // save the notice links we currently have in .sq_notice_links file
1142  if (!string_to_file(serialize($replace_assetids), $this->data_path.'/.sq_system/.sq_notice_links')) {
1143  trigger_localised_error('CORE0048', E_USER_WARNING, $this->name);
1144  return FALSE;
1145  }
1146 
1147  return TRUE;
1148 
1149  }//end saveSystemVersion()
1150 
1151 
1158  public function clearSystemVersion()
1159  {
1160  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1161 
1162  // make sure our data directory exists
1163  if (!create_directory($this->data_path)) {
1164  trigger_localised_error('SYS0148', E_USER_WARNING, $this->name);
1165  return FALSE;
1166  }
1167 
1168  // make sure our system directory exists
1169  if (!create_directory($this->data_path.'/.sq_system')) {
1170  trigger_localised_error('SYS0299', E_USER_WARNING, $this->name);
1171  return FALSE;
1172  }
1173 
1174  if (!clear_directory($this->data_path.'/.sq_system')) {
1175  trigger_localised_error('SYS0147', E_USER_WARNING, $this->name);
1176  return FALSE;
1177  }
1178 
1179  return TRUE;
1180 
1181  }//end clearSystemVersion()
1182 
1183 
1193  public function revertToSystemVersion()
1194  {
1195  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = TRUE;
1196  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1197 
1198  // copy over the old files we stored in the system directory
1199  $files_to_copy = list_files($this->data_path.'/.sq_system');
1200  foreach ($files_to_copy as $filename) {
1201  // skip hidden files
1202  if (strpos($filename, '.') === 0) continue;
1203 
1204  if (!copy_file($this->data_path.'/.sq_system/'.$filename, $this->data_path.'/'.$filename)) {
1205  trigger_localised_error('SYS0162', E_USER_WARNING, $this->name, $filename);
1206  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1207  return FALSE;
1208  }
1209  }
1210 
1211  // load our object data in as it was before
1212  $current_contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1213  $contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
1214  foreach ($contextids as $contextid) {
1215  if ($contextid !== $current_contextid) {
1216  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
1217  $contexted_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($this->id);
1218  } else {
1219  $contexted_asset = $this;
1220  }
1221 
1222 
1223  $filename = '.object_data'.(($contextid === 0) ? '' : '.'.$contextid);
1224  if (is_file($this->data_path.'/.sq_system/'.$filename)) {
1225  $old_version = unserialize(file_to_string($this->data_path.'/.sq_system/'.$filename));
1226  } else if (is_file($this->data_path.'/.sq_system/.object_data')) {
1227  // The context mustn't have existed when we saved the system
1228  // version, so use the default context version instead...?
1229  $old_version = unserialize(file_to_string($this->data_path.'/.sq_system/.object_data'));
1230  } else {
1231  trigger_localised_error('SYS0163', E_USER_WARNING, $this->name);
1232  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1233  return FALSE;
1234  }
1235 
1236  // update all the vars
1237  foreach ($old_version->vars as $var_name => $var_data) {
1238  if (!$contexted_asset->setAttrValue($var_name, $var_data['value'], TRUE)) {
1239  trigger_localised_error('SYS0165', E_USER_WARNING, $this->name, $var_name);
1240  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1241  return FALSE;
1242  }
1243  }
1244 
1245  if (!$contexted_asset->saveAttributes()) {
1246  trigger_localised_error('SYS0164', E_USER_WARNING, $this->name);
1247  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1248  return FALSE;
1249  }
1250 
1251  if ($contextid !== $current_contextid) {
1252  $GLOBALS['SQ_SYSTEM']->restoreContext();
1253  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($contexted_asset);
1254  unset($contexted_asset);
1255  }
1256 
1257  }//end foreach
1258 
1259  if (!$this->revertNoticeLinksToSystemVersion()) return FALSE;
1260 
1261  // If we have metadata, load the old version so we can revert the values
1262  $metadata_filename = $this->data_path.'/.sq_system/metadata_field_values.php';
1263  if (is_file($metadata_filename)) {
1264  $old_metadata = unserialize(file_to_string($metadata_filename));
1265 
1266  // Acquire the "metadata" lock to set values
1267  $GLOBALS['SQ_SYSTEM']->am->acquireLock($this->id, 'metadata');
1268 
1269  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
1270 
1271  // Set the defaults and any customised values
1272  if (!$mm->setMetadata($this->id, $old_metadata)) {
1273  trigger_localised_error('SYS0331', E_USER_WARNING, $this->name);
1274  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1275  return FALSE;
1276  }
1277 
1278  // ...and we're done
1279  $GLOBALS['SQ_SYSTEM']->am->releaseLock($this->id, 'metadata');
1280  }
1281 
1282  if (!$this->clearSystemVersion()) {
1283  trigger_localised_error('SYS0161', E_USER_WARNING, $this->name);
1284  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1285  return FALSE;
1286  }
1287 
1288  // ITS ALIVE... ITS ALIVE...
1289  foreach (get_object_vars($old_version) as $key => $value) {
1290  $this->$key = $value;
1291  }
1292 
1293  // also keep the data in asset to notify other parts of matrix
1294  // that we are reverting back to system version
1295  $this->_tmp['reverting_to_system_version'] = TRUE;
1296 
1297  // save this old version to the db
1298  $this->_updated();
1299 
1300  $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] = FALSE;
1301  return TRUE;
1302 
1303  }//end revertToSystemVersion()
1304 
1305 
1313  {
1314  // we need to reconstruct the notice links (for nested contents) before the parent function removes the files in .sq_system!
1315  // so, read the notice links information from the system version file
1316  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
1317  $notice_links_file = file_to_string($this->data_path.'/.sq_system/.sq_notice_links');
1318 
1319  // if we have any notice links preserved in the system version file, remove the current notice links
1320  // and create new ones based on the information from the system version file
1321  if (!empty($notice_links_file)) {
1322  $system_ver_notice_links = unserialize($notice_links_file);
1323  $notice_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_NOTICE);
1324 
1325  $links_to_preserve = Array(
1326  'nested_asset',
1327  'paint_with_layout',
1328  'root',
1329  'redirect_asset',
1330  'thumbnail',
1331  );
1332  foreach ($notice_links as $index => $info) {
1333  if (in_array($info['value'], $links_to_preserve)) {
1334  foreach($system_ver_notice_links as $prev_index => $prev_info) {
1335  if (array_diff($info, $prev_info) == Array()) {
1336  unset($system_ver_notice_links[$prev_index]); //not going to create
1337  unset($notice_links[$index]); //not going to delete
1338  }
1339  }
1340  } else {
1341  unset($notice_links[$index]); //not going to delete
1342  }
1343  }
1344  if (empty($system_ver_notice_links) && empty($notice_links)) return TRUE;
1345 
1346  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1347  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1348 
1349  $run_level_changed = FALSE;
1350  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_LOCKING)) {
1351  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() - SQ_SECURITY_LOCKING);
1352  $run_level_changed = TRUE;
1353  }
1354 
1355  // delete existing notice links that are no longer being used.
1356  // the system version file contains only nest content related notice links with a link value, 'nested_asset' or 'paint_with_layout'
1357  // so, what we are deleting here is just nest content related notice links
1358  foreach ($notice_links as $link_data) {
1359  if (!$this->deleteLink($link_data['linkid'])) {
1360  if ($run_level_changed) {
1361  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1362  }
1363  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1364  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1365  return FALSE;
1366  }
1367  }
1368 
1369  // now re-create new nest content related notice links based on the information preserved in the system version file
1370  foreach ($system_ver_notice_links as $link_info) {
1371  // the first element of link info array needs to be an assetid (the minorid)
1372  if (!isset($link_info['minorid']) || !assert_valid_assetid($link_info['minorid'], '', FALSE, FALSE)) {
1373  continue;
1374  }
1375  $minor_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($link_info['minorid']);
1376  if (is_null($minor_asset)) continue;
1377 
1378  // notice link the minor asset to this asset with a link value kept in the 2nd element of link info array
1379  $link_val = '';
1380  if (isset($link_info['value'])) {
1381  $link_val = $link_info['value'];
1382  }
1383  $this->createLink($minor_asset, SQ_LINK_NOTICE, $link_val);
1384 
1385  // save attributes to fill in any missing information
1386  if (!$this->saveAttributes()) {
1387  if ($run_level_changed) {
1388  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1389  }
1390  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1391  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1392  return FALSE;
1393  }
1394 
1395  }//end foreach
1396 
1397  if ($run_level_changed) {
1398  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1399  }
1400 
1401  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1402  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1403 
1404  }//end if
1405 
1406  return TRUE;
1407 
1408  }//end revertNoticeLinksToSystemVersion()
1409 
1410 
1411 
1426  public function _updated($update_parents=TRUE)
1427  {
1428  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
1429  require_once SQ_FUDGE_PATH.'/general/general.inc';
1430 
1431  if (isset($this->_tmp['__creating__']) && $this->_tmp['__creating__']) {
1432  return TRUE;
1433  }
1434 
1435  $updated = time();
1436  $updated_userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
1437  $default_context = 0;
1438  $name = check_char_encoding(substr($this->_getName(FALSE, $default_context), 0, 255));
1439  $short_name = check_char_encoding(substr($this->_getName(TRUE, $default_context), 0, 255));
1440 
1441 
1442 
1443  // if we have an ID hit the DB
1444  if ($this->id) {
1445 
1446  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1447  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1448  $bind_vars = Array();
1449 
1450  $sql = 'UPDATE
1451  sq_ast
1452  SET ';
1453 
1454  if (($name === '0') || (!empty($name))) {
1455  $bind_vars['name'] = $name;
1456  $sql .= 'name = :name,';
1457  }
1458  if (($short_name === '0') || (!empty($short_name))) {
1459  $bind_vars['short_name'] = $short_name;
1460  $sql .= 'short_name = :short_name,';
1461  }
1462 
1463  $sql .= '
1464  languages = :languages,
1465  charset = :charset,
1466  force_secure = :force_secure,
1467  updated = :updated,
1468  updated_userid = :updated_userid
1469  WHERE
1470  assetid = :assetid';
1471 
1472  $bind_vars['languages'] = $this->languages;
1473  $bind_vars['charset'] = $this->charset;
1474  $bind_vars['force_secure'] = $this->force_secure;
1475  $bind_vars['updated'] = ts_iso8601($updated);
1476  $bind_vars['updated_userid'] = $updated_userid;
1477  $bind_vars['assetid'] = $this->id;
1478 
1479  try {
1480  $query = MatrixDAL::preparePdoQuery($sql);
1481  foreach ($bind_vars as $bind_var => $bind_value) {
1482  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
1483  }
1484  MatrixDAL::execPdoQuery($query);
1485  } catch (Exception $e) {
1486  throw new Exception('Unable to update asset "'.$this->_getName().'" (#'.$this->id.') due to database error: '.$e->getMessage());
1487  }
1488 
1489  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
1490 
1491  $fields = Array(
1492  'name',
1493  'short_name',
1494  'updated_userid',
1495  );
1496 
1497  // We know that 'updated' field ALWAYS changes, no need to check the value changes
1498  $changed_data = Array('updated');
1499  foreach ($fields as $field) {
1500  if ($this->$field != $$field) {
1501  $changed_data[] = $field;
1502  }
1503  }
1504 
1505  // set these components before broadcasting the event
1506  $this->name = $name;
1507  $this->short_name = $short_name;
1508  $this->updated = $updated;
1509  $this->updated_userid = $updated_userid;
1510 
1511  // increment the micro version number
1512  if (!$this->incrementVersion('micro', $update_parents)) {
1513  trigger_localised_error('SYS0183', E_USER_WARNING, $this->name);
1514  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1515  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1516  return FALSE;
1517  }
1518 
1519  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1520  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1521 
1522  $em->broadcastEvent($this, 'AssetUpdate', $changed_data);
1523 
1524  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_asset_updated', $this);
1525 
1526  }//end if $this->id
1527 
1528  return TRUE;
1529 
1530  }//end _updated()
1531 
1532 
1546  protected function _getName($short_name=FALSE, $contextid=NULL)
1547  {
1548  return $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name').' #'.$this->id;
1549 
1550  }//end _getName()
1551 
1552 
1563  public function type()
1564  {
1565  return strtolower(get_class($this));
1566 
1567  }//end type()
1568 
1569 
1580  public function getTypeAncestors($include_asset=TRUE)
1581  {
1582  return $GLOBALS['SQ_SYSTEM']->am->getTypeAncestors($this->type(), $include_asset);
1583 
1584  }//end getTypeAncestors()
1585 
1586 
1598  public function incrementVersion($number='micro', $update_parents=TRUE)
1599  {
1600  if (!$this->id) return FALSE;
1601 
1602  switch ($number) {
1603  case 'major' :
1604  $part_num = 0;
1605  break;
1606  case 'minor' :
1607  $part_num = 1;
1608  break;
1609  case 'micro' :
1610  $part_num = 2;
1611  break;
1612  default :
1613  trigger_localised_error('SYS0157', E_USER_WARNING, $this->name, $this->id, $number);
1614  return FALSE;
1615  break;
1616  }
1617 
1618  // work out the new version number based on the part of the
1619  // version that we have been asked to update (major|minor|micro)
1620  $version_parts = explode('.', $this->version);
1621  for (reset($version_parts); NULL !== ($k = key($version_parts)); next($version_parts)) {
1622  $value =& $version_parts[$k];
1623  $value = (int)$value;
1624  if ($k == $part_num) {
1625  $value++;
1626  } else if ($k > $part_num) {
1627  $value = 0;
1628  }
1629  }
1630 
1631 
1632  $new_version = implode('.', $version_parts);
1633 
1634  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1635  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1636  $db = MatrixDAL::getDb();
1637 
1638  $sql = 'UPDATE
1639  sq_ast
1640  SET
1641  version = :version
1642  WHERE
1643  assetid = :assetid';
1644 
1645  try {
1646  $query = MatrixDAL::preparePdoQuery($sql);
1647  MatrixDAL::bindValueToPdo($query, 'version', $new_version, PDO::PARAM_STR);
1648  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id, PDO::PARAM_STR);
1649  MatrixDAL::execPdoQuery($query);
1650  } catch (Exception $e) {
1651  throw new Exception('Unable to increment version of "'.$this->_getName().'" (#'.$this->id.') due to database error: '.$e->getMessage());
1652  }
1653 
1654  if ($update_parents) {
1655  // increment the micro version of all dependant parents because a 'part' of them
1656  // has been updated - even though they havnt been updated directly
1657  $dependant_parents = $GLOBALS['SQ_SYSTEM']->am->getDependantParents($this->id);
1658  foreach ($dependant_parents as $parentid) {
1659  $dep_parent = $GLOBALS['SQ_SYSTEM']->am->getAsset($parentid);
1660  if (!is_null($dep_parent)) {
1661  if (!$dep_parent->_updated(FALSE)) {
1662  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1663  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1664  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($dep_parent);
1665  trigger_localised_error('SYS0158', E_USER_WARNING, $this->name, $this->id, $dep_parent->name, $dep_parent->id);
1666  return FALSE;
1667  }
1668  }
1669  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($dep_parent);
1670  }
1671  }
1672 
1673  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
1674  $em->broadcastEvent($this, 'AssetVersionUpdate', Array(
1675  'old_version' => $this->version,
1676  'new_version' => $new_version,
1677  )
1678  );
1679 
1680  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1681  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1682 
1683  $this->version = $new_version;
1684  return TRUE;
1685 
1686  }//end incrementVersion()
1687 
1688 
1695  public function getLanguages()
1696  {
1697  $langs = trim($this->languages, ', ');
1698  if ($langs == '') return Array();
1699  return explode(',', $langs);
1700 
1701  }//end getLanguages()
1702 
1703 
1712  public function setLanguages(Array $languages)
1713  {
1714  $set_languages = implode(',', $languages);
1715  if ($set_languages == $this->languages) return FALSE;
1716 
1717  $old_languages = $this->languages;
1718  $this->languages = $set_languages;
1719  if (!$this->_updated()) {
1720  trigger_localised_error('SYS0172', E_USER_WARNING, $this->name);
1721  $this->languages = $old_languages;
1722  return FALSE;
1723  }
1724 
1725  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
1726  $em->broadcastEvent($this, 'AssetLanguageUpdate', Array(
1727  'old_languages' => $old_languages,
1728  'new_languages' => $this->languages,
1729  )
1730  );
1731  return TRUE;
1732 
1733  }//end setLanguages()
1734 
1735 
1744  public function setCharset($charset)
1745  {
1746  if ($charset == $this->charset) return FALSE;
1747  $old_charset = $this->charset;
1748  $this->charset = $charset;
1749  if (!$this->_updated()) {
1750  trigger_localised_error('SYS0170', E_USER_WARNING, $this->name);
1751  $this->charset = $old_charset;
1752  return FALSE;
1753  }
1754 
1755  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
1756  $em->broadcastEvent($this, 'AssetCharsetUpdate', Array(
1757  'old_charset' => $old_charset,
1758  'new_charset' => $this->charset,
1759  )
1760  );
1761  return TRUE;
1762 
1763  }//end setCharset()
1764 
1765 
1786  public function setForceSecure($force_secure)
1787  {
1788  if (!$this->adminAccess('settings')) {
1789  trigger_localised_error('SYS0252', E_USER_WARNING);
1790  return FALSE;
1791  }
1792 
1793  if ($force_secure !== '-') {
1794  $force_secure = ($force_secure) ? '1' : '0';
1795  }
1796  if ($force_secure == $this->force_secure) return FALSE;
1797  $old_force_secure = $this->force_secure;
1798  $this->force_secure = $force_secure;
1799  if (!$this->_updated()) {
1800  trigger_localised_error('SYS0169', E_USER_WARNING, $this->name, $this->id);
1801  $this->force_secure = $old_force_secure;
1802  return FALSE;
1803  }
1804 
1805  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
1806  $em->broadcastEvent($this, 'AssetForceSecureUpdate', Array(
1807  'old_force_secure' => $old_force_secure,
1808  'old_force_secure' => $this->force_secure,
1809  )
1810  );
1811 
1812  return TRUE;
1813 
1814  }//end setForceSecure()
1815 
1816 
1825  public function remapAssetids(Array $map)
1826  {
1827  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1828  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1829  // try and acquire locks on our dependants
1830  $dependant_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_SIGNIFICANT, '', TRUE, 'major', NULL, 1);
1831  if (!empty($dependant_links)) {
1832  $am = $GLOBALS['SQ_SYSTEM']->am;
1833  foreach ($dependant_links as $link) {
1834  $asset = $am->getAsset($link['minorid'], $link['minor_type_code']);
1835  if (!$asset->remapAssetids($map)) {
1836  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1837  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1838  return FALSE;
1839  }
1840  }
1841  }
1842 
1843  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1844  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1845  return TRUE;
1846 
1847  }//end remapAssetids()
1848 
1849 
1862  public function morph($new_type_code)
1863  {
1864  $new_type_code = strtolower($new_type_code);
1865  $old_type_code = $this->type();
1866 
1867  if ($this->type() == $new_type_code) return $this;
1868 
1869  if (!$GLOBALS['SQ_SYSTEM']->am->installed($new_type_code)) {
1870  trigger_localised_error('SYS0085', E_USER_WARNING, $new_type_code);
1871  return NULL;
1872  }
1873 
1874  $am = $GLOBALS['SQ_SYSTEM']->am;
1875 
1876  // open the transaction
1877  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1878  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1879  $db = MatrixDAL::getDb();
1880 
1881  // is this new type one of our decendents or ancestors?
1882  $sql = 'SELECT COUNT(*)
1883  FROM sq_ast_typ_inhd
1884  WHERE (inhd_type_code = :type1 AND type_code = :type2)
1885  OR (inhd_type_code = :type2 AND type_code = :type1)';
1886  try {
1887  $query = MatrixDAL::preparePdoQuery($sql);
1888  MatrixDAL::bindValueToPdo($query, 'type1', $this->type());
1889  MatrixDAL::bindValueToPdo($query, 'type2', $new_type_code);
1890  $count = MatrixDAL::executePdoOne($query);
1891  } catch (Exception $e) {
1892  throw new Exception('Unable to get number of inherited type codes for this inherited type code: '.$this->type().' and this type code: '.$new_type_code.' due to database error: '.$e->getMessage());
1893  }
1894 
1895  if ($count == 0) {
1896  trigger_localised_error('SYS0234', E_USER_WARNING, $this->name, $new_type_code, $this->type());
1897  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1898  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1899  return NULL;
1900  }
1901 
1902  $am->includeAsset($new_type_code);
1903  $tmp = new $new_type_code();
1904 
1906 
1907  // if there any children links
1908  if ($num_links = $GLOBALS['SQ_SYSTEM']->am->countLinks($this->id, 'major')) {
1909 
1910  $links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_ALL);
1911  foreach ($links as $link) {
1912  // Shadow asset links (such as tags) are not encumbered by
1913  // type checks in createAssetLink(), so no restrictions here.
1914  if (strpos($link['minorid'], ':') !== FALSE) {
1915  continue;
1916  }
1917  if ($GLOBALS['SQ_SYSTEM']->am->canLinkToType($tmp, $link['minor_type_code'], $link['link_type'], 0, $link['is_exclusive']) !== TRUE) {
1918  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
1919  trigger_localised_error('SYS0236', E_USER_WARNING, $this->name, $new_type_code, $link['minor_type_code'], link_type_name($link['link_type']), $new_type_code);
1920  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1921  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1922  return NULL;
1923  }
1924  }// end foreach
1925 
1926  }// end if child links
1927 
1928  // if there any parent links
1929  $num_links = $GLOBALS['SQ_SYSTEM']->am->countLinks($this->id, 'minor');
1930  if ($num_links) {
1931 
1932  $links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_ALL, '', TRUE, 'minor');
1933  foreach ($links as $link) {
1934  $parent = $am->getAsset($link['majorid'], $link['major_type_code']);
1935  if (is_null($parent)) continue;
1936  if (($err_msg = $GLOBALS['SQ_SYSTEM']->am->canLinkToType($parent, $new_type_code, $link['link_type'], $link['linkid'], $link['is_exclusive'])) !== TRUE) {
1937  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
1938  trigger_localised_error('SYS0235', E_USER_WARNING, $this->name, $new_type_code, $parent->name, $parent->id, $err_msg);
1939  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1940  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1941  return NULL;
1942  }
1943  }// end foreach
1944 
1945  }// end if parent links
1946 
1947 
1948  $current_var_list = array_keys($this->vars);
1949  $new_var_list = array_keys($tmp->vars);
1950 
1951  // Get all the common vars and update the attribute ids to the new values
1952  // so the asset gets to keep the common values already set
1953  $common_var_list = array_intersect($current_var_list, $new_var_list);
1954 
1955  if ($common_var_list) {
1956  foreach ($common_var_list as $var_name) {
1957 
1958  $current_id = $this->vars[$var_name]['attrid'];
1959  $new_id = $tmp->vars[$var_name]['attrid'];
1960 
1961 
1962  try {
1963  $bind_vars = Array (
1964  'newattrid' => $new_id,
1965  'assetid' => $this->id,
1966  'currentattrid' => $current_id,
1967  );
1968  $result = MatrixDAL::executeQuery('core', 'updateAttrId', $bind_vars);
1969  } catch (Exception $e) {
1970  throw new Exception('Unable to update attribute id for asset: '.$this->id.' from current attribute id: '.$current_id.' to new attribute id: '.$new_id.' due to database error: '.$e->getMessage());
1971  }
1972  }
1973  }//end if common var list
1974 
1975  // Get all the vars that aren't available in the new type and delete them
1976  $deletes_var_list = array_diff($current_var_list, $new_var_list);
1977 
1978  if ($deletes_var_list) {
1979  $deletes_attributeids = '';
1980  foreach ($deletes_var_list as $var_name) {
1981  $deletes_attributeids .= (($deletes_attributeids) ? ',' : '').MatrixDAL::quote($this->vars[$var_name]['attrid']);
1982  }
1983 
1984  $sql = 'DELETE FROM
1985  sq_ast_attr_val
1986  WHERE
1987  assetid = :assetid
1988  AND attrid IN
1989  (
1990  '.$deletes_attributeids.'
1991  )';
1992 
1993  try {
1994  $query = MatrixDAL::preparePdoQuery($sql);
1995  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
1996  $result = MatrixDAL::execPdoQuery($query);
1997  } catch (Exception $e) {
1998  throw new Exception('Unable to delete attribute values for asset: '.$this->id.' due to database error: '.$e->getMessage());
1999  }
2000 
2001  }// end if delete var list
2002 
2003  try {
2004  $bind_vars = Array (
2005  'type_code' => $new_type_code,
2006  'assetid' => $this->id,
2007  );
2008  $result = MatrixDAL::executeQuery('core', 'updateAstTypeCode', $bind_vars);
2009  } catch (Exception $e) {
2010  throw new Exception('Unable to update type code for asset: '.$this->id.' due to database error: '.$e->getMessage());
2011  }
2012 
2013  // now load this new asset into the temporary
2014  $tmp->load($this->id);
2015  $all_ok = FALSE;
2016  if ($tmp->id) {
2017  // OK if we got this far let's move the directory (if it exists)
2018  if (is_dir($this->data_path)) {
2019  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
2020  // make sure the parent directory exists, then move the our directory to it's new home
2021  if (create_directory(dirname($tmp->data_path)) && rename($this->data_path, $tmp->data_path)) {
2022  $all_ok = TRUE;
2023  }
2024  } else {
2025  // if there isn't a directory then everything is fine
2026  $all_ok = TRUE;
2027  }
2028 
2029  }// end if
2030 
2031  // before we override ourselves, do any cleaning up that we might need
2032  if ($all_ok && !$this->_morphCleanup($new_type_code)) {
2033  $all_ok = FALSE;
2034  }
2035 
2036  // all is OK so override ourselves with the temporary
2037  if ($all_ok) {
2038  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2039  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2040 
2041  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
2042  $em->broadcastEvent($this, 'AssetTypeUpdate', Array(
2043  'old_type' => $old_type_code,
2044  'new_type' => $new_type_code,
2045  )
2046  );
2047 
2048  // We beed to forget the asset because we are changed the
2049  // type_code, and it does not get updated in the asset cache
2050  // because of the way it does references. Then we get a fresh copy
2051  // from asset manager which will repopoulate the cache.
2052  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($tmp, TRUE);
2053  return $GLOBALS['SQ_SYSTEM']->am->getAsset($tmp->id, $tmp->type());
2054 
2055  } else {
2056  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2057  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2058  return NULL;
2059 
2060  }// end if
2061 
2062  }//end morph()
2063 
2064 
2073  public function _morphCleanup($new_type_code)
2074  {
2075  return TRUE;
2076 
2077  }//end _morphCleanup()
2078 
2079 
2093  public function onRequestKeywords(Asset $broadcaster, Array $vars=Array())
2094  {
2095  if (!isset($vars['keywords'])) return;
2096  $vars['keywords'] = array_merge($vars['keywords'], $this->getAvailableKeywords());
2097 
2098  }//end onRequestKeywords()
2099 
2100 
2101 //-- CLONING --//
2102 
2103 
2112  public function canClone()
2113  {
2114  return TRUE;
2115 
2116  }//end canClone()
2117 
2118 
2148  public function cloneComponents(Asset $clone, Array $components, $override=FALSE)
2149  {
2150  if (!is_array($components) || empty($components)) {
2151  return FALSE;
2152  }
2153  if (is_null($clone)) return FALSE;
2154 
2155  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2156  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2157 
2158  $GLOBALS['SQ_CLONE_COMPONENTS'] = TRUE;
2159 
2160  $all_contexts = $GLOBALS['SQ_SYSTEM']->getAllContexts();
2161 
2165  if (in_array('attributes', $components) || in_array('all', $components)) {
2166  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->writeAccess('attributes')) {
2167  // Save the current variables so we can restore them afterwards
2168  $current_vars = $this->vars;
2169 
2170  foreach ($all_contexts as $contextid => $context_data) {
2171  // Change the context, then reload the attributes
2172  // (we don't need to change the clone, because saveAttributes()
2173  // already works with the current context)
2174  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
2175  $this->_loadVars();
2176 
2177  // Now we set all the attributes
2178  foreach ($this->vars as $name => $data) {
2179  if (($contextid === 0) || ((boolean)$data['use_default'] === FALSE)) {
2180  if (!$clone->setAttrValue($name, $data['value'])) {
2181  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2182  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2183  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2184  return FALSE;
2185  }
2186  }
2187  }
2188  if (!$clone->saveAttributes()) {
2189  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2190  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2191  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2192  return FALSE;
2193  }
2194 
2195  $GLOBALS['SQ_SYSTEM']->restoreContext();
2196  }//end foreach
2197 
2198  // Move the original context's vars back
2199  $this->vars = $current_vars;
2200  } else {
2201  trigger_localised_error('SYS0328', E_USER_WARNING, $clone->name);
2202  }//end if (permissions)
2203  }//end if attributes
2204 
2205 
2209 
2210  if (in_array('permissions', $components) || in_array('all', $components)) {
2211  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->adminAccess('permissions')) {
2212  $this->_tmp[__CLASS__.'_in_create_cascading'] = TRUE;
2213  foreach (Array(SQ_PERMISSION_READ, SQ_PERMISSION_WRITE, SQ_PERMISSION_ADMIN) as $perm) {
2214  $set_perms = $GLOBALS['SQ_SYSTEM']->am->getAssetPermissionByCascade($this->id, $perm, NULL, TRUE);
2215  foreach ($set_perms as $perm_info) {
2216  if (!$GLOBALS['SQ_SYSTEM']->am->setPermission($clone->id, $perm_info['userid'], $perm, $perm_info['granted'], TRUE, $override)) {
2217  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2218  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2219  unset($this->_tmp[__CLASS__.'_in_create_cascading']);
2220  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2221  return FALSE;
2222  }
2223  }
2224  }//end foreach
2225  unset($this->_tmp[__CLASS__.'_in_create_cascading']);
2226  } else {
2227  trigger_localised_error('SYS0322', E_USER_WARNING, $clone->name);
2228  }
2229  }//end if permissions
2230 
2231 
2235 
2236  if (in_array('roles', $components) || in_array('all', $components)) {
2237  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->adminAccess('roles')) {
2238  $roles = $GLOBALS['SQ_SYSTEM']->am->getRole($this->id);
2239  foreach ($roles as $roleid => $ids) {
2240  foreach ($ids as $row => $userid) {
2241  if (!$GLOBALS['SQ_SYSTEM']->am->setRole($clone->id, $roleid, $userid)) {
2242  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2243  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2244  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2245  return FALSE;
2246  }
2247  }
2248  }
2249  } else {
2250  trigger_localised_error('SYS0325', E_USER_WARNING, $clone->name);
2251  }
2252  }//end if roles
2253 
2254 
2258 
2259  // first up, clone the schemas
2260  if (in_array('metadata_schemas', $components) || in_array('all', $components)) {
2261  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->adminAccess('metadata')) {
2262  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
2263  if ($mm->allowsMetadata($clone->id)) {
2264  // apply the schemas from the cloner to the clonee
2265  // But only if they're set to cascade to new assets
2266  $schemas = $mm->getSchemas($this->id, NULL, TRUE);
2267 
2268  $schema_count = 0;
2269  $metadata = Array();
2270  foreach ($schemas as $schemaid => $granted) {
2271  if (!$GLOBALS['SQ_SYSTEM']->am->assetExists($schemaid)) {
2272  continue;
2273  }
2274 
2275  // FastTrack the "metadata content file generation" task for all schemas except the last one
2276  if (++$schema_count == count($schemas)) {
2277  $clone->unFastTrack('metadata_manager_generate_content_file');
2278  } else {
2279  $clone->fastTrack('metadata_manager_generate_content_file');
2280  }
2281 
2282  if (!$mm->setSchema($clone->id, $schemaid, $granted, TRUE, $override)) {
2283  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2284  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2285  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2286  return FALSE;
2287  }
2288  }//end foreach
2289  }
2290  } else {
2291  trigger_localised_error('SYS0323', E_USER_WARNING, $clone->name);
2292  }//end if access
2293  }//end if metadata schemas
2294 
2295  // apply the actual metadata from the cloner to the clonee
2296  if (in_array('metadata', $components) || in_array('all', $components)) {
2297  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
2298  if ($mm->allowsMetadata($clone->id)) {
2299  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->writeAccess('metadata')) {
2300  $ok = TRUE;
2301  $context_count = 0;
2302 
2303  foreach ($all_contexts as $contextid => $context_data) {
2304  // Change the context, then reload the attributes
2305  // (we don't need to change the clone, because saveAttributes()
2306  // already works with the current context)
2307  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
2308 
2309  $metadata = $mm->getMetadata($this->id);
2310  if (!empty($metadata)) {
2311  // apply the metadata from $this to the clone - setMetadata() will ignore any values
2312  // belonging to fields that don't exist on the clone, so we're fine even if the
2313  // schemas weren't cloned
2314  if (!$mm->setMetadata($clone->id, $metadata) || !$mm->regenerateMetadata($clone->id)) {
2315  $ok = FALSE;
2316  }
2317  }
2318 
2319  $GLOBALS['SQ_SYSTEM']->restoreContext();
2320 
2321  if ($ok === FALSE) break;
2322 
2323  }//end foreach
2324 
2325 
2326  if ($ok === FALSE) {
2327  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2328  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2329  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2330  return FALSE;
2331  }
2332 
2333  } else {
2334  trigger_localised_error('SYS0327', E_USER_WARNING, $clone->name);
2335  }//end if ($clone->writeAccess('metadata'))
2336  }//end if ($mm->allowsMetadata($clone->id))
2337  }//end if metadata
2338 
2339 
2343 
2344  if (in_array('workflow', $components) || in_array('all', $components)) {
2345  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || $clone->adminAccess('workflow')) {
2346  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
2347  // Get all schemas that are set to cascade down to new assets
2348  $schemas = $wfm->getSchemas($this->id, NULL, FALSE, TRUE);
2349  foreach ($schemas as $schemaid => $granted) {
2350  if (!$wfm->setSchema($clone->id, $schemaid, $granted, TRUE, $override)) {
2351  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2352  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2353  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2354  return FALSE;
2355  }
2356  }//end foreach
2357  } else {
2358  trigger_localised_error('SYS0324', E_USER_WARNING, $clone->name);
2359  }
2360  }//end if workflow
2361 
2362 
2366 
2367  if (in_array('data', $components) || in_array('all', $components)) {
2368  // make sure we have write access before we start mucking around with files
2369  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) && !$clone->writeAccess('attributes')) {
2370  // if permission check is on, and user does not have permission
2371  trigger_localised_error('SYS0321', E_USER_WARNING, $clone->name);
2372  } else {
2373  // copy the directory (if it exists)
2374  if (is_dir($this->data_path)) {
2375  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
2376  // something slightly more catastrophic happened
2377  if (!copy_directory($this->data_path, $clone->data_path)) {
2378  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2379  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2380  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2381  return FALSE;
2382  }
2383  }
2384  }
2385  }//end if data
2386 
2387 
2391 
2392  if (in_array('content_tags', $components) || in_array('all', $components)) {
2393  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS) || ($clone->writeAccess('attributes') && $clone->writeAccess('links'))) {
2394  $tag_manager = $GLOBALS['SQ_SYSTEM']->getTagManager();
2395 
2396  // don't clone content tags if there's no tag manager yet
2397  if (!is_null($tag_manager)) {
2398  $current_tag_links = $tag_manager->getTagLinks($this->id);
2399  foreach ($current_tag_links as $link) {
2400  $tag_manager->setTag($clone->id, $link['minorid'], $link['value']);
2401  }
2402  }
2403  } else {
2404  trigger_localised_error('SYS0326', E_USER_WARNING, $clone->name);
2405  }
2406  }//end if content tags
2407 
2408  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2409  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2410  $GLOBALS['SQ_CLONE_COMPONENTS'] = FALSE;
2411  return TRUE;
2412 
2413  }//end cloneComponents()
2414 
2415 
2438  public function cloneComponentsAdditional(Asset $clone, Array $components)
2439  {
2440  return TRUE;
2441 
2442  }//end cloneComponentsAdditional()
2443 
2444 
2456  public function cloneLinks(Asset $clone)
2457  {
2458  // we do not want to clone any assets that are type_3 or type_notice linked to the asset we are
2459  // cloning. Instead, we will just link them up to the cloner's original link counter parts
2460  $orig_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_TYPE_3 | SQ_LINK_NOTICE, '', TRUE, 'major', NULL, FALSE, NULL);
2461  foreach ($orig_links as $orig_link) {
2462  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($orig_link['minorid'], $orig_link['minor_type_code']);
2463  if ($asset->canCloneLink()) {
2464  $linkid = $clone->createLink($asset, $orig_link['link_type'], $orig_link['value'], $orig_link['sort_order'], $orig_link['is_dependant'], $orig_link['is_exclusive']);
2465  if (!$linkid) return FALSE;
2466  }
2467 
2468  }//end foreach
2469  return TRUE;
2470 
2471  }//end cloneLinks()
2472 
2473 
2474 //-- STATUS --//
2475 
2476 
2483  public function getStatus()
2484  {
2485  if (!isset($this->_tmp['status_object'])) {
2486  // work out the name of our status file
2487  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
2488  $status_code = get_bit_names('SQ_STATUS_', $this->status);
2489  $status_code = 'asset_status_'.strtolower($status_code);
2490  require_once SQ_INCLUDE_PATH.'/asset_status/'.$status_code.'.inc';
2491  $this->_tmp['status_object'] = new $status_code($this);
2492  }
2493  return $this->_tmp['status_object'];
2494 
2495  }//end getStatus()
2496 
2497 
2505  public function getAvailableStatii()
2506  {
2507  $status = $this->getStatus();
2508  $statii = $status->getAvailableStatii();
2509  ksort($statii, SORT_NUMERIC);
2510  return array_reverse($statii, TRUE);
2511 
2512  }//end getAvailableStatii()
2513 
2514 
2521  public function getStatusDescription()
2522  {
2523  $status = $this->getStatus();
2524  return $status->getDescription();
2525 
2526  }//end getStatusDescription()
2527 
2528 
2539  public function processStatusChange($new_status, $update_parents=TRUE, $run_updated=TRUE)
2540  {
2541  $old_status = $this->status;
2542 
2543  // if we are dependant minor assets, we can only change our status
2544  // to a status higher than or equal to all the statii of our parents -
2545  // BUT only if we are going down status - we are allowed to play
2546  // 'catch-up' to a parent status but not retreat from it
2547  if ($this->status > $new_status) {
2548  // Allow downgrades anyway if in a workflow status - we still want
2549  // people to reject a workflow in this situation
2550  if ($this->status & ~(SQ_SC_STATUS_PENDING | SQ_SC_STATUS_ALL_APPROVED)) {
2551  $dependant_parents = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_SIGNIFICANT, '', TRUE, 'minor', NULL, 1);
2552  if (!empty($dependant_parents)) {
2553  $am = $GLOBALS['SQ_SYSTEM']->am;
2554  foreach ($dependant_parents as $link) {
2555  $asset = $am->getAsset($link['majorid'], $link['major_type_code']);
2556  if ($asset->status > $new_status) {
2557  // we dont want to rollback, but we dont
2558  // want to change our status for real
2559  return TRUE;
2560  }
2561  }
2562  }
2563  }
2564  }
2565 
2566  $status = $this->getStatus();
2567  if (!$status->processStatusChange($new_status)) {
2568  return FALSE;
2569  }
2570 
2571  if ($new_status != $old_status) {
2572  // the status has actually changed to a different one
2573  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2574  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2575  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
2576  $db = MatrixDAL::getDb();
2577 
2578  $sql = 'UPDATE
2579  sq_ast
2580  SET
2581  status = :status,
2582  status_changed = :status_changed_date,
2583  status_changed_userid = :userid
2584  WHERE
2585  assetid = :assetid';
2586 
2587  try {
2588  $query = MatrixDAL::preparePdoQuery($sql);
2589  MatrixDAL::bindValueToPdo($query, 'status', $new_status);
2590  MatrixDAL::bindValueToPdo($query, 'status_changed_date', ts_iso8601(time()));
2591  MatrixDAL::bindValueToPdo($query, 'userid', $GLOBALS['SQ_SYSTEM']->currentUserId());
2592  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
2593  MatrixDAL::execPdoQuery($query);
2594  } catch (Exception $e) {
2595  throw new Exception('Unable to update status of asset "'.$this->name.'" (#'.$this->id.'), due to database error: '.$e->getMessage());
2596  }
2597 
2598  $this->status = $new_status;
2599  unset($this->_tmp['status_object']);
2600 
2601  if ($run_updated) $this->_updated($update_parents);
2602 
2603  // if we have just made this asset live, we update its minor version
2604  if ($new_status == SQ_STATUS_LIVE) {
2605  if (!$this->incrementVersion('minor', FALSE)) {
2606  trigger_localised_error('SYS0271', E_USER_WARNING, $this->name);
2607  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2608  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2609  return FALSE;
2610  }
2611  }
2612 
2613  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2614  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2615 
2616  // broadcast event notifying of status change
2617  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
2618  $data = Array(
2619  'old_status' => $old_status,
2620  'new_status' => $new_status,
2621  );
2622  $em->broadcastEvent($this, 'AssetStatusUpdate', $data);
2623 
2624  // send message notifying of status change
2625  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
2626  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
2627  $msg_reps = Array(
2628  'asset_name' => $this->name,
2629  'old_status' => get_status_description($old_status),
2630  'new_status' => get_status_description($new_status),
2631  );
2632  $message = $ms->newMessage(Array(), 'asset.status', $msg_reps);
2633  $message->parameters['assetid'] = $this->id;
2634  $message->send();
2635 
2636  // prepare the event data and fire trigger_event_status_changed
2637  $event_data['old_status'] = $old_status;
2638  $event_data['new_status'] = $new_status;
2639  if (isset($this->_tmp['old_urls'])) {
2640  $event_data['old_urls'] = $this->_tmp['old_urls'];
2641  }
2642  if (isset($this->_tmp['allow_unrestricted'])) {
2643  $event_data['allow_unrestricted'] = $this->_tmp['allow_unrestricted'];
2644  }
2645  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_status_changed', $this, $event_data);
2646  }//end if
2647 
2648  return TRUE;
2649 
2650  }//end processStatusChange()
2651 
2652 
2666  public function setDate($date_type, $time=NULL, $userid=NULL)
2667  {
2668  if (!in_array($date_type, Array('published' ,'created'))) {
2669  trigger_localised_error('SYS0198', E_USER_WARNING);
2670  return FALSE;
2671  }
2672 
2673  if (is_null($userid)) {
2674  $userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
2675  }
2676  if (is_null($time)) {
2677  if ($date_type == 'created') {
2678  $time = time();
2679  } else {
2680  // a NULL published date means that the asset has never been published
2681  // so we also set the userid to NULL
2682  $userid = NULL;
2683  }
2684  }
2685 
2686  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
2687  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
2688 
2689  // construct a string to represent the current date value
2690  if ($this->$date_type != NULL) {
2691  $old_date_string = readable_datetime($this->$date_type);
2692  $current_user = $date_type.'_userid';
2693  if ($this->$current_user != NULL) {
2694  $old_date_string .= ' by user #'.$this->$current_user;
2695  } else {
2696  $old_date_string .= ' by [unknown user]';
2697  }
2698  } else {
2699  $old_date_string = 'never '.$date_type;
2700  }
2701 
2702  // construct a string to represent the new date value
2703  if ($time != NULL) {
2704  $new_date_string = readable_datetime($time);
2705  if ($userid != NULL) {
2706  $new_date_string .= ' by user #'.$userid;
2707  } else {
2708  $new_date_string .= ' by [unknown user]';
2709  }
2710  } else {
2711  $new_date_string = 'never '.$date_type;
2712  }
2713 
2714  // if the date and user has not changed, dont process anything
2715  if ($this->$date_type == $time) {
2716  // if we are not changing dates - check if we are changing users
2717  $user_type = $date_type.'_userid';
2718  if ($this->$user_type == $userid) return TRUE;
2719  }
2720 
2721  // begin a transaction
2722  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2723  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2724  $db = MatrixDAL::getDb();
2725 
2726  $sql = 'UPDATE
2727  sq_ast
2728  SET
2729  '.$date_type.' = :'.$date_type.'_date,
2730  '.$date_type.'_userid = :userid
2731  WHERE
2732  assetid = :assetid';
2733 
2734  try {
2735  $query = MatrixDAL::preparePdoQuery($sql);
2736  MatrixDAL::bindValueToPdo($query, $date_type.'_date', ts_iso8601($time));
2737  MatrixDAL::bindValueToPdo($query, 'userid', $userid);
2738  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
2739  MatrixDAL::execPdoQuery($query);
2740  } catch (Exception $e) {
2741  throw new Exception('Unable to update '.str_replace('_', ' ', $date_type).' date of asset "'.$this->name.'" (#'.$this->id.'), due to database error: '.$e->getMessage());
2742  }
2743 
2744  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2745  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2746 
2747  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
2748  $msg_reps = Array(
2749  'date_type' => $date_type,
2750  'asset_name' => $this->name,
2751  'old_date' => $old_date_string,
2752  'new_date' => $new_date_string,
2753  );
2754  $message = $ms->newMessage(Array(), 'asset.dates', $msg_reps);
2755  $message->parameters['assetid'] = $this->id;
2756  $message->send();
2757 
2758  eval('$this->'.$date_type.' = $time;');
2759  eval('$this->'.$date_type.'_userid = $userid;');
2760 
2761  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
2762  $em->broadcastEvent($this, 'AssetUpdate', Array($date_type));
2763 
2764 
2765  return TRUE;
2766 
2767  }//end setDate()
2768 
2769 
2770 //-- LOCKING --//
2771 
2772 
2779  public function lockTypes()
2780  {
2781  $lock_types = Array(
2782  'settings' => 1,
2783  'attributes' => 2,
2784  'permissions' => 4,
2785  'links' => 8,
2786  'workflow' => 16,
2787  'metadata' => 32,
2788  'lookups' => 64,
2789  'roles' => 256,
2790  );
2791 
2792  // all locks
2793  $all = 0;
2794  foreach ($lock_types as $lock_type) {
2795  $all = $all | $lock_type;
2796  }
2797  $lock_types['all'] = $all;
2798 
2799  // a menu lock allows editing of both attributes (such as name etc)
2800  // and links (such as position in menu)
2801  $lock_types['menu'] = $lock_types['attributes'] | $lock_types['links'];
2802  $lock_types['lookupValues'] = $lock_types['attributes'] | $lock_types['links'] | $lock_types['lookups'];
2803 
2804  return $lock_types;
2805 
2806  }//end lockTypes()
2807 
2808 
2818  public function canForceablyAcquireLock($lock_type)
2819  {
2820  $current_locks = $GLOBALS['SQ_SYSTEM']->am->getLockInfo($this->id, $lock_type, TRUE, FALSE);
2821 
2822  // lock type not known
2823  if (empty($current_locks)) return FALSE;
2824 
2825  foreach ($current_locks as $lock) {
2826  if (empty($lock)) continue;
2827  $user = NULL;
2828  if ($lock['userid']) {
2829  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($lock['userid']);
2830  }
2831  // lets work out if the current user has a high
2832  // enough level of access to forceably acquire the lock
2833  if (!is_null($user)) {
2834  // locked by root, no-one can force acquire...
2835  if ($GLOBALS['SQ_SYSTEM']->userRoot($user)) {
2836  return FALSE;
2837  }
2838 
2839  // locked by a sysadmin? only root can force acquire...
2840  if ($GLOBALS['SQ_SYSTEM']->userSystemAdmin($user) && !$GLOBALS['SQ_SYSTEM']->userRoot()) {
2841  return FALSE;
2842  }
2843  }
2844 
2845  // locked by someone else? need to be a system admin to acquire this lock
2846  if (!$GLOBALS['SQ_SYSTEM']->userRoot() && !$GLOBALS['SQ_SYSTEM']->userSystemAdmin()) {
2847  return FALSE;
2848  }
2849 
2850  }//end foreach
2851  return TRUE;
2852 
2853  }//end canForceablyAcquireLock()
2854 
2855 
2864  public function getEditingLocks($keywords)
2865  {
2866  return Array();
2867 
2868  }//end getEditingLocks()
2869 
2870 
2871 //-- LINKING --//
2872 
2873 
2887  public function _getAllowedLinks()
2888  {
2889  return Array(
2890  SQ_LINK_TYPE_1 => Array(),
2891  SQ_LINK_TYPE_2 => Array(),
2892  SQ_LINK_TYPE_3 => Array(),
2893  SQ_LINK_NOTICE => Array(
2894  'image' => Array('card' => 'M', 'exclusive' => FALSE),
2895  'design' => Array('card' => 'M', 'exclusive' => FALSE),
2896  'paint_layout_page' => Array('card' => 'M', 'exclusive' => FALSE),
2897  ),
2898  );
2899 
2900  }//end _getAllowedLinks()
2901 
2902 
2923  public function createLink(Asset $minor, $link_type, $value='', $sort_order=NULL, $dependant='0', $exclusive='0', $moving=FALSE, $locked=0)
2924  {
2925  if (!$this->id) return 0;
2926  return $GLOBALS['SQ_SYSTEM']->am->createAssetLink($this, $minor, $link_type, $value, $sort_order, $dependant, $exclusive, $moving, $locked);
2927 
2928  }//end createLink()
2929 
2930 
2951  public function prepareLink(Asset $asset, $side_of_link, &$link_type, &$value, &$sort_order, &$dependant, &$exclusive)
2952  {
2953  return FALSE;
2954 
2955  }//end prepareLink()
2956 
2957 
2968  public function canCreateLink(Asset $minor, $link_type, $exclusive)
2969  {
2970  if (!$this->id) return translate('asset_not_created');
2971  return $GLOBALS['SQ_SYSTEM']->am->canCreateLink($this, $minor, $link_type, $exclusive);
2972 
2973  }//end canCreateLink()
2974 
2975 
2994  public function canMoveLink(Asset $minor, Asset$old_major, $link_type)
2995  {
2996  return $this->canCreateLink($minor, $link_type, 0);
2997 
2998  }//end canMoveLink()
2999 
3000 
3009  public function describeLink($linkid)
3010  {
3011  return '';
3012 
3013  }//end describeLink()
3014 
3015 
3024  public function isDeletableLink($linkid)
3025  {
3026  return TRUE;
3027 
3028  }//end isDeletableLink()
3029 
3030 
3041  public function canDeleteLink($linkid)
3042  {
3043  if (!$this->id) return translate('asset_not_created');
3044  if (($err_msg = $this->isDeletableLink($linkid)) !== TRUE) {
3045  return $err_msg;
3046  }
3047  // we only need permissions, not effective access, to delete a significant link
3048  if (!$this->writeAccess()) {
3049  return translate('permission_denied');
3050  }
3051  return TRUE;
3052 
3053  }//end canDeleteLink()
3054 
3055 
3066  public function deleteLink($linkid, $check_locked=TRUE)
3067  {
3068  $link = $GLOBALS['SQ_SYSTEM']->am->getLinkById($linkid, $this->id);
3069  if (empty($link)) {
3070  trigger_localised_error('SYS0243', E_USER_NOTICE, $linkid);
3071  return FALSE;
3072  } else {
3073  return $GLOBALS['SQ_SYSTEM']->am->deleteAssetLink($linkid, $check_locked);
3074  }
3075 
3076  }//end deleteLink()
3077 
3078 
3089  public function linksUpdated()
3090  {
3091 
3092 
3093  }//end linksUpdated()
3094 
3095 
3102  public function canCloneLink()
3103  {
3104  return TRUE;
3105 
3106  }//end isDeletableLink()
3107 
3108 
3109 //-- ATTRIBUTES --//
3110 
3111 
3123  public function saveAttributes($dont_run_updated=FALSE, $log_message=TRUE)
3124  {
3125  if (!$this->id) return TRUE;
3126  if (empty($this->_tmp['vars_set'])) return TRUE;
3127 
3128  if (!$GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] && !$this->writeAccess('attributes')) {
3129  trigger_localised_error('CORE0121', E_USER_WARNING, $this->name, $this->id);
3130  return FALSE;
3131  }
3132 
3133  $save_vars = Array();
3134  $attr_ids = Array();
3135  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
3136 
3137  $db = MatrixDAL::getDb();
3138 
3139  // open a queue for all messages we are going to be logging
3140  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
3141  $ms->openLog();
3142 
3143  $changed_array = Array();
3144 
3145  foreach ($this->_tmp['vars_set'] as $var_name => $var_data) {
3146  $attr_id = $this->vars[$var_name]['attrid'];
3147  $is_contextable = array_get_index($this->vars[$var_name], 'is_contextable', FALSE) ? TRUE : FALSE;
3148 
3149  // if this is a unique attribute then let's make sure that this isn't already set
3150  $owning_attributeid = 0;
3151  $attribute = $this->getAttribute($var_name);
3152  if ($attribute->uniq) {
3153 
3154  $sql = 'SELECT oa.attrid
3155  FROM sq_ast_attr a
3156  INNER JOIN sq_ast_attr oa ON (a.owning_type_code = oa.type_code AND a.name = oa.name)
3157  WHERE a.name = :name
3158  AND a.type_code = :type_code';
3159 
3160  try {
3161  $query = MatrixDAL::preparePdoQuery($sql);
3162  MatrixDAL::bindValueToPdo($query, 'name', $var_name);
3163  MatrixDAL::bindValueToPdo($query, 'type_code', $this->type());
3164  $owning_attributeid = MatrixDAL::executePdoOne($query);
3165  } catch (Exception $e) {
3166  throw new Exception('Cannot check for attribute value "'.$name.'" of type code "'.$this->type().'" due to database error: '.$e->getMessage());
3167  }
3168 
3169  }// end if
3170 
3171  $attr_value = $this->vars[$var_name]['value'];
3172  if ($this->vars[$var_name]['type'] == 'serialise') {
3173  $attr_value = serialize($attr_value);
3174  }
3175  $save_vars[$attr_id]['value'] = $attr_value;
3176  $save_vars[$attr_id]['name'] = $var_name;
3177  $save_vars[$attr_id]['owning_attrid'] = $owning_attributeid;
3178  $attr_ids[] = $attr_id;
3179 
3180  // log a message for the asset - this wont get logged until we close the queue
3181  if($log_message) {
3182  $msg_reps = Array(
3183  'asset_name' => $this->name,
3184  'attr_name' => $attribute->name,
3185  );
3186  $msg_type = 'asset.attributes.fulllog';
3187  if (is_scalar($var_data['old_value']) && is_scalar($attr_value)) {
3188  $msg_reps['old_value'] = $var_data['old_value'];
3189  $msg_reps['new_value'] = $attr_value;
3190  $msg_type .= '.scalar';
3191  }
3192  $message = $ms->newMessage(Array(), $msg_type, $msg_reps);
3193  $message->parameters['assetid'] = $this->id;
3194  $ms->logMessage($message);
3195 
3196  // log a smaller message for users to view - this wont get logged until we close the queue
3197  require_once SQ_FUDGE_PATH.'/general/general.inc';
3198  $msg_type = 'asset.attributes';
3199  if (is_scalar($var_data['old_value']) && is_scalar($attr_value)) {
3200  $msg_reps['old_value'] = ellipsisize($var_data['old_value'],30);
3201  $msg_reps['new_value'] = ellipsisize($attr_value,30);
3202  $msg_type .= '.scalar';
3203  }
3204 
3205  $sml_message = $ms->newMessage(Array(), $msg_type, $msg_reps);
3206  $sml_message->parameters['assetid'] = $this->id;
3207  $ms->logMessage($sml_message);
3208  }
3209 
3210  // add attribute to the event broadcaster array (for notification of what has changed)
3211  // this allows event listener to act only on what has changed
3212 
3213  $changed_array[] = $attribute->name;
3214 
3215  }//end foreach
3216 
3217  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
3218  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
3219  $db = MatrixDAL::getDb();
3220 
3221  // find any previous entries
3222  $atrr_id_cond = 'IN ('.implode(', ', $attr_ids).')';
3223  $sql = 'SELECT attrid FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_val ';
3224  $where = 'assetid = :assetid
3225  AND attrid '.$atrr_id_cond.'
3226  AND contextid = :contextid';
3227  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
3228 
3229  try {
3230  $query = MatrixDAL::preparePdoQuery($sql.$where);
3231  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
3232  MatrixDAL::bindValueToPdo($query, 'contextid', $contextid);
3233  $existing = MatrixDAL::executePdoAssoc($query, 0);
3234  } catch (Exception $e) {
3235  throw new Exception('Unable to get attributes for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
3236  }
3237 
3238  $new = array_diff($attr_ids, $existing);
3239 
3240  // update existing custom attribute values
3241  if (!empty($existing)) {
3242 
3243  foreach ($existing as $attr_id) {
3244  $attr_id = (int) $attr_id;
3245  $attr_value = $save_vars[$attr_id]['value'];
3246 
3247  // if this is a unique attribute then let's make sure we register that in the unique value table
3248  $attribute = $this->getAttribute($save_vars[$attr_id]['name']);
3249  $is_contextable = array_get_index($this->vars[$save_vars[$attr_id]['name']], 'is_contextable', FALSE) ? TRUE : FALSE;
3250 
3251  try {
3252  if (($contextid === 0) || ($is_contextable === FALSE) || (array_key_exists('__creating__', $this->_tmp) === TRUE)) {
3253  // If updating in the default context, we should be updating all
3254  // context values that are still using the default
3255  // If not contextable, just keep them linked together as well
3256  $unique_query_name = 'updateDefaultContextUniqueAttrValue';
3257  $query_name = 'updateDefaultContextAttrValue';
3258  $bind_vars = Array(
3259  'custom_val' => $attr_value,
3260  'assetid' => $this->id,
3261  );
3262  } else {
3263  // Updating in alternate context. Set the value and mark that
3264  // we have now broken away from the default
3265  $unique_query_name = 'updateAlternateContextUniqueAttrValue';
3266  $query_name = 'updateAlternateContextAttrValue';
3267  $bind_vars = Array(
3268  'custom_val' => $attr_value,
3269  'contextid' => $contextid,
3270  'assetid' => $this->id,
3271  );
3272  $this->vars[$save_vars[$attr_id]['name']]['use_default'] = 0;
3273  }
3274  if ((boolean)$attribute->uniq === TRUE) {
3275  $bind_vars['attrid'] = $save_vars[$attr_id]['owning_attrid'];
3276  MatrixDAL::executeQuery('core', $unique_query_name, $bind_vars);
3277  }
3278  $bind_vars['attrid'] = $attr_id;
3279  MatrixDAL::executeQuery('core', $query_name, $bind_vars);
3280  } catch (Exception $e) {
3281  throw new Exception('Unable to update attribute values for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
3282  }
3283 
3284  }//end foreach
3285 
3286  }//end updating existing custom attribute values
3287 
3288  // insert new custom attribute values
3289  if (!empty($new)) {
3290 
3291  foreach ($new as $attr_id) {
3292  $attr_id = (int) $attr_id;
3293  $attr_value = $save_vars[$attr_id]['value'];
3294 
3295  // if this is a unique attribute then let's make sure we register that in the unique value table
3296  $attribute = $this->getAttribute($save_vars[$attr_id]['name']);
3297  $is_contextable = array_get_index($this->vars[$save_vars[$attr_id]['name']], 'is_contextable', FALSE) ? TRUE : FALSE;
3298 
3299  try {
3300  if (($contextid === 0) || ($is_contextable === FALSE) || (array_key_exists('__creating__', $this->_tmp) === TRUE)) {
3301  // Inserting into the default context. Insert into all other
3302  // existing contexts that don't yet have a value set, and
3303  // mark them as using the default
3304  $unique_query_name = 'insertDefaultContextUniqueAttrValue';
3305  $query_name = 'insertDefaultContextAttrValue';
3306  $bind_vars = Array(
3307  'custom_val' => $attr_value,
3308  'assetid' => $this->id,
3309  );
3310  // Oracle has problems handling INSERT..SELECT, because we
3311  // can't make the DAL know whether it's a LOB or not.
3312  // Changes have been made to MatrixDAL so we can pass LOB
3313  // objects to bindings DAL has not assigned to a field.
3314  // Two LOBs are needed if we are inserting unique values,
3315  // since one is invalidated after each query is run.
3316  if (MatrixDAL::getDbType() === 'oci') {
3317  if ((boolean)$attribute->uniq === TRUE) {
3318  $lob1 = oci_new_descriptor(MatrixDAL::getDb(), OCI_D_LOB);
3319  $lob1->writeTemporary($bind_vars['custom_val'], OCI_TEMP_CLOB);
3320  }
3321  $lob2 = oci_new_descriptor(MatrixDAL::getDb(), OCI_D_LOB);
3322  $lob2->writeTemporary($bind_vars['custom_val'], OCI_TEMP_CLOB);
3323  }
3324  } else {
3325  // Inseting into an alternate context. (Strange...)
3326  // Set up the row, set as broken away from default
3327  $unique_query_name = 'insertAlternateContextUniqueAttrValue';
3328  $query_name = 'insertAlternateContextAttrValue';
3329  $bind_vars = Array(
3330  'custom_val' => $attr_value,
3331  'contextid' => $contextid,
3332  'assetid' => $this->id,
3333  );
3334  $this->vars[$save_vars[$attr_id]['name']]['use_default'] = 0;
3335  }
3336  if ((boolean)$attribute->uniq === TRUE) {
3337  if (isset($lob1) === TRUE) $bind_vars['custom_val'] = $lob1;
3338  $bind_vars['attrid'] = $save_vars[$attr_id]['owning_attrid'];
3339  MatrixDAL::executeQuery('core', $unique_query_name, $bind_vars);
3340  }
3341  if (isset($lob2) === TRUE) $bind_vars['custom_val'] = $lob2;
3342  $bind_vars['attrid'] = $attr_id;
3343  MatrixDAL::executeQuery('core', $query_name, $bind_vars);
3344  } catch (Exception $e) {
3345  throw new Exception('Unable to insert new attribute values for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
3346  }
3347 
3348  }//end foreach
3349 
3350  }//end new custom attribute values
3351 
3352  // tell, the asset it has updated
3353  if (!$dont_run_updated && !$this->_updated()) {
3354  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
3355  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
3356  $ms->abortLog();
3357  return FALSE;
3358  }
3359 
3360  // if we get this far, then it's all OK
3361  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
3362  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
3363  $ms->closeLog();
3364 
3365  if (!empty($this->_tmp['vars_set'])) {
3366  // prepare to broadcast the attributes_changed
3367  foreach ($this->_tmp['vars_set'] as $attr => $details) {
3368  $changed_vars[$attr] = Array(
3369  'type' => $this->vars[$attr]['type'],
3370  'old_value' => $details['old_value'],
3371  'new_value' => $this->attr($attr),
3372  );
3373  }
3374  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_attributes_changed', $this, $changed_vars);
3375  }
3376 
3377  unset($this->_tmp['vars_set']);
3378 
3379  // notify anyone interested that attributes changed
3380  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
3381  $em->broadcastEvent($this, 'attributeChange', $changed_array);
3382  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_attributes_saved', $this);
3383 
3384  return TRUE;
3385 
3386  }//end saveAttributes()
3387 
3388 
3402  public function setAttrValue($name, $value)
3403  {
3404  if (empty($this->vars[$name])) {
3405  trigger_localised_error('SYS0093', E_USER_WARNING, $name, $this->type());
3406  return FALSE;
3407  }
3408 
3409  if(!SQ_IN_BACKEND && !SQ_IN_LIMBO && !SQ_IN_LOGIN && !SQ_IN_CRON && !SQ_PHP_CLI) {
3410  $filter_enabled = $GLOBALS['SQ_SYSTEM']->getUserPrefs('user', 'SQ_USER_FILTER_FRONT_END_INPUT');
3411  if(isset($GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'] ) && $GLOBALS['SQ_REVERT_TO_SYSTEM_VERSION'])
3412  $filter_enabled = FALSE;
3413  else if (isset($GLOBALS['SQ_CLONE_COMPONENTS'] ) && $GLOBALS['SQ_CLONE_COMPONENTS'])
3414  $filter_enabled = FALSE;
3415 
3416  //Any identified attributes that shouldn't be filtered
3417  $type_excl = Array ('form_submission');
3418  $attr_excl = Array ('xml');
3419  if ($filter_enabled && !(in_array($this->type(), $type_excl) && in_array($name, $attr_excl))){
3420  if (!empty($value)) {
3421  $value = filter_content($value);
3422  if ($value === FALSE){
3423  trigger_localised_error('SYS0346', E_USER_WARNING, $name, $this->type());
3424  return FALSE;
3425  }
3426  }
3427  }
3428  }
3429 
3430  $attribute = $this->getAttribute($name);
3431  if (!$attribute->setValue($value)) {
3432  trigger_localised_error('SYS0073', E_USER_WARNING, $value, $name);
3433  $attribute->setValue($this->vars[$name]['value']);
3434  return FALSE;
3435  }
3436 
3437  if ($this->vars[$name]['type'] == 'serialise') {
3438  $value = unserialize($value);
3439  }
3440 
3441  // if the value being set is the same as the current value - don't do anything
3442  // take into consideration Oracle's treatment of empty strings - they are retrieved as NULL values
3443  // this condition is satisfied when assigning an empty string in place of a pre-existing NULL value in the case of Oracle systems
3444  if (($this->vars[$name]['value'] === $value) || (is_null($this->vars[$name]['value']) && $value === '')) {
3445  return TRUE;
3446  }
3447 
3448  // if this is a uniq attribute then let's make sure that this isn't already set
3449  if ($attribute->uniq) {
3450  $db = MatrixDAL::getDb();
3451 
3452  $sql = 'SELECT COUNT(*)
3453  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_attr_uniq_val uv
3454  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_attr oa ON uv.owning_attrid = oa.attrid
3455  INNER JOIN sq_ast_attr a ON (oa.type_code = a.owning_type_code AND oa.name = a.name)
3456  ';
3457  $where = ' uv.custom_val = :custom_val
3458  AND a.name = :name
3459  AND a.type_code = :type_code
3460  AND uv.contextid = :contextid';
3461  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'uv');
3462 
3463  try {
3464  $query = MatrixDAL::preparePdoQuery($sql.$where);
3465  MatrixDAL::bindValueToPdo($query, 'custom_val', $value);
3466  MatrixDAL::bindValueToPdo($query, 'name', $name);
3467  MatrixDAL::bindValueToPdo($query, 'type_code', $this->type());
3468  MatrixDAL::bindValueToPdo($query, 'contextid', $GLOBALS['SQ_SYSTEM']->getContextId());
3469  $result = MatrixDAL::executePdoOne($query);
3470  } catch (Exception $e) {
3471  throw new Exception('Cannot check for unique attribute value "'.$name.'" of type code "'.$this->type().'" due to database error: '.$e->getMessage());
3472  }
3473 
3474  if ((int) $result > 0) {
3475  trigger_localised_error('SYS0251', E_USER_WARNING, $name, $value);
3476  $attribute->setValue($this->vars[$name]['value']);
3477  return FALSE;
3478  }
3479 
3480  }// end if
3481 
3482  if (!isset($this->_tmp['vars_set'])) {
3483  $this->_tmp['vars_set'] = Array();
3484  }
3485  if (is_scalar($this->vars[$name]['value'])) {
3486  $this->_tmp['vars_set'][$name]['old_value'] = $this->vars[$name]['value'];
3487  } else {
3488  $this->_tmp['vars_set'][$name]['old_value'] = '__(old value)__';
3489  }
3490 
3491  $this->vars[$name]['value'] = $value;
3492 
3493 
3494 
3495  return TRUE;
3496 
3497  }//end setAttrValue()
3498 
3499 
3508  public function attr($name)
3509  {
3510  if (!isset($this->vars[$name])) {
3511  trigger_localised_error('SYS0092', E_USER_WARNING, $name, $this->name, $this->id);
3512  return NULL;
3513  }
3514  return $this->vars[$name]['value'];
3515 
3516  }//end attr()
3517 
3518 
3527  public function &attrByRef($name)
3528  {
3529  if (!isset($this->vars[$name])) {
3530  trigger_localised_error('SYS0092', E_USER_WARNING, $name, $this->name, $this->id);
3531  return NULL;
3532  }
3533  return $this->vars[$name]['value'];
3534 
3535  }//end attrByRef()
3536 
3537 
3549  public function getAttribute($name, $mute_errors=FALSE)
3550  {
3551  if (!isset($this->_tmp['attributes'][$name]) || !is_object($this->_tmp['attributes'][$name])) {
3552 
3553  if (empty($this->_tmp['attributes'])) {
3554  $this->_tmp['attributes'] = Array();
3555  }
3556 
3557  if (empty($this->vars[$name])) {
3558  if (!$mute_errors) {
3559  trigger_localised_error('SYS0094', E_USER_WARNING, $name, $this->type());
3560  }
3561 
3562  $this->_tmp['attributes'][$name] = NULL;
3563  } else {
3564 
3565  require_once SQ_ATTRIBUTES_PATH.'/'.$this->vars[$name]['type'].'/'.$this->vars[$name]['type'].'.inc';
3566  $attr_class = 'Asset_Attribute_'.$this->vars[$name]['type'];
3567  $this->_tmp['attributes'][$name] = new $attr_class($this->vars[$name]['attrid'], $this->vars[$name]['value']);
3568 
3569  }//end if
3570 
3571  }//end if
3572 
3573  return $this->_tmp['attributes'][$name];
3574 
3575  }//end getAttribute()
3576 
3577 
3598  public function getAssetKeywords($descriptions=FALSE)
3599  {
3600 
3601  $keywords = Array();
3602  $default_keywords = Array(
3603  'asset_name',
3604  'asset_type',
3605  'asset_short_name',
3606  'asset_version',
3607  'asset_version_major',
3608  'asset_version_minor',
3609  'asset_version_micro',
3610  'asset_created',
3611  'asset_updated',
3612  'asset_created_short',
3613  'asset_updated_short',
3614  'asset_created_readable',
3615  'asset_updated_readable',
3616  'asset_created_iso8601',
3617  'asset_updated_iso8601',
3618  'asset_created_rfc2822',
3619  'asset_updated_rfc2822',
3620  'asset_created_ical',
3621  'asset_updated_ical',
3622  'asset_created_by_name',
3623  'asset_updated_by_name',
3624  'asset_published',
3625  'asset_published_short',
3626  'asset_published_readable',
3627  'asset_published_iso8601',
3628  'asset_published_rfc2822',
3629  'asset_published_ical',
3630  'asset_published_by_name',
3631  'asset_status_changed',
3632  'asset_status_changed_short',
3633  'asset_status_changed_readable',
3634  'asset_status_changed_iso8601',
3635  'asset_status_changed_rfc2822',
3636  'asset_status_changed_ical',
3637  'asset_status_changed_by_name',
3638  'asset_url',
3639  'asset_href',
3640  'asset_thumbnail',
3641  'asset_thumbnail_url',
3642  'asset_thumbnail_caption',
3643  'asset_thumbnail_assetid',
3644  'asset_thumbnail_title',
3645  'asset_thumbnail_name',
3646  );
3647 
3648  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
3649  $mm_keywords = $mm->generateKeywordReplacements($this, $default_keywords, FALSE);
3650  foreach ($default_keywords as $keyword) {
3651  // we need to check. otherwise we get a undefined index error if mm_keywords for
3652  // one of the default keywords is not set
3653  if(isset($mm_keywords[$keyword])) $keywords[$keyword]['value'] = $mm_keywords[$keyword];
3654  }
3655 
3656  foreach ($this->vars as $name => $info) {
3657  if ($info['type'] == 'serialise') continue;
3658  $keywords['asset_attribute_'.$name]['value'] = $info['value'];
3659 
3660  if ($descriptions) {
3661  $attr = $this->getAttribute($name);
3662  $keywords['asset_attribute_'.$name]['description'] = $attr->description;
3663  }
3664  }
3665 
3666  $keywords['asset_assetid']['value'] = $this->id;
3667 
3668  if ($descriptions) {
3669  $keywords['asset_assetid']['description'] = 'The ID of the asset';
3670  $keywords['asset_name']['description'] = 'Full name of the asset';
3671  $keywords['asset_type']['description'] = 'Asset\'s type';
3672  $keywords['asset_short_name']['description'] = 'Short name of the asset';
3673  $keywords['asset_version']['description'] = 'Version of the asset being displayed';
3674  $keywords['asset_version_major']['description'] = 'Major version number of the asset being displayed';
3675  $keywords['asset_version_minor']['description'] = 'Minor version number of the asset being displayed';
3676  $keywords['asset_version_micro']['description'] = 'Micro version number of the asset being displayed';
3677  $keywords['asset_created']['description'] = 'The date and time the asset was created (yyyy-mm-dd hh:mm:ss)';
3678  $keywords['asset_updated']['description'] = 'The date and time the asset was last updated (yyyy-mm-dd hh:mm:ss)';
3679  $keywords['asset_status_updated']['description'] = 'The date and time the asset\'s status was last updated (yyyy-mm-dd hh:mm:ss)';
3680  $keywords['asset_published']['description'] = 'The date and time the asset was last published (yyyy-mm-dd hh:mm:ss)';
3681  $keywords['asset_created_short']['description'] = 'The date when the asset was created (yyyy-mm-dd)';
3682  $keywords['asset_updated_short']['description'] = 'The date when the asset was last updated (yyyy-mm-dd)';
3683  $keywords['asset_published_short']['description'] = 'The date when the asset was last published (yyyy-mm-dd)';
3684  $keywords['asset_status_updated_short']['description'] = 'The date when the asset\'s status was last changed (yyyy-mm-dd)';
3685  $keywords['asset_created_readable']['description'] = 'The date when the asset was created (dd m yyyy h:mm[am pm])';
3686  $keywords['asset_updated_readable']['description'] = 'The date when the asset was last updated (dd m yyyy h:mm[am pm])';
3687  $keywords['asset_created_readabledate']['description'] = 'The date when the asset was created (date only - dd m yyyy)';
3688  $keywords['asset_updated_readabledate']['description'] = 'The date when the asset was last updated (date only - dd m yyyy)';
3689  $keywords['asset_created_readabletime']['description'] = 'The date when the asset was created (time only - h:mm[am pm])';
3690  $keywords['asset_updated_readabletime']['description'] = 'The date when the asset was last updated (time only - h:mm[am pm])';
3691  $keywords['asset_created_iso8601']['description'] = 'The date when the asset was created (ISO8601 format)';
3692  $keywords['asset_updated_iso8601']['description'] = 'The date when the asset was last updated (ISO8601 format)';
3693  $keywords['asset_published_iso8601']['description'] = 'The date when the asset was last published (ISO8601 format)';
3694  $keywords['asset_created_rfc2822']['description'] = 'The date when the asset was created (RFC2822 format)';
3695  $keywords['asset_updated_rfc2822']['description'] = 'The date when the asset was last updated (RFC2822 format)';
3696  $keywords['asset_published_rfc2822']['description'] = 'The date when the asset was last published (RFC2822 format)';
3697  $keywords['asset_created_ical']['description'] = 'The date when the asset was created (iCalendar format)';
3698  $keywords['asset_updated_ical']['description'] = 'The date when the asset was last updated (iCalendar format)';
3699  $keywords['asset_published_ical']['description'] = 'The date when the asset was last published (iCalendar format)';
3700  $keywords['asset_published_readable']['description'] = 'The date when the asset was last published (dd m yyyy h:mm[am pm])';
3701  $keywords['asset_status_changed_readable']['description'] = 'The date when the asset\'s status was last changed (dd m yyyy h:mm[am pm])';
3702  $keywords['asset_published_readabledate']['description'] = 'The date when the asset was last published (date only - dd m yyyy)';
3703  $keywords['asset_status_changed_readabledate']['description'] = 'The date when the asset\'s status was last changed (date only - dd m yyyy)';
3704  $keywords['asset_published_readabletime']['description'] = 'The date when the asset was last published (time only - h:mm[am pm])';
3705  $keywords['asset_status_changed_readabletime']['description'] = 'The date when the asset\'s status was last changed (time only - h:mm[am pm])';
3706  $keywords['asset_created_by_name']['description'] = 'The name of the user who created this asset';
3707  $keywords['asset_updated_by_name']['description'] = 'The name of the user who last updated this asset';
3708  $keywords['asset_published_by_name']['description'] = 'The name of the user who last published this asset';
3709  $keywords['asset_status_changed_by_name']['description'] = 'The name of the user who last changed the status of this asset';
3710  $keywords['asset_url']['description'] = 'The absolute url to the asset';
3711  $keywords['asset_href']['description'] = 'The relative href to the asset';
3712  $keywords['asset_thumbnail']['description'] = 'The img tag for the thumbnail attached to the asset';
3713  $keywords['asset_thumbnail_url']['description'] = 'The URL only for the thumbnail attached to the asset';
3714  $keywords['asset_thumbnail_caption']['description'] = 'The caption for the thumbnail attached to the asset';
3715  $keywords['asset_thumbnail_assetid']['description'] = 'The asset id for the thumbnail attached to the asset';
3716  $keywords['asset_thumbnail_title']['description'] = 'The friendly name for the thumbnail attached to the asset';
3717  $keywords['asset_thumbnail_name']['description'] = 'The file name for the thumbnail attached to the asset';
3718 
3719  }//end if
3720 
3721  return $keywords;
3722 
3723  }//end getAssetKeywords()
3724 
3725 
3740  public function getAvailableKeywords()
3741  {
3742 
3743  $keywords = Array();
3744  $default_keywords = Array(
3745  'asset_assetid',
3746  'asset_name',
3747  'asset_type',
3748  'asset_short_name',
3749  'asset_version',
3750  'asset_version_major',
3751  'asset_version_minor',
3752  'asset_version_micro',
3753  'asset_created',
3754  'asset_updated',
3755  'asset_created_short',
3756  'asset_updated_short',
3757  'asset_created_readable',
3758  'asset_updated_readable',
3759  'asset_created_readabledate',
3760  'asset_updated_readabledate',
3761  'asset_created_readabletime',
3762  'asset_updated_readabletime',
3763  'asset_created_iso8601',
3764  'asset_updated_iso8601',
3765  'asset_created_rfc2822',
3766  'asset_updated_rfc2822',
3767  'asset_created_ical',
3768  'asset_updated_ical',
3769  'asset_created_by_name',
3770  'asset_updated_by_name',
3771  'asset_published',
3772  'asset_published_short',
3773  'asset_published_readable',
3774  'asset_published_readabledate',
3775  'asset_published_readabletime',
3776  'asset_published_iso8601',
3777  'asset_published_rfc2822',
3778  'asset_published_ical',
3779  'asset_published_by_name',
3780  'asset_status_changed',
3781  'asset_status_changed_short',
3782  'asset_status_changed_readable',
3783  'asset_status_changed_readabledate',
3784  'asset_status_changed_readabletime',
3785  'asset_status_changed_iso8601',
3786  'asset_status_changed_rfc2822',
3787  'asset_status_changed_ical',
3788  'asset_status_changed_by_name',
3789  'asset_url',
3790  'asset_href',
3791  'asset_thumbnail',
3792  'asset_thumbnail_url',
3793  'asset_thumbnail_caption',
3794  'asset_thumbnail_assetid',
3795  'asset_thumbnail_title',
3796  'asset_thumbnail_name',
3797  'asset_status_description',
3798  'asset_status_colour',
3799  );
3800 
3801  // add default keywords first
3802  foreach ($default_keywords as $keyword) {
3803  $keywords[$keyword] = translate($keyword);
3804  }
3805 
3806  // add attribute
3807  foreach ($this->vars as $name => $info) {
3808  $attr = $this->getAttribute($name);
3809  if (!$attr->is_admin) {
3810  $keywords['asset_attribute_'.$name] = empty($attr->description) ? 'Asset Attribute: '.$name : $attr->description;
3811  }
3812  }
3813 
3814  return $keywords;
3815 
3816  }//end getAvailableKeywords()
3817 
3818 
3836  public function getKeywordReplacement($keyword)
3837  {
3838  $replacement = $this->_getKeywordReplacement($keyword);
3839  return $replacement;
3840 
3841  }//end getKeywordReplacement()
3842 
3843 
3860  protected function _getKeywordReplacement($keyword)
3861  {
3862  if (empty($keyword)) return '';
3863 
3864  if (($keyword == 'asset_contents') || (0 === strpos($keyword, 'globals_'))) {
3865  return "%$keyword%";
3866  }
3867 
3868  // Remove any modifiers from keyword, and keep them for later.
3869  $keyword = parse_keyword($keyword, $modifiers);
3870  $contextid = extract_context_modifier($modifiers);
3871 
3872  if ($contextid !== NULL) {
3873  // If we were able to extract a context ID to change to, and it's
3874  // different to our current one, then change and then reload a copy
3875  // of the asset in that context (as we would need to do anyway)
3876 
3877  if ((int)$contextid !== $GLOBALS['SQ_SYSTEM']->getContextId()) {
3878  $GLOBALS['SQ_SYSTEM']->changeContext($contextid);
3879  $contexted_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($this->id);
3880 
3881  // Get the keyword without any modifiers
3882  $replacement = $contexted_asset->getKeywordReplacement($keyword);
3883 
3884  // Then apply the ones we had remaining, then return it
3885  apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $contexted_asset->id));
3886 
3887  unset($contexted_asset);
3888  $GLOBALS['SQ_SYSTEM']->restoreContext();
3889  return $replacement;
3890 
3891  }//end if contextid is not the currently active context
3892 
3893  }//end if contextid is not NULL
3894 
3895  $replacement = NULL;
3896  $tmp_keyword = strtr($keyword, '_', ' ');
3897  $tmp_keyword = ucwords($tmp_keyword);
3898  $tmp_keyword = preg_replace('/\s+/', '', $tmp_keyword);
3899  $func_name = 'get'.$tmp_keyword.'KeywordReplacement';
3900 
3901  //check if the keyword starts with asset_ because the global asset keyword %globals_asset_% would invoke this
3902  //method through the replace_global_keywords() function (general.inc) with the keyword starts with asset_
3903  $possible_func_name = '';
3904  if (0 === strpos($keyword, 'asset_')) {
3905  //get the method name without the start "Asset"
3906  $possible_tmp_keyword = substr($tmp_keyword, 5);
3907  if ($possible_tmp_keyword != '') {
3908  $possible_func_name = 'get'.$possible_tmp_keyword.'KeywordReplacement';
3909  }
3910  }
3911 
3912  if (method_exists($this, $func_name)) {
3913  $replacement = $this->$func_name();
3914  } else if (($possible_func_name != '') && method_exists($this, $possible_func_name)) {
3915  //allow the use of %globals_asset_<keyword>:<assetid>% if the %<keyword>% is available for
3916  //the <assetid> asset through the get<Keyword>KeywordReplacement() method
3917  $replacement = $this->$possible_func_name();
3918  } else if (strcmp('asset_assetid', $keyword) == 0) {
3919  $replacement = $this->id;
3920  } else if (strcmp('asset_name', $keyword) == 0) {
3921  $replacement = $this->name;
3922  $replacement = str_replace('&', '&amp;', $replacement);
3923  } else if (strcmp('asset_name_linked', $keyword) == 0) {
3924  $name = str_replace('&', '&amp;', $this->name);
3925  $replacement = '<a href="'.$this->getURL().'">'.$name.'</a>';
3926  } else if (strcmp('asset_short_name', $keyword) == 0) {
3927  $replacement = $this->short_name;
3928  $replacement = str_replace('&', '&amp;', $replacement);
3929  } else if (strcmp('asset_type', $keyword) == 0) {
3930  $replacement = $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name');
3931  } else if (strcmp('asset_type_icon', $keyword) == 0) {
3932  $replacement = sq_get_icon($GLOBALS['SQ_SYSTEM']->am->getAssetIconURL($this->type()), 16, 16, '');
3933  } else if (strcmp('asset_type_icon_url', $keyword) == 0) {
3934  $replacement = $GLOBALS['SQ_SYSTEM']->am->getAssetIconURL($this->type());
3935  } else if (0 === strpos($keyword, 'asset_attribute_')) {
3936  $attr_name = substr($keyword, strlen('asset_attribute_'));
3937  if (array_key_exists($attr_name, $this->vars)) {
3938  $attr = $this->getAttribute($attr_name);
3939  if (!is_null($attr) && !$attr->is_admin) {
3940  $replacement = $attr->getKeywordValue();
3941  }
3942  } else {
3943  preg_match('/(_([^_]*)){0,1}$/', $attr_name, $matches);
3944  if (!empty($matches[1])) {
3945  $attr_name = substr($attr_name, 0, (strlen($attr_name) - strlen($matches[1])));
3946  $format = $matches[2];
3947  $attr = $this->getAttribute($attr_name);
3948  if (!is_null($attr)) {
3949  $replacement = $attr->getKeywordValue($format);
3950  }
3951  }
3952  }
3953  } else if (substr($keyword, 0, 15) == 'asset_metadata_') {
3954  $metadata_keyword = substr($keyword, 15);
3955  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
3956 
3957  $current_context = $GLOBALS['SQ_SYSTEM']->getContextId();
3958  if ($current_context === NULL) {
3959  $current_context = 0;
3960  }
3961 
3962  if (!isset($this->_tmp['mm_replacements'][$current_context])) {
3963  // generate all metadata keywords in one go
3964  $this->_tmp['mm_replacements'][$current_context] = $mm->getMetadataFieldValues($this->id);
3965  }
3966  $additional_keyword = '';
3967  $replacement = NULL;
3968 
3969  // With "additional" keywords, we don't know where our metadata
3970  // field name ends and the additional keyword begins...so we will
3971  // try and split it up by underscores
3972  while (strlen($metadata_keyword) > 0) {
3973  $spaced_metadata_keyword = str_replace('_', ' ', $metadata_keyword);
3974 
3975  if (isset($this->_tmp['mm_replacements'][$current_context][$metadata_keyword]) === TRUE) {
3976  if ($additional_keyword !== '') {
3977  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
3978  $field_assetid = $mm->getFieldAssetIdFromName($this->id, $metadata_keyword);
3979 
3980  if ($field_assetid !== FALSE) {
3981  $field = $GLOBALS['SQ_SYSTEM']->am->getAsset($field_assetid);
3982  $replacement = $field->getAdditionalKeywordReplacement($this->id, $additional_keyword, NULL);
3983  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($field);
3984  unset($field);
3985  }
3986  } else {
3987  $replacement = $this->_tmp['mm_replacements'][$current_context][$metadata_keyword];
3988  }
3989 
3990  } else if (isset($this->_tmp['mm_replacements'][$current_context][$spaced_metadata_keyword]) === TRUE) {
3991 
3992  // Try it with metadata keyword underscores replaced with spaces
3993  // (additional keyword never has its spaces removed when
3994  // sent to the metadata field)
3995  if ($additional_keyword !== '') {
3996  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
3997  $field_assetid = $mm->getFieldAssetIdFromName($this->id, $spaced_metadata_keyword);
3998  if ($field_assetid !== FALSE) {
3999  $field = $GLOBALS['SQ_SYSTEM']->am->getAsset($field_assetid);
4000  $replacement = $field->getAdditionalKeywordReplacement($this->id, $additional_keyword, NULL);
4001  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($field);
4002  unset($field);
4003  }
4004  } else {
4005  $replacement = $this->_tmp['mm_replacements'][$current_context][$spaced_metadata_keyword];
4006  }
4007 
4008  }//end if
4009 
4010  // Do we have a replacement?
4011  if ($replacement !== NULL) break;
4012 
4013  // There are no underscores to break apart, so we've run out of
4014  // possibilities
4015  $underscore_pos = strrpos($metadata_keyword, '_');
4016  if ($underscore_pos === FALSE) break;
4017 
4018  if (strlen($additional_keyword) > 0) {
4019  $additional_keyword = '_'.$additional_keyword;
4020  }
4021 
4022  $additional_keyword = substr($metadata_keyword, $underscore_pos + 1).$additional_keyword;
4023  $metadata_keyword = substr($metadata_keyword, 0, $underscore_pos);
4024  }
4025 
4026  // If no replacement yet, then perhaps its a related metadata field keyword
4027  if ($replacement === NULL) {
4028  $metadata_keyword = substr($keyword, 15);
4029  // Related metadata fields names, if any, needs to be sorted out before doing the replacement
4030  $related_assetids = $this->_tmp['mm_replacements'][$current_context];
4031  array_sort_by_length($related_assetids, FALSE, FALSE);
4032 
4033  foreach($related_assetids as $field_name => $related_assetid) {
4034  if (strpos($metadata_keyword, $field_name.'_') !== FALSE) {
4035  $asset_keyword = substr($metadata_keyword, strlen($field_name)+1, strlen($metadata_keyword) - strlen($field_name));
4036  if ($GLOBALS['SQ_SYSTEM']->am->getAssetInfo($related_assetid)) {
4037  $related_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($related_assetid);
4038  $replacement = $related_asset->getKeywordReplacement($asset_keyword);
4039  $this->_tmp['mm_replacements'][$current_context][$metadata_keyword] = $replacement;
4040  }
4041  break;
4042  }//end if
4043  }//end foreach
4044  }//end if
4045 
4046  // No replacement? Oh well, back to square one
4047  if ($replacement === NULL) {
4048  unset($replacement);
4049  }
4050 
4051  } else if (0 === strpos($keyword, 'asset_charset')) {
4052  $replacement = ($this->charset) ? $this->charset : SQ_CONF_DEFAULT_CHARACTER_SET;
4053 
4054  } else if (0 === strpos($keyword, 'asset_languages')) {
4055  $replacement = ($this->languages) ? $this->languages : SQ_CONF_DEFAULT_FRONTEND_LANGUAGE;
4056 
4057  } else if (0 === strpos($keyword, 'asset_summary')) {
4058  $temp_assetid = substr($keyword, 14);
4059  $temp_assetid = trim($temp_assetid);
4060  $temp_asset_info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($temp_assetid);
4061  if (!empty($temp_asset_info)) {
4062  $temp_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($temp_assetid);
4063  if (method_exists($temp_asset, 'getAssetSummary')) {
4064  $replacement = $temp_asset->getAssetSummary();
4065  }
4066  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($temp_asset);
4067  }
4068  } else if (0 === strpos($keyword, 'asset_paint_layout_id')) {
4069  $replacement = $GLOBALS['SQ_SYSTEM']->getGlobalDefine('SQ_PAINT_LAYOUT_ID', 0);
4070  if (empty($replacement)) {
4071  $url = strip_url(current_url(FALSE, TRUE));
4072  $layout_name = $this->getCurrentPaintLayoutName();
4073  $replacement = $GLOBALS['SQ_SYSTEM']->am->getValueFromURL($url, $layout_name);
4074 
4075  // if no layout was found, try and get the default frontend
4076  // layout if we were not already trying to get it
4077  if (empty($replacement) && $layout_name != 'paint_layout::system::frontend') {
4078  $replacement = $GLOBALS['SQ_SYSTEM']->am->getValueFromURL($url, 'paint_layout::system::frontend');
4079  }
4080  }
4081  } else if (0 === strpos($keyword, 'asset_workflow_')) {
4082  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
4083 
4084  if (!isset($this->_tmp['wfm_schema_workflows'])) {
4085  $schema_workflows = $wfm->getSchemaWorkflows($this->id);
4086  $this->_tmp['wfm_schema_workflows'] = $schema_workflows;
4087  } else {
4088  $schema_workflows = $this->_tmp['wfm_schema_workflows'];
4089  }
4090 
4091  if (0 === strpos($keyword, 'asset_workflow_current_')){
4092  $workflow_keyword = substr($keyword, strlen('asset_workflow_current_'));
4093  $workflow_keyword_parts = explode(':', $workflow_keyword);
4094 
4095  $schema_ids = Array();
4096  $wf_replacements = Array();
4097 
4098  if (isset($workflow_keyword_parts[1])) {
4099  $workflow_keyword = $workflow_keyword_parts[0];
4100  $schema_ids[] = $workflow_keyword_parts[1];
4101  } else {
4102  $schema_ids = array_keys($schema_workflows);
4103  }
4104 
4105  foreach($schema_ids as $schema_id) {
4106  //filter out non-running schemas
4107  if (empty($schema_workflows[$schema_id])) continue;
4108 
4109  if (0 === strpos($workflow_keyword, 'step_')){
4110  //filter out schemas with no steps/no current step(complete)
4111  if (empty($schema_workflows[$schema_id]['steps']) || empty($schema_workflows[$schema_id]['current_step'])) continue;
4112 
4113  $current_step = $wfm->getCurrentStep($schema_workflows[$schema_id]);
4114  switch ($workflow_keyword) {
4115  case 'step_name':
4116  $wf_replacements[] = $current_step['step_name'];
4117  break;
4118  case 'step_start_time':
4119  $wf_replacements[] = date('d-m-Y H:i:s', $current_step['started']);
4120  break;
4121  case 'step_expiry_time':
4122  $wf_replacements[] = (!empty($current_step['expiry_time'])) ? date('d-m-Y H:i:s', $current_step['started'] + $current_step['expiry_time']) : '';
4123  break;
4124  case 'step_conditions':
4125  $asset_names = Array();
4126  foreach (array_keys($current_step['conditions']) as $assetid){
4127  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
4128  if ($asset instanceof User) {
4129  $asset_names[] = $asset->_getName();
4130  } else {
4131  $asset_names[] = $asset->name;
4132  }
4133  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
4134  unset($asset);
4135  }
4136  $wf_replacements[] = implode(', ', $asset_names);
4137  break;
4138  case 'step_condition_ids':
4139  $wf_replacements[] = implode(', ', array_keys($current_step['conditions']));
4140  break;
4141  }
4142  } else {
4143  switch ($workflow_keyword) {
4144  case 'start_time':
4145  $wf_replacements[] = date('d-m-Y H:i:s', $schema_workflows[$schema_id]['started']);
4146  break;
4147  case 'stream_name':
4148  $wf_replacements[] = $schema_workflows[$schema_id]['stream_name'];
4149  break;
4150  }
4151  }
4152  }
4153 
4154  $replacement = implode(', ', $wf_replacements);
4155  if (empty($replacement)) return NULL;
4156 
4157  } else {
4158  $workflow_keyword = substr($keyword, strlen('asset_workflow_'));
4159  switch ($workflow_keyword) {
4160  case 'applied_schemas':
4161  $replacement = implode(', ', array_keys($schema_workflows));
4162  break;
4163  default:
4164  return $replacement;
4165  }
4166  }
4167 
4168  } else if (0 === strpos($keyword, 'asset_frontend_metadata')) {
4169  // Returns the asset's frontend metadata content
4170  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
4171  $metadata_default_name = 'metadata.php';
4172  $metadata_basename = ($contextid === 0) ? 'metadata.php' : 'metadata.'.$contextid.'.php';
4173 
4174  ob_start();
4175  if (file_exists($this->data_path.'/'.$metadata_basename)) {
4176  require $this->data_path.'/'.$metadata_basename;
4177  } else if (file_exists($this->data_path.'/'.$metadata_default_name)) {
4178  require $this->data_path.'/'.$metadata_default_name;
4179  }
4180  $replacement = ob_get_clean();
4181 
4182  } else {
4183  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
4184  $mm_keyword = $mm->generateKeywordReplacements($this, Array($keyword), FALSE);
4185  if (isset($mm_keyword[$keyword])) {
4186  $replacement = $mm_keyword[$keyword];
4187  } else {
4188  // If we got here, we couldn't do anything with the keyword.
4189  // If this is because of a modifier on a keyword that's
4190  // supposed to be dealt with higher up the inheritance chain..
4191  if (count($modifiers) > 1) {
4192  // Go back up the chain trying to call with just the keyword part.
4193  $replacement = $this->getKeywordReplacement($keyword);
4194  } else {
4195  // replace at higher level
4196  return "%$keyword%";
4197  }//end if
4198  }
4199  }
4200 
4201  apply_keyword_modifiers($replacement, $modifiers, Array('assetid' => $this->id));
4202 
4203  return $replacement;
4204 
4205  }//end _getKeywordReplacement()
4206 
4207 
4216  {
4217  $webpaths = $this->getWebPaths();
4218 
4219  if (empty($webpaths)) {
4220  return NULL;
4221  }
4222 
4223  $shortest_wp = $webpaths[0];
4224 
4225  foreach($webpaths as $webpath) {
4226 
4227  $wp_len = strlen($webpath);
4228  $shortest_wp_len = strlen($shortest_wp);
4229 
4230  if ($wp_len <= $shortest_wp_len) {
4231  $shortest_wp = ($wp_len == $shortest_wp_len) ? min($shortest_wp, $webpath) : $webpath;
4232  }
4233  }
4234 
4235  return $shortest_wp;
4236 
4237  }//end getAssetWebPathKeywordReplacement()
4238 
4239 
4248  public function getCustomKeywordReplacements($keywords=Array(), $invoke_backend = FALSE)
4249  {
4250  return Array();
4251 
4252  }//end getCustomKeywordReplacements()
4253 
4254 
4266  public function processCustomKeywords($keywords=Array())
4267  {
4268  return TRUE;
4269 
4270  }//end processCustomKeywords()
4271 
4272 
4279  public function getContent()
4280  {
4281  return '';
4282 
4283  }//end getContent()
4284 
4285 
4292  public function setContent()
4293  {
4294  return FALSE;
4295 
4296  }//end setContent()
4297 
4298 
4310  public function getEffectiveLastUpdatedTime($assetids)
4311  {
4312  $db = MatrixDAL::getDb();
4313 
4314  $assetids[] = $this->id; // add self
4315 
4316  // do not include shadow assets
4317  foreach ($assetids as $key => $assetid) {
4318  $id_parts = explode(':', $assetid);
4319  if (isset($id_parts[1])) {
4320  unset($assetids[$key]);
4321  } else {
4322  $assetids[$key] = MatrixDAL::quote((string) $assetid);
4323  }
4324  }
4325 
4326  // if only shadow assets are loaded, ignore
4327  if (empty($assetids)) return 0;
4328 
4329  // break up the assets into chunks of 1000 so that oracle does not complain
4330  $in_clauses = Array();
4331  foreach (array_chunk($assetids, 999) as $chunk) {
4332  $in_clauses[] = ' (assetid IN ('.implode(', ', $chunk).'))';
4333  }
4334  $where = '('.implode(' OR ', $in_clauses).')';
4335 
4336  try {
4337  // get all updated time from DB
4338  $sql = 'SELECT MAX(updated)
4339  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast
4340  WHERE '.$where;
4341  $most_recent = MatrixDAL::executeSqlOne($sql);
4342  } catch (Exception $e) {
4343  throw new Exception('Can not get updated time for asset due to the following database error:'.$e->getMessage());
4344  }//end try catch
4345 
4346  return (!is_null($most_recent)) ? strtotime($most_recent) : 0;
4347 
4348  }//end getEffectiveLastUpdatedTime()
4349 
4350 
4351 //-- PERMISSIONS/ACCESS --//
4352 
4353 
4363  public function readAccess(Array $assetids=Array())
4364  {
4365  if (!$this->id) return TRUE;
4366  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
4367  return TRUE;
4368  }
4369 
4370  // if asset is not live we really need to check for write access
4371  if ($this->status < SQ_STATUS_LIVE) {
4372  // if we are in rollback view mode, bypass extra write access checks or we
4373  // we always be returning FALSE here (writeAccess returns FALSE)
4374  if (SQ_ROLLBACK_VIEW) {
4375  return $this->_checkPermissionAccess(SQ_PERMISSION_WRITE, $assetids, FALSE);
4376  }
4377  return $this->writeAccess('', $assetids, FALSE);
4378  }
4379 
4380  if ($this->_checkPermissionAccess(SQ_PERMISSION_READ, $assetids)){
4381  return TRUE;
4382  } else if ($this->status & SQ_SC_STATUS_PENDING) {
4383  // if the asset is in the middle of workflow and the current user
4384  // can approve the asset, we really need to give them read access to it
4385  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
4386  $publishers = $wfm->whoCanPublish($this->id);
4387  if (!empty($publishers) && in_array($GLOBALS['SQ_SYSTEM']->currentUserId(), $publishers)) {
4388  return TRUE;
4389  }
4390  }
4391 
4392  return FALSE;
4393 
4394  }//end readAccess()
4395 
4396 
4415  public function writeAccess($lock_type='', Array $assetids=Array(), $only_workflow=TRUE)
4416  {
4417  if (SQ_ROLLBACK_VIEW) return FALSE;
4418 
4419  if (!$this->id) return TRUE;
4420  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
4421  return TRUE;
4422  }
4423 
4424  if ($this->status == SQ_STATUS_LIVE) {
4425  return $this->liveEditAccess($lock_type);
4426  }
4427  return $this->checkAccess(SQ_PERMISSION_WRITE, $lock_type, $assetids, $only_workflow);
4428 
4429  }//end writeAccess()
4430 
4431 
4444  public function adminAccess($lock_type='', Array $assetids=Array())
4445  {
4446  if (SQ_ROLLBACK_VIEW) return FALSE;
4447 
4448  if (!$this->id || !empty($this->_tmp[__CLASS__.'_in_create_cascading'])) {
4449  return TRUE;
4450  }
4451  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
4452  return TRUE;
4453  }
4454 
4455  $access = $this->checkAccess(SQ_PERMISSION_ADMIN, $lock_type, $assetids);
4456  if ($access && $this->status == SQ_STATUS_LIVE) {
4457  return $this->liveEditAccess($lock_type);
4458  }
4459  return $access;
4460 
4461  }//end adminAccess()
4462 
4463 
4470  public function backendAccess()
4471  {
4472  return $GLOBALS['SQ_SYSTEM']->user->canAccessBackend();
4473 
4474  }//end backendAccess()
4475 
4476 
4489  public function liveEditAccess($lock_type)
4490  {
4491  if (empty($lock_type) || $this->canLiveEdit($lock_type)) {
4492  return $this->checkAccess(SQ_PERMISSION_WRITE, $lock_type);
4493  }
4494  return FALSE;
4495 
4496  }//end liveEditAccess()
4497 
4498 
4511  public function canliveEdit($lock_type)
4512  {
4513  if (SQ_ROLLBACK_VIEW) return FALSE;
4514  if (!$this->id) return TRUE;
4515  if (!($this->status & SQ_SC_STATUS_LIVE_EDIT)) {
4516  return TRUE;
4517  }
4518 
4519  if (!empty($lock_type)) {
4520  if ($lock_type != 'attributes') {
4521  $locks = $GLOBALS['SQ_SYSTEM']->am->getLockInfo($this->id, $lock_type, TRUE);
4522  // if we are getting a lock type that cant be safe edited (eg permissions), you can live edit
4523  // if you dont have the attributes lock also (which must be safe edited)
4524  if (!in_array('attributes', array_keys($locks))) {
4525  return TRUE;
4526  }
4527  }
4528  }
4529 
4530  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
4531  $schemas = $wfm->getSchemas($this->id, TRUE);
4532 
4533  if (empty($schemas)) return TRUE;
4534 
4535  // if there is workflow defined for this asset, the current user
4536  // must be the only concerned user to be able to live edit
4537  $wf_complete = $wfm->testPublish($this->id, $GLOBALS['SQ_SYSTEM']->currentUserId());
4538  if ($wf_complete) return TRUE;
4539 
4540  return FALSE;
4541 
4542  }//end canliveEdit()
4543 
4544 
4551  public function effectiveUnrestricted()
4552  {
4553  // This is not readAccess() because if this is FALSE we don't refer to the writeAccess() setting
4554  $public_userid = $GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('public_user');
4555  return ($this->status >= SQ_STATUS_LIVE && $this->_checkPermissionAccess(SQ_PERMISSION_READ, Array($public_userid)));
4556 
4557  }//end effectiveUnrestricted()
4558 
4559 
4577  public function checkAccess($perm, $lock_type, Array $assetids=Array(), $only_workflow=TRUE)
4578  {
4579  if ($perm == SQ_PERMISSION_READ) {
4580  trigger_localised_error('SYS0272', E_USER_ERROR, __CLASS__, __FUNCTION__);
4581  }
4582  if ($lock_type == '') {
4583  return $this->_checkPermissionAccess($perm, $assetids, $only_workflow);
4584 
4585  } else {
4586  // effective access is only valid for the current user
4587  if (!empty($assetids)) return FALSE;
4588 
4589  $locks = $GLOBALS['SQ_SYSTEM']->am->getLockInfo($this->id, $lock_type, TRUE);
4590  // if there is no lock or
4591  if (empty($locks)) return FALSE;
4592 
4593  foreach ($locks as $lock) {
4594  // if any one of these locks is not owned by the current user they can't edit
4595  if (empty($lock) || $lock['userid'] != $GLOBALS['SQ_SYSTEM']->currentUserId()) {
4596  return FALSE;
4597  }
4598  }
4599 
4600  // If we are purging trash, don't bother about statuses
4601  // Links are not part of workflow either, so don't bother checking
4602  // on statuses if we are checking links access
4603  if (!($GLOBALS['SQ_PURGING_TRASH'] || $lock_type == 'links')) {
4604  if (!$this->accessEffective()) return FALSE;
4605  }
4606 
4607  // otherwise they can edit if they have permission access
4608  return $this->_checkPermissionAccess($perm, Array(), $only_workflow);
4609 
4610  }//end else
4611 
4612  }//end checkAccess()
4613 
4614 
4625  public function accessEffective()
4626  {
4627  // if we are in a state of total approval no editing allowed
4628  if ($this->status & SQ_SC_STATUS_ALL_APPROVED) {
4629  return FALSE;
4630  }
4631 
4632  // if the asset is archived no editing allowed
4633  if ($this->status & SQ_STATUS_ARCHIVED) {
4634  return FALSE;
4635  }
4636  return TRUE;
4637 
4638  }//end accessEffective()
4639 
4640 
4653  protected function _checkPermissionAccess($perm, $assetids=Array(), $only_workflow=TRUE)
4654  {
4655  if (empty($assetids)) {
4656  if (!$GLOBALS['SQ_SYSTEM']->userPublic()) {
4657  // if we are the root user, we can do anything
4658  if ($GLOBALS['SQ_SYSTEM']->userRoot()) return TRUE;
4659 
4660  // if we are a system administrator, we can do anything
4661  // unless an asset overwrites this function
4662  if ($GLOBALS['SQ_SYSTEM']->userSystemAdmin()) {
4663  return TRUE;
4664  }
4665  }
4666 
4667  // get the current user and groups we are not restricted from
4668  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($GLOBALS['SQ_SYSTEM']->currentUserId());
4669  $assetids = $user->getUserGroups();
4670  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($user);
4671 
4672  $assetids[] = $GLOBALS['SQ_SYSTEM']->currentUserId();
4673  }
4674 
4675  if (!$GLOBALS['SQ_PURGING_TRASH']) {
4676  // if we are in workflow no editing allowed
4677  // except by those users who can currently approve the asset
4678  if ($this->status & SQ_SC_STATUS_PENDING) {
4679  if ($perm == SQ_PERMISSION_WRITE) {
4680  $wfm = $GLOBALS['SQ_SYSTEM']->getWorkflowManager();
4681  $publishers = $wfm->whoCanPublish($this->id);
4682  if (in_array($GLOBALS['SQ_SYSTEM']->currentUserId(), $publishers)) {
4683  return TRUE;
4684  }
4685 
4686  // Those with admin permissions are now allowed to have a
4687  // crack at write permission throughout the workflow
4688  if ($this->_checkPermissionAccess(SQ_PERMISSION_ADMIN, $assetids)) {
4689  return TRUE;
4690  }
4691 
4692  if ($only_workflow) return FALSE;
4693  }
4694  }
4695  }
4696 
4697  $grants = $GLOBALS['SQ_SYSTEM']->am->getPermission($this->id, $perm, TRUE, TRUE, FALSE, FALSE, FALSE);
4698  $revokes = $GLOBALS['SQ_SYSTEM']->am->getPermission($this->id, $perm, FALSE, FALSE, FALSE, FALSE, FALSE);
4699 
4700  // has their access been revoked at all?
4701  if (!empty($revokes)) {
4702  $revoked = array_intersect($assetids, $revokes);
4703  if (!empty($revoked)) return FALSE;
4704  }
4705 
4706  // Add the public user to the mix, because if the public can see it then everyone should see it.
4707  // we add it after the revoke check because we otherwise if the publc is revoked then everyone bar a sysadmin or Root
4708  // will be blocked
4709  $assetids[] = $GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('public_user');
4710  // have they been granted access?
4711  $common = array_intersect($assetids, $grants);
4712 
4713  return (!empty($common));
4714 
4715  }//end _checkPermissionAccess()
4716 
4717 
4726  public function permissionsUpdated()
4727  {
4728  return TRUE;
4729 
4730  }//end permissionsUpdated()
4731 
4732 
4733 //-- PAINTING --//
4734 
4735 
4743  public function printFrontend()
4744  {
4745  $this->printFrontendAsset($this);
4746 
4747  }//end printFrontend()
4748 
4749 
4760  public function printFrontendAsset(Asset $asset, $design=NULL)
4761  {
4762  if (!is_null($design)) {
4763  assert_is_a($design, 'design');
4764  }
4765 
4766  if (!$asset->readAccess()) {
4767  $GLOBALS['SQ_SYSTEM']->paintLogin(translate('login'), translate('cannot_access_asset', $asset->name));
4768  return;
4769  }
4770 
4771  $current_protocol = current_protocol();
4772  $valid_protocols = $this->getValidProtocols();
4773  if (!empty($valid_protocols) && !in_array($current_protocol, $valid_protocols)) {
4774  $new_url = (($current_protocol == 'http') ? 'https' : 'http').'://'.current_url(FALSE);
4775  do_redirect($new_url); // exits
4776  }
4777 
4778  if (!headers_sent()) {
4779  if ($asset->charset) {
4780  header('Content-type: text/html; charset='.$asset->charset);
4781  } else {
4782  header('Content-type: text/html; charset='.SQ_CONF_DEFAULT_CHARACTER_SET);
4783  }
4784 
4785  if ($asset->languages) {
4786  header('Content-language: '.$asset->languages);
4787  }
4788  }
4789 
4790  $original_asset = NULL;
4791  if (is_null($GLOBALS['SQ_SYSTEM']->frontend_asset) || $asset->id != $GLOBALS['SQ_SYSTEM']->frontend_asset->id) {
4792  $original_asset = $GLOBALS['SQ_SYSTEM']->frontend_asset;
4793  $GLOBALS['SQ_SYSTEM']->frontend_asset = &$asset;
4794  }
4795 
4796  $db = MatrixDAL::getDb();
4797  $url = strip_url(current_url(FALSE, TRUE));
4798 
4799  // No design has been passed in, so we need to find one
4800  if (is_null($design)) {
4801  $design_name = $this->getCurrentDesignName();
4802  $result = $GLOBALS['SQ_SYSTEM']->am->getDesignFromURL($url, $design_name);
4803 
4804  // if no design was found, try and get the default frontend
4805  // design if we were not already trying to get it
4806  if (empty($result) && $design_name != 'design::system::frontend') {
4807  $result = $GLOBALS['SQ_SYSTEM']->am->getDesignFromURL($url, 'design::system::frontend');
4808  }
4809 
4810  // if no design was found, try using the asset's URL from the DB
4811  if (empty($result)) {
4812  $url = strip_url($asset->getUrl(), TRUE);
4813  $result = $GLOBALS['SQ_SYSTEM']->am->getDesignFromURL($url, $design_name);
4814  if (empty($result) && $design_name != 'design::system::frontend') {
4815  $result = $GLOBALS['SQ_SYSTEM']->am->getDesignFromURL($url, 'design::system::frontend');
4816  }
4817  }
4818  } else {
4819  $result = TRUE;
4820  }
4821 
4822  if (!is_null($design) || $result) {
4823  if (is_null($design)) {
4824  // we have found the design to use
4825  $design = $GLOBALS['SQ_SYSTEM']->am->getAsset($result['designid'], $result['type_code']);
4826  }
4827  $design->paint($asset);
4828  } else {
4829  // we can't find a design, oh well let's just print out our body
4830  $asset->printBody();
4831  }
4832 
4833  if (!is_null($original_asset)) {
4834  $GLOBALS['SQ_SYSTEM']->frontend_asset = &$original_asset;
4835  }
4836 
4837  }//end printFrontendAsset()
4838 
4839 
4846  public function getValidProtocols()
4847  {
4848  if (!isset($this->_tmp['valid_protocols'])) {
4849  $protocol = current_protocol();
4850  $db = MatrixDAL::getDb();
4851  try {
4852  $bind_vars = Array (
4853  'assetid' => $this->id,
4854  'url' => strip_url(current_url(FALSE)),
4855  );
4856  $result = MatrixDAL::executeAll('core', 'getValidProtocols', $bind_vars);
4857  if (isset($result[0])) {
4858  $res = $result[0];
4859  } else
4860  $res = $result;
4861  } catch (Exception $e) {
4862  throw new Exception('Unable to get valid protocols for asset: '.$this->id.' with this URL: '.strip_url(current_url(FALSE)).' due to database error: '.$e->getMessage());
4863  }
4864 
4865  $this->_tmp['valid_protocols'] = Array();
4866  if (is_array($res)) {
4867  foreach ($res as $protocol => $valid) {
4868  // Force secure
4869  if (($this->force_secure === '1') && ($protocol == 'http')) {
4870  continue;
4871  }
4872  // Force INsecure
4873  if (($this->force_secure === '-') && ($protocol == 'https')) {
4874  continue;
4875  }
4876  if ($valid) {
4877  $this->_tmp['valid_protocols'][] = $protocol;
4878  }
4879  }
4880  }
4881  }
4882  return $this->_tmp['valid_protocols'];
4883 
4884  }//end getValidProtocols()
4885 
4886 
4894  public function printHead()
4895  {
4896 
4897  }//end printHead()
4898 
4899 
4908  public function printBody()
4909  {
4910 
4911  }//end printBody()
4912 
4913 
4920  public function getCurrentPaintLayoutName()
4921  {
4922  // Have they set a paint layout name that they want to view ??
4923  if (!empty($_GET['SQ_PAINT_LAYOUT_NAME'])) {
4924  $layout_name = 'paint_layout::user::'.$_GET['SQ_PAINT_LAYOUT_NAME'];
4925  } else if (!empty($_SESSION['SQ_PAINT_LAYOUT_NAME'])) {
4926  // is it in the session ?
4927  $layout_name = 'paint_layout::user::'.$_SESSION['SQ_PAINT_LAYOUT_NAME'];
4928  } else if (!empty($_GET['SQ_DESIGN_NAME'])) {
4929  // Maybe they have a design name that we can use to match against
4930  $layout_name = 'paint_layout::user::'.$_GET['SQ_DESIGN_NAME'];
4931  } else if (!empty($_SESSION['SQ_DESIGN_NAME'])) {
4932  $layout_name = 'paint_layout::user::'.$_SESSION['SQ_DESIGN_NAME'];
4933  } else {
4934  $layout_name = 'paint_layout::system::frontend';
4935  }
4936  return $layout_name;
4937 
4938  }//end getCurrentPaintLayoutName()
4939 
4940 
4947  public function getCurrentDesignName()
4948  {
4949  // Have they set a design name that they want to view ??
4950  if (!empty($_GET['SQ_DESIGN_NAME'])) {
4951  $design_name = 'design::user::'.$_GET['SQ_DESIGN_NAME'];
4952  } else if (!empty($_SESSION['SQ_DESIGN_NAME'])) {
4953  $design_name = 'design::user::'.$_SESSION['SQ_DESIGN_NAME'];
4954  } else {
4955  $design_name = 'design::system::frontend';
4956  }
4957  return $design_name;
4958 
4959  }//end getCurrentDesignName()
4960 
4961 
4970  public function printBodyWithPaintLayout($layout_id='')
4971  {
4972  if (empty($layout_id)) {
4973  $layout_id = $GLOBALS['SQ_SYSTEM']->getGlobalDefine('SQ_PAINT_LAYOUT_ID', 0);
4974  }
4975  if (empty($layout_id)) {
4976  $url = strip_url(current_url(FALSE, TRUE));
4977  $layout_name = $this->getCurrentPaintLayoutName();
4978  $layout_id = $GLOBALS['SQ_SYSTEM']->am->getValueFromURL($url, $layout_name);
4979 
4980  // if no layout was found, try and get the default frontend
4981  // layout if we were not already trying to get it
4982  if (empty($layout_id) && $layout_name != 'paint_layout::system::frontend') {
4983  $layout_id = $GLOBALS['SQ_SYSTEM']->am->getValueFromURL($url, 'paint_layout::system::frontend');
4984  }
4985  }
4986 
4987  if ($layout_id) {
4988  $replacement = $GLOBALS['SQ_SYSTEM']->setGlobalDefine('SQ_PAINT_LAYOUT_ID', $layout_id);
4989  // we have found the design to use... we might need to fool the system
4990  // into thinking we are printing the frontend of that asset
4991  $switched_frontend = FALSE;
4992 
4993  // Firstly check if the "frontend_asset" is set, and then try to extract the id from it
4994  if (isset($GLOBALS['SQ_SYSTEM']->frontend_asset) && $GLOBALS['SQ_SYSTEM']->frontend_asset->id !== $this->id) {
4995  $current_frontend_asset = &$GLOBALS['SQ_SYSTEM']->frontend_asset;
4996  $GLOBALS['SQ_SYSTEM']->frontend_asset = &$this;
4997  $switched_frontend = TRUE;
4998  }
4999 
5000  $layout = $GLOBALS['SQ_SYSTEM']->am->getAsset($layout_id);
5001  $layout->paint($this);
5002 
5003  if ($switched_frontend) {
5004  $GLOBALS['SQ_SYSTEM']->frontend_asset = &$current_frontend_asset;
5005  }
5006 
5007  if (!is_null($replacement)) {
5008  $GLOBALS['SQ_SYSTEM']->setGlobalDefine('SQ_PAINT_LAYOUT_ID', $replacement);
5009  } else {
5010  $GLOBALS['SQ_SYSTEM']->unsetGlobalDefine('SQ_PAINT_LAYOUT_ID');
5011  }
5012  } else {
5013  // we can't find a layout, oh well let's just print out our body
5014  $this->printBody();
5015  }
5016 
5017  }//end printBodyWithPaintLayout()
5018 
5019 
5030  public function initLimbo()
5031  {
5032  $o = $GLOBALS['SQ_SYSTEM']->backend->out;
5033  $o->addHiddenField('asset_action', 'limbo');
5034  $o->openRaw();
5035  $this->paintBackend($o);
5036  $o->closeRaw();
5037 
5038  }//end initLimbo()
5039 
5040 
5052  public function printLimbo()
5053  {
5054  $o = $GLOBALS['SQ_SYSTEM']->backend->out;
5055  if (is_null($o)) {
5056  trigger_localised_error('SYS0245', E_USER_ERROR, current_url());
5057  return;
5058  }
5059  $o->paint();
5060 
5061  }//end printLimbo()
5062 
5063 
5072  public function paintBackend(Backend_Outputter $o)
5073  {
5074  $o->addHiddenField('asset_action', (($this->id) ? 'edit' : 'create'));
5075  if ($this->charset) $o->setCharset($this->charset);
5076  $ei = $this->getEI();
5077  if ($this->id !== 0) {
5078  $owner = $GLOBALS['SQ_SYSTEM']->am->getAsset($this->id);
5079  } else {
5080  $owner = $this;
5081  }
5082  $ei->paint($owner, $o, empty($this->id));
5083 
5084  }//end paintBackend()
5085 
5086 
5093  public function getPrefix()
5094  {
5095  return str_replace(':', '_', str_replace(' ', '_', $this->type().'_'.$this->id));
5096 
5097  }//end getPrefix()
5098 
5099 
5110  public function processBackend(Backend_Outputter $o, Array &$link)
5111  {
5112  $ei = $this->getEI();
5113 
5114  switch ($_POST['asset_action']) {
5115  case 'create' :
5116  if ($ei->process($this, $o, TRUE)) {
5117  return (bool) $this->create($link);
5118  } else {
5119  return FALSE;
5120  }
5121  break;
5122 
5123  default :
5124  if ($this->id !== 0) {
5125  $owner = $GLOBALS['SQ_SYSTEM']->am->getAsset($this->id);
5126  } else {
5127  $owner = $this;
5128  }
5129  return $ei->process($owner, $o, FALSE);
5130  break;
5131  }
5132 
5133  }//end processBackend()
5134 
5135 
5142  public function getEI()
5143  {
5144  if (empty($this->_tmp['ei']) || get_class($this->_tmp['ei']) != 'asset_edit_interface') {
5145  require_once SQ_INCLUDE_PATH.'/asset_edit_interface.inc';
5146  $this->_tmp['ei'] = new Asset_Edit_Interface($this->type());
5147  }
5148  return $this->_tmp['ei'];
5149 
5150  }//end getEI()
5151 
5152 
5159  public function getEditFns()
5160  {
5161  if (!isset($this->_tmp['edit_fns'])) {
5162  $class_name = $this->type().'_edit_fns';
5163  require_once SQ_SYSTEM_ROOT.'/'.$GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'dir').'/'.$class_name.'.inc';
5164  $this->_tmp['edit_fns'] = new $class_name();
5165  }
5166 
5167  return $this->_tmp['edit_fns'];
5168 
5169  }//end getEditFns()
5170 
5171 
5172 //-- URL's & HREF's --//
5173 
5174 
5186  public function getBackendHref($screen='', $backend_page=TRUE)
5187  {
5188  $href = $GLOBALS['SQ_SYSTEM']->am->getAssetBackendHref(Array($this->id => $screen), $backend_page);
5189  return $href[$this->id];
5190 
5191  }//end getBackendHref()
5192 
5193 
5205  public function getURL($base_url=NULL, $ignore_rollback=FALSE, $base_contextid = NULL)
5206  {
5207  return $GLOBALS['SQ_SYSTEM']->am->getAssetURL($this->id, $base_url, $ignore_rollback, $base_contextid);
5208 
5209  }//end getURL()
5210 
5211 
5218  public function getURLs()
5219  {
5220  return $GLOBALS['SQ_SYSTEM']->am->getURLs($this->id);
5221 
5222  }//end getURLs()
5223 
5224 
5235  public function getHref($base_url=NULL, $ignore_rollback=FALSE)
5236  {
5237  return $GLOBALS['SQ_SYSTEM']->am->getAssetHref($this->id, $base_url, $ignore_rollback);
5238 
5239  }//end getHref()
5240 
5241 
5248  public function getWebDataPath()
5249  {
5250  return sq_web_path('data').'/assets/'.$this->type().'/'.get_asset_hash($this->id).'/'.$this->id;
5251 
5252  }//end getWebDataPath()
5253 
5254 
5261  public function getWebPaths()
5262  {
5263  if (!isset($this->_tmp['paths'])) {
5264  $db = MatrixDAL::getDb();
5265  $where = 'assetid = :assetid';
5266  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
5267  $sql = 'SELECT path
5268  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_path '.$where.'
5269  ORDER BY sort_order';
5270 
5271  try {
5272  $query = MatrixDAL::preparePdoQuery($sql);
5273  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5274  $result = MatrixDAL::executePdoAssoc($query, 0);
5275  } catch (Exception $e) {
5276  throw new Exception('Unable to get web paths for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
5277  }
5278 
5279  $this->_tmp['paths'] = $result;
5280 
5281  }// end if
5282 
5283  return $this->_tmp['paths'];
5284 
5285  }//end getWebPaths()
5286 
5287 
5299  protected function makeAndSaveInitialWebPath($path, $parent_link=NULL)
5300  {
5301  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5302  $valid_paths = make_valid_web_paths(Array($path), $this->id);
5303 
5304  // don't check for path conflicts if we have no parent
5305  if (!empty($parent_link['asset'])) {
5306  $valid_paths = $GLOBALS['SQ_SYSTEM']->am->webPathsInUse($parent_link['asset'], $valid_paths, $this->id, TRUE);
5307  }
5308 
5309  return $this->saveWebPaths($valid_paths);
5310 
5311  }//end makeAndSaveInitialWebPath($path)
5312 
5313 
5323  public function saveWebPaths($paths, $auto_add_remaps = TRUE)
5324  {
5325  $current_paths = $this->getWebPaths();
5326  $save_paths = Array();
5327 
5328  // make sure these paths are compliant
5329  require_once SQ_INCLUDE_PATH.'/general_occasional.inc';
5330  $save_paths = make_valid_web_paths($paths, $this->id ? $this->id : '');
5331 
5332  // do not run hipo if any of the paths start with __ or is reserved by Matrix
5333  foreach ($paths as $orig_path) {
5334  if (preg_match('/^__/', $orig_path) || $orig_path == SQ_CONF_BACKEND_SUFFIX || $orig_path == SQ_CONF_LIMBO_SUFFIX || $orig_path == SQ_CONF_NOCACHE_SUFFIX) {
5335  return FALSE;
5336  }
5337  }
5338 
5339  // if there is no difference in the arrays (including in the sort order - the index),
5340  // then there is nothing to do
5341  if (!array_diff_assoc($save_paths, $current_paths) && !array_diff_assoc($current_paths, $save_paths)) {
5342  return TRUE;
5343  }
5344 
5346 
5347  // change db connection because in a nested db transaction lookup on db1 may not produce a valid result
5348  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
5349 
5350  // to make sure that they path name is not in use by any of our siblings
5351  $inserts = array_diff($save_paths, $current_paths);
5352  if (!empty($inserts)) {
5353  // now we need to check that none of our parents have
5354  // any nav kids that are also using this path name
5355  $parents = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_WEB_PATHS, '', NULL, 'minor');
5356  for ($i = 0; $i < count($parents); $i++) {
5357  $parent = $GLOBALS['SQ_SYSTEM']->am->getAsset($parents[$i]['majorid'], $parents[$i]['major_type_code']);
5358  if (is_null($parent)) continue;
5359  $bad_paths = $GLOBALS['SQ_SYSTEM']->am->webPathsInUse($parent, $inserts, $this->id);
5360  if (!empty($bad_paths)) {
5361  trigger_localised_error('SYS0201', E_USER_WARNING, implode('", "', $bad_paths), $parent->name);
5362  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5363  return FALSE;
5364  }
5365  }
5366  }// end if inserts
5367 
5368  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5369 
5370  $db = MatrixDAL::getDb();
5371 
5372  $sql = 'DELETE FROM
5373  sq_ast_path
5374  WHERE
5375  assetid = :assetid';
5376 
5377  try {
5378  $query = MatrixDAL::preparePdoQuery($sql);
5379  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5380  MatrixDAL::execPdoQuery($query);
5381  } catch (Exception $e) {
5382  throw new Exception('Unable to delete web paths for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
5383  }
5384 
5385  foreach ($save_paths as $sort_order => $path) {
5386  $sql = 'INSERT INTO
5387  sq_ast_path
5388  (
5389  path,
5390  assetid,
5391  sort_order
5392  )
5393  VALUES
5394  (
5395  :path,
5396  :assetid,
5397  :sort_order
5398  )';
5399 
5400  try {
5401  $query = MatrixDAL::preparePdoQuery($sql);
5402  MatrixDAL::bindValueToPdo($query, 'path', $path);
5403  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5404  MatrixDAL::bindValueToPdo($query, 'sort_order', $sort_order);
5405  MatrixDAL::execPdoQuery($query);
5406  } catch (Exception $e) {
5407  throw new Exception('Unable to insert web paths for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
5408  }
5409  }
5410 
5411  unset($this->_tmp['paths']);
5412  unset($this->_tmp['lookups']);
5413 
5414  if ($this->updateLookups($auto_add_remaps)) {
5415  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5416  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5417  return TRUE;
5418  } else {
5419  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5420  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5421  return FALSE;
5422  }
5423 
5424  }//end saveWebPaths()
5425 
5426 
5427 //-- LOOKUPS --//
5428 
5429 
5439  public function getLookups($field='')
5440  {
5441  if (!isset($this->_tmp['lookups'])) {
5442  $db = MatrixDAL::getDb();
5443 
5444  $sql = 'SELECT url, http, https, root_urlid
5445  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup ';
5446  $where = 'assetid = :assetid';
5447  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where);
5448 
5449  try {
5450  $query = MatrixDAL::preparePdoQuery($sql.$where);
5451  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5452  $result = MatrixDAL::executePdoAll($query);
5453  } catch (Exception $e) {
5454  throw new Exception('Unable to get lookups for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
5455  }
5456 
5457  $this->_tmp['lookups'] = $result;
5458 
5459  }//end if
5460 
5461  if (!$field) {
5462  return $this->_tmp['lookups'];
5463  } else {
5464  $ret_val = Array();
5465  foreach ($this->_tmp['lookups'] as $data) {
5466  $ret_val[] = $data[$field];
5467  }
5468  return $ret_val;
5469  }
5470 
5471  }//end getLookups()
5472 
5473 
5480  public function getDesignLookups()
5481  {
5482  if (!isset($this->_tmp['design_lookups'])) {
5483  $this->_tmp['design_lookups'] = Array();
5484 
5485  $db = MatrixDAL::getDb();
5486 
5487  $sql = 'SELECT l.url, lv.name, lv.value as designid
5488  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup l
5489  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup_value lv ON ((l.url LIKE lv.url || \'/%\')
5490  OR (l.url = lv.url)
5491  OR (l.url || \'/\' = lv.url))
5492  ';
5493  $where = 'l.assetid = :assetid
5494  AND lv.name LIKE '.MatrixDAL::quote('design::%');
5495 
5496  // sort ascending so the override designs overwrite the array index for the normal design in the loop below
5497  $order_by = ' ORDER BY lv.depth ASC';
5498  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
5499  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lv');
5500 
5501  try {
5502  $query = MatrixDAL::preparePdoQuery($sql.$where.$order_by);
5503  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5504  $result = MatrixDAL::executePdoAssoc($query);
5505  } catch (Exception $e) {
5506  throw new Exception('Unable to get design lookup information for asset: '.$this->id.' due to database error: '.$e->getMessage());
5507  }
5508 
5509  foreach ($result as $row) {
5510  if (!isset($this->_tmp['design_lookups'][$row['url']])) {
5511  $this->_tmp['design_lookups'][$row['url']] = Array();
5512  }
5513  $this->_tmp['design_lookups'][$row['url']][$row['name']] = $row['designid'];
5514  }
5515 
5516  }//end if
5517 
5518  return $this->_tmp['design_lookups'];
5519 
5520  }//end getDesignLookups()
5521 
5522 
5548  public function getLookupValues($inherited=NULL, $prefix='', $like_search=TRUE, $ignore_override=FALSE)
5549  {
5550  $db = MatrixDAL::getDb();
5551 
5552  // return normal and regular values in sq_lookup_value corresponding exactly to this asset's URL(s), i.e. the parents
5553  // called by updateLookups() and by the lookup settings screen
5554  // 'inhd' column no longer exists, legacy value kept because other classes depend on seeing 'inhd'.
5555  // select the lookup_value's URL column because it will indicate whether the value is an override
5556  $sql_default = 'SELECT lv.url, lv.name, lv.value, \'0\' as inhd, lv.depth
5557  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup l
5558  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup_value lv ON (l.url = lv.url)
5559  ';
5560 
5561  $include_override = ' OR (l.url || \'/\' = lv.url)';
5562 
5563  $where = ' l.assetid = :assetid';
5564 
5565  // return all rows which are children, i.e. they inherit, override values by definition are not inherited
5566  // called by lookupValues.inc to paint the lookup settings screen
5567  $sql_inherited = 'SELECT l.url, lv.name, lv.value, \'1\' as inhd, lv.depth
5568  FROM '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup l
5569  INNER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast_lookup_value lv ON (l.url LIKE lv.url || \'/%\')
5570  ';
5571 
5572  if ($prefix !== '') {
5573  $where .= ' AND lv.name '.((!$like_search) ? 'NOT' : '').' LIKE :lookup_value';
5574  }
5575 
5576  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'l');
5577  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'lv');
5578 
5579  $sql_order_by = ' ORDER BY depth ASC ';
5580 
5581  try {
5582  if (!is_null($inherited) && $inherited) {
5583  $full_sql = $sql_inherited.$where.' UNION '.$sql_default.$include_override.$where.$sql_order_by;
5584  }
5585  else if ($ignore_override) {
5586  $full_sql = $sql_default.$where;
5587  }
5588  else {
5589  $full_sql = $sql_default.$include_override.$where;
5590  }
5591 
5592  $query = MatrixDAL::preparePdoQuery($full_sql);
5593  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5594  if ($prefix !== '') {
5595  MatrixDAL::bindValueToPdo($query, 'lookup_value', $prefix.'%');
5596  }
5597  $result = MatrixDAL::executePdoAssoc($query);
5598  } catch (Exception $e) {
5599  throw new Exception('Unable to get lookup values for asset: '.$this->id.' due to database error: '.$e->getMessage());
5600  }
5601 
5602  $ret_val = Array();
5603  foreach ($result as $row) {
5604  if (!isset($ret_val[$row['url']])) {
5605  $ret_val[$row['url']] = Array();
5606  }
5607  $ret_val[$row['url']][$row['name']] = Array('value' => $row['value'], 'inhd' => $row['inhd']);
5608  }
5609 
5610  return $ret_val;
5611 
5612  }//end getLookupValues()
5613 
5614 
5625  public function deleteLookupValue($layout_name, $layout_value)
5626  {
5627  $lookup_values = $this->getLookupValues(FALSE);
5628 
5629  foreach ($lookup_values as $resource => $data) {
5630  foreach ($data as $name => $value) {
5631  if (strstr($layout_name, 'override::')) {
5632  if (preg_match('/.+\/$/', $resource)) {
5633  // override layout
5634  if ((str_replace('override::', '', $layout_name) === $name) && ($layout_value === $value['value'])) {
5635  unset($lookup_values[$resource][$name]);
5636  }
5637  }
5638  } else {
5639  if (preg_match('/.+\/{0}$/', $resource)) {
5640  // normal layout
5641  if (($layout_name === $name) && ($layout_value === $value['value'])) {
5642  unset($lookup_values[$resource][$name]);
5643  }
5644  }
5645  }
5646  }
5647  }
5648 
5649  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
5650  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5651 
5652  if (!$this->setLookupValues($lookup_values)) {
5653  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5654  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5655  return FALSE;
5656  }
5657 
5658  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5659  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5660 
5661  return $lookup_values;
5662 
5663  }//end deleteLookupValue()
5664 
5665 
5681  public function setPaintLayouts($layouts)
5682  {
5683  if (!is_array($layouts)) return FALSE;
5684  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
5685  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5686 
5687  $modified = FALSE;
5688  // work out what links we have currently and create new ones if they do not exist
5689  foreach ($layouts as $layout_name => $assetid) {
5690  if (!empty($assetid)) {
5691  $layout = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
5692  if (!($layout instanceof Paint_Layout_Page)) return FALSE;
5693 
5694  $create_link = FALSE;
5695  $link = $GLOBALS['SQ_SYSTEM']->am->getLink($this->id, SQ_LINK_NOTICE, 'paint_layout_page', TRUE, $layout_name);
5696  if (isset($link['minorid'])) {
5697  // this asset already has an asset based layout
5698  if ($link['minorid'] !== $assetid) {
5699  // different so delete existing link so we can create a new one
5700  if (!$this->deleteLink($link['linkid'])) {
5701  trigger_localised_error('CORE0283', E_USER_WARNING, $link['linkid'], $layout_name, $this->id);
5702  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5703  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5704  return FALSE;
5705  }
5706 
5707  // also need to delete any other URLs which use the same value
5708  // deleting an asset based value means all other URLs which use the same value are also deleted
5709  if (!$this->deleteLookupValue($layout_name, $link['minorid'])) {
5710  trigger_localised_error('CORE0284', E_USER_WARNING, $layout_name, $this->id);
5711  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5712  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5713  return FALSE;
5714  }
5715 
5716  $create_link = TRUE;
5717  }
5718  } else {
5719  // no existing link so let's just create one
5720  $create_link = TRUE;
5721  }
5722 
5723  if ($create_link) {
5724  $modified = TRUE;
5725  if (!$GLOBALS['SQ_SYSTEM']->am->createAssetLink($this, $layout, SQ_LINK_NOTICE, $layout_name)) {
5726  trigger_localised_error('CORE0285', E_USER_WARNING, $layout->id, $layout_name, $this->id);
5727  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5728  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5729  return FALSE;
5730  }
5731  }
5732  }
5733  }
5734 
5735  if ($modified && !$this->updateLookups()) {
5736  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
5737  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5738  return FALSE;
5739  }
5740 
5741  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
5742  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
5743 
5744  return TRUE;
5745 
5746  }//end setPaintLayouts()
5747 
5748 
5758  public function updateLookups($auto_add_remaps = TRUE)
5759  {
5760  $paths = $this->getWebPaths();
5761 
5762  // get ready to log what happens
5763  $ms = $GLOBALS['SQ_SYSTEM']->getMessagingService();
5764  $ms->openLog();
5765 
5766  unset($this->_tmp['lookups']);
5767  unset($this->_tmp['design_lookups']);
5768  $GLOBALS['SQ_SYSTEM']->am->clearLookupsCache($this->id);
5769 
5770  require_once SQ_FUDGE_PATH.'/db_extras/db_extras.inc';
5771  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
5772  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
5773  $db = MatrixDAL::getDb();
5774 
5775  // retrieve all existing URLs for this asset
5776  // include sort order so that we can figure out which one is which
5777  $sql = 'SELECT l.url, l.http, l.https, u.urlid
5778  FROM sq_ast_lookup l
5779  LEFT OUTER JOIN sq_ast_url u ON l.root_urlid = u.urlid
5780  LEFT OUTER JOIN sq_ast_path p ON l.assetid = p.assetid
5781  WHERE l.assetid = :assetid
5782  ORDER BY p.sort_order DESC';
5783 
5784  try {
5785  $query = MatrixDAL::preparePdoQuery($sql);
5786  MatrixDAL::bindValueToPdo($query, 'assetid', $this->id);
5787  $old_urls = MatrixDAL::executePdoAssoc($query);
5788  } catch (Exception $e) {
5789  throw new Exception('Unable to get old lookups for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
5790  }
5791 
5792  // prepare trigger event data
5793  $this->_tmp['old_urls'] = $old_urls;
5794  $parameter['old_urls'] = $old_urls;
5795 
5796  // get all the lookup information that we have set (i.e. no inherited values), ignoring designs as they are set on every url
5797  $our_lookup_values = $this->getLookupValues(FALSE, $prefix='design::', FALSE);
5798  $our_edit_layouts = $this->getLookupValues(FALSE, 'layout::');
5799 
5800  $tmp_old_urls = Array();
5801 
5802  $current_urls = Array();
5803  $old_web_paths = Array();
5804  foreach ($old_urls as $url_data) {
5805  $url = $url_data['url'];
5806  $current_urls[] = $url;
5807  if ((strpos($url, '__data') === FALSE) && ($url_data['urlid']) && !empty($paths)) {
5808  // get the asset lineage and use that as the basis for indexing old URLs
5809  $url_lineage = $GLOBALS['SQ_SYSTEM']->am->getLineageFromURL(NULL, $url);
5810  $url_assetids = Array();
5811  foreach ($url_lineage as $url_lineage_item) {
5812  $url_assetids[] = $url_lineage_item['assetid'];
5813  }
5814 
5815  $parent_url = substr($url, 0, strrpos($url, '/'));
5816  $web_path = substr($url, strrpos($url, '/') + 1);
5817 
5818  $tmp_old_urls[] = Array(
5819  'url' => $url,
5820  'parent_url' => $parent_url,
5821  'web_path' => $web_path,
5822  'lineage' => $url_assetids,
5823  );
5824  $old_web_paths[] = $web_path;
5825  }
5826  }
5827  $old_web_paths = array_unique($old_web_paths);
5828 
5829  // find the differences between the two - from both directions -
5830  // and reindex them so they both start from zero
5831  $old_to_new = array_values(array_diff($old_web_paths, $paths));
5832  $new_to_old = array_values(array_diff($paths, $old_web_paths));
5833 
5834  $web_path_changes = Array();
5835 
5836  if ((count($old_to_new) == 1) && (count($new_to_old) == 1)) {
5837  $web_path_changes[$old_to_new[0]] = $new_to_old[0];
5838  } else if ((count($old_to_new) > 1) && (count($new_to_old) > 1)) {
5839  // if more than one web path has changed...
5840  // if the number of web paths changing are the same, we should
5841  for ($i = 0; $i < min(count($old_to_new), count($new_to_old)); $i++) {
5842  $web_path_changes[$old_to_new[$i]] = $new_to_old[$i];
5843  }
5844  }
5845 
5846  if (!empty($current_urls)) {
5847  // delete rows which have normal values applied
5848  $url_chunks = array_chunk($current_urls, 1000, TRUE);
5849  foreach ($url_chunks as $url_chunk) {
5850  try {
5851  $bind_vars = Array(
5852  'urls' => $url_chunk,
5853  );
5854  MatrixDAL::executeQuery('core', 'deleteLookupValueByUrls', $bind_vars);
5855  } catch (Exception $e) {
5856  throw new Exception('Unable to delete URLs from lookup value table due to database error: '.$e->getMessage());
5857  }
5858  }//end foreach
5859 
5860  // delete rows which have override values applied
5861  for ($i=0; $i<count($current_urls); $i++) {
5862  $current_urls[$i] .= '/';
5863  }
5864  $url_chunks = array_chunk($current_urls, 1000, TRUE);
5865  foreach ($url_chunks as $url_chunk) {
5866  try {
5867  $bind_vars = Array(
5868  'urls' => $url_chunk,
5869  );
5870  MatrixDAL::executeQuery('core', 'deleteLookupValueByUrls', $bind_vars);
5871  } catch (Exception $e) {
5872  throw new Exception('Unable to delete URLs with override values from lookup value table due to database error: '.$e->getMessage());
5873  }
5874  }//end foreach
5875 
5876  try {
5877  $bind_vars = Array('assetid' => $this->id);
5878  $result = MatrixDAL::executeQuery('core', 'deleteLookup', $bind_vars);
5879  } catch (Exception $e) {
5880  throw new Exception('Unable to delete lookup information for asset: '.$this->id.' due to database error: '.$e->getMessage());
5881  }
5882  }//end if
5883  // if we have paths then do some URL inserting
5884  if (!empty($paths)) {
5885 
5886  $num_paths = count($paths);
5887 
5888  $our_design_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_NOTICE, 'design', FALSE);
5889  $our_designs = Array();
5890  foreach ($our_design_links as $link) {
5891  if (preg_match('/^design::(system|user)::.*$/', $link['value'])) {
5892  $our_designs[$link['value']] = Array(
5893  'value' => $link['minorid'],
5894  );
5895  }
5896  }
5897 
5898  // override designs take priority over normal, inheritable designs
5899  $override_designs = Array();
5900  foreach ($our_design_links as $link) {
5901  if (preg_match('/^override::design::(system|user)::.*$/', $link['value'])) {
5902  $name = preg_replace('/^override::(.*)$/', '$1', $link['value']);
5903  $override_designs[$name] = Array(
5904  'value' => $link['minorid'],
5905  );
5906  }
5907  }
5908  unset($our_design_links);
5909 
5910  $our_layout_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_LINK_NOTICE, 'paint_layout_page', FALSE);
5911  $layouts = Array();
5912  foreach ($our_layout_links as $link) {
5913  if (preg_match('/^paint_layout::(system|user)::.*$/', $link['value'])) {
5914  $layouts[$link['value']] = Array(
5915  'value' => $link['minorid'],
5916  );
5917  }
5918  }
5919 
5920  // override layouts take priority over normal, inheritable layouts
5921  $override_layouts = Array();
5922  foreach ($our_layout_links as $link) {
5923  if (preg_match('/^override::paint_layout::system::.*$/', $link['value'])) {
5924  $name = preg_replace('/^override::(.*)$/', '$1', $link['value']);
5925  $override_layouts[$name] = Array(
5926  'value' => $link['minorid'],
5927  );
5928  }
5929  }
5930  unset($link);
5931  unset($our_layout_links);
5932 
5933  // edit layouts applied to the asset
5934  $edit_layouts = Array();
5935  foreach($our_edit_layouts as $layout_url => $layouts) {
5936  foreach($layouts as $name => $layout_data) {
5937  if (!isset($edit_layouts[$name])) {
5938  $edit_layouts[$name] = Array(
5939  'value' => $layout_data['value'],
5940  );
5941  }//end if
5942  }//end foreach
5943  }//end foreach
5944  unset($our_edit_layouts);
5945 
5946  $parents = $GLOBALS['SQ_SYSTEM']->am->getLinks($this->id, SQ_SC_LINK_WEB_PATHS, '', NULL, 'minor');
5947 
5948  $done_urls = Array();
5949  $done_parents = Array();
5950  for ($i = 0; $i < count($parents); $i++) {
5951  if (in_array($parents[$i]['majorid'], $done_parents)) {
5952  continue;
5953  }
5954  $parent = $GLOBALS['SQ_SYSTEM']->am->getAsset($parents[$i]['majorid'], $parents[$i]['major_type_code']);
5955  if (is_null($parent) || !is_object($parent)) {
5956  continue;
5957  }
5958  $done_parents[] = $parent->id;
5959 
5960  $parent_urls = $parent->getLookups();
5961 
5962  for ($j = 0, $num_parent_urls = count($parent_urls); $j < $num_parent_urls; $j++) {
5963 
5964  for ($k = 0; $k < $num_paths; $k++) {
5965  $new_url = $parent_urls[$j]['url'].'/'.$paths[$k];
5966  if (isset($done_urls[$new_url])) continue;
5967 
5968  // Get the protocol info for this new url
5969  $site_url_info = $GLOBALS['SQ_SYSTEM']->am->getRootURL($new_url);
5970 
5971  $site_https = isset($site_url_info['https']) ? $site_url_info['https'] : 0;
5972  $site_http = isset($site_url_info['http']) ? $site_url_info['http'] : 0;
5973 
5974  $https = ($this->force_secure === '-') ? '0' : $site_https;
5975  $http = ($this->force_secure === '1') ? '0' : $site_http;
5976 
5977  if (!$http && !$https) {
5978  $https = $site_https;
5979  $http = $site_http;
5980  }
5981 
5982  // if we have no protocol then we can just ignore this URL
5983  if (!$http && !$https) {
5984  continue;
5985  }
5986 
5987  // check if another asset is using the new URL
5988  $sql = 'SELECT assetid FROM sq_ast_lookup WHERE url = :new_url';
5989  $existing_asset_with_url_id = NULL;
5990  try {
5991  $query = MatrixDAL::preparePdoQuery($sql);
5992  MatrixDAL::bindValueToPdo($query, 'new_url', $new_url);
5993  $existing_asset_with_url_id = MatrixDAL::executePdoAssoc($query, 0);
5994  } catch (Exception $e) {
5995  throw new Exception('Cannot check if another asset is already using URL "'.$new_url.'": '.$e->getMessage());
5996  }
5997  if (!empty($existing_asset_with_url_id)) {
5998  // if an existing asset has the same url as the new URL throw an error and rollback
5999  $existing_asset_with_url_info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($existing_asset_with_url_id);
6000  trigger_localised_error('CORE0081', E_USER_WARNING, $new_url, $existing_asset_with_url_info[$existing_asset_with_url_id[0]]['name'], $existing_asset_with_url_id[0]);
6001  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
6002  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6003  return FALSE;
6004  }
6005 
6006  try {
6007  $bind_vars = Array (
6008  'url' => $new_url,
6009  'assetid' => $this->id,
6010  'http' => $http,
6011  'https' => $https,
6012  'root_urlid' => $parent_urls[$j]['root_urlid'],
6013  );
6014  $result = MatrixDAL::executeQuery('core', 'insertLookup', $bind_vars);
6015  } catch (Exception $e) {
6016  throw new Exception('Unable to insert lookups for asset "'.$this->name.'" (#'.$this->id.') due to database error: '.$e->getMessage());
6017  }
6018 
6019  $done_urls[] = $new_url;
6020 
6021  // get the asset lineage and use that as the index
6022  $url_lineage = $GLOBALS['SQ_SYSTEM']->am->getLineageFromURL(NULL, $parent_urls[$j]['url']);
6023  $new_url_assetids = Array();
6024  foreach ($url_lineage as $url_lineage_item) {
6025  $new_url_assetids[] = $url_lineage_item['assetid'];
6026  }
6027  $new_url_assetids[] = $this->id;
6028 
6029  // holds paint layout values and design values
6030  $lookup_values = Array();
6031  // holds override paint layout values and override design values
6032  $override_lookup_values = Array();
6033 
6034  // look for valid paint layouts to insert or update
6035  foreach ($tmp_old_urls as $tmp_old_url) {
6036  if ($tmp_old_url['url'] == $new_url) {
6037  // exact same URL as before
6038  $lookup_values = isset($our_lookup_values[$tmp_old_url['url']]) ? $our_lookup_values[$tmp_old_url['url']] : Array();
6039  // look for an override paint layout value
6040  $override_lookup_values = isset($our_lookup_values[$tmp_old_url['url'].'/']) ? $our_lookup_values[$tmp_old_url['url'].'/'] : Array();
6041  break;
6042  } else if (($tmp_old_url['parent_url'] == $parent_urls[$j]['url']) && ($tmp_old_url['web_path'] == $paths[$k])) {
6043  // the same parent URL and the same web path
6044  $lookup_values = isset($our_lookup_values[$tmp_old_url['url']]) ? $our_lookup_values[$tmp_old_url['url']] : Array();
6045  // look for an override paint layout value
6046  $override_lookup_values = isset($our_lookup_values[$tmp_old_url['url'].'/']) ? $our_lookup_values[$tmp_old_url['url'].'/'] : Array();
6047  break;
6048  } else if ($tmp_old_url['parent_url'] == $parent_urls[$j]['url']) {
6049  // same parent URL but different web path so change the affected paint layout entries
6050 
6051  // ensure web path changes are valid
6052  if (isset($web_path_changes[$tmp_old_url['web_path']]) && $web_path_changes[$tmp_old_url['web_path']] == $paths[$k]) {
6053  // retrieve normal paint layout lookup value
6054  $lookup_values = isset($our_lookup_values[$tmp_old_url['url']]) ? $our_lookup_values[$tmp_old_url['url']] : Array();
6055  // look for an override paint layout value
6056  $override_lookup_values = isset($our_lookup_values[$tmp_old_url['url'].'/']) ? $our_lookup_values[$tmp_old_url['url'].'/'] : Array();
6057 
6058  // update each affected paint layout's URL
6059  try {
6060  $bind_vars = Array (
6061  'url' => $tmp_old_url['url'].'%',
6062  'name' => 'paint_layout::%',
6063  );
6064  $applied_layout = MatrixDAL::executeAssoc('core', 'getLookupValueUrlsByPattern', 0, $bind_vars);
6065  } catch (Exception $e) {
6066  throw new Exception('Unable to get paint layouts for URL pattern: '.$tmp_old_url['url'].'%'.' due to database error: '.$e->getMessage());
6067  }
6068 
6069  foreach ($applied_layout as $key => $old_layout_url) {
6070  $new_layout_url = str_replace($tmp_old_url['url'], $new_url, $old_layout_url);
6071  try {
6072  $bind_vars = Array (
6073  'seturl' => $new_layout_url,
6074  'whereurl' => $old_layout_url,
6075  );
6076  $result = MatrixDAL::executeQuery('core', 'updateLookupValueUrl', $bind_vars);
6077  } catch (Exception $e) {
6078  throw new Exception('Unable to update lookup value table for existing URL: '.$old_layout_url.' with new URL: '.$new_layout_url.' due to database error: '.$e->getMessage());
6079  }
6080  }
6081  break;
6082  }
6083  }//end if
6084  }//end foreach
6085 
6086  // merge in design lookup values with our paint layout lookup values
6087  foreach ($our_designs as $name => $data) {
6088  $lookup_values[$name] = $data;
6089  }
6090  foreach ($override_designs as $name => $data) {
6091  $override_lookup_values[$name] = $data;
6092  }
6093 
6094  // merge in asset based paint layout values if they don't exist currently for this URL
6095  foreach ($layouts as $name => $data) {
6096  if (!in_array($name, array_keys($lookup_values))) {
6097  $lookup_values[$name] = $data;
6098  }
6099  }
6100  foreach ($override_layouts as $name => $data) {
6101  if (!in_array($name, array_keys($override_lookup_values))) {
6102  $override_lookup_values[$name] = $data;
6103  }
6104  }
6105 
6106  // merge in edit layout lookup values
6107  foreach($edit_layouts as $name => $data) {
6108  if (!in_array($name, array_keys($lookup_values))) {
6109  $lookup_values[$name] = $data;
6110  }
6111  }
6112 
6113  // calculate the depth of the url
6114  $exploded_url = explode('/', $new_url);
6115  $page_depth = count($exploded_url)-1;
6116 
6117  foreach ($lookup_values as $name => $data) {
6118  try {
6119  $bind_vars = Array (
6120  'url' => $new_url,
6121  'name' => $name,
6122  );
6123  $result = MatrixDAL::executeAssoc('core', 'getLookupValueUrls', $bind_vars);
6124  if (isset($result[0])) {
6125  $result = $result[0];
6126  }
6127  } catch (Exception $e) {
6128  throw new Exception('Unable to get lookup value URL for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6129  }
6130 
6131  // if the lookup value already exists, re-use it (this could happen
6132  // when a file changes back from a __data url)
6133  if (empty($result)) {
6134  try {
6135  $bind_vars = Array (
6136  'url' => $new_url,
6137  'name' => $name,
6138  'value' => $data['value'],
6139  'depth' => $page_depth,
6140  );
6141  $result = MatrixDAL::executeQuery('core', 'insertLookupValue', $bind_vars);
6142  } catch (Exception $e) {
6143  throw new Exception('Unable to insert new lookup value for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6144  }
6145  } else {
6146  try {
6147  $bind_vars = Array (
6148  'value' => $data['value'],
6149  'url' => $new_url,
6150  'name' => $name,
6151  );
6152  $result = MatrixDAL::executeQuery('core', 'updateLookupValue', $bind_vars);
6153  } catch (Exception $e) {
6154  throw new Exception('Unable to update lookup value for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6155  }
6156  }
6157 
6158  }//end foreach
6159 
6160  // for override values
6161  foreach ($override_lookup_values as $name => $data) {
6162  try {
6163  $bind_vars = Array (
6164  'url' => $new_url.'/',
6165  'name' => $name,
6166  );
6167  $result = MatrixDAL::executeAssoc('core', 'getLookupValueUrls', $bind_vars);
6168  if (isset($result[0])) {
6169  $result = $result[0];
6170  }
6171  } catch (Exception $e) {
6172  throw new Exception('Unable to get lookup value URL for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6173  }
6174 
6175  // if the lookup value already exists, re-use it (this could happen
6176  // when a file changes back from a __data url)
6177  if (empty($result)) {
6178  try {
6179  $bind_vars = Array (
6180  'url' => $new_url.'/',
6181  'name' => $name,
6182  'value' => $data['value'],
6183  'depth' => $page_depth+1,
6184  );
6185  $result = MatrixDAL::executeQuery('core', 'insertLookupValue', $bind_vars);
6186  } catch (Exception $e) {
6187  throw new Exception('Unable to insert new lookup value for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6188  }
6189  } else {
6190  try {
6191  $bind_vars = Array (
6192  'value' => $data['value'],
6193  'url' => $new_url,
6194  'name' => $name,
6195  );
6196  $result = MatrixDAL::executeQuery('core', 'updateLookupValue', $bind_vars);
6197  } catch (Exception $e) {
6198  throw new Exception('Unable to update lookup value for specified URL: '.$new_url.' due to database error: '.$e->getMessage());
6199  }
6200  }
6201 
6202  }//end foreach
6203 
6204  }//end for paths
6205 
6206  }//end for parent_urls
6207 
6208  }//end for parents
6209 
6210  // send a message to tell everyone the good news
6211  $msg = $ms->newMessage();
6212  $msg->subject = 'Asset Lookups Updated';
6213  $msg->type = 'asset.lookups.updated';
6214 
6215  $msg_reps = Array(
6216  'asset_name' => $this->name,
6217  );
6218  $msg->replacements = $msg_reps;
6219  $msg->parameters['assetid'] = $this->id;
6220  $msg->send();
6221 
6222  if (!empty($old_urls)) {
6223  if ($auto_add_remaps) {
6224  $rm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('remap_manager');
6225 
6226  // get the first new URL
6227  // don't add a remap if we don't have a URL to map to
6228  if (count($done_urls) > 0) {
6229  foreach ($old_urls as $url_data) {
6230  $old_url = $url_data['url'];
6231 
6232  // Search the new URLs for the one closest to an old
6233  // URL..
6234  $closest_url = NULL;
6235  $most_bits_matched = -1;
6236 
6237  foreach ($done_urls as $new_url) {
6238  $new_url = strip_url($new_url, TRUE);
6239  $new_url_bits = explode('/', $new_url);
6240  $old_url_bits = explode('/', $old_url);
6241 
6242  for ($bits = 0; $bits < count($old_url_bits); $bits++) {
6243  if ((string)$old_url_bits[$bits] !== (string)$new_url_bits[$bits]) {
6244  break;
6245  }
6246  }//end for
6247 
6248  if ($bits > $most_bits_matched) {
6249  $closest_url = $new_url;
6250  $most_bits_matched = $bits;
6251  }
6252 
6253  }//end foreach URL
6254 
6255  // this URL is already in the list of new URLs, therefore we don't need a remap
6256  // because we're only making a remap to ourselves
6257  if (!in_array($old_url, $done_urls)) {
6258  // don't remap __data URLs, because they aren't served by Matrix
6259  foreach (explode("\n", SQ_CONF_SYSTEM_ROOT_URLS) as $root_url) {
6260  $data_url = $root_url.'/__data';
6261  if (substr($old_url, 0, strlen($data_url)) == $data_url) {
6262  continue 2;
6263  }
6264  }
6265  if ($url_data['http']) {
6266  $rm->addRemapURL('http://'.$old_url, 'http://'.$closest_url);
6267  }
6268  if ($url_data['https']) {
6269  $rm->addRemapURL('https://'.$old_url, 'https://'.$closest_url);
6270  }
6271  }
6272  }
6273  }
6274  }
6275  }//end if !empty($old_urls)
6276 
6277  }//end if count paths
6278 
6279  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6280  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6281 
6282  // notify anyone interested that lookups have been updated
6283  $em = $GLOBALS['SQ_SYSTEM']->getEventManager();
6284  $em->broadcastEvent($this, 'LookupsUpdated', Array('asset_name' => $this->name));
6285  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_lookups_updated', $this, $parameter);
6286  $ms->closeLog();
6287 
6288  return TRUE;
6289 
6290  }//end updateLookups()
6291 
6292 
6309  public function setLookupValues(Array $values)
6310  {
6311  if (!$this->writeAccess('lookups')) {
6312  trigger_localised_error('SYS0265', E_USER_WARNING, $this->name, $this->id);
6313  return FALSE;
6314  }
6315 
6316  $db = MatrixDAL::getDb();
6317  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
6318  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
6319 
6320  // delete all lookup value URLs for this asset, including override values
6321  $sql = 'DELETE FROM
6322  sq_ast_lookup_value
6323  WHERE
6324  url IN
6325  (
6326  (SELECT
6327  url
6328  FROM
6329  sq_ast_lookup l
6330  WHERE
6331  assetid = :assetid_1)
6332  UNION
6333  (SELECT
6334  url || \'/\'
6335  FROM
6336  sq_ast_lookup l
6337  WHERE
6338  assetid = :assetid_2)
6339  )';
6340  try {
6341  $query = MatrixDAL::preparePdoQuery($sql);
6342  MatrixDAL::bindValueToPdo($query, 'assetid_1', $this->id);
6343  MatrixDAL::bindValueToPdo($query, 'assetid_2', $this->id);
6344  $result = MatrixDAL::execPdoQuery($query);
6345  } catch (Exception $e) {
6346  throw new Exception('Unable to delete lookup values for asset: '.$this->id.' due to database error: '.$e->getMessage());
6347  }
6348 
6349  $lookups = $this->getLookups('url');
6350 
6351  foreach ($values as $url => $values_data) {
6352  // check if this is a valid URL reflected in our lookups table
6353  $valid_url = FALSE;
6354  foreach ($lookups as $lookup_url) {
6355  if ($url === $lookup_url) {
6356  // normal value
6357  $valid_url = TRUE;
6358  } else if ($url === $lookup_url.'/') {
6359  // override value
6360  $valid_url = TRUE;
6361  }
6362  }
6363  if (!$valid_url) continue;
6364  if (empty($values_data)) continue;
6365 
6366  // calculate the depth of the url
6367  $exploded_url = explode('/', $url);
6368  $page_depth = count($exploded_url)-1;
6369 
6370  foreach ($values_data as $value_name => $value_data) {
6371  try {
6372  $bind_vars = Array (
6373  'url' => $url,
6374  'name' => $value_name,
6375  'value' => $value_data['value'],
6376  'depth' => $page_depth,
6377  );
6378  $result = MatrixDAL::executeQuery('core', 'insertLookupValue', $bind_vars);
6379  } catch (Exception $e) {
6380  throw new Exception('Unable to insert new lookup value for specified URL: '.$url.' due to database error: '.$e->getMessage());
6381  }
6382  }
6383  }//end foreach
6384 
6385  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
6386  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
6387  return TRUE;
6388 
6389  }//end setLookupValues()
6390 
6391 
6400  {
6401  return get_status_description($this->status);
6402 
6403  }//end getAssetStatusDescriptionKeywordReplacement()
6404 
6405 
6415  {
6416  return '#'.get_status_colour($this->status);
6417 
6418  }//end getAssetStatusColourKeywordReplacement()
6419 
6420 
6431  public function replaceKeywordsInString($string)
6432  {
6433  $replacements = Array();
6434  $keyword_list = extract_keywords($string);
6435  if (empty($keyword_list) || !is_array($keyword_list)) {
6436  return $string;
6437  }
6438 
6439  foreach ($keyword_list as $keyword) {
6440  $replacements[$keyword] = $this->getKeywordReplacement($keyword);
6441  }
6442 
6443  replace_keywords($string, $replacements);
6444 
6445  return $string;
6446 
6447  }//end replaceKeywordsInString()
6448 
6449 
6482  public function fastTrack($tasks)
6483  {
6484  if (!is_array($tasks)) $tasks = Array($tasks);
6485  if (!isset($this->_tmp['fast_track'])) $this->_tmp['fast_track'] = Array();
6486 
6487  foreach($tasks as $task) {
6488  // Initialise the "fast tracked assetids" array only if we have not fast tracked this task before,
6489  // or if we have removed it from being fast tracked
6490  if (!isset($this->_tmp['fast_track'][$task.'_assetids']) || !isset($this->_tmp['fast_track'][$task]) || !$this->_tmp['fast_track'][$task]) {
6491  $this->_tmp['fast_track'][$task.'_assetids'] = Array();
6492  }
6493 
6494  $this->_tmp['fast_track'][$task] = TRUE;
6495  }
6496 
6497  }//end fastTrack()
6498 
6499 
6510  public function unFastTrack($tasks)
6511  {
6512  if (!is_array($tasks)) $tasks = Array($tasks);
6513  if (!isset($this->_tmp['fast_track'])) $this->_tmp['fast_track'] = Array();
6514 
6515  foreach($tasks as $task) {
6516  $this->_tmp['fast_track'][$task] = FALSE;
6517  }
6518 
6519  }//end unFastTrack()
6520 
6521 
6534  public function shouldFastTrack($task, $assetid=NULL)
6535  {
6536  if (isset($this->_tmp['fast_track'][$task]) && $this->_tmp['fast_track'][$task]) {
6537  // Keep track of assetids associated with the task that has been fast tracked
6538  if (!is_null($assetid)) {
6539  $this->_tmp['fast_track'][$task.'_assetids'][] = $assetid;
6540  }
6541  return TRUE;
6542  }
6543 
6544  return FALSE;
6545 
6546  }//end shouldFastTrack()
6547 
6548 
6558  public function getFaskTrackedTaskAssetids($task)
6559  {
6560  return !empty($this->_tmp['fast_track'][$task.'_assetids']) ? $this->_tmp['fast_track'][$task.'_assetids'] : Array();
6561 
6562  }//end getFaskTrackedTaskAssetids()
6563 
6564 
6571  public function getDependantParentsURL()
6572  {
6573  $asset_url = $this->getURL();
6574  if (empty($asset_url)) {
6575  $dependant_parents = $GLOBALS['SQ_SYSTEM']->am->getDependantParents($this->id, '', TRUE, FALSE);
6576 
6577  $dependant_parent = isset($dependant_parents[0]) ? $GLOBALS['SQ_SYSTEM']->am->getAsset($dependant_parents[0]) : NULL;
6578  if (!is_null($dependant_parent)) {
6579  $asset_url = $dependant_parent->getURL();
6580  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($dependant_parent);
6581  }
6582  }//end if
6583 
6584  return $asset_url;
6585 
6586  }//end getDependantParentURL()
6587 
6588 }//end class
6589 
6590 ?>