heartbeat.module

Tracking 7.x-1.x branch
  1. drupal
    1. 6 contributions/heartbeat/heartbeat.module
    2. 7 contributions/heartbeat/heartbeat.module

Module file for heartbeat activity. Basic hook implementations and helper functions will be found here.

See also

heartbeat.streams.inc for functions on the streams

heartbeat.entity.inc for the definition of the entities/types.

Classes

NameDescription
HeartbeatCtoolsObjectClass HeartbeatCtoolsObject
HeartbeatMessagePoolClass to keep HeartbeatActivity messages in a pool so plugins and such can get the message instead of reloading them.
InvalidHeartbeatCrudOperationExceptionHeartbeat invalid crud data exception.
PagerActivityQuery extender for heartbeat pager queries.

Functions & methods

NameDescription
heartbeat_activity_deleteDeletes a heartbeat activity messages.
heartbeat_activity_loadFunction to load one activity message.
heartbeat_activity_load_multipleLoad multiple activity records by user activity ID's.
heartbeat_activity_viewheartbeat_activity_view().
heartbeat_allowed_html_tags
heartbeat_api_logAPI function to log a message from custom code
heartbeat_api_most_active_usersAPI function to retrieve the most active users.
heartbeat_block_configureImplements hook_block_configure().
heartbeat_block_infoImplements hook_block_info().
heartbeat_block_saveImplements hook_block_save().
heartbeat_block_viewImplements hook_block_view().
heartbeat_cronImplements hook_cron(). Delete too old message if this option is set and logs where the node does not exist anymore.
heartbeat_ctools_modal_prepareHelper function to prepare a custom CTools Modal window.
heartbeat_ctools_plugin_apiImplements hook_ctools_plugin_api().
heartbeat_decode_message_variablesDecode heartbeat message variables
heartbeat_ds_layout_infoImplements hook_ds_layout_info().
heartbeat_encode_message_variablesEncode heartbeat message variables
heartbeat_formsImplements hook_forms(). All heartbeat template forms share the same form handler.
heartbeat_form_user_profile_form_alterImplements hook_form_FORM_ID_alter().
heartbeat_heartbeat_activity_viewImplements hook_heartbeat_activity_view().
heartbeat_heartbeat_related_uidsImplements hook_heartbeat_related_uids().
heartbeat_helpImplements hook_help().
heartbeat_image_default_stylesImplements hook_image_default_styles().
heartbeat_initImplements hook_init().
heartbeat_logUser activity logger function
heartbeat_menuImplements hook_menu().
heartbeat_message_id_titleFunction to load the title of message template pages.
heartbeat_message_template_deleteFunction to delete heartbeat message templates.
heartbeat_message_template_loadFunction to load heartbeat message templates.
heartbeat_modules_enabledImplements hook_modules_enabled($modules).
heartbeat_node_deleteImplements hook_node_delete().
heartbeat_permissionImplements hook_permission().
heartbeat_preprocess_views_viewPreprocess the primary theme implementation for a view.
heartbeat_print_jsonHelper function to print JSON data.
heartbeat_related_uidsReturns a set of users related to a central user.
heartbeat_stream_more_linkHelper function for a more link on streams (older messages) Should only be called when hasMoreMessages resulted to TRUE
heartbeat_templates_namesGet the heartbeat template messages names.
heartbeat_template_set_defaultsSet the default values for a heartbeat_template.
heartbeat_themeImplements hook_theme().
heartbeat_user_deleteImplements hook_user_delete().
heartbeat_user_templatesAdd the heartbeat template field to the user edit form.
heartbeat_user_templates_loadHelper function to load heartbeat user template settings.
heartbeat_user_templates_submitSubmit handler to save a users profile templates.
heartbeat_views_apiImplementation of hook_views_api().
template_preprocess_heartbeat_activityProcess variables for heartbeat-activity.tpl.php.
theme_activity_pagerReturns HTML for a query pager for heartbeat activity.
theme_heartbeat_activity_avatarThe function for the avatar in a heartbeat activity message.
theme_heartbeat_buttonsTheme function for messages buttons.
theme_heartbeat_listTheme function for a list of heartbeat activity messages.
theme_heartbeat_message_user_select_formTheme function for the user profile form.
theme_heartbeat_time_agoTheme function for the timestamp of a message.
_heartbeat_activity_get_accessReturns the permission to log a Message based on the access of the Template or the User setting.
_heartbeat_activity_loadFunction to load one activity message.
_heartbeat_map_assocHelper function to map a array to dropdown with a field and value for the options
_heartbeat_message_delete_accessHelper function to check if a user has access to delete a message
_heartbeat_message_has_accessHelper function to check access on an Access type activity stream
_heartbeat_perms_optionsHelper function to get the options for perm types
_heartbeat_user_loadHelper function to load the users from static cache. There should be something in core to handle this.
_theme_time_agoHeartbeat typical time ago
_theme_user_message_select_formHelper theme function for the activity selection in the user profile form

Constants

