| 1 |
<?php |
<?php |
| 2 |
// $Id: recent_changes.module,v 1.4 2007/03/07 03:20:03 roetzi Exp $ |
// $Id: recent_changes.module,v 1.1.2.5 2008/02/20 23:45:22 roetzi Exp $ |
| 3 |
|
|
| 4 |
/** |
/** |
| 5 |
* @file |
* @file |
| 9 |
/** |
/** |
| 10 |
* Implementation of hook_help(). |
* Implementation of hook_help(). |
| 11 |
*/ |
*/ |
| 12 |
function recent_changes_help($section) { |
function recent_changes_help($path, $arg) { |
| 13 |
switch ($section) { |
switch ($path) { |
| 14 |
case 'admin/help#recent_changes': |
case 'admin/help#recent_changes': |
| 15 |
$output = '<p>'.t('The recent changes module offers a page which shows all recent changes on nodes. The page can be viewed by anyone with the <em>view revisions</em> permission. If you have the <em>diff</em> module installed, a link to the diff-view will be offered in the table.').'</p>'; |
$output = '<p>'.t('The recent changes module offers a page which shows all recent changes on nodes. The page can be viewed by anyone with the <em>access content</em> permission, but no content is shown for node types the user has no permissions for. If you have the <em>diff</em> module installed, a link to the diff-view will be offered in the table. A feed of all recent changes, or per content type is also available. Headers of inaccessible content can be shown in feeds, depending on the settings in the recent changes administrative page.').'</p>'; |
| 16 |
return $output; |
return $output; |
| 17 |
break; |
break; |
| 18 |
} |
} |
| 21 |
/** |
/** |
| 22 |
* Implementation of hook_menu(). |
* Implementation of hook_menu(). |
| 23 |
*/ |
*/ |
| 24 |
function recent_changes_menu($may_cache) { |
function recent_changes_menu() { |
| 25 |
$items = array(); |
$items = array(); |
| 26 |
if ($may_cache) { |
$items['recent_changes'] = array( |
| 27 |
$items[] = array( |
'title' => t('Recent changes'), |
| 28 |
'title' => t('Recent changes'), |
'description' => t('List of recent changes'), |
| 29 |
'path' => 'recent_changes', |
'page callback' => 'recent_changes_view', |
| 30 |
'callback' => 'recent_changes_view', |
'access arguments' => array('access content'), |
| 31 |
'access' => user_access('view revisions'), |
'type' => MENU_NORMAL_ITEM, |
| 32 |
'type' => MENU_NORMAL_ITEM |
); |
| 33 |
); |
$items['admin/settings/recent_changes'] = array( |
| 34 |
} |
'title' => t('Recent changes'), |
| 35 |
|
'description' => t('Adjust the settings of the recent changes page and feed.'), |
| 36 |
|
'page callback' => 'drupal_get_form', |
| 37 |
|
'page arguments' => array('recent_changes_admin_settings'), |
| 38 |
|
'access arguments' => array('administer site configuration'), |
| 39 |
|
'type' => MENU_NORMAL_ITEM, |
| 40 |
|
); |
| 41 |
return $items; |
return $items; |
| 42 |
} |
} |
| 43 |
|
|
| 44 |
|
|
| 45 |
|
/** |
| 46 |
|
* Settings form |
| 47 |
|
*/ |
| 48 |
|
function recent_changes_admin_settings() { |
| 49 |
|
$form = array(); |
| 50 |
|
|
| 51 |
|
$form['general_settings'] = array( |
| 52 |
|
'#type' => 'fieldset', |
| 53 |
|
'#title' => t('Recent changes general settings'), |
| 54 |
|
'#collapsible' => TRUE, |
| 55 |
|
'#collapsed' => FALSE, |
| 56 |
|
'#weight' => -3 |
| 57 |
|
); |
| 58 |
|
$form['general_settings']['recent_changes_allowed_node_types'] = array( |
| 59 |
|
'#type' => 'select', |
| 60 |
|
'#title' => t('Allowed content types'), |
| 61 |
|
'#default_value' => variable_get('recent_changes_allowed_node_types', array()), |
| 62 |
|
'#options' => node_get_types('names'), |
| 63 |
|
'#multiple' => TRUE, |
| 64 |
|
'#description' => t('Select the content types that will be shown on the recent changes page and in recent changes feeds. Select none to list all content types. Note that nodes or comments that are inaccessible to a user will not be listed on his or her recent changes page, but headers of inaccessible nodes and comments might show up in recent changes feeds, depending on the settings below.'), |
| 65 |
|
); |
| 66 |
|
$form['general_settings']['recent_changes_show_comments']= array( |
| 67 |
|
'#type' => 'checkbox', |
| 68 |
|
'#title' => t('Show comments'), |
| 69 |
|
'#return_value' => 2, |
| 70 |
|
'#default_value' => variable_get('recent_changes_show_comments', TRUE), |
| 71 |
|
'#description' => t('Add a comments option to the content type filter on the recent changes page. To enable the selection of comments for a certain content type in the content type filter, you should enable multiple filter selection below.'), |
| 72 |
|
); |
| 73 |
|
$form['page_settings'] = array( |
| 74 |
|
'#type' => 'fieldset', |
| 75 |
|
'#title' => t('Recent changes page settings'), |
| 76 |
|
'#collapsible' => TRUE, |
| 77 |
|
'#collapsed' => FALSE, |
| 78 |
|
'#weight' => -2 |
| 79 |
|
); |
| 80 |
|
$form['page_settings']['recent_changes_entries_per_page']= array( |
| 81 |
|
'#type' => 'textfield', |
| 82 |
|
'#title' => t('Entries per page'), |
| 83 |
|
'#size' => 5, |
| 84 |
|
'#maxlength' => 4, |
| 85 |
|
'#default_value' => variable_get('recent_changes_entries_per_page', 50), |
| 86 |
|
'#description' => t('The number of entries per page'), |
| 87 |
|
); |
| 88 |
|
$form['page_settings']['recent_changes_show_filter']= array( |
| 89 |
|
'#type' => 'checkbox', |
| 90 |
|
'#title' => t('Show content type filter'), |
| 91 |
|
'#return_value' => 1, |
| 92 |
|
'#default_value' => variable_get('recent_changes_show_filter', TRUE), |
| 93 |
|
'#description' => t('Enable to show the content type filter on top of the recent changes page'), |
| 94 |
|
); |
| 95 |
|
$form['page_settings']['recent_changes_type_select_size'] = array( |
| 96 |
|
'#type' => 'select', |
| 97 |
|
'#title' => t('Number of visible content types in content type filter'), |
| 98 |
|
'#default_value' => variable_get('recent_changes_type_select_size', 5), |
| 99 |
|
'#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)), |
| 100 |
|
'#description' => t('The number of rows that will be displayed in the content type filter. Hint: select 1 in case multiple selection is disabled!'), |
| 101 |
|
); |
| 102 |
|
$form['page_settings']['recent_changes_allow_multiple_filter']= array( |
| 103 |
|
'#type' => 'checkbox', |
| 104 |
|
'#title' => t('Allow multiple content type selection'), |
| 105 |
|
'#return_value' => 1, |
| 106 |
|
'#default_value' => variable_get('recent_changes_allow_multiple_filter', TRUE), |
| 107 |
|
'#description' => t('Allow multiple selection in the content type filter.'), |
| 108 |
|
); |
| 109 |
|
$form['feed_settings'] = array( |
| 110 |
|
'#type' => 'fieldset', |
| 111 |
|
'#title' => t('Recent changes feed settings'), |
| 112 |
|
'#collapsible' => TRUE, |
| 113 |
|
'#collapsed' => FALSE, |
| 114 |
|
'#weight' => -1, |
| 115 |
|
'#description' => t('A feed is available for the recent items page. Links to feeds per content type are also available when a single content type is selected in the content type filter.'), |
| 116 |
|
); |
| 117 |
|
$form['feed_settings']['recent_changes_list_inacces_revisions_in_feeds']= array( |
| 118 |
|
'#type' => 'checkbox', |
| 119 |
|
'#title' => t('List inaccessible revisions'), |
| 120 |
|
'#return_value' => 1, |
| 121 |
|
'#default_value' => variable_get('recent_changes_list_inacces_revisions_in_feeds', TRUE), |
| 122 |
|
'#description' => t('Usually no login takes place when the recent changes feed is accessed, which means that older revisions might not be accessible. Enable this option to list revisions in feeds even if the \'view revisions\' permission is disabled for anonymous users. Note that titles of inaccessible content can be replaced by \'access denied\' messages by disabling the checkbox below.'), |
| 123 |
|
); |
| 124 |
|
$form['feed_settings']['recent_changes_list_inacces_nodes_in_feeds']= array( |
| 125 |
|
'#type' => 'checkbox', |
| 126 |
|
'#title' => t('List inaccessible nodes'), |
| 127 |
|
'#return_value' => 1, |
| 128 |
|
'#default_value' => variable_get('recent_changes_list_inacces_nodes_in_feeds', TRUE), |
| 129 |
|
'#description' => t('Usually no login takes place when the recent changes feed is accessed, which means certain content types might be inaccessible. Enable this option to list inaccessible nodes in feeds. Note that titles of inaccessible content can be replaced by \'access denied\' messages by disabling the checkbox below.'), |
| 130 |
|
); |
| 131 |
|
$form['feed_settings']['recent_changes_show_access_denied_titles']= array( |
| 132 |
|
'#type' => 'checkbox', |
| 133 |
|
'#title' => t('Show titles of inaccessible content'), |
| 134 |
|
'#return_value' => 1, |
| 135 |
|
'#default_value' => variable_get('recent_changes_show_access_denied_titles', TRUE), |
| 136 |
|
'#description' => t('Usually no login takes place when the recent changes feed is accessed, which means certain nodes, comments or revisions migth be inaccessible. Enable this option to show the titles of inaccessible nodes. Note that the content of inaccessible nodes will never be displayed in feeds.'), |
| 137 |
|
); |
| 138 |
|
$form['feed_settings']['recent_changes_show_diff']= array( |
| 139 |
|
'#type' => 'checkbox', |
| 140 |
|
'#title' => t('Show diffs in feeds'), |
| 141 |
|
'#return_value' => 1, |
| 142 |
|
'#default_value' => variable_get('recent_changes_show_diffs', TRUE), |
| 143 |
|
'#description' => t('When the diff module is installed and the length of XML items (see below) is set to \'Full text\', the difference between a revision of a node and its predecessor can be shown. Check this box to enable this feature.'), |
| 144 |
|
); |
| 145 |
|
$form['feed_settings']['recent_changes_feed_default_items'] = array( |
| 146 |
|
'#type' => 'select', |
| 147 |
|
'#title' => t('Number of items per feed'), |
| 148 |
|
'#default_value' => variable_get('recent_changes_feed_default_items', 10), |
| 149 |
|
'#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)), |
| 150 |
|
'#description' => t('The default number of items to include in the recent changes feed.'), |
| 151 |
|
); |
| 152 |
|
$form['feed_settings']['recent_changes_feed_item_type']= array( |
| 153 |
|
'#type' => 'select', |
| 154 |
|
'#title' => t('Display of XML feed items'), |
| 155 |
|
'#default_value' => variable_get('recent_changes_feed_item_type', 'teaser'), |
| 156 |
|
'#options' => array('title' => 'Titles only', 'teaser' => 'Titles plus teaser', 'fulltext' => 'Full text'), |
| 157 |
|
'#multiple' => FALSE, |
| 158 |
|
'#description' => t('The length of XML items in the recent changes feed.'), |
| 159 |
|
); |
| 160 |
|
|
| 161 |
|
return system_settings_form($form); |
| 162 |
|
} |
| 163 |
|
|
| 164 |
|
|
| 165 |
/** |
/** |
| 166 |
* Menu callback which displays a list of recent changes. |
* Menu callback. |
| 167 |
*/ |
*/ |
| 168 |
function recent_changes_view() { |
function recent_changes_view() { |
| 169 |
// Print feed if requested or display normal page otherwise |
// Print feed if requested or display normal page otherwise |
| 170 |
if (arg(1) == 'feed') { |
if (arg(1) === 'feed') { |
| 171 |
return recent_changes_feed(arg(2)); |
return recent_changes_feed(arg(2)); |
| 172 |
} |
} |
| 173 |
else { |
else { |
| 174 |
return recent_changes_page(); |
return recent_changes_page(); |
| 175 |
} |
} |
| 176 |
} |
} |
| 177 |
|
|
| 178 |
|
|
| 179 |
/** |
/** |
| 180 |
* Page display of recent changes. |
* Page display of recent changes. |
| 181 |
*/ |
*/ |
| 182 |
function recent_changes_page() { |
function recent_changes_page() { |
| 183 |
$output = ''; |
$node_types = ($_REQUEST['op'] === t('Filter')) ? _recent_changes_parse_node_types($_REQUEST['recent_changes_filter_node_types']) : _recent_changes_parse_node_types(); |
|
|
|
|
// Add stylesheet for page. |
|
|
drupal_add_css(drupal_get_path('module', 'recent_changes').'/recent_changes.css'); |
|
| 184 |
|
|
| 185 |
// Show filter if enabled |
// Unset the page in pager query if the filter button was pressed. |
| 186 |
if (recent_changes_show_filter()) { |
if ($_POST['op'] === t('Filter')) { |
| 187 |
$output .= drupal_get_form('recent_changes_filter_form'); |
unset($_GET['page']); |
| 188 |
} |
} |
| 189 |
|
|
| 190 |
|
// Initialize |
| 191 |
|
$output = ''; |
| 192 |
|
$day = -1; |
| 193 |
|
$rows = array(); |
| 194 |
|
|
| 195 |
if ($_REQUEST['op'] == t('Filter')) { |
// Add rss feed and stylesheet to page. |
| 196 |
// Use submitted values if filter form was used. |
drupal_add_css(drupal_get_path('module', 'recent_changes').'/recent_changes.css'); |
|
$show_comments = check_plain($_REQUEST['recent_changes_show_comments']); |
|
|
$node_type = check_plain($_REQUEST['recent_changes_show_node_type']); |
|
|
} |
|
|
else { |
|
|
// Default values |
|
|
$show_comments = recent_changes_show_comments(); |
|
|
$node_type = ''; |
|
|
} |
|
| 197 |
|
|
| 198 |
// Add feed |
// Add feed |
| 199 |
if ($node_type) { |
if (count($node_types) == 1) { |
| 200 |
// Filter by node type is active |
// Single node type was selected in filter |
| 201 |
drupal_add_feed(url('recent_changes/feed/' . $node_type, null, null, true), t('recent changes of !site filtered by node type !node_type', array('!site' => variable_get('site_name', 'drupal'), '!node_type' => $node_type))); |
drupal_add_feed(url('recent_changes/feed/' . $node_types[0], array('absolute' => TRUE)), t('Recent changes of !site filtered by content type !node_type', array('!site' => variable_get('site_name', 'drupal'), '!node_type' => $node_type_name = _recent_changes_node_type_name($node_types[0])))); |
| 202 |
} |
} |
| 203 |
else { |
else { |
| 204 |
// No filtering |
drupal_add_feed(url('recent_changes/feed', array('absolute' => TRUE)), t('Recent changes of !site', array('!site' => variable_get('site_name', 'drupal')))); |
|
drupal_add_feed(url('recent_changes/feed', null, null, true), t('recent changes of !site', array('!site' => variable_get('site_name', 'drupal')))); |
|
| 205 |
} |
} |
| 206 |
|
|
| 207 |
|
// Show filter if enabled |
| 208 |
|
$output .= (variable_get('recent_changes_show_filter', TRUE)) ? drupal_get_form('recent_changes_filter_form') : ''; |
| 209 |
|
|
| 210 |
// Header for recent changes table. |
// Header for recent changes table. |
| 211 |
$header = array( |
$header = array('', '', t('Type'), t('Title'), t('User'), t('Log')); |
|
'', // operations |
|
|
'', // time |
|
|
t('Type'), |
|
|
t('Title'), |
|
|
t('User'), |
|
|
t('Log'), |
|
|
); |
|
|
$rows = array(); |
|
|
|
|
|
// Build SQL query depending on node type filter and comments filter. |
|
|
|
|
|
// Query to get all revisions of the selected node types |
|
|
$revisions_query = recent_changes_revisions_query($node_type); |
|
|
if (module_exists('comment') && $show_comments) { |
|
|
// Query to get all comments of the selected node types |
|
|
$comments_query = recent_changes_comments_query($node_type); |
|
|
// Final query as a combination of node revisions and comments |
|
|
$sql = "($revisions_query) UNION ALL ($comments_query) ORDER BY timestamp DESC"; |
|
|
if ($node_type) { |
|
|
// Number of total nodes and comments if a specific node type is selected |
|
|
$count_sql = "SELECT (SELECT COUNT(*) FROM {node_revisions} r JOIN {node} n ON r.nid = n.nid WHERE n.type = '" . check_plain($node_type) . "') + (SELECT COUNT(*) FROM {comments} c LEFT JOIN {node} n ON c.nid = n.nid WHERE n.type = '" . check_plain($node_type) . "')"; |
|
|
} |
|
|
else { |
|
|
// Number of total nodes and comments if all node types are shown |
|
|
$count_sql = "SELECT (SELECT COUNT(*) FROM {node_revisions}) + (SELECT COUNT(*) FROM {comments})"; |
|
|
} |
|
|
// Check MySQL version |
|
|
$version = db_version(); |
|
|
if ($GLOBALS['db_type'] == 'mysql' && version_compare($version, '4.1.0') < 0) { |
|
|
// MySQL before 4.1.0 cannot execute the subqueries. Just ignore comments for total count. |
|
|
if ($node_type) { |
|
|
// Number of total node revisions if a specific node type is selected |
|
|
$count_sql = "SELECT COUNT(*) FROM {node_revisions} r JOIN {node} n ON r.nid = n.nid WHERE n.type = '" . check_plain($node_type) . "'"; |
|
|
} |
|
|
else { |
|
|
// Number of total node revisions if all node types are shown |
|
|
$count_sql = "SELECT COUNT(*) FROM {node_revisions}"; |
|
|
} |
|
|
} |
|
|
} |
|
|
else { |
|
|
// Query to select all node revisions |
|
|
$sql = "$revisions_query ORDER BY timestamp DESC"; |
|
|
if ($node_type) { |
|
|
// Number of total node revisions if a specific node type is selected |
|
|
$count_sql = "SELECT COUNT(*) FROM {node_revisions} r JOIN {node} n ON r.nid = n.nid WHERE n.type = '" . check_plain($node_type) . "'"; |
|
|
} |
|
|
else { |
|
|
// Number of total node revisions if all node types are shown |
|
|
$count_sql = "SELECT COUNT(*) FROM {node_revisions}"; |
|
|
} |
|
|
} |
|
|
|
|
|
// SQL result provided by pager implementation |
|
|
$result = pager_query($sql, recent_changes_count_per_page(), 0, $count_sql); |
|
| 212 |
|
|
| 213 |
$day = -1; |
// SQL results provided by pager implementation |
| 214 |
$has_revisions = array(); |
list($sql, $count_sql) = _recent_changes_query($node_types, user_access('view revisions')); |
| 215 |
// List all results |
$sql_results = pager_query($sql, variable_get('recent_changes_entries_per_page', 50), 0, $count_sql); |
| 216 |
while ($change = db_fetch_object($result)) { |
|
| 217 |
|
// put database results in $results |
| 218 |
|
$results = array(); |
| 219 |
|
while ($db_item = db_fetch_object($sql_results)) { |
| 220 |
|
$results[] = $db_item; |
| 221 |
|
} |
| 222 |
|
|
| 223 |
|
// parse results into items |
| 224 |
|
$items = _recent_changes_parse_results($results); |
| 225 |
|
|
| 226 |
|
foreach ($items as $item) { |
| 227 |
// Check if day of week changed and if yes output the new day. |
// Check if day of week changed and if yes output the new day. |
| 228 |
$current_day = format_date($change->timestamp, 'custom', 'z'); |
$current_day = format_date($item->timestamp, 'custom', 'z'); |
| 229 |
if ($day != $current_day) { |
if ($day != $current_day) { |
| 230 |
$day = $current_day; |
$day = $current_day; |
| 231 |
$rows[] = array( |
$rows[] = array( |
| 232 |
array( |
array( |
| 233 |
'data' => format_date($change->timestamp, 'custom', 'D, j F Y'), |
'data' => format_date($item->timestamp, 'custom', 'D, j F Y'), |
| 234 |
'colspan' => '6', |
'colspan' => '6', |
| 235 |
'class' => 'date' |
'class' => 'date' |
| 236 |
) |
) |
| 237 |
); |
); |
| 238 |
} |
} |
|
// Comments have a non-zero 'cid', node revisions a non-zero 'vid'. Both have a 'nid' |
|
|
$is_comment = ($change->cid != 0); |
|
|
if ($is_comment) { |
|
|
// Format the row if the change is a comment |
|
|
$rows[] = array( |
|
|
// Link to comment |
|
|
l('(comment)', "node/$change->nid", null, null, "comment-$change->cid"), |
|
|
// Time of submit |
|
|
format_date($change->timestamp, 'custom', 'H:i'), |
|
|
// Node type of comment's node |
|
|
$change->type, |
|
|
// Title of comment's node |
|
|
l($change->title, "node/$change->nid"), |
|
|
// User which submitted comment |
|
|
theme('username', $change), |
|
|
// Title of comment |
|
|
filter_xss($change->log), |
|
|
); |
|
|
} else { |
|
|
// Format the row if the change is a node revision |
|
|
|
|
|
// Check if that node has any revisions at all and cache the result. |
|
|
if (!isset($has_revisions[$change->nid])) { |
|
|
$has_revisions[$change->nid] = db_result(db_query('SELECT COUNT(*) FROM {node_revisions} WHERE nid=%d', $change->nid)) > 1; |
|
|
} |
|
|
|
|
|
// Get the version and title of the previous revision |
|
|
$old_node = db_fetch_object(db_query('SELECT vid, title FROM {node_revisions} WHERE nid=%d AND vid<%d ORDER BY vid DESC LIMIT 1', $change->nid, $change->vid)); |
|
|
|
|
|
$operations = ''; |
|
|
// Add a link to the diff between the current version and the one before that. |
|
|
if (module_exists('diff')) { |
|
|
if ($old_node->vid) { |
|
|
$operations .= l('(diff)', "node/$change->nid/revisions/view/$old_node->vid/$change->vid").' '; |
|
|
} |
|
|
else { |
|
|
$operations = '(diff) '; |
|
|
} |
|
|
} |
|
|
// Add a link to the revisions history of the node. |
|
|
if ($has_revisions[$change->nid]) { |
|
|
$operations .= l('(hist)', "node/$change->nid/revisions"); |
|
|
} |
|
|
else { |
|
|
$operations .= '(hist)'; |
|
|
} |
|
| 239 |
|
|
| 240 |
$special = ''; |
// Add the items |
| 241 |
if ($old_node->vid == 0) { |
$rows[] = array( |
| 242 |
// The node is new. |
$item->op_link, |
| 243 |
$special = '<span class="change-flag">new</span> '; |
format_date($item->timestamp, 'custom', 'H:i'), |
| 244 |
} |
$item->node_type_name, |
| 245 |
elseif ($old_node->title != $change->title) { |
'<span class="change-flag">' . $item->flag . '</span> ' . call_user_func_array('l', array_merge(array($item->item_title), $item->link_array)) . (($item->item_title_add) ? '<br>'. $item->item_title_add : ''), |
| 246 |
// The node title changed. |
$item->user_link, |
| 247 |
$special = '<span class="change-flag">moved</span> '; |
$item->log_text, |
| 248 |
} |
); |
|
$rows[] = array( |
|
|
// Link to diff and history |
|
|
$operations, |
|
|
// Submit time |
|
|
format_date($change->timestamp, 'custom', 'H:i'), |
|
|
// Node type |
|
|
$change->type, |
|
|
// Title of node |
|
|
$special . l($change->title, "node/$change->nid"), |
|
|
// User which submitted node |
|
|
theme('username', $change), |
|
|
// Log message of revision |
|
|
filter_xss($change->log), |
|
|
); |
|
|
} |
|
| 249 |
} |
} |
| 250 |
|
|
| 251 |
// Print table and pager |
// Print table and pager |
| 252 |
$output .= theme('table', $header, $rows, array('class' => 'recent-changes')); |
$output .= theme('table', $header, $rows, array('class' => 'recent-changes')); |
| 253 |
$output .= theme('pager', NULL, recent_changes_count_per_page, 0); |
$output .= theme('pager', NULL, variable_get('recent_changes_entries_per_page', 50), 0); |
| 254 |
|
return($output); |
|
return $output; |
|
| 255 |
} |
} |
| 256 |
|
|
|
/** |
|
|
* Form for input filter. |
|
|
*/ |
|
|
function recent_changes_filter_form() { |
|
|
$form = array(); |
|
|
$form['#attributes'] = array('class' => 'inline'); |
|
|
// Enable/Disable comments in recent changes view |
|
|
if (module_exists('comment')) { |
|
|
$form['recent_changes_show_comments'] = array( |
|
|
'#type' => 'checkbox', |
|
|
'#title' => t('Show comments'), |
|
|
'#default_value' => isset($_REQUEST['form_id']) ? isset($_REQUEST['recent_changes_show_comments']) : recent_changes_show_comments(), |
|
|
); |
|
|
} |
|
|
// Filter node type of recent changes view |
|
|
$node_types = array('' => 'all node types'); |
|
|
$node_types = array_merge($node_types, node_get_types('names')); |
|
|
$form['recent_changes_show_node_type'] = array( |
|
|
'#type' => 'select', |
|
|
'#default_value' => isset($_REQUEST['recent_changes_show_node_type']) ? $_REQUEST['recent_changes_show_node_type'] : '', |
|
|
'#options' => $node_types, |
|
|
); |
|
|
$form['submit'] = array( |
|
|
'#type' => 'button', |
|
|
'#value' => t('Filter') |
|
|
); |
|
|
return $form; |
|
|
} |
|
| 257 |
|
|
| 258 |
/** |
/** |
| 259 |
* RSS feed of recent changes. |
* RSS feed of recent changes. |
| 260 |
*/ |
*/ |
| 261 |
function recent_changes_feed($node_type = NULL) { |
function recent_changes_feed($node_types = NULL) { |
| 262 |
global $base_url; |
global $base_url; |
| 263 |
|
|
| 264 |
// Query to get all revisions of the selected node types |
$list_inaccess_content = variable_get('recent_changes_list_inacces_nodes_in_feeds', TRUE); |
| 265 |
$revisions_query = recent_changes_revisions_query($node_type); |
$node_types = ($list_inaccess_content) ? _recent_changes_array_node_types($node_types) : _recent_changes_parse_node_types($node_types); |
| 266 |
if (module_exists('comment')) { |
$show_access_denied_titles = variable_get('recent_changes_show_access_denied_titles', TRUE); |
| 267 |
// Query to get all comments of the selected node types |
$item_type = variable_get('recent_changes_feed_item_type', 'teaser'); |
| 268 |
$comments_query = recent_changes_comments_query($node_type); |
$teaser = ($item_type == 'teaser'); |
| 269 |
// Final query as a combination of node revisions and comments |
$load_revisions = user_access('view revisions') || variable_get('recent_changes_list_inacces_revisions_in_feeds', TRUE); |
| 270 |
$sql = "($revisions_query) UNION ALL ($comments_query) ORDER BY timestamp DESC LIMIT %d"; |
|
| 271 |
|
// perform sql query |
| 272 |
|
list($sql, $count_sql) = _recent_changes_query($node_types, $load_revisions); |
| 273 |
|
$sql_results = db_query($sql . " LIMIT %d", variable_get('recent_changes_feed_default_items', 10)); |
| 274 |
|
|
| 275 |
|
// put database results in $results |
| 276 |
|
$results = array(); |
| 277 |
|
while ($db_item = db_fetch_object($sql_results)) { |
| 278 |
|
$results[] = $db_item; |
| 279 |
} |
} |
|
else { |
|
|
$sql = "$revisions_query ORDER BY timestamp DESC LIMIT %d"; |
|
|
} |
|
|
$result = db_query($sql, variable_get('feed_default_items', 10)); |
|
| 280 |
|
|
| 281 |
// Type of RSS feed: 'title', 'teaser', 'full' |
// parse results into items |
| 282 |
$item_length = variable_get('feed_item_length', 'teaser'); |
$items = _recent_changes_parse_results($results); |
| 283 |
// Is node teaser displayed (only used if $item_length not 'title') |
|
| 284 |
$teaser = ($item_length == 'teaser') ? true : false; |
// initialize |
|
// RSS namespace used |
|
| 285 |
$namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"'); |
$namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"'); |
| 286 |
// RSS text |
$feed_output = ''; |
| 287 |
$items = ''; |
|
| 288 |
while ($item = db_fetch_object($result)) { |
foreach ($items as $item) { |
|
// Comments have a non-zero 'cid', node revisions a non-zero 'vid'. Both have a 'nid' |
|
| 289 |
$is_comment = ($item->cid != 0); |
$is_comment = ($item->cid != 0); |
| 290 |
if ($is_comment) { |
|
| 291 |
// Format comment for RSS: Comment title as item title, comment body as text |
$text = ''; |
| 292 |
$comment = db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $item->cid)); |
$url = call_user_func_array('url', array_merge($item->url_array, array(TRUE))); |
| 293 |
|
$title = '(' . $item->flag . ') ' . $item->node_type_name . ': ' . $item->item_title; |
| 294 |
// RSS Title: title of comment with comment mark |
$extra = array(); |
| 295 |
$title = t('Comment') . ': ' . $comment->subject; |
|
| 296 |
// RSS Link: Direct link to comment |
// load node to check node access, and check access if the loaded node is an older revision |
| 297 |
$link = url("node/$comment->nid", null, "comment-$comment->cid", 1); |
$node = node_load($item->nid, $item->vid); |
| 298 |
// Node title comment belongs to (used in feed body) |
$node_access = node_access('view', $node) && ($item->is_current_revision || user_access('view revisions')); |
| 299 |
$node_title = l($item->title,"node/$item->nid", null, null, null, 1); |
|
| 300 |
// User which submitted comment (used in feed body) |
$prev_revision = ($item->prev_revision) ? node_load($item->nid, $item->prev_revision->vid) : NULL; |
| 301 |
$username = ($comment->uid ? l($comment->name, "user/$comment->uid", null, null, null, 1) : $comment->name); |
$prev_revision_access = ($prev_revision) ? (node_access('view', $prev_revision) && user_access('view revisions')) : FALSE; |
| 302 |
// RSS body: has meta info about comment |
|
| 303 |
$item_text = '<p><em>' . t('Comment on !node_title from !user', array('!node_title' => $node_title, '!user' => $username)) . '</em></p><hr/>'; |
$load_diff = module_exists('diff') && variable_get('recent_changes_show_diff', TRUE) && !$is_comment && $prev_revision_access && ($item_type == 'fulltext'); |
| 304 |
if ($item_length != 'title') { |
$load_node_text = $node_access && ($item_type != 'title') && !$load_diff; |
| 305 |
// If not just titles are in RSS feeds, put the comment in RSS body |
|
| 306 |
$item_text .= check_markup($comment->comment, $comment->format); |
// load node or comment body text |
| 307 |
} |
if ($load_node_text) { |
| 308 |
// No extras here (like attachments or taxonomy) |
if ($is_comment) { |
| 309 |
$extra = array(); |
$body_text = $item->comment_text; |
| 310 |
} |
$teaser_text = node_teaser($body_text, $node->format); |
| 311 |
else { |
$node->body = check_markup($body_text, $node->format, FALSE); |
| 312 |
// Format comment for RSS |
$node->teaser = check_markup($teaser_text, $node->format, FALSE); |
| 313 |
$node = node_load($item->nid, $item->vid); |
$node->readmore = ($teaser && (strlen($teaser_text) < strlen($body_text))); |
|
// Select the older revision if available. Set flag accordingly |
|
|
$old_entry = db_fetch_object(db_query('SELECT vid, timestamp FROM {node_revisions} WHERE nid = %d AND vid < %d ORDER BY vid DESC LIMIT 1', $item->nid, $item->vid)); |
|
|
if ($old_entry->vid) { |
|
|
$old_node = node_load($node->nid, $old_entry->vid); |
|
|
if ($old_node->title != $node->title) { |
|
|
$flag = t('Moved'); |
|
|
} |
|
|
else { |
|
|
$flag = t('Edited'); |
|
|
} |
|
| 314 |
} |
} |
| 315 |
else { |
else { |
| 316 |
$flag = t('New'); |
$node = (node_hook($node, 'view')) ? node_invoke($node, 'view', $teaser, FALSE) : node_prepare($node, $teaser); |
| 317 |
|
node_invoke_nodeapi($node, 'view', $teaser, FALSE); |
| 318 |
|
$extra = node_invoke_nodeapi($node, 'rss item'); |
| 319 |
} |
} |
| 320 |
|
} |
| 321 |
|
|
| 322 |
// RSS title |
// load diff |
| 323 |
$title = $flag . ' ' . $node->type . ': ' . $node->title; |
if($load_diff) { |
| 324 |
// RSS link: link to node |
$node->body = _recent_changes_get_diff($node, $prev_revision); |
| 325 |
$link = url("node/$node->nid", null, null, 1); |
} |
|
|
|
|
// RSS body |
|
|
$item_text = ''; |
|
|
if ($old_entry->vid && module_exists('diff')) { |
|
|
// If diff module exists, put a diff in the RSS feed |
|
|
$rows = array(); |
|
|
$header = array( |
|
|
'', |
|
|
t('Revision of %date', array('%date' => format_date($old_entry->timestamp))), |
|
|
'', |
|
|
t('Revision of %date', array('%date' => format_date($item->timestamp))) |
|
|
); |
|
|
$rows = _diff_body_rows($old_node, $node); |
|
|
$item_text .= theme('table', $header, $rows, array('style' => 'width:100%')); |
|
|
// Replace css classes from diff with actual style elements since we cannot reference |
|
|
// a css file from the rss feed. |
|
|
$patterns = array( |
|
|
'/class=["\']diff-section-title["\']/', |
|
|
'/class=["\']diff-deletedline["\']/', |
|
|
'/class=["\']diff-addedline["\']/', |
|
|
'/class=["\']diff-context["\']/', |
|
|
'/class=["\']diffchange["\']/', |
|
|
); |
|
|
$replacements = array( |
|
|
'style="background-color: #f0f0ff;"', |
|
|
'style="background-color: #ffa;width: 50%"', |
|
|
'style="background-color: #afa;width: 50%"', |
|
|
'style="background-color: #fafafa"', |
|
|
'style="color: #f00;font-weight:bold"', |
|
|
); |
|
|
$item_text = preg_replace($patterns, $replacements, $item_text); |
|
|
$item_text .= "<hr/>\n"; |
|
|
} |
|
| 326 |
|
|
| 327 |
if ($item_length != 'title') { |
// check access for content and title |
| 328 |
// Load node to display in feed |
if (!$node_access) { |
| 329 |
if (node_hook($node, 'view')) { |
$node->body = t('You are not authorized to access this item.'); |
| 330 |
$node = node_invoke($node, 'view', $teaser, false); |
$node->teaser = t('You are not authorized to access this item.'); |
| 331 |
} |
$title = ($show_access_denied_titles) ? $title : t('Access denied'); |
| 332 |
else { |
} |
|
$node = node_prepare($node, $teaser); |
|
|
} |
|
|
// If only teaser is shown, add 'read more' link to feed |
|
|
if ($teaser) { |
|
|
$item_text .= $node->teaser; |
|
|
if ($node->readmore) { |
|
|
$item_text .= '<p>'. l(t('read more'), 'node/'. $node->nid, null, null, null, true) .'</p>'; |
|
|
} |
|
|
} |
|
|
else { |
|
|
$item_text .= $node->body; |
|
|
} |
|
|
} |
|
| 333 |
|
|
| 334 |
// RSS extras: load extras from node modules |
// add additional info at the first line of the body text |
| 335 |
$extra = node_invoke_nodeapi($node, 'rss item'); |
if (($item->item_title_add) && ($node_access || $show_access_denied_titles)) { |
| 336 |
|
$text = '<p><em>' . (($is_comment) ? t('Comment') . ' ' . $item->item_title_add : ucfirst($item->item_title_add)) . '</em></p><hr/>'; |
| 337 |
} |
} |
| 338 |
// Put in normal extra information |
|
| 339 |
|
// set the body text |
| 340 |
|
switch ($item_type) { |
| 341 |
|
case 'fulltext': |
| 342 |
|
$text .= $node->body; |
| 343 |
|
break; |
| 344 |
|
case 'teaser': |
| 345 |
|
$text .= $node->teaser; |
| 346 |
|
if ($node->readmore) { |
| 347 |
|
$text .= '<p>'. l(t('read more'), 'node/'. $node->nid, array('fragment' => ($is_comment) ? "comment-$item->cid" : NULL, 'absolute' => TRUE)) .'</p>'; |
| 348 |
|
} |
| 349 |
|
break; |
| 350 |
|
case 'title': |
| 351 |
|
$text .= ''; |
| 352 |
|
break; |
| 353 |
|
} |
| 354 |
|
|
| 355 |
|
// put in normal extra information |
| 356 |
$extra = array_merge($extra, |
$extra = array_merge($extra, |
| 357 |
array( |
array( |
| 358 |
array('key' => 'pubDate', 'value' => date('r', $item->timestamp)), |
array('key' => 'pubDate', 'value' => date('r', $item->timestamp)), |
| 359 |
array('key' => 'dc:creator', 'value' => $item->name), |
array('key' => 'dc:creator', 'value' => $item->user_name), |
| 360 |
array('key' => 'guid', 'value' => $item->nid .' at '. $base_url . ($is_comment ? ' comment '.$item->cid : ' revision '.$item->vid), 'attributes' => array('isPermaLink' => 'false')) |
array('key' => 'guid', 'value' => $item->nid .' at '. $base_url . (($is_comment) ? ' comment '.$item->cid : ' revision '.$item->vid), 'attributes' => array('isPermaLink' => 'false')), |
| 361 |
) |
) |
| 362 |
); |
); |
| 363 |
|
|
| 364 |
|
$feed_output .= format_rss_item($title, $url, $text, $extra); |
| 365 |
|
|
| 366 |
|
// Add namespaces |
| 367 |
foreach ($extra as $element) { |
foreach ($extra as $element) { |
| 368 |
if ($element['namespace']) { |
if ($element['namespace']) { |
| 369 |
$namespaces = array_merge($namespaces, $element['namespace']); |
$namespaces = array_merge($namespaces, $element['namespace']); |
| 370 |
} |
} |
| 371 |
} |
} |
|
// Add rss feed item to output |
|
|
$items .= format_rss_item($title, $link, $item_text, $extra); |
|
| 372 |
} |
} |
| 373 |
|
|
| 374 |
// RSS channel information |
// RSS channel information |
| 375 |
|
$single_node_type = count($node_types) == 1; |
| 376 |
|
$filter_text = ($single_node_type) ? ' filtered by '. _recent_changes_node_type_name($node_types[0]) : ''; |
| 377 |
$channel = array( |
$channel = array( |
| 378 |
'version' => '2.0', |
'version' => '2.0', |
| 379 |
'link' => $base_url, |
'link' => $base_url, |
| 380 |
|
'title' => variable_get('site_name', 'drupal') .' - ' . t('Recent changes') . $filter_text, |
| 381 |
'description' => t('Recent changes of nodes and comments on %site', array('%site' => variable_get('site_name', 'drupal'))), |
'description' => t('Recent changes of nodes and comments on %site', array('%site' => variable_get('site_name', 'drupal'))), |
| 382 |
'language' => $GLOBALS['locale'], |
'language' => $GLOBALS['locale'], |
| 383 |
); |
); |
| 384 |
if ($node_type) { |
|
|
$channel['title'] = variable_get('site_name', 'drupal') .' - ' . t('Recent changes filtered by !node_type', array('!node_type' => $node_type)); |
|
|
} |
|
|
else { |
|
|
$channel['title'] = variable_get('site_name', 'drupal') .' - ' . t('Recent changes'); |
|
|
} |
|
| 385 |
// Construct actual RSS feed text |
// Construct actual RSS feed text |
| 386 |
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; |
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; |
| 387 |
$output .= "<rss version=\"". $channel["version"] . "\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n"; |
$output .= "<rss version=\"". $channel["version"] . "\" xml:base=\"". $base_url ."\" ". implode(' ', $namespaces) .">\n"; |
| 388 |
$output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']); |
$output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $feed_output, $channel['language']); |
| 389 |
$output .= "</rss>\n"; |
$output .= "</rss>\n"; |
| 390 |
|
|
| 391 |
// Set MIME type in HTML header |
// Set MIME type in HTML header |
| 392 |
drupal_set_header('Content-Type: application/rss+xml; charset=utf-8'); |
drupal_set_header('Content-Type: application/rss+xml; charset=utf-8'); |
| 393 |
|
|
| 394 |
// Print feed text |
// Print feed text |
| 395 |
print $output; |
print $output; |
| 396 |
} |
} |
| 397 |
|
|
|
/* |
|
|
* Settings |
|
|
*/ |
|
| 398 |
|
|
| 399 |
/** |
/** |
| 400 |
* Number of entries per page. |
* Parsing of recent changes results |
| 401 |
*/ |
*/ |
| 402 |
function recent_changes_count_per_page($value = NULL) { |
function _recent_changes_parse_results($results) { |
| 403 |
if (is_null($value)) { |
// initialize |
| 404 |
return variable_get('recent_changes_entries_per_page', 50); |
$has_revisions = array(); |
| 405 |
|
$nids = array(); |
| 406 |
|
$items = array(); |
| 407 |
|
$access_revisions = user_access('view revisions'); |
| 408 |
|
$exists_diff = module_exists('diff'); |
| 409 |
|
|
| 410 |
|
// put node nids in the the nids array (needed for querying previous revisions on this page) |
| 411 |
|
foreach ($results as $result) { |
| 412 |
|
$nids[$result->nid] = TRUE; |
| 413 |
|
} |
| 414 |
|
|
| 415 |
|
// get previous revisions |
| 416 |
|
$all_node_nid = implode(", ", array_keys($nids, TRUE)); |
| 417 |
|
$where_nid = ($all_node_nid) ? "WHERE n.nid IN ($all_node_nid)" : "WHERE FALSE"; |
| 418 |
|
$revisions_result = db_query("SELECT n.nid, n.vid AS curr_vid, p.vid, p.title FROM {node_revisions} n LEFT JOIN {node_revisions} p ON ((n.nid = p.nid) AND (n.vid > p.vid)) $where_nid ORDER BY p.vid DESC"); |
| 419 |
|
|
| 420 |
|
while ($revision = db_fetch_object($revisions_result)) { |
| 421 |
|
$curr_vid = $revision->curr_vid; |
| 422 |
|
if(is_null($revision->vid)) { |
| 423 |
|
break; |
| 424 |
|
} |
| 425 |
|
$has_revisions[$revision->nid] = TRUE; |
| 426 |
|
$prev_revisions[$curr_vid] = (!isset($prev_revisions[$curr_vid])) ? $revision : $prev_revisions[$curr_vid]; |
| 427 |
} |
} |
| 428 |
variable_set('recent_changes_entries_per_page', $value); |
|
| 429 |
|
// parse results |
| 430 |
|
foreach ($results as $result) { |
| 431 |
|
$is_comment = ($result->cid != 0); |
| 432 |
|
$prev_revision = $prev_revisions[$result->vid]; |
| 433 |
|
|
| 434 |
|
// the owner is the user specified in the {node} table (also called "author" in drupal terminology) |
| 435 |
|
$owner_user->uid = $result->uid; |
| 436 |
|
$owner_user->name = $result->name; |
| 437 |
|
|
| 438 |
|
// the active user is the user that performed the update, or posted the revision or comment |
| 439 |
|
$active_user->uid = $result->auid; |
| 440 |
|
$active_user->name = $result->aname; |
| 441 |
|
|
| 442 |
|
$is_current_revision = $result->vid == $result->cvid; |
| 443 |
|
$show_revisions = $access_revisions && $has_revisions[$result->nid]; |
| 444 |
|
$show_diff_link = $prev_revision->vid && $exists_diff && $access_revisions; |
| 445 |
|
$moved = !$is_comment && $prev_revision->vid && ($prev_revision->title !== $result->title); |
| 446 |
|
|
| 447 |
|
$flags = array( |
| 448 |
|
'comment' => $is_comment, |
| 449 |
|
'updated' => !$is_comment && !$prev_revision->vid && ($result->timestamp != $result->created), |
| 450 |
|
'new' => !$is_comment && !$prev_revision->vid && ($result->timestamp == $result->created), |
| 451 |
|
'revised' => !$is_comment && $prev_revision->vid && ($prev_revision->title === $result->title), |
| 452 |
|
'moved' => $moved, |
| 453 |
|
); |
| 454 |
|
|
| 455 |
|
$comment_link = l('(comment)', "node/$result->nid", array('fragment' => "comment-$result->cid")); |
| 456 |
|
$operations = ($show_diff_link) ? l('(diff)', "node/$result->nid/revisions/view/$prev_revision->vid/$result->vid") : '(diff)'; |
| 457 |
|
$operations .= ' '. (($show_revisions) ? l('(hist)', "node/$result->nid/revisions") : '(hist)'); |
| 458 |
|
|
| 459 |
|
$result_title = ($is_comment) ? $result->subject : $result->title; |
| 460 |
|
$result_link = array("node/$result->nid" . (($is_comment || $is_current_revision) ? '' : "/revisions/$result->vid/view"), array('fragment' => ($is_comment) ? "comment-$result->cid" : NULL)); |
| 461 |
|
$result_url = array("node/$result->nid" . (($is_comment || $is_current_revision) ? '' : "/revisions/$result->vid/view"), array('fragment' => ($is_comment) ? "comment-$result->cid" : NULL )); |
| 462 |
|
|
| 463 |
|
$owner_intro_text = ($moved && $access_revisions) ? ', ' . t('from') . ' ' : t('author') . ': '; |
| 464 |
|
$result_title_add = ($moved && $access_revisions) ? t('source: ') . l($prev_revision->title, "node/$result->nid/revisions/$prev_revision->vid/view") : ''; |
| 465 |
|
$result_title_add .= (!$is_comment && ($result->uid != $result->auid)) ? $owner_intro_text . theme('username', $owner_user) : ''; |
| 466 |
|
$result_title_add .= ($is_comment) ? t('on') . ' ' . l($result->title, "node/$result->nid") . ' ' . t('from') . ' ' . theme('username', $owner_user) : ''; |
| 467 |
|
|
| 468 |
|
$items[] = (object) array( |
| 469 |
|
'is_current_revision' => $is_current_revision, |
| 470 |
|
'cid' => $result->cid, |
| 471 |
|
'nid' => $result->nid, |
| 472 |
|
'vid' => $result->vid, |
| 473 |
|
'prev_revision' => $prev_revision, |
| 474 |
|
'op_link' => ($is_comment) ? $comment_link : $operations, |
| 475 |
|
'timestamp' => $result->timestamp, |
| 476 |
|
'node_type_name' => _recent_changes_node_type_name($result->type), |
| 477 |
|
'flag' => t(array_search(TRUE, $flags)), |
| 478 |
|
'item_title' => $result_title, |
| 479 |
|
'link_array' => $result_link, |
| 480 |
|
'url_array' => $result_url, |
| 481 |
|
'item_title_add' => $result_title_add, |
| 482 |
|
'user_link' => theme('username', $active_user), |
| 483 |
|
'user_name' => $active_user->name, |
| 484 |
|
'log_text' => filter_xss($result->log), |
| 485 |
|
'comment_text' => $result->comment, |
| 486 |
|
); |
| 487 |
|
} |
| 488 |
|
|
| 489 |
|
return($items); |
| 490 |
|
} |
| 491 |
|
|
| 492 |
|
|
| 493 |
|
/** |
| 494 |
|
* Build SQL query depending on node type filter and comments filter. |
| 495 |
|
* Inaccessible node types should already have been filtered out by this stage. |
| 496 |
|
* If the load_revisions is false, only the recent changes from the {node} table are used, otherwise older revisions might also show up if they were recently changed. |
| 497 |
|
*/ |
| 498 |
|
function _recent_changes_query($node_types, $load_revisions) { |
| 499 |
|
// show comments |
| 500 |
|
$comment_index = array_search('comment', $node_types); |
| 501 |
|
$show_comments = (!is_bool($comment_index) && !is_null($comment_index)); |
| 502 |
|
|
| 503 |
|
// if only comments is selected, show comments for all allowed node types |
| 504 |
|
$comments_only = (count($node_types) == 1 && $node_types[0] === 'comment'); |
| 505 |
|
$node_types = $comments_only ? _recent_changes_parse_node_types() : $node_types; |
| 506 |
|
|
| 507 |
|
// construct node type query (note that leading and trailing single quotes (') need to be added later) |
| 508 |
|
$where_type = implode("', '", $node_types); |
| 509 |
|
|
| 510 |
|
// construct where queries depending on whether the revision, node, or comment should be displayed |
| 511 |
|
$node_where = ($where_type && !$comments_only && !$load_revisions) ? "WHERE n.type IN ('$where_type')" : "WHERE FALSE"; |
| 512 |
|
$revision_where = ($where_type && !$comments_only && $load_revisions) ? "WHERE n.type IN ('$where_type')" : "WHERE FALSE"; |
| 513 |
|
$comment_where = ($where_type && $show_comments) ? "WHERE n.type IN ('$where_type')" : "WHERE FALSE"; |
| 514 |
|
|
| 515 |
|
// queries to get the selected nodes, comments and revisions for the selected node types |
| 516 |
|
$nodes_query = "SELECT n.nid, n.vid, n.vid AS cvid, n.uid, u.name, r.title, '' AS log, n.created, r.timestamp, n.type, 0 AS cid, '' AS subject, '' AS comment, r.uid AS auid, a.name AS aname FROM {node} n LEFT JOIN {node_revisions} r ON n.vid = r.vid LEFT JOIN {users} u ON n.uid = u.uid LEFT JOIN {users} a ON r.uid = a.uid $node_where AND n.status >= 1"; |
| 517 |
|
$revisions_query = "SELECT r.nid, r.vid, n.vid AS cvid, n.uid, u.name, r.title, r.log, n.created, r.timestamp, n.type, 0 AS cid, '' AS subject, '' AS comment, r.uid AS auid, a.name AS aname FROM {node_revisions} r LEFT JOIN {node} n ON r.nid = n.nid LEFT JOIN {users} u ON n.uid = u.uid LEFT JOIN {users} a ON r.uid = a.uid $revision_where AND n.status >= 1"; |
| 518 |
|
$comments_query = "SELECT c.nid, n.vid, n.vid AS cvid, n.uid, u.name, n.title, '' AS log, c.timestamp AS created, c.timestamp, n.type, c.cid, c.subject, c.comment, c.uid AS auid, c.name AS aname FROM {comments} c LEFT JOIN {node} n ON c.nid = n.nid LEFT JOIN {users} u ON n.uid = u.uid $comment_where AND n.status >= 1"; |
| 519 |
|
|
| 520 |
|
// counter queries |
| 521 |
|
$node_count = "SELECT COUNT(*) FROM {node} n $node_where AND n.status >= 1"; |
| 522 |
|
$revision_count = "SELECT COUNT(*) FROM {node_revisions} r JOIN {node} n ON r.nid = n.nid $revision_where AND n.status >= 1"; |
| 523 |
|
$comment_count = "SELECT COUNT(*) FROM {comments} c LEFT JOIN {node} n ON c.nid = n.nid $comment_where AND n.status >= 1"; |
| 524 |
|
|
| 525 |
|
// unify queries |
| 526 |
|
$sql = "($nodes_query) UNION ALL ($revisions_query) UNION ALL ($comments_query) ORDER BY timestamp DESC"; |
| 527 |
|
$sql_count = "SELECT ($node_count) + ($revision_count) + ($comment_count)"; |
| 528 |
|
|
| 529 |
|
return(array($sql, $sql_count)); |
| 530 |
} |
} |
| 531 |
|
|
| 532 |
|
|
| 533 |
/** |
/** |
| 534 |
* Is the filter form shown on the recent changes page? |
* Form for input filter. |
| 535 |
*/ |
*/ |
| 536 |
function recent_changes_show_filter($value = NULL) { |
function recent_changes_filter_form() { |
| 537 |
if (is_null($value)) { |
$node_types = _recent_changes_parse_node_types(); |
| 538 |
return variable_get('recent_changes_show_filter', true); |
$allow_multiple = variable_get('recent_changes_allow_multiple_filter', TRUE); |
| 539 |
|
$default_value = ($_REQUEST['op'] === t('Filter')) ? $_REQUEST['recent_changes_filter_node_types'] : ''; |
| 540 |
|
|
| 541 |
|
// get associative array with content types and human-friendly content names |
| 542 |
|
$node_names = array(); |
| 543 |
|
foreach ($node_types AS $type) { |
| 544 |
|
$node_names[$type] = _recent_changes_node_type_name($type); |
| 545 |
|
} |
| 546 |
|
|
| 547 |
|
// content types have no weights, so we can safely sort them here |
| 548 |
|
asort($node_names); |
| 549 |
|
|
| 550 |
|
// add "All content types" if only single selection is allowed |
| 551 |
|
$node_names = ($allow_multiple) ? $node_names : array_merge(array(FALSE => t('All content types')), $node_names); |
| 552 |
|
|
| 553 |
|
$form = array(); |
| 554 |
|
$form['#attributes'] = array('class' => 'inline'); |
| 555 |
|
|
| 556 |
|
$form['recent_changes_filter_node_types'] = array( |
| 557 |
|
'#type' => 'select', |
| 558 |
|
'#title' => t('Filter content type'), |
| 559 |
|
'#default_value' => $default_value, |
| 560 |
|
'#options' => $node_names, |
| 561 |
|
'#multiple' => $allow_multiple, |
| 562 |
|
'#size' => min(count($node_names), variable_get('recent_changes_type_select_size', 5)), |
| 563 |
|
); |
| 564 |
|
|
| 565 |
|
$form['submit'] = array( |
| 566 |
|
'#type' => 'button', |
| 567 |
|
'#value' => t('Filter') |
| 568 |
|
); |
| 569 |
|
|
| 570 |
|
return $form; |
| 571 |
|
} |
| 572 |
|
|
| 573 |
|
|
| 574 |
|
/* |
| 575 |
|
* Interface with the diff module. Returns a table with the differences between $node and $prev_revision |
| 576 |
|
*/ |
| 577 |
|
function _recent_changes_get_diff($node, $prev_revision) { |
| 578 |
|
$item_text = '<table style="width:100%">'; |
| 579 |
|
$item_text .= '<thead><tr><th></th><th>Revision of '. format_date($prev_revision->revision_timestamp) .'</th><th></th><th>Revision of '. format_date($node->revision_timestamp) .'</th></tr></thead>'; |
| 580 |
|
$item_text .= _diff_table_body($prev_revision, $node); |
| 581 |
|
$item_text .= '</table>'; |
| 582 |
|
|
| 583 |
|
// Replace css classes from diff with actual style elements since we cannot reference a css file from the rss feed. |
| 584 |
|
$patterns = array( |
| 585 |
|
'/class=["\']diff-section-title["\']/', |
| 586 |
|
'/class=["\']diff-deletedline["\']/', |
| 587 |
|
'/class=["\']diff-addedline["\']/', |
| 588 |
|
'/class=["\']diff-context["\']/', |
| 589 |
|
'/class=["\']diffchange["\']/', |
| 590 |
|
); |
| 591 |
|
|
| 592 |
|
$replacements = array( |
| 593 |
|
'style="background-color: #f0f0ff;"', |
| 594 |
|
'style="background-color: #ffa;width: 50%"', |
| 595 |
|
'style="background-color: #afa;width: 50%"', |
| 596 |
|
'style="background-color: #fafafa"', |
| 597 |
|
'style="color: #f00;font-weight:bold"', |
| 598 |
|
); |
| 599 |
|
|
| 600 |
|
$item_text = preg_replace($patterns, $replacements, $item_text); |
| 601 |
|
$item_text .= "<hr/>\n"; |
| 602 |
|
|
| 603 |
|
return($item_text); |
| 604 |
|
} |
| 605 |
|
|
| 606 |
|
|
| 607 |
|
/* |
| 608 |
|
* Removes the node types to which the user has no access |
| 609 |
|
*/ |
| 610 |
|
function _recent_changes_access_check($node_types) { |
| 611 |
|
|
| 612 |
|
// Check for each node type (and comments) whether current user has 'view' access to it |
| 613 |
|
foreach ($node_types as $key=>$type) { |
| 614 |
|
$access = FALSE; |
| 615 |
|
if ($type == 'comment') { |
| 616 |
|
$access = module_exists('comment') && user_access('access comments'); |
| 617 |
|
} |
| 618 |
|
else { |
| 619 |
|
$module = node_get_types('module', $type); |
| 620 |
|
if ($module == 'node') { |
| 621 |
|
$access = user_access('access content'); |
| 622 |
|
} |
| 623 |
|
else { |
| 624 |
|
// this would be the right way to implement access checks, but unfortunately most modules don't implement it |
| 625 |
|
// This is not correct as the 'view' parameter also needs that a node object is passed. |
| 626 |
|
// Only for 'create' parameters it is sufficent to pass a node type |
| 627 |
|
// $access = module_invoke($module, 'access', 'view', NULL); |
| 628 |
|
$access = NULL; |
| 629 |
|
|
| 630 |
|
// If $access is empty, we try it another way: |
| 631 |
|
// First we request all possible permissions, then we check the first one with the word 'access' in it with the user_access command. If there is no permission with the word access in it, user_access('access content') is used. This works most of the time. |
| 632 |
|
if (is_null($access)) { |
| 633 |
|
$perms = module_invoke($module, 'perm'); |
| 634 |
|
$pattern = "/(?m)^access.*$/"; |
| 635 |
|
preg_match($pattern, ((empty($perms)) ? '' : implode("\n", $perms)), $matches); |
| 636 |
|
$selected_perm = (empty($matches)) ? 'access content' : $matches[0]; |
| 637 |
|
$access = user_access($selected_perm); |
| 638 |
|
} |
| 639 |
|
} |
| 640 |
|
} |
| 641 |
|
|
| 642 |
|
// If the user has no access, remove the content type from the list |
| 643 |
|
if (!$access) { |
| 644 |
|
unset($node_types[$key]); |
| 645 |
|
} |
| 646 |
} |
} |
| 647 |
variable_set('recent_changes_show_filter', $value); |
|
| 648 |
|
return($node_types); |
| 649 |
} |
} |
| 650 |
|
|
| 651 |
|
|
| 652 |
/** |
/** |
| 653 |
* Are comments shown by default? |
* Return allowed node types, including a 'comments' option |
| 654 |
*/ |
*/ |
| 655 |
function recent_changes_show_comments($value = NULL) { |
function _recent_changes_get_allowed_node_types() { |
| 656 |
if (is_null($value)) { |
$allowed_node_types = variable_get('recent_changes_allowed_node_types', array()); |
| 657 |
return variable_get('recent_changes_show_comments', true); |
$allowed_node_types = ($allowed_node_types) ? $allowed_node_types : array_keys(node_get_types('names')); |
| 658 |
} |
|
| 659 |
variable_set('recent_changes_show_comments', $value); |
// add comments option |
| 660 |
|
$allowed_node_types = (variable_get('recent_changes_show_comments', TRUE)) ? array_merge(array('comment'), $allowed_node_types) : $allowed_node_types; |
| 661 |
|
|
| 662 |
|
return $allowed_node_types; |
| 663 |
} |
} |
| 664 |
|
|
| 665 |
|
|
| 666 |
/** |
/** |
| 667 |
* SQL query for all node revisions. |
* Parse (and cache) node types, and perform access check |
| 668 |
*/ |
*/ |
| 669 |
function recent_changes_revisions_query($node_type = NULL) { |
function _recent_changes_parse_node_types($node_types = NULL) { |
| 670 |
$result = 'SELECT n.type, r.nid, r.vid, 0 AS cid, r.title, r.timestamp, r.log, r.uid, u.name FROM {node_revisions} r LEFT JOIN {node} n ON r.nid = n.nid LEFT JOIN {users} u ON r.uid = u.uid'; |
// do some caching in case node_types is NULL |
| 671 |
// Change for query if access right restrictions apply |
static $all_parsed_node_types; |
| 672 |
list($join, $where, $distinct) = _db_rewrite_sql($result, 'r', 'nid'); |
|
| 673 |
if ($node_type) { |
$parse_all_node_types = is_null($node_types); |
| 674 |
// Filter on node type |
|
| 675 |
$where .= " AND n.type = '" . check_plain($node_type) . "'"; |
// return cached version if available |
| 676 |
} |
if ($parse_all_node_types && isset($all_parsed_node_types)) { |
| 677 |
if ($join) { |
return $all_parsed_node_types; |
|
$result .= ' ' . $join; |
|
| 678 |
} |
} |
| 679 |
if ($where) { |
|
| 680 |
$result .= ' WHERE ' . $where; |
// put content types in an array, and use allowed_content_types if node_types was empty |
| 681 |
|
$node_types = _recent_changes_array_node_types($node_types); |
| 682 |
|
|
| 683 |
|
// perform access check |
| 684 |
|
$node_types = _recent_changes_access_check($node_types); |
| 685 |
|
|
| 686 |
|
// cache all parsed node types if all node types were requested |
| 687 |
|
if ($parse_all_node_types) { |
| 688 |
|
$all_parsed_node_types = $node_types; |
| 689 |
} |
} |
| 690 |
return $result; |
|
| 691 |
|
return($node_types); |
| 692 |
} |
} |
| 693 |
|
|
| 694 |
|
|
| 695 |
/** |
/** |
| 696 |
* SQL query for all comments. |
* Given an array or of node types or a single node type, this function returns the node types that are also in allowed node types |
| 697 |
|
* If the input argument $node_types is NULL, all allowed node types are returned |
| 698 |
|
* Note that no access checking is performed here! |
| 699 |
*/ |
*/ |
| 700 |
function recent_changes_comments_query($node_type = NULL) { |
function _recent_changes_array_node_types($node_types = NULL) { |
| 701 |
$result = 'SELECT n.type, c.nid, 0 AS vid, c.cid, n.title, c.timestamp, c.subject AS log, c.uid, c.name FROM {comments} c LEFT JOIN {node} n ON c.nid = n.nid'; |
$allowed_node_types = _recent_changes_get_allowed_node_types(); |
| 702 |
list($join, $where, $distinct) = _db_rewrite_sql($result, 'c', 'nid'); |
|
| 703 |
if ($node_type) { |
// in case $node_types is empty ('All content types' was selected), set it to all allowed node types |
| 704 |
$where .= " AND n.type = '" . check_plain($node_type) . "'"; |
$node_types = ($node_types) ? $node_types : $allowed_node_types; |
| 705 |
} |
|
| 706 |
if ($join) { |
// in case $node_types is a single node type (single selection), put it in an array |
| 707 |
$result .= ' ' . $join; |
$node_types = (is_array($node_types)) ? $node_types : array($node_types); |
| 708 |
} |
|
| 709 |
if ($where) { |
// check whether the $node_types are actually in $allowed_node types, and make sure the key starts at index 0 (with array_values) |
| 710 |
$result .= ' WHERE ' . $where; |
$node_types = array_values(array_intersect($node_types, $allowed_node_types)); |
| 711 |
} |
|
| 712 |
return $result; |
return($node_types); |
| 713 |
|
} |
| 714 |
|
|
| 715 |
|
/** |
| 716 |
|
* Returns the node type name, given the node type, and handles 'comment' as a special case because 'comment' is not an actual node type |
| 717 |
|
*/ |
| 718 |
|
function _recent_changes_node_type_name($type) { |
| 719 |
|
$node_type_name = ($type === 'comment') ? t('Comment') : node_get_types('name', $type); |
| 720 |
|
return $node_type_name; |
| 721 |
} |
} |
| 722 |
|
|
| 723 |
|
// query that returns which nodes have more than one revision |
| 724 |
|
//$revisions_result = db_query("SELECT nid, COUNT(vid) AS num FROM {node_revisions} GROUP BY nid HAVING (COUNT(nid) > 1)"); |
| 725 |
|
//while ($has_revision_nid = db_fetch_object($revisions_result)) { |
| 726 |
|
// $has_revisions[$has_revision_nid->nid] = TRUE; |
| 727 |
|
//} |