NameDescription
HEARTBEAT_NONE
HEARTBEAT_PRIVATE
HEARTBEAT_PUBLIC_TO_ADDRESSEE
HEARTBEAT_PUBLIC_TO_ALL
HEARTBEAT_PUBLIC_TO_CONNECTED

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Module file for heartbeat activity.
  5. * Basic hook implementations and helper functions will be found here.
  6. *
  7. * @see heartbeat.streams.inc for functions on the streams
  8. * @see heartbeat.entity.inc for the definition of the entities/types.
  9. *
  10. */
  11. /**
  12. * Message access
  13. *
  14. * What people can see and are entitled to see. This permission
  15. * on messages can be set as default per Heartbeat stream but
  16. * can be overriden in the configuration of a heartbeat message.
  17. */
  18. // Always block from display
  19. define('HEARTBEAT_NONE', -1);
  20. // Display only activity messages that are mine or addressed to me
  21. define('HEARTBEAT_PRIVATE', 0);
  22. // Only the person that is chosen by the actor, can see the message
  23. define('HEARTBEAT_PUBLIC_TO_ADDRESSEE', 1);
  24. // Display activity message of all my user relations, described in contributed modules
  25. define('HEARTBEAT_PUBLIC_TO_CONNECTED', 2);
  26. // Everyone can see this activity message, unless this type of message is set to private
  27. define('HEARTBEAT_PUBLIC_TO_ALL', 4);
  28. /**
  29. * Heartbeat message states to describe how they were built
  30. */
  31. /**
  32. * @file
  33. * Heartbeat module file
  34. */
  35. module_load_include('inc', 'heartbeat', 'heartbeat.streams');
  36. module_load_include('inc', 'heartbeat', 'heartbeat.entity');
  37. /**
  38. * Implements hook_help().
  39. */
  40. function heartbeat_help($path, $arg) {
  41. switch ($path) {
  42. case 'admin/help#heartbeat':
  43. $output = '';
  44. $output .= '<h3>' . t('About') . '</h3>';
  45. return $output;
  46. case 'admin/structure/heartbeat':
  47. return '<p>' . t('Heartbeat activity lets you create streams, composed together with message templates that are parsed into activity messages.') . '</p>';
  48. }
  49. }
  50. /**
  51. * Implements hook_init().
  52. */
  53. function heartbeat_init() {
  54. drupal_add_js(array('heartbeat_language' => $GLOBALS['language']->language), "setting");
  55. // Define the valid uri so javascript knows what to call.
  56. drupal_add_js(array('heartbeat_poll_url' => url('heartbeat/js/poll', array('absolute' => TRUE))), "setting");
  57. }
  58. /**
  59. * Implements hook_cron().
  60. * Delete too old message if this option is set and logs where
  61. * the node does not exist anymore.
  62. */
  63. function heartbeat_cron() {
  64. $uaids = array();
  65. $cron_delete_time = variable_get('heartbeat_activity_log_cron_delete', 2678400);
  66. $keep_latest_number = variable_get('heartbeat_activity_records_per_user', 10);
  67. $microseconds = microtime();
  68. // Delete activity older than the expiration date, while
  69. // keeping the latest X for each user.
  70. if ($cron_delete_time) {
  71. $expire = $_SERVER['REQUEST_TIME'] - $cron_delete_time;
  72. // Activity Ids that can not be removed (latest activity per user)
  73. $keep_uaids = array(0 => 0);
  74. // Calculate the latest activity for each user.
  75. $result = db_query("SELECT
  76. t1.uid,
  77. t1.uaid as 'uaid',
  78. COUNT(*) as 'rows_per_user',
  79. t1.timestamp as 'real_date',
  80. MIN(t2.timestamp) as 'oldest_date',
  81. count(t2.uid) AS 'count'
  82. FROM {heartbeat_activity} AS t1
  83. INNER JOIN {heartbeat_activity} AS t2 ON t1.uid = t2.uid AND t2.timestamp >= t1.timestamp
  84. WHERE (t1.timestamp, t1.uaid) < (t2.timestamp, t2.uaid)
  85. GROUP BY t1.uid, t1.uaid HAVING COUNT(t2.uid) <= :latest
  86. ORDER BY t1.uid, t1.uaid, t1.timestamp DESC", array(':latest' => $keep_latest_number));
  87. // $users = db_query("SELECT uid FROM {users} WHERE status = 1");
  88. // $query = array();
  89. // $args = array();
  90. // foreach ($users as $key => $account) {
  91. // $query[] = " ( SELECT uid, uaid FROM {heartbeat_activity} WHERE uid = :uid_$key ORDER BY uaid DESC LIMIT 0, :latest ) ";
  92. // $args[':uid_' . $key] = $account->uid;
  93. // $args[':latest'] = (int) $keep_latest_number;
  94. // }
  95. //
  96. // $result = db_query(implode("UNION", $query), $args);
  97. foreach ($result as $row) {
  98. $keep_uaids[$row->uaid] = $row->uaid;
  99. }
  100. //$arguments = array_merge(array($expire), $keep_uaids);
  101. $delete_result = db_query("SELECT uaid FROM {heartbeat_activity}
  102. WHERE timestamp < :expire AND uaid NOT IN (:uaids) ", array(':expire' => $expire, ':uaids' => $keep_uaids));
  103. foreach ($delete_result as $row) {
  104. $uaids[] = $row->uaid;
  105. }
  106. }
  107. if (!empty($uaids)) {
  108. heartbeat_activity_delete($uaids);
  109. }
  110. $microseconds_final = microtime();
  111. watchdog('cron', 'Cron finished in %secs seconds', array('%secs' => $microseconds_final - $microseconds));
  112. }
  113. /**
  114. * Implements hook_menu().
  115. */
  116. function heartbeat_menu() {
  117. $items = array();
  118. // Menu page callbacks for each heartbeat stream.
  119. $streams = heartbeat_stream_config_load_all(TRUE);
  120. foreach ($streams as $class => $stream) {
  121. if (!empty($stream->stream_path)) {
  122. $items[$stream->stream_path] = array(
  123. 'title' => $stream->title,
  124. 'description' => $stream->name . ' page',
  125. 'page callback' => 'heartbeat_messages_page',
  126. 'page arguments' => array($stream->class),
  127. 'access callback' => 'heartbeat_stream_has_access',
  128. 'access arguments' => array($stream->class),
  129. 'file' => 'heartbeat.pages.inc',
  130. 'type' => MENU_CALLBACK,
  131. );
  132. }
  133. if (!empty($stream->stream_profile_path)) {
  134. $items['user/%user/' . $stream->stream_profile_path] = array(
  135. 'title' => $stream->title,
  136. 'page callback' => 'heartbeat_messages_page',
  137. 'page arguments' => array($stream->class, '0', 1),
  138. 'access callback' => 'heartbeat_stream_has_access',
  139. 'access arguments' => array($stream->class),
  140. 'type' => MENU_LOCAL_TASK,
  141. 'file' => 'heartbeat.pages.inc',
  142. 'weight' => 50,
  143. );
  144. }
  145. }
  146. // Display one activity entity.
  147. $items['heartbeat/message/%heartbeat_activity'] = array(
  148. 'title' => 'Single message',
  149. 'description' => 'Activity message',
  150. 'page callback' => 'heartbeat_message_activity',
  151. 'page arguments' => array(2),
  152. 'access callback' => '_heartbeat_message_has_access',
  153. 'access arguments' => array(2),
  154. 'file' => 'heartbeat.pages.inc',
  155. );
  156. // Ajax driven callback to delete activity
  157. $items['heartbeat/%ctools_js/activity/delete/%heartbeat_activity'] = array(
  158. 'title' => 'Delete activity',
  159. 'page callback' => 'heartbeat_activity_modal_delete',
  160. 'page arguments' => array(1, 4),
  161. 'access callback' => '_heartbeat_message_delete_access',
  162. 'access arguments' => array(4),
  163. 'file' => 'heartbeat.pages.inc',
  164. 'type' => MENU_CALLBACK,
  165. );
  166. $items['heartbeat/js/poll'] = array(
  167. 'page callback' => 'heartbeat_activity_poll',
  168. 'access callback' => 'user_access',
  169. 'access arguments' => array('view heartbeat messages'),
  170. 'type' => MENU_CALLBACK,
  171. 'file' => 'heartbeat.pages.inc',
  172. );
  173. $items['heartbeat/js/older'] = array(
  174. 'page callback' => 'heartbeat_activity_older',
  175. 'access callback' => 'user_access',
  176. 'access arguments' => array('view heartbeat messages'),
  177. 'type' => MENU_CALLBACK,
  178. 'file' => 'heartbeat.pages.inc',
  179. );
  180. return $items;
  181. }
  182. /**
  183. * Implements hook_permission().
  184. */
  185. function heartbeat_permission() {
  186. $permissions = array(
  187. 'admin heartbeat templates' => array(
  188. 'title' => t('Administer heartbeat templates'),
  189. 'description' => t('Manage the heartbeat templates.')
  190. ),
  191. 'admin heartbeat delete all' => array(
  192. 'title' => t('Delete all activity'),
  193. 'description' => t('Master permission to delete all activity.')
  194. ),
  195. 'admin heartbeat delete own' => array(
  196. 'title' => t('Delete own activity'),
  197. 'description' => t('Permission for the actor to delete own activity.')
  198. ),
  199. 'view heartbeat messages' => array(
  200. 'title' => t('View activity'),
  201. 'description' => t('Global permission to view heartbeat activity.')
  202. ),
  203. 'access heartbeat activity profiles' => array(
  204. 'title' => t('Access heartbeat activity profiles'),
  205. 'description' => t('Permission to see user profiles or links to the user profile.')
  206. ),
  207. );
  208. foreach (heartbeat_stream_config_load_all(TRUE) as $streamConfig) {
  209. $permissions['view ' . $streamConfig->name . ' stream'] = array(
  210. 'title' => t('View activity in ' . $streamConfig->name),
  211. 'description' => t('Stream access: ' . $streamConfig->name . '.')
  212. );
  213. }
  214. return $permissions;
  215. }
  216. /**
  217. * Implements hook_user_delete().
  218. */
  219. function heartbeat_user_delete($account) {
  220. // Delete messages from removed users.
  221. $query = db_select('heartbeat_activity', 'ha');
  222. $query->addField('ha', 'uaid');
  223. $query->condition(db_or()
  224. ->condition('uid', $account->uid)
  225. ->condition('uid_target', $account->uid));
  226. foreach ($query->execute() as $row_object) {
  227. $uaids[] = $row_object->uaid;
  228. }
  229. if (!empty($uaids)) {
  230. heartbeat_activity_delete($uaids);
  231. }
  232. }
  233. /**
  234. * Implements hook_theme().
  235. */
  236. function heartbeat_theme() {
  237. return array(
  238. 'heartbeat_activity' => array(
  239. 'render element' => 'elements',
  240. 'template' => 'heartbeat-activity'
  241. ),
  242. 'heartbeat_activity_avatar' => array(
  243. 'variables' => array('heartbeatactivity' => NULL, 'uri' => NULL),
  244. ),
  245. 'activity_pager' => array(
  246. 'variables' => array('stream' => NULL),
  247. ),
  248. 'heartbeat_list' => array(
  249. 'variables' => array('stream' => NULL, 'content' => NULL),
  250. ),
  251. 'heartbeat_buttons' => array(
  252. 'variables' => array('heartbeat_activity' => NULL),
  253. ),
  254. 'heartbeat_time_ago' => array(
  255. 'variables' => array('heartbeat_activity' => NULL),
  256. ),
  257. 'heartbeat_message_user_select_form' => array(
  258. 'render element' => 'form',
  259. ),
  260. );
  261. }
  262. /**
  263. * Implements hook_block_info().
  264. */
  265. function heartbeat_block_info() {
  266. $blocks = array();
  267. $streams = heartbeat_stream_config_load_all(TRUE);
  268. // A block for each stream.
  269. foreach ($streams as $key => $stream_config) {
  270. if ($stream_config->has_block) {
  271. $blocks[$stream_config->class]['info'] = drupal_ucfirst($stream_config->title);
  272. }
  273. }
  274. // Heartbeat most active users.
  275. $blocks['heartbeat_active_users']['info'] = t('Heartbeat most active users');
  276. return $blocks;
  277. }
  278. /**
  279. * Implements hook_block_view().
  280. */
  281. function heartbeat_block_view($delta = '') {
  282. if ($delta == 'heartbeat_active_users') {
  283. $block['subject'] = t('Most active users');;
  284. $block['content'] = drupal_render(heartbeat_api_most_active_users(variable_get('heartbeat_active_users', 'default')));
  285. return $block;
  286. }
  287. // For blocks calling this page in general.
  288. $account = NULL;
  289. if (variable_get('heartbeat_show_user_profile_messages_' . $delta, 0) && arg(0) == 'user' && is_numeric(arg(1))) {
  290. $account = user_load(arg(1));
  291. }
  292. if ($heartbeatStream = heartbeat_stream($delta, FALSE, $account)) {
  293. if (variable_get('exclude_og_' . $delta, 0)) {
  294. $heartbeatStream->excludeOg(TRUE);
  295. }
  296. heartbeat_stream_build($heartbeatStream);
  297. $block_content = heartbeat_stream_view($heartbeatStream, $heartbeatStream->config->block_view_mode);
  298. if (!empty($block_content)) {
  299. $content = array();
  300. $content['#theme'] = 'heartbeat_list';
  301. $content['#stream'] = $heartbeatStream;
  302. $content['#content'] = $block_content;
  303. $content['#attached']['js'][] = drupal_get_path('module', 'heartbeat') . '/js/heartbeat.js';
  304. if (variable_get('heartbeat_include_default_style', 1)) {
  305. $content['#attached']['css'][] = drupal_get_path('module', 'heartbeat') . '/css/heartbeat.css';
  306. }
  307. // Dirty hack to fix polled streams when no js/css can be included on custom ajax command.
  308. $content['#attached']['css'][] = drupal_get_path('module', 'heartbeat') . '/layouts/heartbeat_2col/heartbeat_2col.css';
  309. $block['content'] = $content;
  310. }
  311. $block['subject'] = t($heartbeatStream->config->title);
  312. }
  313. else {
  314. return NULL;
  315. }
  316. return $block;
  317. }
  318. /**
  319. * Implements hook_block_configure().
  320. */
  321. function heartbeat_block_configure($delta = '') {
  322. if ($delta == 'heartbeat_active_users') {
  323. $info = entity_get_info('user');
  324. $options = array('default' => t('default')) + drupal_map_assoc(array_keys($info['view modes']));
  325. $form = array('view_mode' => array(
  326. '#type' => 'select',
  327. '#title' => t('Select view mode to render the users.'),
  328. '#default_value' => variable_get('heartbeat_active_users', 'default'),
  329. '#options' => $options,
  330. ));
  331. }
  332. else {
  333. $stream = heartbeat_stream_config_load($delta);
  334. $form = array('items' => array(
  335. '#type' => 'checkbox',
  336. '#title' => t('Show activity for the displayed user on the user profile page'),
  337. '#description' => t('By default heartbeat will show activity in relation to the
  338. currently logged in user. With this setting enabled and only on the user profile page,
  339. the messages will be shown in relation to the user profile.'),
  340. '#default_value' => variable_get('heartbeat_show_user_profile_messages_' . drupal_strtolower($stream->class), 0),
  341. ));
  342. if (module_exists('heartbeat_og')) {
  343. $form['exclude_og'] = array(
  344. '#type' => 'checkbox',
  345. '#title' => t('Exclude messages within Organic Group context.'),
  346. '#default_value' => variable_get('exclude_og_' . drupal_strtolower($stream->class), 0),
  347. );
  348. }
  349. }
  350. return $form;
  351. }
  352. /**
  353. * Implements hook_block_save().
  354. */
  355. function heartbeat_block_save($delta = '', $edit = array()) {
  356. if ($delta == 'heartbeat_active_users') {
  357. variable_set('heartbeat_active_users', (isset($edit['view_mode']) ? $edit['view_mode'] : 'default'));
  358. return;
  359. }
  360. $stream = heartbeat_stream_config_load($delta);
  361. variable_set('heartbeat_show_user_profile_messages_' . drupal_strtolower($stream->class), $edit['items']);
  362. variable_set('exclude_og_' . drupal_strtolower($stream->class), $edit['exclude_og']);
  363. }
  364. /**
  365. * Implements hook_node_delete().
  366. */
  367. function heartbeat_node_delete($node) {
  368. // Delete messages from deleted nodes.
  369. $query = db_select('heartbeat_activity', 'ha');
  370. $query->addField('ha', 'uaid');
  371. $query->condition('nid', $node->nid);
  372. foreach ($query->execute() as $row_object) {
  373. $uaids[] = $row_object->uaid;
  374. }
  375. if (!empty($uaids)) {
  376. heartbeat_activity_delete($uaids);
  377. }
  378. }
  379. /**
  380. * Implements hook_form_FORM_ID_alter().
  381. */
  382. function heartbeat_form_user_profile_form_alter(&$form, &$form_state) {
  383. if ($form['#user_category'] == 'account') {
  384. $profile_templates = variable_get('heartbeat_profile_message_templates', array());
  385. if (count($profile_templates)) {
  386. $form_state['heartbeat_templates'] = array();
  387. foreach (heartbeat_templates_names() as $id => $description) if (isset($profile_templates[$id])) {
  388. $form_state['heartbeat_templates'][$id] = $description;
  389. }
  390. heartbeat_user_templates($form, $form_state);
  391. }
  392. return $form;
  393. }
  394. }
  395. /**
  396. * Implements hook_heartbeat_related_uids().
  397. */
  398. function heartbeat_heartbeat_related_uids($uid) {
  399. $uids = array();
  400. if (module_exists('flag_friend')) {
  401. foreach (flag_friend_get_friends($uid) as $account) {
  402. $uids[$account->uid] = $account->uid;
  403. }
  404. }
  405. if (module_exists('user_relationships')) {
  406. $result = user_relationships_load(array('user' => $uid, 'approved' => 1));
  407. foreach ($result as $account) {
  408. $uids[$account->requestee_id] = $account->requestee_id;
  409. }
  410. }
  411. return $uids;
  412. }
  413. /**
  414. * Add the heartbeat template field to the user edit form.
  415. */
  416. function heartbeat_user_templates(&$form, &$form_state) {
  417. $account = $form['#user'];
  418. // The heartbeat privacy settings.
  419. $form['heartbeat'] = array(
  420. '#type' => 'fieldset',
  421. '#title' => t('Activity settings'),
  422. '#weight' => 7,
  423. '#collapsible' => TRUE,
  424. );
  425. $templates = heartbeat_user_templates_load($account->uid);
  426. // Privacy settings for streams.
  427. $form['heartbeat']['privacy'] = array('#tree' => TRUE);
  428. $form['heartbeat']['privacy']['default_template'] = array(
  429. '#type' => 'radios',
  430. '#title' => t("Privacy settings"),
  431. '#description' => t("This setting will apply to status updates to the profile when no access restriction is known (E.g. activity being logged from external sources)."),
  432. '#options' => _heartbeat_perms_options(),
  433. '#default_value' => isset($templates['0']) ? $templates['0']->status : HEARTBEAT_PRIVATE,
  434. );
  435. // Privacy settings on Heartbeat Templates.
  436. $form['heartbeat']['templates'] = array('#tree' => TRUE);
  437. foreach ($form_state['heartbeat_templates'] as $template_id => $description) {
  438. $template = heartbeat_message_template_load($template_id);
  439. $form['heartbeat']['templates'][$template_id] = array(
  440. '#type' => 'select',
  441. '#title' => $description,
  442. '#default_value' => isset($templates[$template_id]) ? $templates[$template_id]->status : HEARTBEAT_PUBLIC_TO_ALL,
  443. '#options' => _heartbeat_perms_options(TRUE, $template->perms),
  444. );
  445. }
  446. $hook = 'heartbeat_user_settings';
  447. foreach (module_implements($hook) as $module) {
  448. $function = $module .'_'. $hook;
  449. $function($form, $form_state);
  450. }
  451. $form['#submit'][] = 'heartbeat_user_templates_submit';
  452. }
  453. /**
  454. * Submit handler to save a users profile templates.
  455. */
  456. function heartbeat_user_templates_submit($form, $form_state) {
  457. if (!empty($form_state['values']['templates'])) {
  458. // Message templates for user will have the options:
  459. // HEARTBEAT_NONE, HEARTBEAT_PRIVATE, HEARTBEAT_PUBLIC_TO_ALL, HEARTBEAT_PUBLIC_TO_CONNECTED.
  460. db_delete('heartbeat_user_templates')
  461. ->condition('uid', $form['#user']->uid)
  462. ->execute();
  463. foreach ($form_state['values']['templates'] as $template_id => $permission) {
  464. db_insert('heartbeat_user_templates')
  465. ->fields(array('uid', 'message_id', 'status'), array($form['#user']->uid, $template_id, $permission))
  466. ->execute();
  467. }
  468. if (isset($form_state['values']['privacy']['default_template'])) {
  469. db_insert('heartbeat_user_templates')
  470. ->fields(array('uid', 'message_id', 'status'), array($form['#user']->uid, "0", $form_state['values']['privacy']['default_template']))
  471. ->execute();
  472. }
  473. }
  474. }
  475. /**
  476. * Helper function to load heartbeat user template settings.
  477. */
  478. function heartbeat_user_templates_load($uid) {
  479. $result = db_query("SELECT message_id, status FROM {heartbeat_user_templates} WHERE uid = :uid ", array(':uid' => $uid));
  480. $templates = array();
  481. foreach ($result as $row) {
  482. $templates[$row->message_id] = $row;
  483. }
  484. return $templates;
  485. }
  486. /**
  487. * Implements hook_ctools_plugin_api().
  488. */
  489. function heartbeat_ctools_plugin_api($owner, $api) {
  490. if ($owner == 'heartbeat' && $api == 'heartbeat') {
  491. return array('version' => 1);
  492. }
  493. }
  494. /**
  495. * Implementation of hook_views_api().
  496. */
  497. function heartbeat_views_api() {
  498. return array(
  499. 'api' => 3,
  500. 'path' => drupal_get_path('module', 'heartbeat'),
  501. );
  502. }
  503. /**
  504. * heartbeat_activity_view().
  505. *
  506. * @param String $message
  507. * The activity message object.
  508. */
  509. function heartbeat_activity_view($message, $view_mode = NULL) {
  510. if (isset($view_mode)) {
  511. $message->view_mode = $view_mode;
  512. }
  513. // Remove previously built content, if exists.
  514. $message->content = array();
  515. // Build fields content.
  516. field_attach_prepare_view('heartbeat_activity', array($message->uaid => $message), $message->view_mode, $message->language);
  517. entity_prepare_view('heartbeat_activity', array($message->uaid => $message), $message->language);
  518. $build = array(
  519. '#theme' => 'heartbeat_activity',
  520. '#heartbeat_activity' => $message,
  521. '#view_mode' => $message->view_mode,
  522. '#language' => $message->language,
  523. );
  524. $build += field_attach_view('heartbeat_activity', $message, $message->view_mode, $message->language);
  525. // Populate $message->content with a render() array.
  526. $hook = 'heartbeat_activity_view';
  527. foreach (module_implements($hook) as $module) {
  528. $function = $module . '_' . $hook;
  529. if (function_exists($function)) {
  530. $result = $function($message, $message->view_mode, $message->language);
  531. }
  532. }
  533. $build += $message->content;
  534. // We don't need duplicate rendering info in $message->content.
  535. unset($message->content);
  536. // Allow modules to modify the structured activity message.
  537. $type = 'heartbeat_activity';
  538. drupal_alter(array('heartbeat_activity_view', 'entity_view'), $build, $type);
  539. return $build;
  540. }
  541. /**
  542. * Implements hook_heartbeat_activity_view().
  543. *
  544. * @param HeartbeatActivity $heartbeatActivity
  545. * The activity message object.
  546. */
  547. function heartbeat_heartbeat_activity_view(HeartbeatActivity $heartbeatActivity, $view_mode = 'full', $language = NULL) {
  548. // Avatar.
  549. if (!empty($heartbeatActivity->actor->picture)) {
  550. if (is_numeric($heartbeatActivity->actor->picture)) {
  551. $uri = file_load($heartbeatActivity->actor->picture)->uri;
  552. }
  553. else {
  554. $uri = $heartbeatActivity->actor->picture->uri;
  555. }
  556. $heartbeatActivity->content['avatar'] = theme('heartbeat_activity_avatar', array('heartbeatactivity' => $heartbeatActivity, 'uri' => $uri));
  557. }
  558. // Default avatar.
  559. elseif (variable_get('user_picture_default', '')) {
  560. $heartbeatActivity->content['avatar'] = theme('heartbeat_activity_avatar', array('heartbeatactivity' => $heartbeatActivity, 'uri' => variable_get('user_picture_default', '')));
  561. }
  562. if ($heartbeatActivity->uid > 0 && $heartbeatActivity->actor) {
  563. $heartbeatActivity->content['username'] = array(
  564. '#markup' => theme('username', array('account' => $heartbeatActivity->actor)),
  565. );
  566. }
  567. // Activity message.
  568. $filter = new stdClass();
  569. $filter->settings = array('filter_url_length' => 60);
  570. $heartbeatActivity->content['message'] = array(
  571. '#attributes' => array('class' => array('activity-message')),
  572. '#title' => t('Heartbeat activity message'),
  573. '#markup' => _filter_url($heartbeatActivity->message, $filter),
  574. );
  575. // Timestamp of occurrence.
  576. $heartbeatActivity->content['time'] = array(
  577. '#title' => t('Activity on'),
  578. '#markup' => theme('heartbeat_time_ago', array('heartbeat_activity' => $heartbeatActivity)),
  579. );
  580. // Buttons for this message.
  581. $heartbeatActivity->content['buttons'] = array(
  582. '#markup' => theme('heartbeat_buttons', array('heartbeat_activity' => $heartbeatActivity)),
  583. );
  584. }
  585. /**
  586. * Process variables for heartbeat-activity.tpl.php.
  587. */
  588. function template_preprocess_heartbeat_activity(&$variables) {
  589. $variables['view_mode'] = $variables['elements']['#view_mode'];
  590. $variables['heartbeat_activity'] = $variables['elements']['#heartbeat_activity'];
  591. $message = $variables['heartbeat_activity'];
  592. $variables['content'] = array();
  593. // Prepare $content variable for template file.
  594. foreach (element_children($variables['elements']) as $key) {
  595. $variables['content'][$key] = $variables['elements'][$key];
  596. }
  597. $variables['classes_array'][] = $variables['zebra'];
  598. $variables['classes_array'][] = 'heartbeat-activity-' . $message->uaid;
  599. $variables['classes_array'][] = $message->message_id;
  600. $variables['attributes_array']['id'] = 'heartbeat-activity-' . $message->uaid;
  601. // Preprocess fields.
  602. field_attach_preprocess('heartbeat_activity', $message, $variables['elements'], $variables);
  603. }
  604. /**
  605. * Implements hook_image_default_styles().
  606. */
  607. function heartbeat_image_default_styles() {
  608. $styles = array();
  609. $styles['activity_avatar'] = array(
  610. 'effects' => array(
  611. array(
  612. 'name' => 'image_scale',
  613. 'data' => array('width' => 50, 'height' => 50, 'upscale' => 1),
  614. 'weight' => 0,
  615. ),
  616. )
  617. );
  618. return $styles;
  619. }
  620. /**
  621. * Implements hook_ds_layout_info().
  622. */
  623. function heartbeat_ds_layout_info() {
  624. $layouts = array(
  625. 'heartbeat_2col' => array(
  626. 'label' => t('Template with left/right for activity'),
  627. 'path' => drupal_get_path('module', 'heartbeat') . '/layouts/heartbeat_2col',
  628. 'regions' => array(
  629. 'heartbeat_left' => t('Left'),
  630. 'heartbeat_content' => t('Content'),
  631. 'heartbeat_footer' => t('Footer'),
  632. ),
  633. 'css' => TRUE,
  634. ),
  635. );
  636. return $layouts;
  637. }
  638. /**
  639. * Implements hook_modules_enabled($modules).
  640. */
  641. function heartbeat_modules_enabled($modules) {
  642. // Add the heartbeat in_group field if it does not exist yet.
  643. if (in_array('og', $modules) && db_table_exists('og')) {
  644. db_query("UPDATE {heartbeat_activity} SET in_group = 1 WHERE nid IN (SELECT DISTINCT etid FROM {og})");
  645. db_query("UPDATE {heartbeat_activity} SET in_group = 1 WHERE nid_target IN (SELECT DISTINCT etid FROM {og})");
  646. }
  647. }
  648. /**
  649. * Preprocess the primary theme implementation for a view.
  650. */
  651. function heartbeat_preprocess_views_view(&$vars) {
  652. $view = $vars['view'];
  653. if ($view->base_table == 'heartbeat_activity') {
  654. $vars['classes_array'][] = 'heartbeat-stream';
  655. $vars['classes_array'][] = 'heartbeat-messages-wrapper';
  656. $vars['classes_array'][] = 'heartbeat-stream-viewsactivity';
  657. }
  658. }
  659. /**
  660. * Heartbeat API functions.
  661. */
  662. /**
  663. * API function to retrieve the most active users.
  664. *
  665. * @param String $language
  666. * The language for the activity.
  667. * @param Integer $count
  668. * The count number / limit.
  669. */
  670. function heartbeat_api_most_active_users($view_mode, $count = 5, $language = NULL) {
  671. /*if (!isset($language)) {
  672. $language = $GLOBALS['language']->language;
  673. }*/
  674. $uids = array();
  675. $result = db_query_range("SELECT uid, COUNT(uaid) AS 'count' FROM {heartbeat_activity} WHERE uid > 0 GROUP BY uid ORDER BY count DESC ", 0, $count);
  676. foreach ($result as $row) {
  677. $uids[$row->uid] = $row->count;
  678. }
  679. $accounts = user_load_multiple(array_keys($uids));
  680. $users = array();
  681. foreach ($accounts as $account) {
  682. $users[$account->uid . '_' . $uids[$account->uid]] = user_view($account, $view_mode, $language);
  683. }
  684. return $users;
  685. }
  686. /**
  687. * API function to log a message from custom code
  688. *
  689. * @param string $message_id
  690. * Id of the message that is known in the message
  691. * @param integer $uid
  692. * Actor or user performing the activity
  693. * @param integer $uid_target [optional]
  694. * user id of the target user if present. Target users can be an addresse or a
  695. * user relation transaction with the actor $uid
  696. * @param integer $nid [optional]
  697. * Node id for content (for context node)
  698. * @param integer $nid_target [optional]
  699. * Node id for content that is related to other content
  700. * @param array $variables [optional]
  701. * Variables can be used if you used them in the used message. Take care to use
  702. * the @-sign for words that are prefix with the question mark sign in the messages
  703. * @param integer $access
  704. * The access to restrict the message
  705. */
  706. function heartbeat_api_log($message_id, $uid, $uid_target = 0, $nid = 0, $nid_target = 0, $variables = array(), $access = NULL, $time = 0, $in_group = 0) {
  707. $template = heartbeat_message_template_load($message_id);
  708. // Access can be given but usually we calculate it from Template Permissions
  709. // and overridable with the setting of the user.
  710. if (!isset($access) || !is_numeric($access)) {
  711. $access = _heartbeat_activity_get_access($uid, $template);
  712. }
  713. $data = array();
  714. // Normal form values
  715. $data['message_id'] = $message_id;
  716. $data['uid'] = $uid;
  717. $data['uid_target'] = $uid_target;
  718. $data['nid'] = $nid;
  719. $data['nid_target'] = $nid_target;
  720. $data['cid'] = isset($variables['cid']) ? $variables['cid'] : 0;
  721. $data['access'] = $access;
  722. $data['in_group'] = $in_group;
  723. $data['timestamp'] = $time == 0 ? $_SERVER['REQUEST_TIME'] : $time;
  724. $data['variables'] = $variables;
  725. return heartbeat_log($data);
  726. }
  727. /**
  728. * User activity logger function
  729. * @param The data to add one row
  730. */
  731. function heartbeat_log($data, $args = array()) {
  732. // Relational message of heartbeat messages
  733. $template = heartbeat_message_template_load($data['message_id']);
  734. $heartbeatactivity = new HeartbeatActivity($data, $template);
  735. // Prepare the fields.
  736. field_attach_presave('heartbeat_activity', $heartbeatactivity);
  737. module_invoke_all('heartbeat_activity_presave', $heartbeatactivity);
  738. // Save the record to the activity table.
  739. $saved = $heartbeatactivity->save($args);
  740. // Save fields.
  741. field_attach_insert("heartbeat_activity", $heartbeatactivity);
  742. // Invoke the heartbeat activity hooks.
  743. module_invoke_all("entity_insert", $heartbeatactivity, 'heartbeat_activity');
  744. module_invoke_all("heartbeat_activity_insert", $heartbeatactivity);
  745. return $saved;
  746. }
  747. /**
  748. * Returns a set of users related to a central user.
  749. */
  750. function heartbeat_related_uids($uid) {
  751. static $uids;
  752. if (!isset($uids[$uid])) {
  753. $uids[$uid] = array($uid => $uid);
  754. foreach (module_implements('heartbeat_related_uids') as $module) {
  755. $function = $module . '_heartbeat_related_uids';
  756. if (function_exists($function)) {
  757. $uids[$uid] += $function($uid);
  758. }
  759. }
  760. $uids[$uid] = array_unique($uids[$uid]);
  761. }
  762. return $uids[$uid];
  763. }
  764. /**
  765. * Function to load one activity message.
  766. */
  767. function heartbeat_activity_load($uaid) {
  768. return HeartbeatMessagePool::getInstance()->getMessage($uaid);
  769. }
  770. /**
  771. * Implements hook_forms().
  772. * All heartbeat template forms share the same form handler.
  773. */
  774. function heartbeat_forms($form_id, $args) {
  775. $forms = array();
  776. if (preg_match('/^heartbeat_activity_form_/', $form_id)) {
  777. $forms[$form_id] = array(
  778. 'callback' => 'heartbeat_activity_form',
  779. );
  780. }
  781. return $forms;
  782. }
  783. /**
  784. * Class to keep HeartbeatActivity messages in a pool so
  785. * plugins and such can get the message instead of reloading them.
  786. */
  787. class HeartbeatMessagePool {
  788. private static $instance = NULL;
  789. private $activity = array();
  790. /**
  791. * Constructor.
  792. */
  793. private function __construct() {
  794. }
  795. /**
  796. * getInstance().
  797. */
  798. public static function getInstance() {
  799. if (!isset(self::$instance)) {
  800. self::$instance = new HeartbeatMessagePool();
  801. }
  802. return self::$instance;
  803. }
  804. /**
  805. * getMessage().
  806. */
  807. public function getMessage($uaid) {
  808. if (!isset($this->activity[$uaid])) {
  809. $activity = _heartbeat_activity_load($uaid);
  810. if (!empty($activity)) {
  811. $this->addMessage($activity);
  812. return $this->activity[$uaid];
  813. }
  814. else {
  815. return NULL;
  816. }
  817. }
  818. else {
  819. return $this->activity[$uaid];
  820. }
  821. }
  822. /**
  823. * addMessage().
  824. */
  825. public function addMessage($heartbeatActivity) {
  826. if (isset($heartbeatActivity)) {
  827. if (!isset($this->activity[$heartbeatActivity->uaid])) {
  828. $this->activity[$heartbeatActivity->uaid] = $heartbeatActivity;
  829. }
  830. }
  831. }
  832. }
  833. /**
  834. * Function to load one activity message.
  835. */
  836. function _heartbeat_activity_load($uaid) {
  837. if (is_numeric($uaid)) {
  838. $activities = heartbeat_activity_load_multiple(array($uaid), array());
  839. return $activities ? $activities[$uaid] : $activities;
  840. }
  841. return FALSE;
  842. }
  843. /**
  844. * Load multiple activity records by user activity ID's.
  845. */
  846. function heartbeat_activity_load_multiple($uaids = array(), $conditions = array()) {
  847. $entities = entity_load('heartbeat_activity', $uaids, $conditions);
  848. $activities = array();
  849. foreach ($uaids as $uaid) {
  850. if (isset($entities[$uaid]) && $template = heartbeat_message_template_load($entities[$uaid]->message_id)) {
  851. $message = new HeartbeatActivity($entities[$uaid], $template);
  852. $message->count = 1;
  853. $activities[$uaid] = $message;
  854. HeartbeatMessagePool::getInstance()->addMessage($message);
  855. }
  856. }
  857. return $activities;
  858. }
  859. /**
  860. * Deletes a heartbeat activity messages.
  861. * @param Array $uaids
  862. * User activity IDs
  863. * @param Boolean $all
  864. * Indicates whether all activity should be deleted.
  865. */
  866. function heartbeat_activity_delete($uaids = array(), $all = FALSE) {
  867. // We don't delete all messages when not intended.
  868. if (empty($uaids) && $all == FALSE) {
  869. return;
  870. }
  871. $query = db_delete('heartbeat_activity');
  872. if (!empty($uaids) && $all == FALSE) {
  873. $query->condition('uaid', $uaids, 'IN');
  874. }
  875. $query->execute();
  876. //->where(" ha.message_id NOT IN (:messages) ", array(':messages' => $denied_messages));
  877. // Allow modules to respond to the deleting of a heartbeat activity message.
  878. module_invoke_all('heartbeat_activity_delete', $uaids, $all);
  879. }
  880. /**
  881. * Get the heartbeat template messages names.
  882. */
  883. function heartbeat_templates_names() {
  884. $names = array();
  885. ctools_include('export');
  886. foreach(ctools_export_crud_load_all('heartbeat_messages') as $template) {
  887. $names[$template->message_id] = $template->description;
  888. }
  889. return $names;
  890. }
  891. /**
  892. * Function to delete heartbeat message templates.
  893. * @param $id Int/String The target value to delete on
  894. * @param $type String The key field to perform delete query on
  895. * message : default
  896. * module : only defined by that module
  897. */
  898. function heartbeat_message_template_delete(HeartbeatMessageTemplate $template) {
  899. $template->delete();
  900. field_attach_delete_bundle('heartbeat_activity_template', $template->message_id);
  901. entity_get_controller('heartbeat_activity_template')->resetCache();
  902. cache_clear_all();
  903. }
  904. /**
  905. * Function to load heartbeat message templates.
  906. *
  907. * @param $id Int/String The target value to delete on
  908. * @param $type String The key field to perform delete query on
  909. * message : default
  910. * module : only defined by that module
  911. */
  912. function heartbeat_message_template_load($message_id) {
  913. ctools_include('export');
  914. return ctools_export_crud_load('heartbeat_messages', $message_id);
  915. }
  916. /**
  917. * Function to load the title of message template pages.
  918. */
  919. function heartbeat_message_id_title($template) {
  920. return $template->message_id;
  921. }
  922. /**
  923. * Set the default values for a heartbeat_template.
  924. *
  925. * The defaults are for a type defined through hook_heartbeat_template_info().
  926. * When populating a custom template $info should have the 'custom'
  927. * key set to 1.
  928. *
  929. * @param $info
  930. * An object or array containing values to override the defaults.
  931. *
  932. * @return
  933. * A heartbeat template object.
  934. */
  935. function heartbeat_template_set_defaults($info = array()) {
  936. $template = &drupal_static(__FUNCTION__);
  937. if (!isset($template)) {
  938. $template = new HeartbeatMessageTemplate();
  939. }
  940. $new_template = clone $template;
  941. $info = (array) $info;
  942. foreach ($info as $key => $data) {
  943. $new_template->$key = $data;
  944. }
  945. if (empty($new_template->module)) {
  946. $new_template->module = $new_template->base == 'heartbeat_content' ? 'heartbeat' : '';
  947. }
  948. $new_template->orig_type = isset($info['template']) ? $info['template'] : '';
  949. return $new_template;
  950. }
  951. /**
  952. * Query extender for heartbeat pager queries.
  953. *
  954. */
  955. class PagerActivity extends SelectQueryExtender {
  956. public $lastActivityId = 0;
  957. /**
  958. * The limit for this pager.
  959. */
  960. protected $limit = 0;
  961. public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
  962. parent::__construct($query, $connection);
  963. // Add pager tag. Do this here to ensure that it is always added before
  964. // preExecute() is called.
  965. $this->addTag('pager');
  966. }
  967. /**
  968. * Override the execute method.
  969. *
  970. * Before we run the query, we need to add pager-based range() instructions
  971. * to it.
  972. */
  973. public function execute() {
  974. // Add convenience tag to mark that this is an extended query. We have to
  975. // do this in the constructor to ensure that it is set before preExecute()
  976. // gets called.
  977. if (!$this->preExecute($this)) {
  978. return NULL;
  979. }
  980. // A NULL limit is the "kill switch" for pager queries.
  981. if (empty($this->limit)) {
  982. return;
  983. }
  984. //$total_items = $this->getCountQuery()->execute()->fetchField();
  985. //$current_page = pager_default_initialize($total_items, $this->limit, $this->element);
  986. $this->range(0, $this->limit);
  987. // Now that we've added our pager-based range instructions, run the query normally.
  988. return $this->query->execute();
  989. }
  990. /**
  991. * Sets the last uaid
  992. */
  993. public function setLastActivityId($lastActivityId) {
  994. $this->lastActivityId = $lastActivityId;
  995. $this->query->condition('ha.uaid', $this->lastActivityId, '>');
  996. }
  997. /**
  998. * Sets the offset timestamps.
  999. */
  1000. public function setOffsetTime($before, $after = 0) {
  1001. $this->query->condition('ha.timestamp', $before, '<');
  1002. if ($after > 0) {
  1003. $this->query->condition('ha.timestamp', $_SERVER['REQUEST_TIME'] - $after, '>');
  1004. }
  1005. }
  1006. /**
  1007. * Specify the maximum number of elements per page for this query.
  1008. *
  1009. * The default if not specified is 10 items per page.
  1010. *
  1011. * @param $limit
  1012. * An integer specifying the number of elements per page. If passed a false
  1013. * value (FALSE, 0, NULL), the pager is disabled.
  1014. */
  1015. public function limit($limit = 10) {
  1016. $this->limit = $limit;
  1017. return $this;
  1018. }
  1019. }
  1020. /**
  1021. * Class HeartbeatCtoolsObject
  1022. *
  1023. * Ctools abstract class to inherit base properties.
  1024. *
  1025. */
  1026. abstract class HeartbeatCtoolsObject {
  1027. // The API version that this object implements.
  1028. public $api_version = 1;
  1029. // A boolean for whether the object is disabled.
  1030. public $disabled = FALSE;
  1031. // For objects that live in code, the module which provides the default object.
  1032. public $export_module = '';
  1033. // A bitmask representation of an object current storage. You can use this bitmask
  1034. // in combination with the EXPORT_IN_CODE and EXPORT_IN_DATABASE constants to test
  1035. // for an object's storage in your code.
  1036. public $export_type = 0;
  1037. // A boolean for whether the object lives only in code.
  1038. public $in_code_only = FALSE;
  1039. // The schema API table that this object belongs to.
  1040. public $table = '';
  1041. // A string representing the storage type of this object. Can be one of the following:
  1042. // * Normal is an object that lives only in the database.
  1043. // * Overridden is an object that lives in the database and is overriding the exported
  1044. // configuration of a corresponding object in code.
  1045. // * Default is an object that lives only in code.
  1046. public $type = 'Overridden';
  1047. }
  1048. /**
  1049. * Theme functions and helpers.
  1050. */
  1051. /**
  1052. * The function for the avatar in a heartbeat activity message.
  1053. */
  1054. function theme_heartbeat_activity_avatar($variables) {
  1055. $filepath = $variables['uri'];
  1056. $alt = t("@user's picture", array('@user' => format_username($variables['heartbeatactivity']->actor)));
  1057. if (module_exists('image') && file_valid_uri($filepath)) {
  1058. $markup = theme('image_style', array(
  1059. 'style_name' => 'activity_avatar',
  1060. 'path' => $filepath,
  1061. 'alt' => $alt,
  1062. 'title' => $alt,
  1063. 'attributes' => array('class' => 'avatar'),
  1064. ));
  1065. }
  1066. else {
  1067. $markup = theme('image', array(
  1068. 'path' => $filepath,
  1069. 'alt' => $alt,
  1070. 'title' => $alt,
  1071. 'attributes' => array('class' => 'avatar'),
  1072. ));
  1073. }
  1074. return array('#markup' => $markup);
  1075. }
  1076. /**
  1077. * Theme function for a list of heartbeat activity messages.
  1078. */
  1079. function theme_heartbeat_list($variables) {
  1080. $heartbeatStream = $variables['stream'];
  1081. if (!$heartbeatStream || !$heartbeatStream->hasAccess()) {
  1082. return '';
  1083. }
  1084. global $user, $language;
  1085. $content = '';
  1086. $content .= $heartbeatStream->prefix;
  1087. if (!isset($heartbeatStream->config) || empty($heartbeatStream->config->class)) {
  1088. $content .= drupal_render($variables['content']);
  1089. }
  1090. else {
  1091. $content .= '<div id="heartbeat-stream-' . $heartbeatStream->config->class . '" class="heartbeat-' . ($heartbeatStream->isPage() ? 'page' : 'block') . ' heartbeat-stream heartbeat-stream-' . $heartbeatStream->config->class . '">';
  1092. $content .= '<div class="heartbeat-messages-wrapper">';
  1093. if (empty($heartbeatStream->messages)) {
  1094. $content .= '<p class="heartbeat-empty"><em>' . t('No activity yet.') . '</em></p>';
  1095. }
  1096. else {
  1097. $content .= drupal_render($variables['content']);
  1098. }
  1099. $content .= '</div></div>';
  1100. }
  1101. $content .= $heartbeatStream->suffix;
  1102. return $content;
  1103. }
  1104. /**
  1105. * Theme function for the timestamp of a message.
  1106. */
  1107. function theme_heartbeat_time_ago($variables) {
  1108. $message = $variables['heartbeat_activity'];
  1109. $time_info = '';
  1110. if ($message->show_message_times) {
  1111. $message_date = _theme_time_ago($message->timestamp);
  1112. if ($message->target_count <= 1 || $message->show_message_times_grouped) {
  1113. $time_info .= '<span class="heartbeat-time-ago">';
  1114. $time_info .= l($message_date, 'heartbeat/message/' . $message->uaid, array('html' => TRUE));
  1115. $time_info .= '</span>';
  1116. }
  1117. }
  1118. return $time_info;
  1119. }
  1120. /**
  1121. * Theme function for messages buttons.
  1122. *
  1123. * @param $variables
  1124. * Array of variables available for output.
  1125. */
  1126. function theme_heartbeat_buttons($variables) {
  1127. $output = '';
  1128. foreach($variables['heartbeat_activity']->buttons as $button) {
  1129. $output .= $button;
  1130. }
  1131. return $output;
  1132. }
  1133. /**
  1134. * Theme function for the user profile form.
  1135. *
  1136. * @param $variables
  1137. * Array of variables available for output.
  1138. */
  1139. function theme_heartbeat_message_user_select_form($variables) {
  1140. $form = $variables['form'];
  1141. $rows = array();
  1142. foreach (element_children($form) as $key) {
  1143. $row = array();
  1144. if (isset($form[$key]['title']) && is_array($form[$key]['title'])) {
  1145. $row[] = drupal_render($form[$key]['title']);
  1146. $row[] = drupal_render($form[$key]['access']);
  1147. }
  1148. $rows[] = $row;
  1149. }
  1150. $headers = array(t('Message types'), t('Operations'));
  1151. $output = theme('table', array('headers' => $headers, 'rows' => $rows));
  1152. return $output;
  1153. }
  1154. /**
  1155. * Helper theme function for the activity selection
  1156. * in the user profile form
  1157. */
  1158. function _theme_user_message_select_form($title, $settings) {
  1159. if (empty($settings)) {
  1160. $settings = array();
  1161. }
  1162. $templates = ctools_export_crud_load_all('heartbeat_messages');
  1163. $options = _heartbeat_perms_options(TRUE);
  1164. $form['heartbeat_activity_settings'] = array(
  1165. '#type' => 'fieldset',
  1166. '#title' => $title,
  1167. '#weight' => 4,
  1168. '#tree' => TRUE,
  1169. '#collapsible' => TRUE,
  1170. '#description' => t('This setting lets you configure the visibility of activity messages.'),
  1171. '#theme' => 'heartbeat_message_user_select_form',
  1172. );
  1173. foreach ($templates as $template) {
  1174. $form['heartbeat_activity_settings'][$template->message_id]['title'] = array(
  1175. '#value' => !empty($template->description) ? $template->description : str_replace('_', ' ', $template->message_id),
  1176. );
  1177. $form['heartbeat_activity_settings'][$template->message_id]['access'] = array(
  1178. '#type' => 'select',
  1179. '#options' => $options,
  1180. '#default_value' => isset($settings[$template->message_id]['access']) ? $settings[$template->message_id]['access'] : HEARTBEAT_PUBLIC_TO_ALL,
  1181. );
  1182. }
  1183. return $form;
  1184. }
  1185. /**
  1186. * Returns HTML for a query pager for heartbeat activity.
  1187. *
  1188. * @param $variables
  1189. * An associative array containing:
  1190. * - tags: An array of labels for the controls in the pager.
  1191. * - element: An optional integer to distinguish between multiple pagers on
  1192. * one page.
  1193. * - parameters: An associative array of query string parameters to append to
  1194. * the pager links.
  1195. * - quantity: The number of pages in the list.
  1196. *
  1197. * @ingroup themeable
  1198. */
  1199. function theme_activity_pager($variables) {
  1200. if ($variables['stream']->hasMoreMessages()) {
  1201. $last_message = end($variables['stream']->messages);
  1202. $link = heartbeat_stream_more_link($variables['stream'], $last_message->timestamp);
  1203. return $link;
  1204. }
  1205. return '';
  1206. }
  1207. /**
  1208. * Helper function for a more link on streams (older messages)
  1209. * Should only be called when hasMoreMessages resulted to TRUE
  1210. */
  1211. function heartbeat_stream_more_link(HeartbeatStream $heartbeatStream, $offset_time, $absolute = FALSE) {
  1212. $attributes = array(
  1213. 'html' => FALSE,
  1214. 'attributes' => array(
  1215. 'class' => array('heartbeat-older-messages')
  1216. )
  1217. );
  1218. $attributes['absolute'] = $absolute;
  1219. $content = '';
  1220. $content .= '<div class="heartbeat-more-messages-wrapper">';
  1221. // Override the viewer if possible.
  1222. $uid = $heartbeatStream->getViewedId();
  1223. $is_page = (int) $heartbeatStream->isPage();
  1224. // Ajax pager.
  1225. if ($heartbeatStream->isAjax()) {
  1226. $attributes['attributes']['onclick'] = 'javascript:Drupal.heartbeat.getOlderMessages(this, {ajax: true, stream_name: "' . $heartbeatStream->config->name . '" ,stream_class: "' . $heartbeatStream->config->class . '" ,offset_time: ' . $offset_time . ',page:' . $is_page . ', uid: '. $uid . ' }); return false;';
  1227. if (method_exists($heartbeatStream, 'getGroup')) {
  1228. $attributes['attributes']['class'][] = 'heartbeat-group-' . $heartbeatStream->getGroup()->nid;
  1229. }
  1230. $content .= l(t('Older messages'), 'heartbeat/js/older', $attributes);
  1231. $content .= '<span class="heartbeat-messages-throbber">&nbsp;</span>';
  1232. }
  1233. // Pager but no ajax.
  1234. elseif ($is_page && !empty($heartbeatStream->config->stream_path)) {
  1235. $attributes['query'] = array(
  1236. 'ajax' => FALSE,
  1237. 'stream_name' => $heartbeatStream->config->name,
  1238. 'stream_class' => $heartbeatStream->config->class,
  1239. 'offset_time' => $offset_time,
  1240. 'page' => $is_page,
  1241. 'uid' => $uid
  1242. );
  1243. $content .= l(t('Older messages'), $heartbeatStream->config->stream_path, $attributes);
  1244. $content .= '<span class="heartbeat-messages-throbber">&nbsp;</span>';
  1245. }
  1246. // Link to the pages.
  1247. if (!$heartbeatStream->isPage() && !empty($heartbeatStream->config->stream_path)
  1248. && (!$heartbeatStream->isAjax() || $heartbeatStream->config->block_show_pager == 3)) {
  1249. $path = $heartbeatStream->config->stream_path;
  1250. if (isset($attributes['attributes']['onclick'])) {
  1251. unset($attributes['attributes']['onclick']);
  1252. }
  1253. $fulllink = '<div class="more fullarchive heartbeat-full">' . l(t('Full list'), $path, $attributes) . '</div>';
  1254. $content .= $fulllink;
  1255. }
  1256. $content .= '</div>';
  1257. return $content;
  1258. }
  1259. /**
  1260. * helper functions.
  1261. */
  1262. /**
  1263. * Helper function to load the users from static cache.
  1264. * There should be something in core to handle this.
  1265. */
  1266. function _heartbeat_user_load($uid) {
  1267. static $users = array();
  1268. if (!isset($users[$uid])) {
  1269. $users[$uid] = user_load($uid);
  1270. }
  1271. return $users[$uid];
  1272. }
  1273. /**
  1274. * Helper function to prepare a custom CTools Modal window.
  1275. */
  1276. function heartbeat_ctools_modal_prepare() {
  1277. static $ran = FALSE;
  1278. if (!$ran) {
  1279. ctools_include('modal');
  1280. ctools_include('ajax');
  1281. // Add CTools' javascript to the page.
  1282. ctools_modal_add_js();
  1283. // Add the effects library.
  1284. drupal_add_library('system', 'effects.highlight');
  1285. drupal_add_library('system', 'effects.blind');
  1286. // Create our own javascript that will be used to theme a modal.
  1287. $style = array(
  1288. 'ctools-heartbeat-style' => array(
  1289. 'modalSize' => array(
  1290. 'type' => 'fixed',
  1291. 'width' => 500,
  1292. 'height' => 300,
  1293. 'addWidth' => 20,
  1294. 'addHeight' => 15,
  1295. ),
  1296. 'modalOptions' => array(
  1297. 'opacity' => .5,
  1298. 'background-color' => '#111',
  1299. ),
  1300. 'animation' => 'fadeIn',
  1301. 'modalTheme' => 'CToolsHeartbeatModal',
  1302. 'throbber' => theme('image', array('path' => drupal_get_path('module', 'heartbeat') . '/images/ajax-loader.gif', 'alt' => t('Loading...'), 'title' => t('Loading'))),
  1303. ),
  1304. );
  1305. drupal_add_js($style, 'setting');
  1306. $ran = TRUE;
  1307. }
  1308. }
  1309. /**
  1310. * Helper function to print JSON data.
  1311. */
  1312. function heartbeat_print_json($data) {
  1313. drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
  1314. print drupal_json_encode($data);
  1315. }
  1316. /**
  1317. * Decode heartbeat message variables
  1318. */
  1319. function heartbeat_decode_message_variables($string, $object = FALSE) {
  1320. if (!is_string($string)) {
  1321. return array();
  1322. }
  1323. // Variable string need to be cleared from spaces to decode properly
  1324. $array = explode("-|-", $string);
  1325. $variables = array();
  1326. if (!empty($array)) {
  1327. foreach ($array as $varvalue) {
  1328. $parts = explode("=|=", $varvalue);
  1329. if (isset($parts[0]) && !empty($parts[0])) {
  1330. if (preg_match("/\*\*\*/", $parts[1])) {
  1331. $parts[1] = explode("***", $parts[1]);
  1332. }
  1333. $variables[$parts[0]] = !is_array($parts[1]) ? (string)$parts[1] : $parts[1];
  1334. }
  1335. }
  1336. }
  1337. return $object ? (object) $variables : (array) $variables;
  1338. }
  1339. /**
  1340. * Encode heartbeat message variables
  1341. */
  1342. function heartbeat_encode_message_variables($array) {
  1343. $string = '';
  1344. foreach ($array as $key => $value) {
  1345. if (is_array($value)) {
  1346. $value = implode('***', $value);
  1347. }
  1348. $string .= $key .'=|='. $value .'-|-';
  1349. }
  1350. //$string = serialize((object)$array);
  1351. return $string;
  1352. }
  1353. /*
  1354. * Helper function to retrieve the allowed html tags.
  1355. */
  1356. function heartbeat_allowed_html_tags() {
  1357. $tags = variable_get('heartbeat_allowed_html_tags', 'a em strong blockquote ul ol li p div');
  1358. return explode(" ", $tags);
  1359. }
  1360. /**
  1361. * Helper function to map a array to dropdown
  1362. * with a field and value for the options
  1363. *
  1364. * @param array $options
  1365. * @param string target $field
  1366. * @param sring target $value
  1367. * @return array mapped for options dropdown
  1368. */
  1369. function _heartbeat_map_assoc($options, $field, $value) {
  1370. $mapped = array();
  1371. foreach ($options as $heartbeat_activity) {
  1372. $mapped[$heartbeat_activity->{$field}] = $heartbeat_activity->{$value};
  1373. }
  1374. return $mapped;
  1375. }
  1376. /**
  1377. * Returns the permission to log a Message based on the access
  1378. * of the Template or the User setting.
  1379. */
  1380. function _heartbeat_activity_get_access($uid, $template) {
  1381. $templates = heartbeat_user_templates_load($uid);
  1382. // If the user has configured access to this type, use it.
  1383. if (isset($templates[$template->message_id])) {
  1384. $access = $templates[$template->message_id]->status;
  1385. }
  1386. else {
  1387. // If the user configured general access.
  1388. if (isset($templates['0'])) {
  1389. $access = $templates['0']->status;
  1390. }
  1391. // Use the general permission for this template.
  1392. else {
  1393. $access = $template->perms;
  1394. }
  1395. }
  1396. return $access;
  1397. }
  1398. /**
  1399. * Helper function to check access on an Access type activity stream
  1400. */
  1401. function _heartbeat_message_has_access($heartbeatActivity) {
  1402. if (user_access('view Single activity stream') && $heartbeatActivity instanceof HeartbeatActivity) {
  1403. return $heartbeatActivity->hasAccess($GLOBALS['user']);
  1404. }
  1405. return FALSE;
  1406. }
  1407. /**
  1408. * Helper function to get the options for perm types
  1409. * @param boolean $profile indicator for personal or profile labels
  1410. * @return array of perm types
  1411. */
  1412. function _heartbeat_perms_options($profile = FALSE, $max_perm = HEARTBEAT_PUBLIC_TO_ALL) {
  1413. $permissions = array();
  1414. if ($profile) {
  1415. $perms = array(
  1416. HEARTBEAT_NONE => ('Never'),
  1417. HEARTBEAT_PRIVATE => t('Only me'),
  1418. HEARTBEAT_PUBLIC_TO_CONNECTED => t('Only my friends'),
  1419. HEARTBEAT_PUBLIC_TO_ALL => t('Everyone'),
  1420. );
  1421. }
  1422. else {
  1423. $perms = array(
  1424. HEARTBEAT_PRIVATE => t('Only the user himself is allowed to see this message'),
  1425. HEARTBEAT_PUBLIC_TO_ADDRESSEE => t('Only the user himself and the addressee are allowed to see this message'),
  1426. HEARTBEAT_PUBLIC_TO_CONNECTED => t('Only user and relations are allowed to see this message'),
  1427. HEARTBEAT_PUBLIC_TO_ALL => t('Everyone can see this message'),
  1428. );
  1429. }
  1430. foreach ($perms as $access => $desc) {
  1431. if ($access <= $max_perm) {
  1432. $permissions[$access] = $desc;
  1433. }
  1434. }
  1435. return $permissions;
  1436. }
  1437. /**
  1438. * Heartbeat typical time ago
  1439. * @return String with the time.
  1440. */
  1441. function _theme_time_ago($time) {
  1442. return t('@time ago', array('@time' => format_interval(($_SERVER['REQUEST_TIME'] - $time), 1))) ;
  1443. }
  1444. /**
  1445. * Helper function to check if a user has access to delete a message
  1446. */
  1447. function _heartbeat_message_delete_access($heartbeatActivity) {
  1448. if (user_access('admin heartbeat delete all')) {
  1449. return TRUE;
  1450. }
  1451. return $heartbeatActivity->uid == $GLOBALS['user']->uid && user_access('admin heartbeat delete own');
  1452. }
  1453. /**
  1454. * Heartbeat invalid crud data exception.
  1455. */
  1456. class InvalidHeartbeatCrudOperationException extends Exception {
  1457. }