Core modules not considered by SuiteDesk
This commit is contained in:
parent
955390e3ec
commit
5ba1cdfa0b
93 changed files with 0 additions and 14121 deletions
|
@ -1,8 +0,0 @@
|
|||
This directory is reserved for core module files. Custom or contributed
|
||||
modules should be placed in their own subdirectory of the sites/all/modules
|
||||
directory. For multisite installations, they can also be placed in a subdirectory
|
||||
under /sites/{sitename}/modules/, where {sitename} is the name of your site
|
||||
(e.g., www.example.com). This will allow you to more easily update Drupal core files.
|
||||
|
||||
For more details, see: http://drupal.org/node/176043
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file aggregator-feed-source.tpl.php
|
||||
* Default theme implementation to present the source of the feed.
|
||||
*
|
||||
* The contents are render above feed listings when browsing source feeds.
|
||||
* For example, "example.com/aggregator/sources/1".
|
||||
*
|
||||
* Available variables:
|
||||
* - $source_icon: Feed icon linked to the source. Rendered through
|
||||
* theme_feed_icon().
|
||||
* - $source_image: Image set by the feed source.
|
||||
* - $source_description: Description set by the feed source.
|
||||
* - $source_url: URL to the feed source.
|
||||
* - $last_checked: How long ago the feed was checked locally.
|
||||
*
|
||||
* @see template_preprocess()
|
||||
* @see template_preprocess_aggregator_feed_source()
|
||||
*/
|
||||
?>
|
||||
<div class="feed-source">
|
||||
<?php print $source_icon; ?>
|
||||
<?php print $source_image; ?>
|
||||
<div class="feed-description">
|
||||
<?php print $source_description; ?>
|
||||
</div>
|
||||
<div class="feed-url">
|
||||
<em><?php print t('URL:'); ?></em> <a href="<?php print $source_url; ?>"><?php print $source_url; ?></a>
|
||||
</div>
|
||||
<div class="feed-updated">
|
||||
<em><?php print t('Updated:'); ?></em> <?php print $last_checked; ?>
|
||||
</div>
|
||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file aggregator-item.tpl.php
|
||||
* Default theme implementation to format an individual feed item for display
|
||||
* on the aggregator page.
|
||||
*
|
||||
* Available variables:
|
||||
* - $feed_url: URL to the originating feed item.
|
||||
* - $feed_title: Title of the feed item.
|
||||
* - $source_url: Link to the local source section.
|
||||
* - $source_title: Title of the remote source.
|
||||
* - $source_date: Date the feed was posted on the remote source.
|
||||
* - $content: Feed item content.
|
||||
* - $categories: Linked categories assigned to the feed.
|
||||
*
|
||||
* @see template_preprocess()
|
||||
* @see template_preprocess_aggregator_item()
|
||||
*/
|
||||
?>
|
||||
<div class="feed-item">
|
||||
<h3 class="feed-item-title">
|
||||
<a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a>
|
||||
</h3>
|
||||
|
||||
<div class="feed-item-meta">
|
||||
<?php if ($source_url) : ?>
|
||||
<a href="<?php print $source_url; ?>" class="feed-item-source"><?php print $source_title; ?></a> -
|
||||
<?php endif; ?>
|
||||
<span class="feed-item-date"><?php print $source_date; ?></span>
|
||||
</div>
|
||||
|
||||
<?php if ($content) : ?>
|
||||
<div class="feed-item-body">
|
||||
<?php print $content; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($categories) : ?>
|
||||
<div class="feed-item-categories">
|
||||
<?php print t('Categories'); ?>: <?php print implode(', ', $categories); ?>
|
||||
</div>
|
||||
<?php endif ;?>
|
||||
|
||||
</div>
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
#aggregator .feed-source .feed-icon {
|
||||
float: left;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file aggregator-summary-item.tpl.php
|
||||
* Default theme implementation to present a linked feed item for summaries.
|
||||
*
|
||||
* Available variables:
|
||||
* - $feed_url: Link to originating feed.
|
||||
* - $feed_title: Title of feed.
|
||||
* - $feed_age: Age of remote feed.
|
||||
* - $source_url: Link to remote source.
|
||||
* - $source_title: Locally set title for the source.
|
||||
*
|
||||
* @see template_preprocess()
|
||||
* @see template_preprocess_aggregator_summary_item()
|
||||
*/
|
||||
?>
|
||||
<a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a> <span class="age"><?php print $feed_age; ?></span><?php if ($source_url) : ?>, <span class="source"><a href="<?php print $source_url; ?>"><?php print $source_title; ?></a></span><?php endif; ?>
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file aggregator-summary-items.tpl.php
|
||||
* Default theme implementation to present feeds as list items.
|
||||
*
|
||||
* Each iteration generates a single feed source or category.
|
||||
*
|
||||
* Available variables:
|
||||
* - $title: Title of the feed or category.
|
||||
* - $summary_list: Unordered list of linked feed items generated through
|
||||
* theme_item_list().
|
||||
* - $source_url: URL to the local source or category.
|
||||
*
|
||||
* @see template_preprocess()
|
||||
* @see template_preprocess_aggregator_summary-items()
|
||||
*/
|
||||
?>
|
||||
<h2><?php print $title; ?></h2>
|
||||
<?php print $summary_list; ?>
|
||||
<div class="links">
|
||||
<a href="<?php print $source_url; ?>"><?php print t('More'); ?></a>
|
||||
</div>
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file comment-wrapper.tpl.php
|
||||
* Default theme implementation to wrap aggregator content.
|
||||
*
|
||||
* Available variables:
|
||||
* - $content: All aggregator content.
|
||||
* - $page: Pager links rendered through theme_pager().
|
||||
*
|
||||
* @see template_preprocess()
|
||||
* @see template_preprocess_comment_wrapper()
|
||||
*/
|
||||
?>
|
||||
<div id="aggregator">
|
||||
<?php print $content; ?>
|
||||
<?php print $pager; ?>
|
||||
</div>
|
|
@ -1,351 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Admin page callbacks for the aggregator module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback; displays the aggregator administration page.
|
||||
*/
|
||||
function aggregator_admin_overview() {
|
||||
return aggregator_view();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the aggregator administration page.
|
||||
*
|
||||
* @return
|
||||
* The page HTML.
|
||||
*/
|
||||
function aggregator_view() {
|
||||
$result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.etag, f.modified, f.image, f.block ORDER BY f.title');
|
||||
|
||||
$output = '<h3>'. t('Feed overview') .'</h3>';
|
||||
|
||||
$header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
|
||||
$rows = array();
|
||||
while ($feed = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
l($feed->title, "aggregator/sources/$feed->fid"),
|
||||
format_plural($feed->items, '1 item', '@count items'),
|
||||
($feed->checked ? t('@time ago', array('@time' => format_interval(time() - $feed->checked))) : t('never')),
|
||||
($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - time()))) : t('never')),
|
||||
l(t('edit'), "admin/content/aggregator/edit/feed/$feed->fid"),
|
||||
l(t('remove items'), "admin/content/aggregator/remove/$feed->fid"),
|
||||
l(t('update items'), "admin/content/aggregator/update/$feed->fid", array('query' => array('token' => drupal_get_token("aggregator/update/$feed->fid")))),
|
||||
);
|
||||
}
|
||||
$output .= theme('table', $header, $rows);
|
||||
|
||||
$result = db_query('SELECT c.cid, c.title, count(ci.iid) as items FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid GROUP BY c.cid, c.title ORDER BY title');
|
||||
|
||||
$output .= '<h3>'. t('Category overview') .'</h3>';
|
||||
|
||||
$header = array(t('Title'), t('Items'), t('Operations'));
|
||||
$rows = array();
|
||||
while ($category = db_fetch_object($result)) {
|
||||
$rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/content/aggregator/edit/category/$category->cid"));
|
||||
}
|
||||
$output .= theme('table', $header, $rows);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; Generate a form to add/edit feed sources.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see aggregator_form_feed_validate()
|
||||
* @see aggregator_form_feed_submit()
|
||||
*/
|
||||
function aggregator_form_feed(&$form_state, $edit = array('refresh' => 900, 'title' => '', 'url' => '', 'fid' => NULL)) {
|
||||
$period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
|
||||
|
||||
if ($edit['refresh'] == '') {
|
||||
$edit['refresh'] = 3600;
|
||||
}
|
||||
|
||||
$form['title'] = array('#type' => 'textfield',
|
||||
'#title' => t('Title'),
|
||||
'#default_value' => $edit['title'],
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('The name of the feed (or the name of the website providing the feed).'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['url'] = array('#type' => 'textfield',
|
||||
'#title' => t('URL'),
|
||||
'#default_value' => $edit['url'],
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('The fully-qualified URL of the feed.'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['refresh'] = array('#type' => 'select',
|
||||
'#title' => t('Update interval'),
|
||||
'#default_value' => $edit['refresh'],
|
||||
'#options' => $period,
|
||||
'#description' => t('The length of time between feed updates. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
|
||||
);
|
||||
|
||||
// Handling of categories:
|
||||
$options = array();
|
||||
$values = array();
|
||||
$categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']);
|
||||
while ($category = db_fetch_object($categories)) {
|
||||
$options[$category->cid] = check_plain($category->title);
|
||||
if ($category->fid) $values[] = $category->cid;
|
||||
}
|
||||
if ($options) {
|
||||
$form['category'] = array('#type' => 'checkboxes',
|
||||
'#title' => t('Categorize news items'),
|
||||
'#default_value' => $values,
|
||||
'#options' => $options,
|
||||
'#description' => t('New feed items are automatically filed in the checked categories.'),
|
||||
);
|
||||
}
|
||||
$form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
|
||||
|
||||
if ($edit['fid']) {
|
||||
$form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
|
||||
$form['fid'] = array('#type' => 'hidden', '#value' => $edit['fid']);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate aggregator_form_feed form submissions.
|
||||
*/
|
||||
function aggregator_form_feed_validate($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Save')) {
|
||||
// Ensure URL is valid.
|
||||
if (!valid_url($form_state['values']['url'], TRUE)) {
|
||||
form_set_error('url', t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
|
||||
}
|
||||
// Check for duplicate titles.
|
||||
if (isset($form_state['values']['fid'])) {
|
||||
$result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = '%s' OR url = '%s') AND fid <> %d", $form_state['values']['title'], $form_state['values']['url'], $form_state['values']['fid']);
|
||||
}
|
||||
else {
|
||||
$result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url = '%s'", $form_state['values']['title'], $form_state['values']['url']);
|
||||
}
|
||||
while ($feed = db_fetch_object($result)) {
|
||||
if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
|
||||
form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_state['values']['title'])));
|
||||
}
|
||||
if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
|
||||
form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_state['values']['url'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process aggregator_form_feed form submissions.
|
||||
*
|
||||
* @todo Add delete confirmation dialog.
|
||||
*/
|
||||
function aggregator_form_feed_submit($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Delete')) {
|
||||
$title = $form_state['values']['title'];
|
||||
// Unset the title:
|
||||
unset($form_state['values']['title']);
|
||||
}
|
||||
aggregator_save_feed($form_state['values']);
|
||||
if (isset($form_state['values']['fid'])) {
|
||||
if (isset($form_state['values']['title'])) {
|
||||
drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_state['values']['title'])));
|
||||
if (arg(0) == 'admin') {
|
||||
$form_state['redirect'] = 'admin/content/aggregator/';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$form_state['redirect'] = 'aggregator/sources/'. $form_state['values']['fid'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $title));
|
||||
drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
|
||||
if (arg(0) == 'admin') {
|
||||
$form_state['redirect'] = 'admin/content/aggregator/';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$form_state['redirect'] = 'aggregator/sources/';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
watchdog('aggregator', 'Feed %feed added.', array('%feed' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
|
||||
drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_state['values']['title'])));
|
||||
}
|
||||
}
|
||||
|
||||
function aggregator_admin_remove_feed($form_state, $feed) {
|
||||
return confirm_form(
|
||||
array(
|
||||
'feed' => array(
|
||||
'#type' => 'value',
|
||||
'#value' => $feed,
|
||||
),
|
||||
),
|
||||
t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed['title'])),
|
||||
'admin/content/aggregator',
|
||||
t('This action cannot be undone.'),
|
||||
t('Remove items'),
|
||||
t('Cancel')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items from a feed and redirect to the overview page.
|
||||
*
|
||||
* @param $feed
|
||||
* An associative array describing the feed to be cleared.
|
||||
*/
|
||||
function aggregator_admin_remove_feed_submit($form, &$form_state) {
|
||||
aggregator_remove($form_state['values']['feed']);
|
||||
$form_state['redirect'] = 'admin/content/aggregator';
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; refreshes a feed, then redirects to the overview page.
|
||||
*
|
||||
* @param $feed
|
||||
* An associative array describing the feed to be refreshed.
|
||||
*/
|
||||
function aggregator_admin_refresh_feed($feed) {
|
||||
if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed['fid'])) {
|
||||
return drupal_access_denied();
|
||||
}
|
||||
aggregator_refresh($feed);
|
||||
drupal_goto('admin/content/aggregator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; Configure the aggregator system.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see system_settings_form()
|
||||
*/
|
||||
function aggregator_admin_settings() {
|
||||
$items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
|
||||
$period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
|
||||
|
||||
$form['aggregator_allowed_html_tags'] = array(
|
||||
'#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255,
|
||||
'#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
|
||||
'#description' => t('A space-separated list of HTML tags allowed in the content of feed items. (Tags in this list are not removed by Drupal.)')
|
||||
);
|
||||
|
||||
$form['aggregator_summary_items'] = array(
|
||||
'#type' => 'select', '#title' => t('Items shown in sources and categories pages') ,
|
||||
'#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items,
|
||||
'#description' => t('Number of feed items displayed in feed and category summary pages.')
|
||||
);
|
||||
|
||||
$form['aggregator_clear'] = array(
|
||||
'#type' => 'select', '#title' => t('Discard items older than'),
|
||||
'#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period,
|
||||
'#description' => t('The length of time to retain feed items before discarding. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status')))
|
||||
);
|
||||
|
||||
$form['aggregator_category_selector'] = array(
|
||||
'#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'),
|
||||
'#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')),
|
||||
'#description' => t('The type of category selection widget displayed on categorization pages. (For a small number of categories, checkboxes are easier to use, while a multiple selector work well with large numbers of categories.)')
|
||||
);
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; Generate a form to add/edit/delete aggregator categories.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see aggregator_form_category_validate()
|
||||
* @see aggregator_form_category_submit()
|
||||
*/
|
||||
function aggregator_form_category(&$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) {
|
||||
$form['title'] = array('#type' => 'textfield',
|
||||
'#title' => t('Title'),
|
||||
'#default_value' => $edit['title'],
|
||||
'#maxlength' => 64,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['description'] = array('#type' => 'textarea',
|
||||
'#title' => t('Description'),
|
||||
'#default_value' => $edit['description'],
|
||||
);
|
||||
$form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
|
||||
|
||||
if ($edit['cid']) {
|
||||
$form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
|
||||
$form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate aggregator_form_feed form submissions.
|
||||
*/
|
||||
function aggregator_form_category_validate($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Save')) {
|
||||
// Check for duplicate titles
|
||||
if (isset($form_state['values']['cid'])) {
|
||||
$category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s' AND cid <> %d", $form_state['values']['title'], $form_state['values']['cid']));
|
||||
}
|
||||
else {
|
||||
$category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s'", $form_state['values']['title']));
|
||||
}
|
||||
if ($category) {
|
||||
form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_state['values']['title'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process aggregator_form_category form submissions.
|
||||
*
|
||||
* @todo Add delete confirmation dialog.
|
||||
*/
|
||||
function aggregator_form_category_submit($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Delete')) {
|
||||
$title = $form_state['values']['title'];
|
||||
// Unset the title:
|
||||
unset($form_state['values']['title']);
|
||||
}
|
||||
aggregator_save_category($form_state['values']);
|
||||
if (isset($form_state['values']['cid'])) {
|
||||
if (isset($form_state['values']['title'])) {
|
||||
drupal_set_message(t('The category %category has been updated.', array('%category' => $form_state['values']['title'])));
|
||||
if (arg(0) == 'admin') {
|
||||
$form_state['redirect'] = 'admin/content/aggregator/';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$form_state['redirect'] = 'aggregator/categories/'. $form_state['values']['cid'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
watchdog('aggregator', 'Category %category deleted.', array('%category' => $title));
|
||||
drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
|
||||
if (arg(0) == 'admin') {
|
||||
$form_state['redirect'] = 'admin/content/aggregator/';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$form_state['redirect'] = 'aggregator/categories/';
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
watchdog('aggregator', 'Category %category added.', array('%category' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator'));
|
||||
drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
#aggregator .feed-source .feed-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
#aggregator .feed-source .feed-image img {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
#aggregator .feed-source .feed-icon {
|
||||
float: right; /* LTR */
|
||||
display: block;
|
||||
}
|
||||
#aggregator .feed-item {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
#aggregator .feed-item-title {
|
||||
margin-bottom: 0;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
#aggregator .feed-item-meta, #aggregator .feed-item-body {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
#aggregator .feed-item-categories {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#aggregator td {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
#aggregator td.categorize-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#aggregator .categorize-item .news-item .body {
|
||||
margin-top: 0;
|
||||
}
|
||||
#aggregator .categorize-item h3 {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 0;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Aggregator
|
||||
description = "Aggregates syndicated content (RSS, RDF, and Atom feeds)."
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function aggregator_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('aggregator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function aggregator_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('aggregator');
|
||||
|
||||
variable_del('aggregator_allowed_html_tags');
|
||||
variable_del('aggregator_summary_items');
|
||||
variable_del('aggregator_clear');
|
||||
variable_del('aggregator_category_selector');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function aggregator_schema() {
|
||||
$schema['aggregator_category'] = array(
|
||||
'description' => 'Stores categories for aggregator feeds and feed items.',
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique aggregator category ID.',
|
||||
),
|
||||
'title' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Title of the category.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Description of the category',
|
||||
),
|
||||
'block' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'The number of recent items to show within the category block.',
|
||||
)
|
||||
),
|
||||
'primary key' => array('cid'),
|
||||
'unique keys' => array('title' => array('title')),
|
||||
);
|
||||
|
||||
$schema['aggregator_category_feed'] = array(
|
||||
'description' => 'Bridge table; maps feeds to categories.',
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => "The feed's {aggregator_feed}.fid.",
|
||||
),
|
||||
'cid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {aggregator_category}.cid to which the feed is being assigned.',
|
||||
)
|
||||
),
|
||||
'primary key' => array('cid', 'fid'),
|
||||
'indexes' => array('fid' => array('fid')),
|
||||
);
|
||||
|
||||
$schema['aggregator_category_item'] = array(
|
||||
'description' => 'Bridge table; maps feed items to categories.',
|
||||
'fields' => array(
|
||||
'iid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => "The feed item's {aggregator_item}.iid.",
|
||||
),
|
||||
'cid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {aggregator_category}.cid to which the feed item is being assigned.',
|
||||
)
|
||||
),
|
||||
'primary key' => array('cid', 'iid'),
|
||||
'indexes' => array('iid' => array('iid')),
|
||||
);
|
||||
|
||||
$schema['aggregator_feed'] = array(
|
||||
'description' => 'Stores feeds to be parsed by the aggregator.',
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique feed ID.',
|
||||
),
|
||||
'title' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Title of the feed.',
|
||||
),
|
||||
'url' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'URL to the feed.',
|
||||
),
|
||||
'refresh' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'How often to check for new feed items, in seconds.',
|
||||
),
|
||||
'checked' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Last time feed was checked for new items, as Unix timestamp.',
|
||||
),
|
||||
'link' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The parent website of the feed; comes from the <link> element in the feed.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => "The parent website's description; comes from the <description> element in the feed.",
|
||||
),
|
||||
'image' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'An image representing the feed.',
|
||||
),
|
||||
'etag' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Entity tag HTTP response header, used for validating cache.',
|
||||
),
|
||||
'modified' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'When the feed was last modified, as a Unix timestamp.',
|
||||
),
|
||||
'block' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => "Number of items to display in the feed's block.",
|
||||
)
|
||||
),
|
||||
'primary key' => array('fid'),
|
||||
'unique keys' => array(
|
||||
'url' => array('url'),
|
||||
'title' => array('title'),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['aggregator_item'] = array(
|
||||
'description' => 'Stores the individual items imported from feeds.',
|
||||
'fields' => array(
|
||||
'iid' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique ID for feed item.',
|
||||
),
|
||||
'fid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {aggregator_feed}.fid to which this item belongs.',
|
||||
),
|
||||
'title' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Title of the feed item.',
|
||||
),
|
||||
'link' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Link to the feed item.',
|
||||
),
|
||||
'author' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Author of the feed item.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Body of the feed item.',
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Post date of feed item, as a Unix timestamp.',
|
||||
),
|
||||
'guid' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Unique identifier for the feed item.',
|
||||
)
|
||||
),
|
||||
'primary key' => array('iid'),
|
||||
'indexes' => array('fid' => array('fid')),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
|
@ -1,940 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Used to aggregate syndicated content (RSS, RDF, and Atom).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function aggregator_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#aggregator':
|
||||
$output = '<p>'. t('The aggregator is a powerful on-site syndicator and news reader that gathers fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>';
|
||||
$output .= '<p>'. t('Feeds contain feed items, or individual posts published by the site providing the feed. Feeds may be grouped in categories, generally by topic. Users view feed items in the <a href="@aggregator">main aggregator display</a> or by <a href="@aggregator-sources">their source</a>. Administrators can <a href="@feededit">add, edit and delete feeds</a> and choose how often to check each feed for newly updated items. The most recent items in either a feed or category can be displayed as a block through the <a href="@admin-block">blocks administration page</a>. A <a href="@aggregator-opml">machine-readable OPML file</a> of all feeds is available. A correctly configured <a href="@cron">cron maintenance task</a> is required to update feeds automatically.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@feededit' => url('admin/content/aggregator'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator/opml'), '@cron' => url('admin/reports/status'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@aggregator">Aggregator module</a>.', array('@aggregator' => 'http://drupal.org/handbook/modules/aggregator/')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/content/aggregator':
|
||||
$output = '<p>'. t('Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>';
|
||||
$output .= '<p>'. t('Current feeds are listed below, and <a href="@addfeed">new feeds may be added</a>. For each feed or feed category, the <em>latest items</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@addfeed' => url('admin/content/aggregator/add/feed'), '@block' => url('admin/build/block'))) .'</p>';
|
||||
return $output;
|
||||
case 'admin/content/aggregator/add/feed':
|
||||
return '<p>'. t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') .'</p>';
|
||||
case 'admin/content/aggregator/add/category':
|
||||
return '<p>'. t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named <em>Sports</em>. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the <em>Categorize</em> page available from feed item listings). Each category provides its own feed page and block.') .'</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme()
|
||||
*/
|
||||
function aggregator_theme() {
|
||||
return array(
|
||||
'aggregator_wrapper' => array(
|
||||
'arguments' => array('content' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
'template' => 'aggregator-wrapper',
|
||||
),
|
||||
'aggregator_categorize_items' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
),
|
||||
'aggregator_feed_source' => array(
|
||||
'arguments' => array('feed' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
'template' => 'aggregator-feed-source',
|
||||
),
|
||||
'aggregator_block_item' => array(
|
||||
'arguments' => array('item' => NULL, 'feed' => 0),
|
||||
),
|
||||
'aggregator_summary_items' => array(
|
||||
'arguments' => array('summary_items' => NULL, 'source' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
'template' => 'aggregator-summary-items',
|
||||
),
|
||||
'aggregator_summary_item' => array(
|
||||
'arguments' => array('item' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
'template' => 'aggregator-summary-item',
|
||||
),
|
||||
'aggregator_item' => array(
|
||||
'arguments' => array('item' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
'template' => 'aggregator-item',
|
||||
),
|
||||
'aggregator_page_opml' => array(
|
||||
'arguments' => array('feeds' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
),
|
||||
'aggregator_page_rss' => array(
|
||||
'arguments' => array('feeds' => NULL, 'category' => NULL),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function aggregator_menu() {
|
||||
$items['admin/content/aggregator'] = array(
|
||||
'title' => 'Feed aggregator',
|
||||
'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.",
|
||||
'page callback' => 'aggregator_admin_overview',
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/add/feed'] = array(
|
||||
'title' => 'Add feed',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_feed'),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'parent' => 'admin/content/aggregator',
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/add/category'] = array(
|
||||
'title' => 'Add category',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_category'),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'parent' => 'admin/content/aggregator',
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/remove/%aggregator_feed'] = array(
|
||||
'title' => 'Remove items',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_admin_remove_feed', 4),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/update/%aggregator_feed'] = array(
|
||||
'title' => 'Update items',
|
||||
'page callback' => 'aggregator_admin_refresh_feed',
|
||||
'page arguments' => array(4),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/list'] = array(
|
||||
'title' => 'List',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -10,
|
||||
);
|
||||
$items['admin/content/aggregator/settings'] = array(
|
||||
'title' => 'Settings',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_admin_settings'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 10,
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['aggregator'] = array(
|
||||
'title' => 'Feed aggregator',
|
||||
'page callback' => 'aggregator_page_last',
|
||||
'access arguments' => array('access news feeds'),
|
||||
'weight' => 5,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/sources'] = array(
|
||||
'title' => 'Sources',
|
||||
'page callback' => 'aggregator_page_sources',
|
||||
'access arguments' => array('access news feeds'),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/categories'] = array(
|
||||
'title' => 'Categories',
|
||||
'page callback' => 'aggregator_page_categories',
|
||||
'access callback' => '_aggregator_has_categories',
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/rss'] = array(
|
||||
'title' => 'RSS feed',
|
||||
'page callback' => 'aggregator_page_rss',
|
||||
'access arguments' => array('access news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/opml'] = array(
|
||||
'title' => 'OPML feed',
|
||||
'page callback' => 'aggregator_page_opml',
|
||||
'access arguments' => array('access news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/categories/%aggregator_category'] = array(
|
||||
'title callback' => '_aggregator_category_title',
|
||||
'title arguments' => array(2),
|
||||
'page callback' => 'aggregator_page_category',
|
||||
'page arguments' => array(2),
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('access news feeds'),
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/categories/%aggregator_category/view'] = array(
|
||||
'title' => 'View',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -10,
|
||||
);
|
||||
$items['aggregator/categories/%aggregator_category/categorize'] = array(
|
||||
'title' => 'Categorize',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_page_category', 2),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/categories/%aggregator_category/configure'] = array(
|
||||
'title' => 'Configure',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_category', 2),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 1,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['aggregator/sources/%aggregator_feed'] = array(
|
||||
'page callback' => 'aggregator_page_source',
|
||||
'page arguments' => array(2),
|
||||
'access arguments' => array('access news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/sources/%aggregator_feed/view'] = array(
|
||||
'title' => 'View',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -10,
|
||||
);
|
||||
$items['aggregator/sources/%aggregator_feed/categorize'] = array(
|
||||
'title' => 'Categorize',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_page_source', 2),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'aggregator.pages.inc',
|
||||
);
|
||||
$items['aggregator/sources/%aggregator_feed/configure'] = array(
|
||||
'title' => 'Configure',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_feed', 2),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 1,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/edit/feed/%aggregator_feed'] = array(
|
||||
'title' => 'Edit feed',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_feed', 5),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
$items['admin/content/aggregator/edit/category/%aggregator_category'] = array(
|
||||
'title' => 'Edit category',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('aggregator_form_category', 5),
|
||||
'access arguments' => array('administer news feeds'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'aggregator.admin.inc',
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback.
|
||||
*
|
||||
* @return
|
||||
* An aggregator category title.
|
||||
*/
|
||||
function _aggregator_category_title($category) {
|
||||
return $category['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_init().
|
||||
*/
|
||||
function aggregator_init() {
|
||||
drupal_add_css(drupal_get_path('module', 'aggregator') .'/aggregator.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find out whether there are any aggregator categories.
|
||||
*
|
||||
* @return
|
||||
* TRUE if there is at least one category and the user has access to them, FALSE otherwise.
|
||||
*/
|
||||
function _aggregator_has_categories() {
|
||||
return user_access('access news feeds') && db_result(db_query('SELECT COUNT(*) FROM {aggregator_category}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function aggregator_perm() {
|
||||
return array('administer news feeds', 'access news feeds');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*
|
||||
* Checks news feeds for updates once their refresh interval has elapsed.
|
||||
*/
|
||||
function aggregator_cron() {
|
||||
$result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time());
|
||||
while ($feed = db_fetch_array($result)) {
|
||||
aggregator_refresh($feed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_block().
|
||||
*
|
||||
* Generates blocks for the latest news items in each category and feed.
|
||||
*/
|
||||
function aggregator_block($op = 'list', $delta = 0, $edit = array()) {
|
||||
if ($op == 'list') {
|
||||
$result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
|
||||
while ($category = db_fetch_object($result)) {
|
||||
$block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
|
||||
}
|
||||
$result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid');
|
||||
while ($feed = db_fetch_object($result)) {
|
||||
$block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
|
||||
}
|
||||
}
|
||||
else if ($op == 'configure') {
|
||||
list($type, $id) = explode('-', $delta);
|
||||
if ($type == 'category') {
|
||||
$value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id));
|
||||
}
|
||||
else {
|
||||
$value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id));
|
||||
}
|
||||
$form['block'] = array('#type' => 'select', '#title' => t('Number of news items in block'), '#default_value' => $value, '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
|
||||
return $form;
|
||||
}
|
||||
else if ($op == 'save') {
|
||||
list($type, $id) = explode('-', $delta);
|
||||
if ($type == 'category') {
|
||||
$value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id);
|
||||
}
|
||||
else {
|
||||
$value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id);
|
||||
}
|
||||
}
|
||||
else if ($op == 'view') {
|
||||
if (user_access('access news feeds')) {
|
||||
list($type, $id) = explode('-', $delta);
|
||||
switch ($type) {
|
||||
case 'feed':
|
||||
if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) {
|
||||
$block['subject'] = check_plain($feed->title);
|
||||
$result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
|
||||
$read_more = theme('more_link', url('aggregator/sources/'. $feed->fid), t("View this feed's recent news."));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'category':
|
||||
if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) {
|
||||
$block['subject'] = check_plain($category->title);
|
||||
$result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block);
|
||||
$read_more = theme('more_link', url('aggregator/categories/'. $category->cid), t("View this category's recent news."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
$items = array();
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$items[] = theme('aggregator_block_item', $item);
|
||||
}
|
||||
|
||||
// Only display the block if there are items to show.
|
||||
if (count($items) > 0) {
|
||||
$block['content'] = theme('item_list', $items) . $read_more;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($block)) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/edit/delete aggregator categories.
|
||||
*
|
||||
* @param $edit
|
||||
* An associative array describing the category to be added/edited/deleted.
|
||||
*/
|
||||
function aggregator_save_category($edit) {
|
||||
$link_path = 'aggregator/categories/';
|
||||
if (!empty($edit['cid'])) {
|
||||
$link_path .= $edit['cid'];
|
||||
if (!empty($edit['title'])) {
|
||||
db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']);
|
||||
$op = 'update';
|
||||
}
|
||||
else {
|
||||
db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']);
|
||||
// Make sure there is no active block for this category.
|
||||
db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'aggregator', 'category-' . $edit['cid']);
|
||||
$edit['title'] = '';
|
||||
$op = 'delete';
|
||||
}
|
||||
}
|
||||
else if (!empty($edit['title'])) {
|
||||
// A single unique id for bundles and feeds, to use in blocks
|
||||
db_query("INSERT INTO {aggregator_category} (title, description, block) VALUES ('%s', '%s', 5)", $edit['title'], $edit['description']);
|
||||
$link_path .= db_last_insert_id('aggregator_category', 'cid');
|
||||
$op = 'insert';
|
||||
}
|
||||
if (isset($op)) {
|
||||
menu_link_maintain('aggregator', $op, $link_path, $edit['title']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/edit/delete an aggregator feed.
|
||||
*
|
||||
* @param $edit
|
||||
* An associative array describing the feed to be added/edited/deleted.
|
||||
*/
|
||||
function aggregator_save_feed($edit) {
|
||||
if (!empty($edit['fid'])) {
|
||||
// An existing feed is being modified, delete the category listings.
|
||||
db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
|
||||
}
|
||||
if (!empty($edit['fid']) && !empty($edit['title'])) {
|
||||
db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
|
||||
}
|
||||
else if (!empty($edit['fid'])) {
|
||||
$items = array();
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$items[] = "iid = $item->iid";
|
||||
}
|
||||
if (!empty($items)) {
|
||||
db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
|
||||
}
|
||||
db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
|
||||
db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
|
||||
// Make sure there is no active block for this feed.
|
||||
db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'aggregator', 'feed-' . $edit['fid']);
|
||||
}
|
||||
else if (!empty($edit['title'])) {
|
||||
db_query("INSERT INTO {aggregator_feed} (title, url, refresh, block, description, image) VALUES ('%s', '%s', %d, 5, '', '')", $edit['title'], $edit['url'], $edit['refresh']);
|
||||
// A single unique id for bundles and feeds, to use in blocks.
|
||||
$edit['fid'] = db_last_insert_id('aggregator_feed', 'fid');
|
||||
}
|
||||
if (!empty($edit['title'])) {
|
||||
// The feed is being saved, save the categories as well.
|
||||
if (!empty($edit['category'])) {
|
||||
foreach ($edit['category'] as $cid => $value) {
|
||||
if ($value) {
|
||||
db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from a feed.
|
||||
*
|
||||
* @param $feed
|
||||
* An associative array describing the feed to be cleared.
|
||||
*/
|
||||
function aggregator_remove($feed) {
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$items[] = "iid = $item->iid";
|
||||
}
|
||||
if (!empty($items)) {
|
||||
db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
|
||||
}
|
||||
db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
|
||||
db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
|
||||
drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call-back function used by the XML parser.
|
||||
*/
|
||||
function aggregator_element_start($parser, $name, $attributes) {
|
||||
global $item, $element, $tag, $items, $channel;
|
||||
|
||||
switch ($name) {
|
||||
case 'IMAGE':
|
||||
case 'TEXTINPUT':
|
||||
case 'CONTENT':
|
||||
case 'SUMMARY':
|
||||
case 'TAGLINE':
|
||||
case 'SUBTITLE':
|
||||
case 'LOGO':
|
||||
case 'INFO':
|
||||
$element = $name;
|
||||
break;
|
||||
case 'ID':
|
||||
if ($element != 'ITEM') {
|
||||
$element = $name;
|
||||
}
|
||||
case 'LINK':
|
||||
if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') {
|
||||
if ($element == 'ITEM') {
|
||||
$items[$item]['LINK'] = $attributes['HREF'];
|
||||
}
|
||||
else {
|
||||
$channel['LINK'] = $attributes['HREF'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ITEM':
|
||||
$element = $name;
|
||||
$item += 1;
|
||||
break;
|
||||
case 'ENTRY':
|
||||
$element = 'ITEM';
|
||||
$item += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
$tag = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call-back function used by the XML parser.
|
||||
*/
|
||||
function aggregator_element_end($parser, $name) {
|
||||
global $element;
|
||||
|
||||
switch ($name) {
|
||||
case 'IMAGE':
|
||||
case 'TEXTINPUT':
|
||||
case 'ITEM':
|
||||
case 'ENTRY':
|
||||
case 'CONTENT':
|
||||
case 'INFO':
|
||||
$element = '';
|
||||
break;
|
||||
case 'ID':
|
||||
if ($element == 'ID') {
|
||||
$element = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call-back function used by the XML parser.
|
||||
*/
|
||||
function aggregator_element_data($parser, $data) {
|
||||
global $channel, $element, $items, $item, $image, $tag;
|
||||
$items += array($item => array());
|
||||
switch ($element) {
|
||||
case 'ITEM':
|
||||
$items[$item] += array($tag => '');
|
||||
$items[$item][$tag] .= $data;
|
||||
break;
|
||||
case 'IMAGE':
|
||||
case 'LOGO':
|
||||
$image += array($tag => '');
|
||||
$image[$tag] .= $data;
|
||||
break;
|
||||
case 'LINK':
|
||||
if ($data) {
|
||||
$items[$item] += array($tag => '');
|
||||
$items[$item][$tag] .= $data;
|
||||
}
|
||||
break;
|
||||
case 'CONTENT':
|
||||
$items[$item] += array('CONTENT' => '');
|
||||
$items[$item]['CONTENT'] .= $data;
|
||||
break;
|
||||
case 'SUMMARY':
|
||||
$items[$item] += array('SUMMARY' => '');
|
||||
$items[$item]['SUMMARY'] .= $data;
|
||||
break;
|
||||
case 'TAGLINE':
|
||||
case 'SUBTITLE':
|
||||
$channel += array('DESCRIPTION' => '');
|
||||
$channel['DESCRIPTION'] .= $data;
|
||||
break;
|
||||
case 'INFO':
|
||||
case 'ID':
|
||||
case 'TEXTINPUT':
|
||||
// The sub-element is not supported. However, we must recognize
|
||||
// it or its contents will end up in the item array.
|
||||
break;
|
||||
default:
|
||||
$channel += array($tag => '');
|
||||
$channel[$tag] .= $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a news feed for new items.
|
||||
*
|
||||
* @param $feed
|
||||
* An associative array describing the feed to be refreshed.
|
||||
*/
|
||||
function aggregator_refresh($feed) {
|
||||
global $channel, $image;
|
||||
|
||||
// Generate conditional GET headers.
|
||||
$headers = array();
|
||||
if ($feed['etag']) {
|
||||
$headers['If-None-Match'] = $feed['etag'];
|
||||
}
|
||||
if ($feed['modified']) {
|
||||
$headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT';
|
||||
}
|
||||
|
||||
// Request feed.
|
||||
$result = drupal_http_request($feed['url'], $headers);
|
||||
|
||||
// Process HTTP response code.
|
||||
switch ($result->code) {
|
||||
case 304:
|
||||
db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
|
||||
drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
|
||||
break;
|
||||
case 301:
|
||||
$feed['url'] = $result->redirect_url;
|
||||
watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
|
||||
// Deliberate no break.
|
||||
case 200:
|
||||
case 302:
|
||||
case 307:
|
||||
// Filter the input data:
|
||||
if (aggregator_parse_feed($result->data, $feed)) {
|
||||
$modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
|
||||
|
||||
// Prepare the channel data.
|
||||
foreach ($channel as $key => $value) {
|
||||
$channel[$key] = trim($value);
|
||||
}
|
||||
|
||||
// Prepare the image data (if any).
|
||||
foreach ($image as $key => $value) {
|
||||
$image[$key] = trim($value);
|
||||
}
|
||||
|
||||
if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) {
|
||||
// Note, we should really use theme_image() here but that only works with local images it won't work with images fetched with a URL unless PHP version > 5
|
||||
$image = '<a href="'. check_url($image['LINK']) .'" class="feed-image"><img src="'. check_url($image['URL']) .'" alt="'. check_plain($image['TITLE']) .'" /></a>';
|
||||
}
|
||||
else {
|
||||
$image = NULL;
|
||||
}
|
||||
|
||||
$etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
|
||||
// Update the feed data.
|
||||
db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel['LINK'], $channel['DESCRIPTION'], $image, $etag, $modified, $feed['fid']);
|
||||
|
||||
// Clear the cache.
|
||||
cache_clear_all();
|
||||
|
||||
watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
|
||||
drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error), WATCHDOG_WARNING);
|
||||
drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
|
||||
* functions do not handle this format.
|
||||
* See http://www.w3.org/TR/NOTE-datetime for more information.
|
||||
* Originally from MagpieRSS (http://magpierss.sourceforge.net/).
|
||||
*
|
||||
* @param $date_str
|
||||
* A string with a potentially W3C DTF date.
|
||||
* @return
|
||||
* A timestamp if parsed successfully or FALSE if not.
|
||||
*/
|
||||
function aggregator_parse_w3cdtf($date_str) {
|
||||
if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
|
||||
list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
|
||||
// calc epoch for current date assuming GMT
|
||||
$epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
|
||||
if ($match[10] != 'Z') { // Z is zulu time, aka GMT
|
||||
list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
|
||||
// zero out the variables
|
||||
if (!$tz_hour) {
|
||||
$tz_hour = 0;
|
||||
}
|
||||
if (!$tz_min) {
|
||||
$tz_min = 0;
|
||||
}
|
||||
$offset_secs = (($tz_hour * 60) + $tz_min) * 60;
|
||||
// is timezone ahead of GMT? then subtract offset
|
||||
if ($tz_mod == '+') {
|
||||
$offset_secs *= -1;
|
||||
}
|
||||
$epoch += $offset_secs;
|
||||
}
|
||||
return $epoch;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a feed and store its items.
|
||||
*
|
||||
* @param $data
|
||||
* The feed data.
|
||||
* @param $feed
|
||||
* An associative array describing the feed to be parsed.
|
||||
* @return
|
||||
* 0 on error, 1 otherwise.
|
||||
*/
|
||||
function aggregator_parse_feed(&$data, $feed) {
|
||||
global $items, $image, $channel;
|
||||
|
||||
// Unset the global variables before we use them:
|
||||
unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
|
||||
$items = array();
|
||||
$image = array();
|
||||
$channel = array();
|
||||
|
||||
// parse the data:
|
||||
$xml_parser = drupal_xml_parser_create($data);
|
||||
xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
|
||||
xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
|
||||
|
||||
if (!xml_parse($xml_parser, $data, 1)) {
|
||||
watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
|
||||
drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
|
||||
return 0;
|
||||
}
|
||||
xml_parser_free($xml_parser);
|
||||
|
||||
// We reverse the array such that we store the first item last, and the last
|
||||
// item first. In the database, the newest item should be at the top.
|
||||
$items = array_reverse($items);
|
||||
|
||||
// Initialize variables.
|
||||
$title = $link = $author = $description = $guid = NULL;
|
||||
foreach ($items as $item) {
|
||||
unset($title, $link, $author, $description, $guid);
|
||||
|
||||
// Prepare the item:
|
||||
foreach ($item as $key => $value) {
|
||||
$item[$key] = trim($value);
|
||||
}
|
||||
|
||||
// Resolve the item's title. If no title is found, we use up to 40
|
||||
// characters of the description ending at a word boundary but not
|
||||
// splitting potential entities.
|
||||
if (!empty($item['TITLE'])) {
|
||||
$title = $item['TITLE'];
|
||||
}
|
||||
elseif (!empty($item['DESCRIPTION'])) {
|
||||
$title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40));
|
||||
}
|
||||
else {
|
||||
$title = '';
|
||||
}
|
||||
|
||||
// Resolve the items link.
|
||||
if (!empty($item['LINK'])) {
|
||||
$link = $item['LINK'];
|
||||
}
|
||||
else {
|
||||
$link = $feed['link'];
|
||||
}
|
||||
|
||||
// Atom feeds use ID rather than GUID.
|
||||
if (isset($item['GUID'])) {
|
||||
$guid = $item['GUID'];
|
||||
}
|
||||
elseif (isset($item['ID'])) {
|
||||
$guid = $item['ID'];
|
||||
}
|
||||
else {
|
||||
$guid = '';
|
||||
}
|
||||
|
||||
// Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag.
|
||||
if (!empty($item['CONTENT:ENCODED'])) {
|
||||
$item['DESCRIPTION'] = $item['CONTENT:ENCODED'];
|
||||
}
|
||||
else if (!empty($item['SUMMARY'])) {
|
||||
$item['DESCRIPTION'] = $item['SUMMARY'];
|
||||
}
|
||||
else if (!empty($item['CONTENT'])) {
|
||||
$item['DESCRIPTION'] = $item['CONTENT'];
|
||||
}
|
||||
|
||||
// Try to resolve and parse the item's publication date. If no date is
|
||||
// found, we use the current date instead.
|
||||
$date = 'now';
|
||||
foreach (array('PUBDATE', 'DC:DATE', 'DCTERMS:ISSUED', 'DCTERMS:CREATED', 'DCTERMS:MODIFIED', 'ISSUED', 'CREATED', 'MODIFIED', 'PUBLISHED', 'UPDATED') as $key) {
|
||||
if (!empty($item[$key])) {
|
||||
$date = $item[$key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1.
|
||||
if ($timestamp <= 0) {
|
||||
$timestamp = aggregator_parse_w3cdtf($date); // Returns FALSE on failure
|
||||
if (!$timestamp) {
|
||||
$timestamp = time(); // better than nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve dc:creator tag as the item author if author tag is not set.
|
||||
if (empty($item['AUTHOR']) && !empty($item['DC:CREATOR'])) {
|
||||
$item['AUTHOR'] = $item['DC:CREATOR'];
|
||||
}
|
||||
|
||||
// Save this item. Try to avoid duplicate entries as much as possible. If
|
||||
// we find a duplicate entry, we resolve it and pass along its ID is such
|
||||
// that we can update it if needed.
|
||||
if (!empty($guid)) {
|
||||
$entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid));
|
||||
}
|
||||
else if ($link && $link != $feed['link'] && $link != $feed['url']) {
|
||||
$entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link));
|
||||
}
|
||||
else {
|
||||
$entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title));
|
||||
}
|
||||
$item += array('AUTHOR' => '', 'DESCRIPTION' => '');
|
||||
aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid: ''), 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid));
|
||||
}
|
||||
|
||||
// Remove all items that are older than flush item timer.
|
||||
$age = time() - variable_get('aggregator_clear', 9676800);
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
|
||||
|
||||
$items = array();
|
||||
$num_rows = FALSE;
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$items[] = $item->iid;
|
||||
$num_rows = TRUE;
|
||||
}
|
||||
if ($num_rows) {
|
||||
db_query('DELETE FROM {aggregator_category_item} WHERE iid IN ('. implode(', ', $items) .')');
|
||||
db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/edit/delete an aggregator item.
|
||||
*
|
||||
* @param $edit
|
||||
* An associative array describing the item to be added/edited/deleted.
|
||||
*/
|
||||
function aggregator_save_item($edit) {
|
||||
if ($edit['iid'] && $edit['title']) {
|
||||
db_query("UPDATE {aggregator_item} SET title = '%s', link = '%s', author = '%s', description = '%s', guid = '%s', timestamp = %d WHERE iid = %d", $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['guid'], $edit['timestamp'], $edit['iid']);
|
||||
}
|
||||
else if ($edit['iid']) {
|
||||
db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']);
|
||||
db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']);
|
||||
}
|
||||
else if ($edit['title'] && $edit['link']) {
|
||||
db_query("INSERT INTO {aggregator_item} (fid, title, link, author, description, timestamp, guid) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s')", $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp'], $edit['guid']);
|
||||
$edit['iid'] = db_last_insert_id('aggregator_item', 'iid');
|
||||
// file the items in the categories indicated by the feed
|
||||
$categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
|
||||
while ($category = db_fetch_object($categories)) {
|
||||
db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an aggregator feed.
|
||||
*
|
||||
* @param $fid
|
||||
* The feed id.
|
||||
* @return
|
||||
* An associative array describing the feed.
|
||||
*/
|
||||
function aggregator_feed_load($fid) {
|
||||
static $feeds;
|
||||
if (!isset($feeds[$fid])) {
|
||||
$feeds[$fid] = db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid));
|
||||
}
|
||||
return $feeds[$fid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an aggregator category.
|
||||
*
|
||||
* @param $cid
|
||||
* The category id.
|
||||
* @return
|
||||
* An associative array describing the category.
|
||||
*/
|
||||
function aggregator_category_load($cid) {
|
||||
static $categories;
|
||||
if (!isset($categories[$cid])) {
|
||||
$categories[$cid] = db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid));
|
||||
}
|
||||
return $categories[$cid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an individual feed item for display in the block.
|
||||
*
|
||||
* @param $item
|
||||
* The item to be displayed.
|
||||
* @param $feed
|
||||
* Not used.
|
||||
* @return
|
||||
* The item HTML.
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_aggregator_block_item($item, $feed = 0) {
|
||||
global $user;
|
||||
|
||||
$output = '';
|
||||
if ($user->uid && module_exists('blog') && user_access('create blog entries')) {
|
||||
if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) {
|
||||
$output .= '<div class="icon">'. l($image, 'node/add/blog', array('attributes' => array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), 'query' => "iid=$item->iid", 'html' => TRUE)) .'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Display the external link to the item.
|
||||
$output .= '<a href="'. check_url($item->link) .'">'. check_plain($item->title) ."</a>\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely render HTML content, as allowed.
|
||||
*
|
||||
* @param $value
|
||||
* The content to be filtered.
|
||||
* @return
|
||||
* The filtered content.
|
||||
*/
|
||||
function aggregator_filter_xss($value) {
|
||||
return filter_xss($value, preg_split('/\s+|<|>/', variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for drupal_map_assoc.
|
||||
*
|
||||
* @param $count
|
||||
* Items count.
|
||||
* @return
|
||||
* Plural-formatted "@count items"
|
||||
*/
|
||||
function _aggregator_items($count) {
|
||||
return format_plural($count, '1 item', '@count items');
|
||||
}
|
|
@ -1,490 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the aggregator module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback; displays the most recent items gathered from any feed.
|
||||
*
|
||||
* @return
|
||||
* The items HTML.
|
||||
*/
|
||||
function aggregator_page_last() {
|
||||
drupal_add_feed(url('aggregator/rss'), variable_get('site_name', 'Drupal') .' '. t('aggregator'));
|
||||
|
||||
$items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC');
|
||||
|
||||
return _aggregator_page_list($items, arg(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays all the items captured from a particular feed.
|
||||
*
|
||||
* If there are two arguments then this function is the categorize form.
|
||||
*
|
||||
* @param $arg1
|
||||
* If there are two arguments then $arg1 is $form_state. Otherwise, $arg1 is $feed.
|
||||
* @param $arg2
|
||||
* If there are two arguments then $arg2 is feed.
|
||||
* @return
|
||||
* The items HTML.
|
||||
*/
|
||||
function aggregator_page_source($arg1, $arg2 = NULL) {
|
||||
// If there are two arguments then this function is the categorize form, and
|
||||
// $arg1 is $form_state and $arg2 is $feed. Otherwise, $arg1 is $feed.
|
||||
$feed = is_array($arg2) ? $arg2 : $arg1;
|
||||
$feed = (object)$feed;
|
||||
drupal_set_title(check_plain($feed->title));
|
||||
$feed_source = theme('aggregator_feed_source', $feed);
|
||||
|
||||
// It is safe to include the fid in the query because it's loaded from the
|
||||
// database by aggregator_feed_load.
|
||||
$items = aggregator_feed_items_load('SELECT * FROM {aggregator_item} WHERE fid = '. $feed->fid .' ORDER BY timestamp DESC, iid DESC');
|
||||
|
||||
return _aggregator_page_list($items, arg(3), $feed_source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays all the items aggregated in a particular category.
|
||||
*
|
||||
* If there are two arguments then this function is called as a form.
|
||||
*
|
||||
* @param $arg1
|
||||
* If there are two arguments then $arg1 is $form_state. Otherwise, $arg1 is $category.
|
||||
* @param $arg2
|
||||
* If there are two arguments then $arg2 is $category.
|
||||
* @return
|
||||
* The items HTML.
|
||||
*/
|
||||
function aggregator_page_category($arg1, $arg2 = NULL) {
|
||||
// If there are two arguments then we are called as a form, $arg1 is
|
||||
// $form_state and $arg2 is $category. Otherwise, $arg1 is $category.
|
||||
$category = is_array($arg2) ? $arg2 : $arg1;
|
||||
|
||||
drupal_add_feed(url('aggregator/rss/'. $category['cid']), variable_get('site_name', 'Drupal') .' '. t('aggregator - @title', array('@title' => $category['title'])));
|
||||
|
||||
// It is safe to include the cid in the query because it's loaded from the
|
||||
// database by aggregator_category_load.
|
||||
$items = aggregator_feed_items_load('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = '. $category['cid'] .' ORDER BY timestamp DESC, i.iid DESC');
|
||||
|
||||
return _aggregator_page_list($items, arg(3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load feed items by passing a SQL query.
|
||||
*
|
||||
* @param $sql
|
||||
* The query to be executed.
|
||||
* @return
|
||||
* An array of the feed items.
|
||||
*/
|
||||
function aggregator_feed_items_load($sql) {
|
||||
$items = array();
|
||||
if (isset($sql)) {
|
||||
$result = pager_query($sql, 20);
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$result_category = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = %d ORDER BY c.title', $item->iid);
|
||||
$item->categories = array();
|
||||
while ($item_categories = db_fetch_object($result_category)) {
|
||||
$item->categories[] = $item_categories;
|
||||
}
|
||||
$items[$item->iid] = $item;
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints an aggregator page listing a number of feed items.
|
||||
*
|
||||
* Various menu callbacks use this function to print their feeds.
|
||||
*
|
||||
* @param $items
|
||||
* The items to be listed.
|
||||
* @param $op
|
||||
* Which form should be added to the items. Only 'categorize' is now recognized.
|
||||
* @param $feed_source
|
||||
* The feed source URL.
|
||||
* @return
|
||||
* The items HTML.
|
||||
*/
|
||||
function _aggregator_page_list($items, $op, $feed_source = '') {
|
||||
if (user_access('administer news feeds') && ($op == 'categorize')) {
|
||||
// Get form data.
|
||||
$output = aggregator_categorize_items($items, $feed_source);
|
||||
}
|
||||
else {
|
||||
// Assemble themed output.
|
||||
$output = $feed_source;
|
||||
foreach ($items as $item) {
|
||||
$output .= theme('aggregator_item', $item);
|
||||
}
|
||||
$output = theme('aggregator_wrapper', $output);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; build the page list form.
|
||||
*
|
||||
* @param $items
|
||||
* An array of the feed items.
|
||||
* @param $feed_source
|
||||
* The feed source URL.
|
||||
* @return
|
||||
* The form structure.
|
||||
* @ingroup forms
|
||||
* @see aggregator_categorize_items_validate()
|
||||
* @see aggregator_categorize_items_submit()
|
||||
*/
|
||||
function aggregator_categorize_items($items, $feed_source = '') {
|
||||
$form['#submit'][] = 'aggregator_categorize_items_submit';
|
||||
$form['#validate'][] = 'aggregator_categorize_items_validate';
|
||||
$form['#theme'] = 'aggregator_categorize_items';
|
||||
$form['feed_source'] = array('#value' => $feed_source);
|
||||
$categories = array();
|
||||
$done = FALSE;
|
||||
$form['items'] = array();
|
||||
$form['categories'] = array('#tree' => TRUE);
|
||||
foreach ($items as $item) {
|
||||
$form['items'][$item->iid] = array('#value' => theme('aggregator_item', $item));
|
||||
$form['categories'][$item->iid] = array();
|
||||
$categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid);
|
||||
$selected = array();
|
||||
while ($category = db_fetch_object($categories_result)) {
|
||||
if (!$done) {
|
||||
$categories[$category->cid] = check_plain($category->title);
|
||||
}
|
||||
if ($category->iid) {
|
||||
$selected[] = $category->cid;
|
||||
}
|
||||
}
|
||||
$done = TRUE;
|
||||
$form['categories'][$item->iid] = array(
|
||||
'#type' => variable_get('aggregator_category_selector', 'checkboxes'),
|
||||
'#default_value' => $selected,
|
||||
'#options' => $categories,
|
||||
'#size' => 10,
|
||||
'#multiple' => TRUE
|
||||
);
|
||||
}
|
||||
$form['submit'] = array('#type' => 'submit', '#value' => t('Save categories'));
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate aggregator_categorize_items form submissions.
|
||||
*/
|
||||
function aggregator_categorize_items_validate($form, &$form_state) {
|
||||
if (!user_access('administer news feeds')) {
|
||||
form_error($form, t('You are not allowed to categorize this feed item.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process aggregator_categorize_items form submissions.
|
||||
*/
|
||||
function aggregator_categorize_items_submit($form, &$form_state) {
|
||||
if (!empty($form_state['values']['categories'])) {
|
||||
foreach ($form_state['values']['categories'] as $iid => $selection) {
|
||||
db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $iid);
|
||||
foreach ($selection as $cid) {
|
||||
if ($cid) {
|
||||
db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $cid, $iid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drupal_set_message(t('The categories have been saved.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the page list form for assigning categories.
|
||||
*
|
||||
* @param $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @return
|
||||
* The output HTML.
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_aggregator_categorize_items($form) {
|
||||
$output = drupal_render($form['feed_source']);
|
||||
$rows = array();
|
||||
if ($form['items']) {
|
||||
foreach (element_children($form['items']) as $key) {
|
||||
if (is_array($form['items'][$key])) {
|
||||
$rows[] = array(
|
||||
drupal_render($form['items'][$key]),
|
||||
array('data' => drupal_render($form['categories'][$key]), 'class' => 'categorize-item'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$output .= theme('table', array('', t('Categorize')), $rows);
|
||||
$output .= drupal_render($form['submit']);
|
||||
$output .= drupal_render($form);
|
||||
return theme('aggregator_wrapper', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process variables for aggregator-wrapper.tpl.php.
|
||||
*
|
||||
* @see aggregator-wrapper.tpl.php
|
||||
*/
|
||||
function template_preprocess_aggregator_wrapper(&$variables) {
|
||||
$variables['pager'] = theme('pager', NULL, 20, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process variables for aggregator-item.tpl.php.
|
||||
*
|
||||
* @see aggregator-item.tpl.php
|
||||
*/
|
||||
function template_preprocess_aggregator_item(&$variables) {
|
||||
$item = $variables['item'];
|
||||
|
||||
$variables['feed_url'] = check_url($item->link);
|
||||
$variables['feed_title'] = check_plain($item->title);
|
||||
$variables['content'] = aggregator_filter_xss($item->description);
|
||||
|
||||
$variables['source_url'] = '';
|
||||
$variables['source_title'] = '';
|
||||
if (isset($item->ftitle) && isset($item->fid)) {
|
||||
$variables['source_url'] = url("aggregator/sources/$item->fid");
|
||||
$variables['source_title'] = check_plain($item->ftitle);
|
||||
}
|
||||
if (date('Ymd', $item->timestamp) == date('Ymd')) {
|
||||
$variables['source_date'] = t('%ago ago', array('%ago' => format_interval(time() - $item->timestamp)));
|
||||
}
|
||||
else {
|
||||
$variables['source_date'] = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i'));
|
||||
}
|
||||
|
||||
$variables['categories'] = array();
|
||||
foreach ($item->categories as $category) {
|
||||
$variables['categories'][$category->cid] = l($category->title, 'aggregator/categories/'. $category->cid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays all the feeds used by the aggregator.
|
||||
*/
|
||||
function aggregator_page_sources() {
|
||||
$result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
|
||||
|
||||
$output = '';
|
||||
while ($feed = db_fetch_object($result)) {
|
||||
// Most recent items:
|
||||
$summary_items = array();
|
||||
if (variable_get('aggregator_summary_items', 3)) {
|
||||
$items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, variable_get('aggregator_summary_items', 3));
|
||||
while ($item = db_fetch_object($items)) {
|
||||
$summary_items[] = theme('aggregator_summary_item', $item);
|
||||
}
|
||||
}
|
||||
$feed->url = url('aggregator/sources/'. $feed->fid);
|
||||
$output .= theme('aggregator_summary_items', $summary_items, $feed);
|
||||
}
|
||||
$output .= theme('feed_icon', url('aggregator/opml'), t('OPML feed'));
|
||||
|
||||
return theme('aggregator_wrapper', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays all the categories used by the aggregator.
|
||||
*/
|
||||
function aggregator_page_categories() {
|
||||
$result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description');
|
||||
|
||||
$output = '';
|
||||
while ($category = db_fetch_object($result)) {
|
||||
if (variable_get('aggregator_summary_items', 3)) {
|
||||
$summary_items = array();
|
||||
$items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = %d ORDER BY i.timestamp DESC', $category->cid, 0, variable_get('aggregator_summary_items', 3));
|
||||
while ($item = db_fetch_object($items)) {
|
||||
$summary_items[] = theme('aggregator_summary_item', $item);
|
||||
}
|
||||
}
|
||||
$category->url = url('aggregator/categories/'. $category->cid);
|
||||
$output .= theme('aggregator_summary_items', $summary_items, $category);
|
||||
}
|
||||
|
||||
return theme('aggregator_wrapper', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; generate an RSS 0.92 feed of aggregator items or categories.
|
||||
*/
|
||||
function aggregator_page_rss() {
|
||||
$result = NULL;
|
||||
// arg(2) is the passed cid, only select for that category
|
||||
if (arg(2)) {
|
||||
$category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
|
||||
$sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = %d ORDER BY timestamp DESC, i.iid DESC';
|
||||
$result = db_query_range($sql, $category->cid, 0, variable_get('feed_default_items', 10));
|
||||
}
|
||||
// or, get the default aggregator items
|
||||
else {
|
||||
$category = NULL;
|
||||
$sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC';
|
||||
$result = db_query_range($sql, 0, variable_get('feed_default_items', 10));
|
||||
}
|
||||
|
||||
$feeds = array();
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$feeds[] = $item;
|
||||
}
|
||||
return theme('aggregator_page_rss', $feeds, $category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the RSS output.
|
||||
*
|
||||
* @param $feeds
|
||||
* An array of the feeds to theme.
|
||||
* @param $category
|
||||
* A common category, if any, for all the feeds.
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_aggregator_page_rss($feeds, $category = NULL) {
|
||||
drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
|
||||
|
||||
$items = '';
|
||||
$feed_length = variable_get('feed_item_length', 'teaser');
|
||||
foreach ($feeds as $feed) {
|
||||
switch ($feed_length) {
|
||||
case 'teaser':
|
||||
$teaser = node_teaser($feed->description);
|
||||
if ($teaser != $feed->description) {
|
||||
$teaser .= '<p><a href="'. check_url($feed->link) .'">'. t('read more') ."</a></p>\n";
|
||||
}
|
||||
$feed->description = $teaser;
|
||||
break;
|
||||
case 'title':
|
||||
$feed->description = '';
|
||||
break;
|
||||
}
|
||||
$items .= format_rss_item($feed->ftitle .': '. $feed->title, $feed->link, $feed->description, array('pubDate' => date('r', $feed->timestamp)));
|
||||
}
|
||||
|
||||
$site_name = variable_get('site_name', 'Drupal');
|
||||
$url = url((isset($category) ? 'aggregator/categories/'. $category->cid : 'aggregator'), array('absolute' => TRUE));
|
||||
$description = isset($category) ? t('@site_name - aggregated feeds in category @title', array('@site_name' => $site_name, '@title' => $category->title)) : t('@site_name - aggregated feeds', array('@site_name' => $site_name));
|
||||
|
||||
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
$output .= "<rss version=\"2.0\">\n";
|
||||
$output .= format_rss_channel(t('@site_name aggregator', array('@site_name' => $site_name)), $url, $description, $items);
|
||||
$output .= "</rss>\n";
|
||||
|
||||
print $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; generates an OPML representation of all feeds.
|
||||
*
|
||||
* @param $cid
|
||||
* If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported.
|
||||
* @return
|
||||
* The output XML.
|
||||
*/
|
||||
function aggregator_page_opml($cid = NULL) {
|
||||
if ($cid) {
|
||||
$result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.fid = c.fid WHERE c.cid = %d ORDER BY title', $cid);
|
||||
}
|
||||
else {
|
||||
$result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title');
|
||||
}
|
||||
|
||||
$feeds = array();
|
||||
while ($item = db_fetch_object($result)) {
|
||||
$feeds[] = $item;
|
||||
}
|
||||
|
||||
return theme('aggregator_page_opml', $feeds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the OPML feed output.
|
||||
*
|
||||
* @param $feeds
|
||||
* An array of the feeds to theme.
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_aggregator_page_opml($feeds) {
|
||||
|
||||
drupal_set_header('Content-Type: text/xml; charset=utf-8');
|
||||
|
||||
$output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
$output .= "<opml version=\"1.1\">\n";
|
||||
$output .= "<head>\n";
|
||||
$output .= '<title>'. check_plain(variable_get('site_name', 'Drupal')) ."</title>\n";
|
||||
$output .= '<dateModified>'. gmdate('r') ."</dateModified>\n";
|
||||
$output .= "</head>\n";
|
||||
$output .= "<body>\n";
|
||||
foreach ($feeds as $feed) {
|
||||
$output .= '<outline text="'. check_plain($feed->title) .'" xmlUrl="'. check_url($feed->url) ."\" />\n";
|
||||
}
|
||||
$output .= "</body>\n";
|
||||
$output .= "</opml>\n";
|
||||
|
||||
print $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process variables for aggregator-summary-items.tpl.php.
|
||||
*
|
||||
* @see aggregator-summary-item.tpl.php
|
||||
*/
|
||||
function template_preprocess_aggregator_summary_items(&$variables) {
|
||||
$variables['title'] = check_plain($variables['source']->title);
|
||||
$variables['summary_list'] = theme('item_list', $variables['summary_items']);
|
||||
$variables['source_url'] = $variables['source']->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process variables for aggregator-summary-item.tpl.php.
|
||||
*
|
||||
* @see aggregator-summary-item.tpl.php
|
||||
*/
|
||||
function template_preprocess_aggregator_summary_item(&$variables) {
|
||||
$item = $variables['item'];
|
||||
|
||||
$variables['feed_url'] = check_url($item->link);
|
||||
$variables['feed_title'] = check_plain($item->title);
|
||||
$variables['feed_age'] = t('%age old', array('%age' => format_interval(time() - $item->timestamp)));
|
||||
|
||||
$variables['source_url'] = '';
|
||||
$variables['source_title'] = '';
|
||||
if (!empty($item->feed_link)) {
|
||||
$variables['source_url'] = check_url($item->feed_link);
|
||||
$variables['source_title'] = check_plain($item->feed_title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process variables for aggregator-feed-source.tpl.php.
|
||||
*
|
||||
* @see aggregator-feed-source.tpl.php
|
||||
*/
|
||||
function template_preprocess_aggregator_feed_source(&$variables) {
|
||||
$feed = $variables['feed'];
|
||||
|
||||
$variables['source_icon'] = theme('feed_icon', $feed->url, t('!title feed', array('!title' => $feed->title)));
|
||||
$variables['source_image'] = $feed->image;
|
||||
$variables['source_description'] = aggregator_filter_xss($feed->description);
|
||||
$variables['source_url'] = check_url(url($feed->link, array('absolute' => TRUE)));
|
||||
|
||||
if ($feed->checked) {
|
||||
$variables['last_checked'] = t('@time ago', array('@time' => format_interval(time() - $feed->checked)));
|
||||
}
|
||||
else {
|
||||
$variables['last_checked'] = t('never');
|
||||
}
|
||||
|
||||
if (user_access('administer news feeds')) {
|
||||
$variables['last_checked'] = l($variables['last_checked'], 'admin/content/aggregator');
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Blog
|
||||
description = Enables keeping easily and regularly updated user web pages or blogs.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables keeping an easily and regularly updated web page or a blog.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_node_info().
|
||||
*/
|
||||
function blog_node_info() {
|
||||
return array(
|
||||
'blog' => array(
|
||||
'name' => t('Blog entry'),
|
||||
'module' => 'blog',
|
||||
'description' => t('A <em>blog entry</em> is a single post to an online journal, or <em>blog</em>.'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function blog_perm() {
|
||||
return array('create blog entries', 'delete own blog entries', 'delete any blog entry', 'edit own blog entries', 'edit any blog entry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_access().
|
||||
*/
|
||||
function blog_access($op, $node, $account) {
|
||||
switch ($op) {
|
||||
case 'create':
|
||||
// Anonymous users cannot post even if they have the permission.
|
||||
return user_access('create blog entries', $account) && $account->uid ? TRUE : NULL;
|
||||
case 'update':
|
||||
return user_access('edit any blog entry', $account) || (user_access('edit own blog entries', $account) && ($node->uid == $account->uid)) ? TRUE : NULL;
|
||||
case 'delete':
|
||||
return user_access('delete any blog entry', $account) || (user_access('delete own blog entries', $account) && ($node->uid == $account->uid)) ? TRUE : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function blog_user($type, &$edit, &$user) {
|
||||
if ($type == 'view' && user_access('create blog entries', $user)) {
|
||||
$user->content['summary']['blog'] = array(
|
||||
'#type' => 'user_profile_item',
|
||||
'#title' => t('Blog'),
|
||||
// l() escapes the attributes, so we should not escape !username here.
|
||||
'#value' => l(t('View recent blog entries'), "blog/$user->uid", array('attributes' => array('title' => t("Read !username's latest blog entries.", array('!username' => $user->name))))),
|
||||
'#attributes' => array('class' => 'blog'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function blog_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#blog':
|
||||
$output = '<p>'. t('The blog module allows registered users to maintain an online journal, or <em>blog</em>. Blogs are made up of individual <em>blog entries</em>, and the blog entries are most often displayed in descending order by creation time.') .'</p>';
|
||||
$output .= '<p>'. t('There is an (optional) <em>Blogs</em> menu item added to the Navigation menu, which displays all blogs available on your site, and a <em>My blog</em> item displaying the current user\'s blog entries. The <em>Blog entry</em> menu item under <em>Create content</em> allows new blog entries to be created.') .'</p>';
|
||||
$output .= '<p>'. t('Each blog entry is displayed with an automatic link to other blogs created by the same user. By default, blog entries have comments enabled and are automatically promoted to the site front page. The blog module also creates a <em>Recent blog posts</em> block that may be enabled at the <a href="@blocks">blocks administration page</a>.', array('@blocks' => url('admin/build/block'))) .'</p>';
|
||||
$output .= '<p>'. t('When using the aggregator module an automatic <em>blog it</em> icon is displayed next to the items in a feed\'s <em>latest items</em> block. Clicking this icon populates a <em>blog entry</em> with a title (the title of the feed item) and body (a link to the source item on its original site and illustrative content suitable for use in a block quote). Blog authors can use this feature to easily comment on items of interest that appear in aggregator feeds from other sites. To use this feature, be sure to <a href="@modules">enable</a> the aggregator module, <a href="@feeds">add and configure</a> a feed from another site, and <a href="@blocks">position</a> the feed\'s <em>latest items</em> block.', array('@modules' => url('admin/build/modules'), '@feeds' => url('admin/content/aggregator'), '@blocks' => url('admin/build/block'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@blog">Blog module</a>.', array('@blog' => 'http://drupal.org/handbook/modules/blog/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_form().
|
||||
*/
|
||||
function blog_form(&$node) {
|
||||
global $nid;
|
||||
$iid = isset($_GET['iid']) ? (int)$_GET['iid'] : 0;
|
||||
$type = node_get_types('type', $node);
|
||||
|
||||
|
||||
if (empty($node->body)) {
|
||||
// If the user clicked a "blog it" link, we load the data from the
|
||||
// database and quote it in the blog.
|
||||
if ($nid && $blog = node_load($nid)) {
|
||||
$node->body = '<em>'. $blog->body .'</em> ['. l($blog->name, "node/$nid") .']';
|
||||
}
|
||||
|
||||
if ($iid && $item = db_fetch_object(db_query('SELECT i.*, f.title as ftitle, f.link as flink FROM {aggregator_item} i, {aggregator_feed} f WHERE i.iid = %d AND i.fid = f.fid', $iid))) {
|
||||
$node->title = $item->title;
|
||||
// Note: $item->description has been validated on aggregation.
|
||||
$node->body = '<a href="'. check_url($item->link) .'">'. check_plain($item->title) .'</a> - <em>'. $item->description .'</em> [<a href="'. check_url($item->flink) .'">'. check_plain($item->ftitle) ."</a>]\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => !empty($node->title) ? $node->title : NULL, '#weight' => -5);
|
||||
$form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_view().
|
||||
*/
|
||||
function blog_view($node, $teaser = FALSE, $page = FALSE) {
|
||||
if ($page) {
|
||||
// Breadcrumb navigation. l() escapes the title, so we should not escape !name.
|
||||
drupal_set_breadcrumb(array(l(t('Home'), NULL), l(t('Blogs'), 'blog'), l(t("!name's blog", array('!name' => $node->name)), 'blog/'. $node->uid)));
|
||||
}
|
||||
return node_prepare($node, $teaser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_link().
|
||||
*/
|
||||
function blog_link($type, $node = NULL, $teaser = FALSE) {
|
||||
$links = array();
|
||||
|
||||
if ($type == 'node' && $node->type == 'blog') {
|
||||
if (arg(0) != 'blog' || arg(1) != $node->uid) {
|
||||
// This goes to l() and therefore escapes !username in both the title and attributes.
|
||||
$links['blog_usernames_blog'] = array(
|
||||
'title' => t("!username's blog", array('!username' => $node->name)),
|
||||
'href' => "blog/$node->uid",
|
||||
'attributes' => array('title' => t("Read !username's latest blog entries.", array('!username' => $node->name)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function blog_menu() {
|
||||
$items['blog'] = array(
|
||||
'title' => 'Blogs',
|
||||
'page callback' => 'blog_page_last',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_SUGGESTED_ITEM,
|
||||
'file' => 'blog.pages.inc',
|
||||
);
|
||||
$items['blog/%user_uid_optional'] = array(
|
||||
'title' => 'My blog',
|
||||
'page callback' => 'blog_page_user',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => 'blog_page_user_access',
|
||||
'access arguments' => array(1),
|
||||
'file' => 'blog.pages.inc',
|
||||
);
|
||||
$items['blog/%user/feed'] = array(
|
||||
'title' => 'Blogs',
|
||||
'page callback' => 'blog_feed_user',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => 'blog_page_user_access',
|
||||
'access arguments' => array(1),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'blog.pages.inc',
|
||||
);
|
||||
$items['blog/feed'] = array(
|
||||
'title' => 'Blogs',
|
||||
'page callback' => 'blog_feed_last',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'blog.pages.inc',
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback for user blog pages.
|
||||
*/
|
||||
function blog_page_user_access($account) {
|
||||
// The visitor must be able to access the site's content.
|
||||
// For a blog to 'exist' the user must either be able to
|
||||
// create new blog entries, or it must have existing posts.
|
||||
return $account->uid && user_access('access content') && (user_access('create blog entries', $account) || _blog_post_exists($account));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if a user has blog posts already.
|
||||
*/
|
||||
function _blog_post_exists($account) {
|
||||
return (bool)db_result(db_query_range(db_rewrite_sql("SELECT 1 FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1"), $account->uid, 0, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_block().
|
||||
*
|
||||
* Displays the most recent 10 blog titles.
|
||||
*/
|
||||
function blog_block($op = 'list', $delta = 0) {
|
||||
global $user;
|
||||
if ($op == 'list') {
|
||||
$block[0]['info'] = t('Recent blog posts');
|
||||
return $block;
|
||||
}
|
||||
else if ($op == 'view') {
|
||||
if (user_access('access content')) {
|
||||
$result = db_query_range(db_rewrite_sql("SELECT n.nid, n.title, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, 10);
|
||||
if ($node_title_list = node_title_list($result)) {
|
||||
$block['content'] = $node_title_list;
|
||||
$block['content'] .= theme('more_link', url('blog'), t('Read the latest blog entries.'));
|
||||
$block['subject'] = t('Recent blog posts');
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Page callback file for the blog module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback; displays a Drupal page containing recent blog entries of a given user.
|
||||
*/
|
||||
function blog_page_user($account) {
|
||||
global $user;
|
||||
|
||||
drupal_set_title($title = t("@name's blog", array('@name' => $account->name)));
|
||||
|
||||
$items = array();
|
||||
|
||||
if (($account->uid == $user->uid) && user_access('create blog entries')) {
|
||||
$items[] = l(t('Post new blog entry.'), "node/add/blog");
|
||||
}
|
||||
else if ($account->uid == $user->uid) {
|
||||
$items[] = t('You are not allowed to post a new blog entry.');
|
||||
}
|
||||
|
||||
$output = theme('item_list', $items);
|
||||
|
||||
$result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid);
|
||||
$has_posts = FALSE;
|
||||
|
||||
while ($node = db_fetch_object($result)) {
|
||||
$output .= node_view(node_load($node->nid), 1);
|
||||
$has_posts = TRUE;
|
||||
}
|
||||
|
||||
if ($has_posts) {
|
||||
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
|
||||
}
|
||||
else {
|
||||
if ($account->uid == $user->uid) {
|
||||
drupal_set_message(t('You have not created any blog entries.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('!author has not created any blog entries.', array('!author' => theme('username', $account))));
|
||||
}
|
||||
}
|
||||
drupal_add_feed(url('blog/'. $account->uid .'/feed'), t('RSS - !title', array('!title' => $title)));
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays a Drupal page containing recent blog entries of all users.
|
||||
*/
|
||||
function blog_page_last() {
|
||||
global $user;
|
||||
|
||||
$output = '';
|
||||
$items = array();
|
||||
|
||||
if (user_access('create blog entries')) {
|
||||
$items[] = l(t('Create new blog entry.'), "node/add/blog");
|
||||
}
|
||||
|
||||
$output = theme('item_list', $items);
|
||||
|
||||
$result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10));
|
||||
$has_posts = FALSE;
|
||||
|
||||
while ($node = db_fetch_object($result)) {
|
||||
$output .= node_view(node_load($node->nid), 1);
|
||||
$has_posts = TRUE;
|
||||
}
|
||||
|
||||
if ($has_posts) {
|
||||
$output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('No blog entries have been created.'));
|
||||
}
|
||||
drupal_add_feed(url('blog/feed'), t('RSS - blogs'));
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays an RSS feed containing recent blog entries of a given user.
|
||||
*/
|
||||
function blog_feed_user($account) {
|
||||
$result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.created DESC"), $account->uid, 0, variable_get('feed_default_items', 10));
|
||||
$channel['title'] = t("!name's blog", array('!name' => $account->name));
|
||||
$channel['link'] = url('blog/'. $account->uid, array('absolute' => TRUE));
|
||||
|
||||
$items = array();
|
||||
while ($row = db_fetch_object($result)) {
|
||||
$items[] = $row->nid;
|
||||
}
|
||||
node_feed($items, $channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays an RSS feed containing recent blog entries of all users.
|
||||
*/
|
||||
function blog_feed_last() {
|
||||
$result = db_query_range(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.created DESC"), 0, variable_get('feed_default_items', 10));
|
||||
$channel['title'] = t('!site_name blogs', array('!site_name' => variable_get('site_name', 'Drupal')));
|
||||
$channel['link'] = url('blog', array('absolute' => TRUE));
|
||||
|
||||
$items = array();
|
||||
while ($row = db_fetch_object($result)) {
|
||||
$items[] = $row->nid;
|
||||
}
|
||||
|
||||
node_feed($items, $channel);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Blog API
|
||||
description = Allows users to post content using applications that support XML-RPC blog APIs.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function blogapi_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('blogapi');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function blogapi_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('blogapi');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function blogapi_schema() {
|
||||
//This table was introduced in Drupal 6.4
|
||||
$schema['blogapi_files'] = array(
|
||||
'description' => 'Stores information for files uploaded via the blogapi.',
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
'description' => 'Primary Key: Unique file ID.',
|
||||
'type' => 'serial',
|
||||
),
|
||||
'uid' => array(
|
||||
'description' => 'The {users}.uid of the user who is associated with the file.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0),
|
||||
'filepath' => array(
|
||||
'description' => 'Path of the file relative to Drupal root.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => ''),
|
||||
'filesize' => array(
|
||||
'description' => 'The size of the file in bytes.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0),
|
||||
),
|
||||
'primary key' => array('fid'),
|
||||
'indexes' => array(
|
||||
'uid' => array('uid'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-5.x-to-6.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inform users about the new permission.
|
||||
*/
|
||||
function blogapi_update_6000() {
|
||||
drupal_set_message("Blog API module does not depend on blog module's permissions anymore, but provides its own 'administer content with blog api' permission instead. Until <a href=\"". url('admin/user/permissions', array('fragment' => 'module-blogapi')) .'">this permission is assigned</a> to at least one user role, only the site administrator will be able to use Blog API features.');
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add blogapi_files table to enable size restriction for BlogAPI file uploads.
|
||||
*
|
||||
* This table was introduced in Drupal 6.4.
|
||||
*/
|
||||
function blogapi_update_6001() {
|
||||
$schema['blogapi_files'] = array(
|
||||
'description' => 'Stores information for files uploaded via the blogapi.',
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
'description' => 'Primary Key: Unique file ID.',
|
||||
'type' => 'serial',
|
||||
),
|
||||
'uid' => array(
|
||||
'description' => 'The {users}.uid of the user who is associated with the file.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0),
|
||||
'filepath' => array(
|
||||
'description' => 'Path of the file relative to Drupal root.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => ''),
|
||||
'filesize' => array(
|
||||
'description' => 'The size of the file in bytes.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0),
|
||||
),
|
||||
'primary key' => array('fid'),
|
||||
'indexes' => array(
|
||||
'uid' => array('uid'),
|
||||
),
|
||||
);
|
||||
|
||||
$ret = array();
|
||||
|
||||
if (!db_table_exists('blogapi_files')) {
|
||||
db_create_table($ret, 'blogapi_files', $schema['blogapi_files']);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-5.x-to-6.x".
|
||||
* The next series of updates should start at 7000.
|
||||
*/
|
||||
|
|
@ -1,949 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enable users to post using applications that support XML-RPC blog APIs.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function blogapi_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#blogapi':
|
||||
$output = '<p>'. t("The Blog API module allows your site's users to access and post to their blogs from external blogging clients. External blogging clients are available for a wide range of desktop operating systems, and generally provide a feature-rich graphical environment for creating and editing posts.") .'</p>';
|
||||
$output .= '<p>'. t('<a href="@ecto-link">Ecto</a>, a blogging client available for both Mac OS X and Microsoft Windows, can be used with Blog API. Blog API also supports <a href="@blogger-api">Blogger API</a>, <a href="@metaweblog-api">MetaWeblog API</a>, and most of the <a href="@movabletype-api">Movable Type API</a>. Blogging clients and other services (e.g. <a href="@flickr">Flickr\'s</a> "post to blog") that support these APIs may also be compatible.', array('@ecto-link' => url('http://infinite-sushi.com/software/ecto/'), '@blogger-api' => url('http://www.blogger.com/developers/api/1_docs/'), '@metaweblog-api' => url('http://www.xmlrpc.com/metaWeblogApi'), '@movabletype-api' => url('http://www.movabletype.org/docs/mtmanual_programmatic.html'), '@flickr' => url('http://www.flickr.com'))) .'</p>';
|
||||
$output .= '<p>'. t('Select the content types available to external clients on the <a href="@blogapi-settings">Blog API settings page</a>. If supported and available, each content type will be displayed as a separate "blog" by the external client.', array('@blogapi-settings' => url('admin/settings/blogapi'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@blogapi">Blog API module</a>.', array('@blogapi' => url('http://drupal.org/handbook/modules/blogapi/'))) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function blogapi_perm() {
|
||||
return array('administer content with blog api');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_xmlrpc().
|
||||
*/
|
||||
function blogapi_xmlrpc() {
|
||||
return array(
|
||||
array(
|
||||
'blogger.getUsersBlogs',
|
||||
'blogapi_blogger_get_users_blogs',
|
||||
array('array', 'string', 'string', 'string'),
|
||||
t('Returns a list of blogs to which an author has posting privileges.')),
|
||||
array(
|
||||
'blogger.getUserInfo',
|
||||
'blogapi_blogger_get_user_info',
|
||||
array('struct', 'string', 'string', 'string'),
|
||||
t('Returns information about an author in the system.')),
|
||||
array(
|
||||
'blogger.newPost',
|
||||
'blogapi_blogger_new_post',
|
||||
array('string', 'string', 'string', 'string', 'string', 'string', 'boolean'),
|
||||
t('Creates a new post, and optionally publishes it.')),
|
||||
array(
|
||||
'blogger.editPost',
|
||||
'blogapi_blogger_edit_post',
|
||||
array('boolean', 'string', 'string', 'string', 'string', 'string', 'boolean'),
|
||||
t('Updates the information about an existing post.')),
|
||||
array(
|
||||
'blogger.getPost',
|
||||
'blogapi_blogger_get_post',
|
||||
array('struct', 'string', 'string', 'string', 'string'),
|
||||
t('Returns information about a specific post.')),
|
||||
array(
|
||||
'blogger.deletePost',
|
||||
'blogapi_blogger_delete_post',
|
||||
array('boolean', 'string', 'string', 'string', 'string', 'boolean'),
|
||||
t('Deletes a post.')),
|
||||
array(
|
||||
'blogger.getRecentPosts',
|
||||
'blogapi_blogger_get_recent_posts',
|
||||
array('array', 'string', 'string', 'string', 'string', 'int'),
|
||||
t('Returns a list of the most recent posts in the system.')),
|
||||
array(
|
||||
'metaWeblog.newPost',
|
||||
'blogapi_metaweblog_new_post',
|
||||
array('string', 'string', 'string', 'string', 'struct', 'boolean'),
|
||||
t('Creates a new post, and optionally publishes it.')),
|
||||
array(
|
||||
'metaWeblog.editPost',
|
||||
'blogapi_metaweblog_edit_post',
|
||||
array('boolean', 'string', 'string', 'string', 'struct', 'boolean'),
|
||||
t('Updates information about an existing post.')),
|
||||
array(
|
||||
'metaWeblog.getPost',
|
||||
'blogapi_metaweblog_get_post',
|
||||
array('struct', 'string', 'string', 'string'),
|
||||
t('Returns information about a specific post.')),
|
||||
array(
|
||||
'metaWeblog.newMediaObject',
|
||||
'blogapi_metaweblog_new_media_object',
|
||||
array('string', 'string', 'string', 'string', 'struct'),
|
||||
t('Uploads a file to your webserver.')),
|
||||
array(
|
||||
'metaWeblog.getCategories',
|
||||
'blogapi_metaweblog_get_category_list',
|
||||
array('struct', 'string', 'string', 'string'),
|
||||
t('Returns a list of all categories to which the post is assigned.')),
|
||||
array(
|
||||
'metaWeblog.getRecentPosts',
|
||||
'blogapi_metaweblog_get_recent_posts',
|
||||
array('array', 'string', 'string', 'string', 'int'),
|
||||
t('Returns a list of the most recent posts in the system.')),
|
||||
array(
|
||||
'mt.getRecentPostTitles',
|
||||
'blogapi_mt_get_recent_post_titles',
|
||||
array('array', 'string', 'string', 'string', 'int'),
|
||||
t('Returns a bandwidth-friendly list of the most recent posts in the system.')),
|
||||
array(
|
||||
'mt.getCategoryList',
|
||||
'blogapi_mt_get_category_list',
|
||||
array('array', 'string', 'string', 'string'),
|
||||
t('Returns a list of all categories defined in the blog.')),
|
||||
array(
|
||||
'mt.getPostCategories',
|
||||
'blogapi_mt_get_post_categories',
|
||||
array('array', 'string', 'string', 'string'),
|
||||
t('Returns a list of all categories to which the post is assigned.')),
|
||||
array(
|
||||
'mt.setPostCategories',
|
||||
'blogapi_mt_set_post_categories',
|
||||
array('boolean', 'string', 'string', 'string', 'array'),
|
||||
t('Sets the categories for a post.')),
|
||||
array(
|
||||
'mt.supportedMethods',
|
||||
'xmlrpc_server_list_methods',
|
||||
array('array'),
|
||||
t('Retrieve information about the XML-RPC methods supported by the server.')),
|
||||
array(
|
||||
'mt.supportedTextFilters',
|
||||
'blogapi_mt_supported_text_filters',
|
||||
array('array'),
|
||||
t('Retrieve information about the text formatting plugins supported by the server.')),
|
||||
array(
|
||||
'mt.publishPost',
|
||||
'blogapi_mt_publish_post',
|
||||
array('boolean', 'string', 'string', 'string'),
|
||||
t('Publish (rebuild) all of the static files related to an entry from your blog. Equivalent to saving an entry in the system (but without the ping).')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Finds the URL of a user's blog.
|
||||
*/
|
||||
|
||||
function blogapi_blogger_get_users_blogs($appid, $username, $password) {
|
||||
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if ($user->uid) {
|
||||
$types = _blogapi_get_node_types();
|
||||
$structs = array();
|
||||
foreach ($types as $type) {
|
||||
$structs[] = array('url' => url('blog/'. $user->uid, array('absolute' => TRUE)), 'blogid' => $type, 'blogName' => $user->name .": ". $type);
|
||||
}
|
||||
return $structs;
|
||||
}
|
||||
else {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Returns profile information about a user.
|
||||
*/
|
||||
function blogapi_blogger_get_user_info($appkey, $username, $password) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
|
||||
if ($user->uid) {
|
||||
$name = explode(' ', $user->realname ? $user->realname : $user->name, 2);
|
||||
return array(
|
||||
'userid' => $user->uid,
|
||||
'lastname' => $name[1],
|
||||
'firstname' => $name[0],
|
||||
'nickname' => $user->name,
|
||||
'email' => $user->mail,
|
||||
'url' => url('blog/'. $user->uid, array('absolute' => TRUE)));
|
||||
}
|
||||
else {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Inserts a new blog post as a node.
|
||||
*/
|
||||
function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $content, $publish) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
|
||||
// Return an error if not configured type.
|
||||
return $error;
|
||||
}
|
||||
|
||||
$edit = array();
|
||||
$edit['type'] = $blogid;
|
||||
// get the node type defaults
|
||||
$node_type_default = variable_get('node_options_'. $edit['type'], array('status', 'promote'));
|
||||
$edit['uid'] = $user->uid;
|
||||
$edit['name'] = $user->name;
|
||||
$edit['promote'] = in_array('promote', $node_type_default);
|
||||
$edit['comment'] = variable_get('comment_'. $edit['type'], 2);
|
||||
$edit['revision'] = in_array('revision', $node_type_default);
|
||||
$edit['format'] = FILTER_FORMAT_DEFAULT;
|
||||
$edit['status'] = $publish;
|
||||
|
||||
// check for bloggerAPI vs. metaWeblogAPI
|
||||
if (is_array($content)) {
|
||||
$edit['title'] = $content['title'];
|
||||
$edit['body'] = $content['description'];
|
||||
_blogapi_mt_extra($edit, $content);
|
||||
}
|
||||
else {
|
||||
$edit['title'] = blogapi_blogger_title($content);
|
||||
$edit['body'] = $content;
|
||||
}
|
||||
|
||||
if (!node_access('create', $edit['type'])) {
|
||||
return blogapi_error(t('You do not have permission to create this type of post.'));
|
||||
}
|
||||
|
||||
if (user_access('administer nodes') && !isset($edit['date'])) {
|
||||
$edit['date'] = format_date(time(), 'custom', 'Y-m-d H:i:s O');
|
||||
}
|
||||
|
||||
node_invoke_nodeapi($edit, 'blogapi new');
|
||||
|
||||
$valid = blogapi_status_error_check($edit, $publish);
|
||||
if ($valid !== TRUE) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
node_validate($edit);
|
||||
if ($errors = form_get_errors()) {
|
||||
return blogapi_error(implode("\n", $errors));
|
||||
}
|
||||
|
||||
$node = node_submit($edit);
|
||||
node_save($node);
|
||||
if ($node->nid) {
|
||||
watchdog('content', '@type: added %title using blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
|
||||
// blogger.newPost returns a string so we cast the nid to a string by putting it in double quotes:
|
||||
return "$node->nid";
|
||||
}
|
||||
|
||||
return blogapi_error(t('Error storing post.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Modifies the specified blog node.
|
||||
*/
|
||||
function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $content, $publish) {
|
||||
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
$node = node_load($postid);
|
||||
if (!$node) {
|
||||
return blogapi_error(t('n/a'));
|
||||
}
|
||||
// Let the teaser be re-generated.
|
||||
unset($node->teaser);
|
||||
|
||||
if (!node_access('update', $node)) {
|
||||
return blogapi_error(t('You do not have permission to update this post.'));
|
||||
}
|
||||
// Save the original status for validation of permissions.
|
||||
$original_status = $node->status;
|
||||
$node->status = $publish;
|
||||
|
||||
// check for bloggerAPI vs. metaWeblogAPI
|
||||
if (is_array($content)) {
|
||||
$node->title = $content['title'];
|
||||
$node->body = $content['description'];
|
||||
_blogapi_mt_extra($node, $content);
|
||||
}
|
||||
else {
|
||||
$node->title = blogapi_blogger_title($content);
|
||||
$node->body = $content;
|
||||
}
|
||||
|
||||
node_invoke_nodeapi($node, 'blogapi edit');
|
||||
|
||||
$valid = blogapi_status_error_check($node, $original_status);
|
||||
if ($valid !== TRUE) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
node_validate($node);
|
||||
if ($errors = form_get_errors()) {
|
||||
return blogapi_error(implode("\n", $errors));
|
||||
}
|
||||
|
||||
if (user_access('administer nodes') && !isset($edit['date'])) {
|
||||
$node->date = format_date($node->created, 'custom', 'Y-m-d H:i:s O');
|
||||
}
|
||||
$node = node_submit($node);
|
||||
node_save($node);
|
||||
if ($node->nid) {
|
||||
watchdog('content', '@type: updated %title using Blog API.', array('@type' => $node->type, '%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return blogapi_error(t('Error storing post.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Returns a specified blog node.
|
||||
*/
|
||||
function blogapi_blogger_get_post($appkey, $postid, $username, $password) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
$node = node_load($postid);
|
||||
|
||||
return _blogapi_get_post($node, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the user has permission to save the node with the chosen status.
|
||||
*
|
||||
* @return
|
||||
* TRUE if no error, or the blogapi_error().
|
||||
*/
|
||||
function blogapi_status_error_check($node, $original_status) {
|
||||
|
||||
$node = (object) $node;
|
||||
|
||||
$node_type_default = variable_get('node_options_'. $node->type, array('status', 'promote'));
|
||||
|
||||
// If we don't have the 'administer nodes' permission and the status is
|
||||
// changing or for a new node the status is not the content type's default,
|
||||
// then return an error.
|
||||
if (!user_access('administer nodes') && (($node->status != $original_status) || (empty($node->nid) && $node->status != in_array('status', $node_type_default)))) {
|
||||
if ($node->status) {
|
||||
return blogapi_error(t('You do not have permission to publish this type of post. Please save it as a draft instead.'));
|
||||
}
|
||||
else {
|
||||
return blogapi_error(t('You do not have permission to save this post as a draft. Please publish it instead.'));
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blogging API callback. Removes the specified blog node.
|
||||
*/
|
||||
function blogapi_blogger_delete_post($appkey, $postid, $username, $password, $publish) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
node_delete($postid);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Returns the latest few postings in a user's blog. $bodies TRUE
|
||||
* <a href="http://movabletype.org/docs/mtmanual_programmatic.html#item_mt%2EgetRecentPostTitles">
|
||||
* returns a bandwidth-friendly list</a>.
|
||||
*/
|
||||
function blogapi_blogger_get_recent_posts($appkey, $blogid, $username, $password, $number_of_posts, $bodies = TRUE) {
|
||||
// Remove unused appkey (from bloggerAPI).
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
|
||||
// Return an error if not configured type.
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ($bodies) {
|
||||
$result = db_query_range("SELECT n.nid, n.title, r.body, r.format, n.comment, n.created, u.name FROM {node} n, {node_revisions} r, {users} u WHERE n.uid = u.uid AND n.vid = r.vid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC", $blogid, $user->uid, 0, $number_of_posts);
|
||||
}
|
||||
else {
|
||||
$result = db_query_range("SELECT n.nid, n.title, n.created, u.name FROM {node} n, {users} u WHERE n.uid = u.uid AND n.type = '%s' AND n.uid = %d ORDER BY n.created DESC", $blogid, $user->uid, 0, $number_of_posts);
|
||||
}
|
||||
$blogs = array();
|
||||
while ($blog = db_fetch_object($result)) {
|
||||
$blogs[] = _blogapi_get_post($blog, $bodies);
|
||||
}
|
||||
return $blogs;
|
||||
}
|
||||
|
||||
function blogapi_metaweblog_new_post($blogid, $username, $password, $content, $publish) {
|
||||
return blogapi_blogger_new_post('0123456789ABCDEF', $blogid, $username, $password, $content, $publish);
|
||||
}
|
||||
|
||||
function blogapi_metaweblog_edit_post($postid, $username, $password, $content, $publish) {
|
||||
return blogapi_blogger_edit_post('0123456789ABCDEF', $postid, $username, $password, $content, $publish);
|
||||
}
|
||||
|
||||
function blogapi_metaweblog_get_post($postid, $username, $password) {
|
||||
return blogapi_blogger_get_post('01234567890ABCDEF', $postid, $username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Inserts a file into Drupal.
|
||||
*/
|
||||
function blogapi_metaweblog_new_media_object($blogid, $username, $password, $file) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
$usersize = 0;
|
||||
$uploadsize = 0;
|
||||
|
||||
$roles = array_intersect(user_roles(FALSE, 'administer content with blog api'), $user->roles);
|
||||
|
||||
foreach ($roles as $rid => $name) {
|
||||
$extensions .= ' '. strtolower(variable_get("blogapi_extensions_$rid", variable_get('blogapi_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp')));
|
||||
$usersize= max($usersize, variable_get("blogapi_usersize_$rid", variable_get('blogapi_usersize_default', 1)) * 1024 * 1024);
|
||||
$uploadsize = max($uploadsize, variable_get("blogapi_uploadsize_$rid", variable_get('blogapi_uploadsize_default', 1)) * 1024 * 1024);
|
||||
}
|
||||
|
||||
$filesize = strlen($file['bits']);
|
||||
|
||||
if ($filesize > $uploadsize) {
|
||||
return blogapi_error(t('It is not possible to upload the file, because it exceeded the maximum filesize of @maxsize.', array('@maxsize' => format_size($uploadsize))));
|
||||
}
|
||||
|
||||
if (_blogapi_space_used($user->uid) + $filesize > $usersize) {
|
||||
return blogapi_error(t('The file can not be attached to this post, because the disk quota of @quota has been reached.', array('@quota' => format_size($usersize))));
|
||||
}
|
||||
|
||||
// Only allow files with whitelisted extensions and convert remaining dots to
|
||||
// underscores to prevent attacks via non-terminal executable extensions with
|
||||
// files such as exploit.php.jpg.
|
||||
|
||||
$whitelist = array_unique(explode(' ', trim($extensions)));
|
||||
|
||||
$name = basename($file['name']);
|
||||
|
||||
if ($extension_position = strrpos($name, '.')) {
|
||||
$filename = drupal_substr($name, 0, $extension_position);
|
||||
$final_extension = drupal_substr($name, $extension_position + 1);
|
||||
|
||||
if (!in_array(strtolower($final_extension), $whitelist)) {
|
||||
return blogapi_error(t('It is not possible to upload the file, because it is only possible to upload files with the following extensions: @extensions', array('@extensions' => implode(' ', $whitelist))));
|
||||
}
|
||||
|
||||
$filename = str_replace('.', '_', $filename);
|
||||
$filename .= '.'. $final_extension;
|
||||
}
|
||||
|
||||
$data = $file['bits'];
|
||||
|
||||
if (!$data) {
|
||||
return blogapi_error(t('No file sent.'));
|
||||
}
|
||||
|
||||
if (!$file = file_save_data($data, $filename)) {
|
||||
return blogapi_error(t('Error storing file.'));
|
||||
}
|
||||
|
||||
$row = new stdClass();
|
||||
$row->uid = $user->uid;
|
||||
$row->filepath = $file;
|
||||
$row->filesize = $filesize;
|
||||
|
||||
drupal_write_record('blogapi_files', $row);
|
||||
|
||||
// Return the successful result.
|
||||
return array('url' => file_create_url($file), 'struct');
|
||||
}
|
||||
/**
|
||||
* Blogging API callback. Returns a list of the taxonomy terms that can be
|
||||
* associated with a blog node.
|
||||
*/
|
||||
function blogapi_metaweblog_get_category_list($blogid, $username, $password) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
if (($error = _blogapi_validate_blogid($blogid)) !== TRUE) {
|
||||
// Return an error if not configured type.
|
||||
return $error;
|
||||
}
|
||||
|
||||
$vocabularies = module_invoke('taxonomy', 'get_vocabularies', $blogid, 'vid');
|
||||
$categories = array();
|
||||
if ($vocabularies) {
|
||||
foreach ($vocabularies as $vocabulary) {
|
||||
$terms = module_invoke('taxonomy', 'get_tree', $vocabulary->vid, 0, -1);
|
||||
foreach ($terms as $term) {
|
||||
$term_name = $term->name;
|
||||
foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
|
||||
$term_name = $parent->name .'/'. $term_name;
|
||||
}
|
||||
$categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $categories;
|
||||
}
|
||||
|
||||
function blogapi_metaweblog_get_recent_posts($blogid, $username, $password, $number_of_posts) {
|
||||
return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, TRUE);
|
||||
}
|
||||
|
||||
function blogapi_mt_get_recent_post_titles($blogid, $username, $password, $number_of_posts) {
|
||||
return blogapi_blogger_get_recent_posts('0123456789ABCDEF', $blogid, $username, $password, $number_of_posts, FALSE);
|
||||
}
|
||||
|
||||
function blogapi_mt_get_category_list($blogid, $username, $password) {
|
||||
return blogapi_metaweblog_get_category_list($blogid, $username, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Returns a list of the taxonomy terms that are
|
||||
* assigned to a particular node.
|
||||
*/
|
||||
function blogapi_mt_get_post_categories($postid, $username, $password) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
$node = node_load($postid);
|
||||
$terms = module_invoke('taxonomy', 'node_get_terms', $node, 'tid');
|
||||
$categories = array();
|
||||
foreach ($terms as $term) {
|
||||
$term_name = $term->name;
|
||||
foreach (module_invoke('taxonomy', 'get_parents', $term->tid, 'tid') as $parent) {
|
||||
$term_name = $parent->name .'/'. $term_name;
|
||||
}
|
||||
$categories[] = array('categoryName' => $term_name, 'categoryId' => $term->tid, 'isPrimary' => TRUE);
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Assigns taxonomy terms to a particular node.
|
||||
*/
|
||||
function blogapi_mt_set_post_categories($postid, $username, $password, $categories) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
|
||||
$node = node_load($postid);
|
||||
$node->taxonomy = array();
|
||||
foreach ($categories as $category) {
|
||||
$node->taxonomy[] = $category['categoryId'];
|
||||
}
|
||||
$validated = blogapi_mt_validate_terms($node);
|
||||
if ($validated !== TRUE) {
|
||||
return $validated;
|
||||
}
|
||||
node_save($node);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API helper - find allowed taxonomy terms for a node type.
|
||||
*/
|
||||
function blogapi_mt_validate_terms($node) {
|
||||
// We do a lot of heavy lifting here since taxonomy module doesn't have a
|
||||
// stand-alone validation function.
|
||||
if (module_exists('taxonomy')) {
|
||||
$found_terms = array();
|
||||
if (!empty($node->taxonomy)) {
|
||||
$term_list = array_unique($node->taxonomy);
|
||||
$params = $term_list;
|
||||
$params[] = $node->type;
|
||||
$result = db_query(db_rewrite_sql("SELECT t.tid, t.vid FROM {term_data} t INNER JOIN {vocabulary_node_types} n ON t.vid = n.vid WHERE t.tid IN (". db_placeholders($term_list) .") AND n.type = '%s'", 't', 'tid'), $params);
|
||||
$found_terms = array();
|
||||
$found_count = 0;
|
||||
while ($term = db_fetch_object($result)) {
|
||||
$found_terms[$term->vid][$term->tid] = $term->tid;
|
||||
$found_count++;
|
||||
}
|
||||
// If the counts don't match, some terms are invalid or not accessible to this user.
|
||||
if (count($term_list) != $found_count) {
|
||||
return blogapi_error(t('Invalid categories submitted.'));
|
||||
}
|
||||
}
|
||||
// Look up all the vocabularies for this node type.
|
||||
$result2 = db_query(db_rewrite_sql("SELECT v.vid, v.name, v.required, v.multiple FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s'", 'v', 'vid'), $node->type);
|
||||
// Check each vocabulary associated with this node type.
|
||||
while ($vocabulary = db_fetch_object($result2)) {
|
||||
// Required vocabularies must have at least one term.
|
||||
if ($vocabulary->required && empty($found_terms[$vocabulary->vid])) {
|
||||
return blogapi_error(t('A category from the @vocabulary_name vocabulary is required.', array('@vocabulary_name' => $vocabulary->name)));
|
||||
}
|
||||
// Vocabularies that don't allow multiple terms may have at most one.
|
||||
if (!($vocabulary->multiple) && (isset($found_terms[$vocabulary->vid]) && count($found_terms[$vocabulary->vid]) > 1)) {
|
||||
return blogapi_error(t('You may only choose one category from the @vocabulary_name vocabulary.'), array('@vocabulary_name' => $vocabulary->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif (!empty($node->taxonomy)) {
|
||||
return blogapi_error(t('Error saving categories. This feature is not available.'));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Sends a list of available input formats.
|
||||
*/
|
||||
function blogapi_mt_supported_text_filters() {
|
||||
// NOTE: we're only using anonymous' formats because the MT spec
|
||||
// does not allow for per-user formats.
|
||||
$formats = filter_formats();
|
||||
|
||||
$filters = array();
|
||||
foreach ($formats as $format) {
|
||||
$filter['key'] = $format->format;
|
||||
$filter['label'] = $format->name;
|
||||
$filters[] = $filter;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blogging API callback. Publishes the given node
|
||||
*/
|
||||
function blogapi_mt_publish_post($postid, $username, $password) {
|
||||
$user = blogapi_validate_user($username, $password);
|
||||
if (!$user->uid) {
|
||||
return blogapi_error($user);
|
||||
}
|
||||
$node = node_load($postid);
|
||||
if (!$node) {
|
||||
return blogapi_error(t('Invalid post.'));
|
||||
}
|
||||
|
||||
// Nothing needs to be done if already published.
|
||||
if ($node->status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node_access('update', $node) || !user_access('administer nodes')) {
|
||||
return blogapi_error(t('You do not have permission to update this post.'));
|
||||
}
|
||||
|
||||
$node->status = 1;
|
||||
node_save($node);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an error message for returning to the XMLRPC caller.
|
||||
*/
|
||||
function blogapi_error($message) {
|
||||
static $xmlrpcusererr;
|
||||
if (!is_array($message)) {
|
||||
$message = array($message);
|
||||
}
|
||||
|
||||
$message = implode(' ', $message);
|
||||
|
||||
return xmlrpc_error($xmlrpcusererr + 1, strip_tags($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the given user has permission to edit a blog.
|
||||
*/
|
||||
function blogapi_validate_user($username, $password) {
|
||||
global $user;
|
||||
|
||||
$user = user_authenticate(array('name' => $username, 'pass' => $password));
|
||||
|
||||
if ($user->uid) {
|
||||
if (user_access('administer content with blog api', $user)) {
|
||||
return $user;
|
||||
}
|
||||
else {
|
||||
return t('You do not have permission to edit this blog.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return t('Wrong username or password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the blogger API, extract the node title from the contents field.
|
||||
*/
|
||||
function blogapi_blogger_title(&$contents) {
|
||||
if (eregi('<title>([^<]*)</title>', $contents, $title)) {
|
||||
$title = strip_tags($title[0]);
|
||||
$contents = ereg_replace('<title>[^<]*</title>', '', $contents);
|
||||
}
|
||||
else {
|
||||
list($title, $contents) = explode("\n", $contents, 2);
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
function blogapi_admin_settings() {
|
||||
$node_types = array_map('check_plain', node_get_types('names'));
|
||||
$defaults = isset($node_types['blog']) ? array('blog' => 1) : array();
|
||||
$form['blogapi_node_types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Enable for external blogging clients'),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => variable_get('blogapi_node_types', $defaults),
|
||||
'#options' => $node_types,
|
||||
'#description' => t('Select the content types available to external blogging clients via Blog API. If supported, each enabled content type will be displayed as a separate "blog" by the external client.')
|
||||
);
|
||||
|
||||
$blogapi_extensions_default = variable_get('blogapi_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp');
|
||||
$blogapi_uploadsize_default = variable_get('blogapi_uploadsize_default', 1);
|
||||
$blogapi_usersize_default = variable_get('blogapi_usersize_default', 1);
|
||||
|
||||
$form['settings_general'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('File settings'),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
|
||||
$form['settings_general']['blogapi_extensions_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default permitted file extensions'),
|
||||
'#default_value' => $blogapi_extensions_default,
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
|
||||
);
|
||||
|
||||
$form['settings_general']['blogapi_uploadsize_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default maximum file size per upload'),
|
||||
'#default_value' => $blogapi_uploadsize_default,
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The default maximum file size a user can upload.'),
|
||||
'#field_suffix' => t('MB')
|
||||
);
|
||||
|
||||
$form['settings_general']['blogapi_usersize_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default total file size per user'),
|
||||
'#default_value' => $blogapi_usersize_default,
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The default maximum size of all files a user can have on the site.'),
|
||||
'#field_suffix' => t('MB')
|
||||
);
|
||||
|
||||
$form['settings_general']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))).'</p>');
|
||||
|
||||
$roles = user_roles(0, 'administer content with blog api');
|
||||
$form['roles'] = array('#type' => 'value', '#value' => $roles);
|
||||
|
||||
foreach ($roles as $rid => $role) {
|
||||
$form['settings_role_'. $rid] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Settings for @role', array('@role' => $role)),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['settings_role_'. $rid]['blogapi_extensions_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Permitted file extensions'),
|
||||
'#default_value' => variable_get('blogapi_extensions_'. $rid, $blogapi_extensions_default),
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
|
||||
);
|
||||
$form['settings_role_'. $rid]['blogapi_uploadsize_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Maximum file size per upload'),
|
||||
'#default_value' => variable_get('blogapi_uploadsize_'. $rid, $blogapi_uploadsize_default),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The maximum size of a file a user can upload (in megabytes).'),
|
||||
);
|
||||
$form['settings_role_'. $rid]['blogapi_usersize_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Total file size per user'),
|
||||
'#default_value' => variable_get('blogapi_usersize_'. $rid, $blogapi_usersize_default),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The maximum size of all files a user can have on the site (in megabytes).'),
|
||||
);
|
||||
}
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
function blogapi_menu() {
|
||||
$items['blogapi/rsd'] = array(
|
||||
'title' => 'RSD',
|
||||
'page callback' => 'blogapi_rsd',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
$items['admin/settings/blogapi'] = array(
|
||||
'title' => 'Blog API',
|
||||
'description' => 'Configure the content types available to external blogging clients.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('blogapi_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_NORMAL_ITEM,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
function blogapi_init() {
|
||||
if (drupal_is_front_page()) {
|
||||
drupal_add_link(array('rel' => 'EditURI',
|
||||
'type' => 'application/rsd+xml',
|
||||
'title' => t('RSD'),
|
||||
'href' => url('blogapi/rsd', array('absolute' => TRUE))));
|
||||
}
|
||||
}
|
||||
|
||||
function blogapi_rsd() {
|
||||
global $base_url;
|
||||
|
||||
$xmlrpc = $base_url .'/xmlrpc.php';
|
||||
$base = url('', array('absolute' => TRUE));
|
||||
$blogid = 1; # until we figure out how to handle multiple bloggers
|
||||
|
||||
drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8');
|
||||
print <<<__RSD__
|
||||
<?xml version="1.0"?>
|
||||
<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
|
||||
<service>
|
||||
<engineName>Drupal</engineName>
|
||||
<engineLink>http://drupal.org/</engineLink>
|
||||
<homePageLink>$base</homePageLink>
|
||||
<apis>
|
||||
<api name="MetaWeblog" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
|
||||
<api name="Blogger" preferred="false" apiLink="$xmlrpc" blogID="$blogid" />
|
||||
<api name="MovableType" preferred="true" apiLink="$xmlrpc" blogID="$blogid" />
|
||||
</apis>
|
||||
</service>
|
||||
</rsd>
|
||||
__RSD__;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles extra information sent by clients according to MovableType's spec.
|
||||
*/
|
||||
function _blogapi_mt_extra(&$node, $struct) {
|
||||
if (is_array($node)) {
|
||||
$was_array = TRUE;
|
||||
$node = (object)$node;
|
||||
}
|
||||
|
||||
// mt_allow_comments
|
||||
if (array_key_exists('mt_allow_comments', $struct)) {
|
||||
switch ($struct['mt_allow_comments']) {
|
||||
case 0:
|
||||
$node->comment = COMMENT_NODE_DISABLED;
|
||||
break;
|
||||
case 1:
|
||||
$node->comment = COMMENT_NODE_READ_WRITE;
|
||||
break;
|
||||
case 2:
|
||||
$node->comment = COMMENT_NODE_READ_ONLY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// merge the 3 body sections (description, mt_excerpt, mt_text_more) into
|
||||
// one body
|
||||
if ($struct['mt_excerpt']) {
|
||||
$node->body = $struct['mt_excerpt'] .'<!--break-->'. $node->body;
|
||||
}
|
||||
if ($struct['mt_text_more']) {
|
||||
$node->body = $node->body .'<!--extended-->'. $struct['mt_text_more'];
|
||||
}
|
||||
|
||||
// mt_convert_breaks
|
||||
if ($struct['mt_convert_breaks']) {
|
||||
$node->format = $struct['mt_convert_breaks'];
|
||||
}
|
||||
|
||||
// dateCreated
|
||||
if ($struct['dateCreated']) {
|
||||
$node->date = format_date(mktime($struct['dateCreated']->hour, $struct['dateCreated']->minute, $struct['dateCreated']->second, $struct['dateCreated']->month, $struct['dateCreated']->day, $struct['dateCreated']->year), 'custom', 'Y-m-d H:i:s O');
|
||||
}
|
||||
|
||||
if ($was_array) {
|
||||
$node = (array)$node;
|
||||
}
|
||||
}
|
||||
|
||||
function _blogapi_get_post($node, $bodies = TRUE) {
|
||||
$xmlrpcval = array(
|
||||
'userid' => $node->name,
|
||||
'dateCreated' => xmlrpc_date($node->created),
|
||||
'title' => $node->title,
|
||||
'postid' => $node->nid,
|
||||
'link' => url('node/'. $node->nid, array('absolute' => TRUE)),
|
||||
'permaLink' => url('node/'. $node->nid, array('absolute' => TRUE)),
|
||||
);
|
||||
if ($bodies) {
|
||||
if ($node->comment == 1) {
|
||||
$comment = 2;
|
||||
}
|
||||
else if ($node->comment == 2) {
|
||||
$comment = 1;
|
||||
}
|
||||
$xmlrpcval['content'] = "<title>$node->title</title>$node->body";
|
||||
$xmlrpcval['description'] = $node->body;
|
||||
// Add MT specific fields
|
||||
$xmlrpcval['mt_allow_comments'] = (int) $comment;
|
||||
$xmlrpcval['mt_convert_breaks'] = $node->format;
|
||||
}
|
||||
|
||||
return $xmlrpcval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate blog ID, which maps to a content type in Drupal.
|
||||
*
|
||||
* Only content types configured to work with Blog API are supported.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the content type is supported and the user has permission
|
||||
* to post, or a blogapi_error() XML construct otherwise.
|
||||
*/
|
||||
function _blogapi_validate_blogid($blogid) {
|
||||
$types = _blogapi_get_node_types();
|
||||
if (in_array($blogid, $types, TRUE)) {
|
||||
return TRUE;
|
||||
}
|
||||
return blogapi_error(t("Blog API module is not configured to support the %type content type, or you don't have sufficient permissions to post this type of content.", array('%type' => $blogid)));
|
||||
}
|
||||
|
||||
function _blogapi_get_node_types() {
|
||||
$available_types = array_keys(array_filter(variable_get('blogapi_node_types', array('blog' => 1))));
|
||||
$types = array();
|
||||
foreach (node_get_types() as $type => $name) {
|
||||
if (node_access('create', $type) && in_array($type, $available_types)) {
|
||||
$types[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
function _blogapi_space_used($uid) {
|
||||
return db_result(db_query('SELECT SUM(filesize) FROM {blogapi_files} f WHERE f.uid = %d', $uid));
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
#placeholder {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
/* Palette */
|
||||
.color-form .form-item {
|
||||
padding-left: 0;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.color-form label {
|
||||
float: right;
|
||||
clear: right;
|
||||
}
|
||||
.color-form .form-text, .color-form .form-select {
|
||||
float: right;
|
||||
}
|
||||
.color-form .form-text {
|
||||
margin-right: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#palette .hook {
|
||||
float: right;
|
||||
}
|
||||
#palette .down, #palette .up, #palette .both {
|
||||
background: url(images/hook-rtl.png) no-repeat 0 0;
|
||||
}
|
||||
#palette .up {
|
||||
background-position: 0 -27px;
|
||||
}
|
||||
#palette .both {
|
||||
background-position: 0 -54px;
|
||||
}
|
||||
|
||||
#palette .lock {
|
||||
float: right;
|
||||
right: -10px;
|
||||
}
|
||||
html.js #preview {
|
||||
float: right;
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
/* Farbtastic placement */
|
||||
.color-form {
|
||||
max-width: 50em;
|
||||
position: relative;
|
||||
}
|
||||
#placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0; /* LTR */
|
||||
}
|
||||
|
||||
/* Palette */
|
||||
.color-form .form-item {
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
padding-left: 1em; /* LTR */
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.color-form label {
|
||||
float: left; /* LTR */
|
||||
clear: left; /* LTR */
|
||||
width: 10em;
|
||||
}
|
||||
.color-form .form-text, .color-form .form-select {
|
||||
float: left; /* LTR */
|
||||
}
|
||||
.color-form .form-text {
|
||||
text-align: center;
|
||||
margin-right: 5px; /* LTR */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#palette .hook {
|
||||
float: left; /* LTR */
|
||||
margin-top: 3px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
#palette .down, #palette .up, #palette .both {
|
||||
background: url(images/hook.png) no-repeat 100% 0; /* LTR */
|
||||
}
|
||||
#palette .up {
|
||||
background-position: 100% -27px; /* LTR */
|
||||
}
|
||||
#palette .both {
|
||||
background-position: 100% -54px; /* LTR */
|
||||
}
|
||||
|
||||
#palette .lock {
|
||||
float: left; /* LTR */
|
||||
position: relative;
|
||||
top: -1.4em;
|
||||
left: -10px; /* LTR */
|
||||
width: 20px;
|
||||
height: 25px;
|
||||
background: url(images/lock.png) no-repeat 50% 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#palette .unlocked {
|
||||
background-position: 50% -22px;
|
||||
}
|
||||
#palette .form-item {
|
||||
width: 20em;
|
||||
}
|
||||
#palette .item-selected {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
/* Preview */
|
||||
#preview {
|
||||
display: none;
|
||||
}
|
||||
html.js #preview {
|
||||
display: block;
|
||||
position: relative;
|
||||
float: left; /* LTR */
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Color
|
||||
description = Allows the user to change the color scheme of certain themes.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
function color_requirements($phase) {
|
||||
$requirements = array();
|
||||
|
||||
if ($phase == 'runtime') {
|
||||
// Check GD library
|
||||
if (function_exists('imagegd2')) {
|
||||
$info = gd_info();
|
||||
$requirements['gd'] = array(
|
||||
'value' => $info['GD Version'],
|
||||
);
|
||||
|
||||
// Check PNG support
|
||||
if (function_exists('imagecreatefrompng')) {
|
||||
$requirements['gd']['severity'] = REQUIREMENT_OK;
|
||||
}
|
||||
else {
|
||||
$requirements['gd']['severity'] = REQUIREMENT_ERROR;
|
||||
$requirements['gd']['description'] = t('The GD library for PHP is enabled, but was compiled without PNG support. Please check the <a href="@url">PHP image documentation</a> for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$requirements['gd'] = array(
|
||||
'value' => t('Not installed'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('The GD library for PHP is missing or outdated. Please check the <a href="@url">PHP image documentation</a> for information on how to correct this.', array('@url' => 'http://www.php.net/manual/en/ref.image.php')),
|
||||
);
|
||||
}
|
||||
$requirements['gd']['title'] = t('GD library');
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn site administrator if unsafe CSS color codes are found in the database.
|
||||
*/
|
||||
function color_update_6001() {
|
||||
$ret = array();
|
||||
$result = db_query("SELECT name FROM {variable} WHERE name LIKE 'color_%_palette'");
|
||||
while ($variable = db_fetch_array($result)) {
|
||||
$palette = variable_get($variable['name'], array());
|
||||
foreach ($palette as $key => $color) {
|
||||
if (!preg_match('/^#([a-f0-9]{3}){1,2}$/iD', $color)) {
|
||||
drupal_set_message('Some of the custom CSS color codes specified via the color module are invalid. Please examine the themes which are making use of the color module at the <a href="'. url('admin/appearance/settings') .'">Appearance settings</a> page to verify their CSS color values.', 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
|
||||
Drupal.behaviors.color = function (context) {
|
||||
// This behavior attaches by ID, so is only valid once on a page.
|
||||
if ($('#color_scheme_form .color-form.color-processed').size()) {
|
||||
return;
|
||||
}
|
||||
var form = $('#color_scheme_form .color-form', context);
|
||||
var inputs = [];
|
||||
var hooks = [];
|
||||
var locks = [];
|
||||
var focused = null;
|
||||
|
||||
// Add Farbtastic
|
||||
$(form).prepend('<div id="placeholder"></div>').addClass('color-processed');
|
||||
var farb = $.farbtastic('#placeholder');
|
||||
|
||||
// Decode reference colors to HSL
|
||||
var reference = Drupal.settings.color.reference;
|
||||
for (i in reference) {
|
||||
reference[i] = farb.RGBToHSL(farb.unpack(reference[i]));
|
||||
}
|
||||
|
||||
// Build preview
|
||||
$('#preview:not(.color-processed)')
|
||||
.append('<div id="gradient"></div>')
|
||||
.addClass('color-processed');
|
||||
var gradient = $('#preview #gradient');
|
||||
var h = parseInt(gradient.css('height')) / 10;
|
||||
for (i = 0; i < h; ++i) {
|
||||
gradient.append('<div class="gradient-line"></div>');
|
||||
}
|
||||
|
||||
// Fix preview background in IE6
|
||||
if (navigator.appVersion.match(/MSIE [0-6]\./)) {
|
||||
var e = $('#preview #img')[0];
|
||||
var image = e.currentStyle.backgroundImage;
|
||||
e.style.backgroundImage = 'none';
|
||||
e.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image.substring(5, image.length - 2) + "')";
|
||||
}
|
||||
|
||||
// Set up colorscheme selector
|
||||
$('#edit-scheme', form).change(function () {
|
||||
var colors = this.options[this.selectedIndex].value;
|
||||
if (colors != '') {
|
||||
colors = colors.split(',');
|
||||
for (i in colors) {
|
||||
callback(inputs[i], colors[i], false, true);
|
||||
}
|
||||
preview();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Render the preview.
|
||||
*/
|
||||
function preview() {
|
||||
// Solid background
|
||||
$('#preview', form).css('backgroundColor', inputs[0].value);
|
||||
|
||||
// Text preview
|
||||
$('#text', form).css('color', inputs[4].value);
|
||||
$('#text a, #text h2', form).css('color', inputs[1].value);
|
||||
|
||||
// Set up gradient
|
||||
var top = farb.unpack(inputs[2].value);
|
||||
var bottom = farb.unpack(inputs[3].value);
|
||||
if (top && bottom) {
|
||||
var delta = [];
|
||||
for (i in top) {
|
||||
delta[i] = (bottom[i] - top[i]) / h;
|
||||
}
|
||||
var accum = top;
|
||||
|
||||
// Render gradient lines
|
||||
$('#gradient > div', form).each(function () {
|
||||
for (i in accum) {
|
||||
accum[i] += delta[i];
|
||||
}
|
||||
this.style.backgroundColor = farb.pack(accum);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift a given color, using a reference pair (ref in HSL).
|
||||
*
|
||||
* This algorithm ensures relative ordering on the saturation and luminance
|
||||
* axes is preserved, and performs a simple hue shift.
|
||||
*
|
||||
* It is also symmetrical. If: shift_color(c, a, b) == d,
|
||||
* then shift_color(d, b, a) == c.
|
||||
*/
|
||||
function shift_color(given, ref1, ref2) {
|
||||
// Convert to HSL
|
||||
given = farb.RGBToHSL(farb.unpack(given));
|
||||
|
||||
// Hue: apply delta
|
||||
given[0] += ref2[0] - ref1[0];
|
||||
|
||||
// Saturation: interpolate
|
||||
if (ref1[1] == 0 || ref2[1] == 0) {
|
||||
given[1] = ref2[1];
|
||||
}
|
||||
else {
|
||||
var d = ref1[1] / ref2[1];
|
||||
if (d > 1) {
|
||||
given[1] /= d;
|
||||
}
|
||||
else {
|
||||
given[1] = 1 - (1 - given[1]) * d;
|
||||
}
|
||||
}
|
||||
|
||||
// Luminance: interpolate
|
||||
if (ref1[2] == 0 || ref2[2] == 0) {
|
||||
given[2] = ref2[2];
|
||||
}
|
||||
else {
|
||||
var d = ref1[2] / ref2[2];
|
||||
if (d > 1) {
|
||||
given[2] /= d;
|
||||
}
|
||||
else {
|
||||
given[2] = 1 - (1 - given[2]) * d;
|
||||
}
|
||||
}
|
||||
|
||||
return farb.pack(farb.HSLToRGB(given));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for Farbtastic when a new color is chosen.
|
||||
*/
|
||||
function callback(input, color, propagate, colorscheme) {
|
||||
// Set background/foreground color
|
||||
$(input).css({
|
||||
backgroundColor: color,
|
||||
'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff'
|
||||
});
|
||||
|
||||
// Change input value
|
||||
if (input.value && input.value != color) {
|
||||
input.value = color;
|
||||
|
||||
// Update locked values
|
||||
if (propagate) {
|
||||
var i = input.i;
|
||||
for (j = i + 1; ; ++j) {
|
||||
if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) break;
|
||||
var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
|
||||
callback(inputs[j], matched, false);
|
||||
}
|
||||
for (j = i - 1; ; --j) {
|
||||
if (!locks[j] || $(locks[j]).is('.unlocked')) break;
|
||||
var matched = shift_color(color, reference[input.key], reference[inputs[j].key]);
|
||||
callback(inputs[j], matched, false);
|
||||
}
|
||||
|
||||
// Update preview
|
||||
preview();
|
||||
}
|
||||
|
||||
// Reset colorscheme selector
|
||||
if (!colorscheme) {
|
||||
resetScheme();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the color scheme selector.
|
||||
*/
|
||||
function resetScheme() {
|
||||
$('#edit-scheme', form).each(function () {
|
||||
this.selectedIndex = this.options.length - 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Focus the Farbtastic on a particular field.
|
||||
function focus() {
|
||||
var input = this;
|
||||
// Remove old bindings
|
||||
focused && $(focused).unbind('keyup', farb.updateValue)
|
||||
.unbind('keyup', preview).unbind('keyup', resetScheme)
|
||||
.parent().removeClass('item-selected');
|
||||
|
||||
// Add new bindings
|
||||
focused = this;
|
||||
farb.linkTo(function (color) { callback(input, color, true, false); });
|
||||
farb.setColor(this.value);
|
||||
$(focused).keyup(farb.updateValue).keyup(preview).keyup(resetScheme)
|
||||
.parent().addClass('item-selected');
|
||||
}
|
||||
|
||||
// Initialize color fields
|
||||
$('#palette input.form-text', form)
|
||||
.each(function () {
|
||||
// Extract palette field name
|
||||
this.key = this.id.substring(13);
|
||||
|
||||
// Link to color picker temporarily to initialize.
|
||||
farb.linkTo(function () {}).setColor('#000').linkTo(this);
|
||||
|
||||
// Add lock
|
||||
var i = inputs.length;
|
||||
if (inputs.length) {
|
||||
var lock = $('<div class="lock"></div>').toggle(
|
||||
function () {
|
||||
$(this).addClass('unlocked');
|
||||
$(hooks[i - 1]).attr('class',
|
||||
locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook'
|
||||
);
|
||||
$(hooks[i]).attr('class',
|
||||
locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook'
|
||||
);
|
||||
},
|
||||
function () {
|
||||
$(this).removeClass('unlocked');
|
||||
$(hooks[i - 1]).attr('class',
|
||||
locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down'
|
||||
);
|
||||
$(hooks[i]).attr('class',
|
||||
locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up'
|
||||
);
|
||||
}
|
||||
);
|
||||
$(this).after(lock);
|
||||
locks.push(lock);
|
||||
};
|
||||
|
||||
// Add hook
|
||||
var hook = $('<div class="hook"></div>');
|
||||
$(this).after(hook);
|
||||
hooks.push(hook);
|
||||
|
||||
$(this).parent().find('.lock').click();
|
||||
this.i = i;
|
||||
inputs.push(this);
|
||||
})
|
||||
.focus(focus);
|
||||
|
||||
$('#palette label', form);
|
||||
|
||||
// Focus first color
|
||||
focus.call(inputs[0]);
|
||||
|
||||
// Render preview
|
||||
preview();
|
||||
};
|
|
@ -1,685 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function color_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#color':
|
||||
$output = '<p>'. t('The color module allows a site administrator to quickly and easily change the color scheme of certain themes. Although not all themes support color module, both Garland (the default theme) and Minnelli were designed to take advantage of its features. By using color module with a compatible theme, you can easily change the color of links, backgrounds, text, and other theme elements. Color module requires that your <a href="@url">file download method</a> be set to public.', array('@url' => url('admin/settings/file-system'))) .'</p>';
|
||||
$output .= '<p>'. t("It is important to remember that color module saves a modified copy of the theme's specified stylesheets in the files directory. This means that if you make any manual changes to your theme's stylesheet, you must save your color settings again, even if they haven't changed. This causes the color module generated version of the stylesheets in the files directory to be recreated using the new version of the original file.") .'</p>';
|
||||
$output .= '<p>'. t('To change the color settings for a compatible theme, select the "configure" link for the theme on the <a href="@themes">themes administration page</a>.', array('@themes' => url('admin/build/themes'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@color">Color module</a>.', array('@color' => 'http://drupal.org/handbook/modules/color/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme().
|
||||
*/
|
||||
function color_theme() {
|
||||
return array(
|
||||
'color_scheme_form' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_form_alter().
|
||||
*/
|
||||
function color_form_alter(&$form, $form_state, $form_id) {
|
||||
// Insert the color changer into the theme settings page.
|
||||
if ($form_id == 'system_theme_settings' && color_get_info(arg(4)) && function_exists('gd_info')) {
|
||||
if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
|
||||
// Disables the color changer when the private download method is used.
|
||||
// TODO: This should be solved in a different way. See issue #181003.
|
||||
drupal_set_message(t('The color picker only works if the <a href="@url">download method</a> is set to public.', array('@url' => url('admin/settings/file-system'))), 'warning');
|
||||
}
|
||||
else {
|
||||
$form['color'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Color scheme'),
|
||||
'#weight' => -1,
|
||||
'#attributes' => array('id' => 'color_scheme_form'),
|
||||
'#theme' => 'color_scheme_form',
|
||||
);
|
||||
$form['color'] += color_scheme_form($form_state, arg(4));
|
||||
$form['#validate'][] = 'color_scheme_form_validate';
|
||||
$form['#submit'][] = 'color_scheme_form_submit';
|
||||
}
|
||||
}
|
||||
|
||||
// Use the generated screenshot in the theme list.
|
||||
if ($form_id == 'system_theme_select_form' || $form_id == 'system_themes') {
|
||||
$themes = list_themes();
|
||||
foreach (element_children($form) as $theme) {
|
||||
if ($screenshot = variable_get('color_'. $theme .'_screenshot', NULL)) {
|
||||
if (isset($form[$theme]['screenshot'])) {
|
||||
$form[$theme]['screenshot']['#value'] = theme('image', $screenshot, '', '', array('class' => 'screenshot'), FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the theme to alter the resources used.
|
||||
*/
|
||||
function _color_page_alter(&$vars) {
|
||||
global $language, $theme_key;
|
||||
|
||||
// Override stylesheets.
|
||||
$color_paths = variable_get('color_'. $theme_key .'_stylesheets', array());
|
||||
if (!empty($color_paths)) {
|
||||
// Loop over theme CSS files and try to rebuild CSS array with rewritten
|
||||
// stylesheets. Keep the orginal order intact for CSS cascading.
|
||||
$new_theme_css = array();
|
||||
|
||||
foreach ($vars['css']['all']['theme'] as $old_path => $old_preprocess) {
|
||||
// Add the non-colored stylesheet first as we might not find a
|
||||
// re-colored stylesheet for replacement later.
|
||||
$new_theme_css[$old_path] = $old_preprocess;
|
||||
|
||||
// Loop over the path array with recolored CSS files to find matching
|
||||
// paths which could replace the non-recolored paths.
|
||||
foreach ($color_paths as $color_path) {
|
||||
// Color module currently requires unique file names to be used,
|
||||
// which allows us to compare different file paths.
|
||||
if (basename($old_path) == basename($color_path)) {
|
||||
// Pull out the non-colored and add rewritten stylesheet.
|
||||
unset($new_theme_css[$old_path]);
|
||||
$new_theme_css[$color_path] = $old_preprocess;
|
||||
|
||||
// If the current language is RTL and the CSS file had an RTL variant,
|
||||
// pull out the non-colored and add rewritten RTL stylesheet.
|
||||
if ($language->direction == LANGUAGE_RTL) {
|
||||
$rtl_old_path = str_replace('.css', '-rtl.css', $old_path);
|
||||
$rtl_color_path = str_replace('.css', '-rtl.css', $color_path);
|
||||
if (file_exists($rtl_color_path)) {
|
||||
unset($new_theme_css[$rtl_old_path]);
|
||||
$new_theme_css[$rtl_color_path] = $old_preprocess;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$vars['css']['all']['theme'] = $new_theme_css;
|
||||
$vars['styles'] = drupal_get_css($vars['css']);
|
||||
}
|
||||
|
||||
// Override logo.
|
||||
$logo = variable_get('color_'. $theme_key .'_logo', NULL);
|
||||
if ($logo && $vars['logo'] && preg_match('!'. $theme_key .'/logo.png$!', $vars['logo'])) {
|
||||
$vars['logo'] = base_path() . $logo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the color.module info for a particular theme.
|
||||
*/
|
||||
function color_get_info($theme) {
|
||||
$path = drupal_get_path('theme', $theme);
|
||||
$file = $path .'/color/color.inc';
|
||||
if ($path && file_exists($file)) {
|
||||
include $file;
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to retrieve the color palette for a particular theme.
|
||||
*/
|
||||
function color_get_palette($theme, $default = false) {
|
||||
// Fetch and expand default palette
|
||||
$fields = array('base', 'link', 'top', 'bottom', 'text');
|
||||
$info = color_get_info($theme);
|
||||
$keys = array_keys($info['schemes']);
|
||||
foreach (explode(',', array_shift($keys)) as $k => $scheme) {
|
||||
$palette[$fields[$k]] = $scheme;
|
||||
}
|
||||
|
||||
// Load variable
|
||||
return $default ? $palette : variable_get('color_'. $theme .'_palette', $palette);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form callback. Returns the configuration form.
|
||||
*/
|
||||
function color_scheme_form(&$form_state, $theme) {
|
||||
$base = drupal_get_path('module', 'color');
|
||||
$info = color_get_info($theme);
|
||||
|
||||
// Add Farbtastic color picker
|
||||
drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE);
|
||||
drupal_add_js('misc/farbtastic/farbtastic.js');
|
||||
|
||||
// Add custom CSS/JS
|
||||
drupal_add_css($base .'/color.css', 'module', 'all', FALSE);
|
||||
drupal_add_js($base .'/color.js');
|
||||
drupal_add_js(array('color' => array(
|
||||
'reference' => color_get_palette($theme, true)
|
||||
)), 'setting');
|
||||
|
||||
// See if we're using a predefined scheme
|
||||
$current = implode(',', variable_get('color_'. $theme .'_palette', array()));
|
||||
// Note: we use the original theme when the default scheme is chosen.
|
||||
$current = isset($info['schemes'][$current]) ? $current : ($current == '' ? reset($info['schemes']) : '');
|
||||
|
||||
// Add scheme selector
|
||||
$info['schemes'][''] = t('Custom');
|
||||
$form['scheme'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Color set'),
|
||||
'#options' => $info['schemes'],
|
||||
'#default_value' => $current,
|
||||
);
|
||||
|
||||
// Add palette fields
|
||||
$palette = color_get_palette($theme);
|
||||
$names = array(
|
||||
'base' => t('Base color'),
|
||||
'link' => t('Link color'),
|
||||
'top' => t('Header top'),
|
||||
'bottom' => t('Header bottom'),
|
||||
'text' => t('Text color')
|
||||
);
|
||||
$form['palette']['#tree'] = true;
|
||||
foreach ($palette as $name => $value) {
|
||||
$form['palette'][$name] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $names[$name],
|
||||
'#default_value' => $value,
|
||||
'#size' => 8,
|
||||
);
|
||||
}
|
||||
$form['theme'] = array('#type' => 'value', '#value' => arg(4));
|
||||
$form['info'] = array('#type' => 'value', '#value' => $info);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme color form.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_color_scheme_form($form) {
|
||||
// Include stylesheet
|
||||
$theme = $form['theme']['#value'];
|
||||
$info = $form['info']['#value'];
|
||||
$path = drupal_get_path('theme', $theme) .'/';
|
||||
drupal_add_css($path . $info['preview_css']);
|
||||
$output = '';
|
||||
// Wrapper
|
||||
$output .= '<div class="color-form clear-block">';
|
||||
|
||||
// Color schemes
|
||||
$output .= drupal_render($form['scheme']);
|
||||
|
||||
// Palette
|
||||
$output .= '<div id="palette" class="clear-block">';
|
||||
foreach (element_children($form['palette']) as $name) {
|
||||
$output .= drupal_render($form['palette'][$name]);
|
||||
}
|
||||
$output .= '</div>';
|
||||
|
||||
// Preview
|
||||
$output .= drupal_render($form);
|
||||
$output .= '<h2>'. t('Preview') .'</h2>';
|
||||
$output .= '<div id="preview"><div id="text"><h2>Lorem ipsum dolor</h2><p>Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud <a href="#">exercitation ullamco</a> laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p></div><div id="img" style="background-image: url('. base_path() . $path . $info['preview_image'] .')"></div></div>';
|
||||
|
||||
// Close wrapper
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation handler for color change form.
|
||||
*/
|
||||
function color_scheme_form_validate($form, &$form_state) {
|
||||
// Only accept hexadecimal CSS color strings to avoid XSS upon use.
|
||||
foreach ($form_state['values']['palette'] as $key => $color) {
|
||||
if (!preg_match('/^#([a-f0-9]{3}){1,2}$/iD', $color)) {
|
||||
form_set_error('palette][' . $key, t('%name must be a valid hexadecimal CSS color value.', array('%name' => $form['color']['palette'][$key]['#title'])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for color change form.
|
||||
*/
|
||||
function color_scheme_form_submit($form, &$form_state) {
|
||||
// Get theme coloring info
|
||||
if (!isset($form_state['values']['info'])) {
|
||||
return;
|
||||
}
|
||||
$theme = $form_state['values']['theme'];
|
||||
$info = $form_state['values']['info'];
|
||||
|
||||
// Resolve palette
|
||||
$palette = $form_state['values']['palette'];
|
||||
if ($form_state['values']['scheme'] != '') {
|
||||
$scheme = explode(',', $form_state['values']['scheme']);
|
||||
foreach ($palette as $k => $color) {
|
||||
$palette[$k] = array_shift($scheme);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure enough memory is available, if PHP's memory limit is compiled in.
|
||||
if (function_exists('memory_get_usage')) {
|
||||
// Fetch source image dimensions.
|
||||
$source = drupal_get_path('theme', $theme) .'/'. $info['base_image'];
|
||||
list($width, $height) = getimagesize($source);
|
||||
|
||||
// We need at least a copy of the source and a target buffer of the same
|
||||
// size (both at 32bpp).
|
||||
$required = $width * $height * 8;
|
||||
$usage = memory_get_usage();
|
||||
$limit = parse_size(ini_get('memory_limit'));
|
||||
if ($usage + $required > $limit) {
|
||||
drupal_set_message(t('There is not enough memory available to PHP to change this theme\'s color scheme. You need at least %size more. Check the <a href="@url">PHP documentation</a> for more information.', array('%size' => format_size($usage + $required - $limit), '@url' => 'http://www.php.net/manual/en/ini.core.php#ini.sect.resource-limits')), 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old files
|
||||
foreach (variable_get('color_'. $theme .'_files', array()) as $file) {
|
||||
@unlink($file);
|
||||
}
|
||||
if (isset($file) && $file = dirname($file)) {
|
||||
@rmdir($file);
|
||||
}
|
||||
|
||||
// Don't render the default colorscheme, use the standard theme instead.
|
||||
if (implode(',', color_get_palette($theme, true)) == implode(',', $palette)
|
||||
|| $form_state['values']['op'] == t('Reset to defaults')) {
|
||||
variable_del('color_'. $theme .'_palette');
|
||||
variable_del('color_'. $theme .'_stylesheets');
|
||||
variable_del('color_'. $theme .'_logo');
|
||||
variable_del('color_'. $theme .'_files');
|
||||
variable_del('color_'. $theme .'_screenshot');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare target locations for generated files.
|
||||
$id = $theme .'-'. substr(md5(serialize($palette) . microtime()), 0, 8);
|
||||
$paths['color'] = file_directory_path() .'/color';
|
||||
$paths['target'] = $paths['color'] .'/'. $id;
|
||||
foreach ($paths as $path) {
|
||||
file_check_directory($path, FILE_CREATE_DIRECTORY);
|
||||
}
|
||||
$paths['target'] = $paths['target'] .'/';
|
||||
$paths['id'] = $id;
|
||||
$paths['source'] = drupal_get_path('theme', $theme) .'/';
|
||||
$paths['files'] = $paths['map'] = array();
|
||||
|
||||
// Save palette and logo location.
|
||||
variable_set('color_'. $theme .'_palette', $palette);
|
||||
variable_set('color_'. $theme .'_logo', $paths['target'] .'logo.png');
|
||||
|
||||
// Copy over neutral images.
|
||||
foreach ($info['copy'] as $file) {
|
||||
$base = basename($file);
|
||||
$source = $paths['source'] . $file;
|
||||
file_copy($source, $paths['target'] . $base);
|
||||
$paths['map'][$file] = $base;
|
||||
$paths['files'][] = $paths['target'] . $base;
|
||||
}
|
||||
|
||||
// Render new images, if image provided.
|
||||
if ($info['base_image']) {
|
||||
_color_render_images($theme, $info, $paths, $palette);
|
||||
}
|
||||
|
||||
// Rewrite theme stylesheets.
|
||||
$css = array();
|
||||
foreach ($info['css'] as $stylesheet) {
|
||||
// Build a temporary array with LTR and RTL files.
|
||||
$files = array();
|
||||
if (file_exists($paths['source'] . $stylesheet)) {
|
||||
$files[] = $stylesheet;
|
||||
|
||||
$rtl_file = str_replace('.css', '-rtl.css', $stylesheet);
|
||||
if (file_exists($paths['source'] . $rtl_file)) {
|
||||
$files[] = $rtl_file;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Aggregate @imports recursively for each configured top level CSS file
|
||||
// without optimization. Aggregation and optimization will be
|
||||
// handled by drupal_build_css_cache() only.
|
||||
$style = drupal_load_stylesheet($paths['source'] . $file, FALSE);
|
||||
|
||||
// Return the path to where this CSS file originated from, stripping
|
||||
// off the name of the file at the end of the path.
|
||||
$base = base_path() . dirname($paths['source'] . $file) .'/';
|
||||
_drupal_build_css_path(NULL, $base);
|
||||
|
||||
// Prefix all paths within this CSS file, ignoring absolute paths.
|
||||
$style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $style);
|
||||
|
||||
// Rewrite stylesheet with new colors.
|
||||
$style = _color_rewrite_stylesheet($theme, $info, $paths, $palette, $style);
|
||||
$base_file = basename($file);
|
||||
$css[] = $paths['target'] . $base_file;
|
||||
_color_save_stylesheet($paths['target'] . $base_file, $style, $paths);
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain list of files.
|
||||
variable_set('color_'. $theme .'_stylesheets', $css);
|
||||
variable_set('color_'. $theme .'_files', $paths['files']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite the stylesheet to match the colors in the palette.
|
||||
*/
|
||||
function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) {
|
||||
$themes = list_themes();
|
||||
|
||||
// Prepare color conversion table
|
||||
$conversion = $palette;
|
||||
unset($conversion['base']);
|
||||
foreach ($conversion as $k => $v) {
|
||||
$conversion[$k] = drupal_strtolower($v);
|
||||
}
|
||||
$default = color_get_palette($theme, true);
|
||||
|
||||
// Split off the "Don't touch" section of the stylesheet.
|
||||
$split = "Color Module: Don't touch";
|
||||
if (strpos($style, $split) !== FALSE) {
|
||||
list($style, $fixed) = explode($split, $style);
|
||||
}
|
||||
|
||||
// Find all colors in the stylesheet and the chunks in between.
|
||||
$style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$is_color = false;
|
||||
$output = '';
|
||||
$base = 'base';
|
||||
|
||||
// Iterate over all parts.
|
||||
foreach ($style as $chunk) {
|
||||
if ($is_color) {
|
||||
$chunk = drupal_strtolower($chunk);
|
||||
// Check if this is one of the colors in the default palette.
|
||||
if ($key = array_search($chunk, $default)) {
|
||||
$chunk = $conversion[$key];
|
||||
}
|
||||
// Not a pre-set color. Extrapolate from the base.
|
||||
else {
|
||||
$chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Determine the most suitable base color for the next color.
|
||||
|
||||
// 'a' declarations. Use link.
|
||||
if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) {
|
||||
$base = 'link';
|
||||
}
|
||||
// 'color:' styles. Use text.
|
||||
else if (preg_match('/(?<!-)color[^{:]*:[^{#]*$/i', $chunk)) {
|
||||
$base = 'text';
|
||||
}
|
||||
// Reset back to base.
|
||||
else {
|
||||
$base = 'base';
|
||||
}
|
||||
}
|
||||
$output .= $chunk;
|
||||
$is_color = !$is_color;
|
||||
}
|
||||
// Append fixed colors segment.
|
||||
if (isset($fixed)) {
|
||||
$output .= $fixed;
|
||||
}
|
||||
|
||||
// Replace paths to images.
|
||||
foreach ($paths['map'] as $before => $after) {
|
||||
$before = base_path() . $paths['source'] . $before;
|
||||
$before = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $before);
|
||||
$output = str_replace($before, $after, $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the rewritten stylesheet to disk.
|
||||
*/
|
||||
function _color_save_stylesheet($file, $style, &$paths) {
|
||||
|
||||
// Write new stylesheet.
|
||||
file_save_data($style, $file, FILE_EXISTS_REPLACE);
|
||||
$paths['files'][] = $file;
|
||||
|
||||
// Set standard file permissions for webserver-generated files.
|
||||
@chmod($file, 0664);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render images that match a given palette.
|
||||
*/
|
||||
function _color_render_images($theme, &$info, &$paths, $palette) {
|
||||
|
||||
// Prepare template image.
|
||||
$source = $paths['source'] .'/'. $info['base_image'];
|
||||
$source = imagecreatefrompng($source);
|
||||
$width = imagesx($source);
|
||||
$height = imagesy($source);
|
||||
|
||||
// Prepare target buffer.
|
||||
$target = imagecreatetruecolor($width, $height);
|
||||
imagealphablending($target, true);
|
||||
|
||||
// Fill regions of solid color.
|
||||
foreach ($info['fill'] as $color => $fill) {
|
||||
imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color]));
|
||||
}
|
||||
|
||||
// Render gradient.
|
||||
for ($y = 0; $y < $info['gradient'][3]; ++$y) {
|
||||
$color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1));
|
||||
imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2], $info['gradient'][1] + $y + 1, $color);
|
||||
}
|
||||
|
||||
// Blend over template.
|
||||
imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
|
||||
|
||||
// Clean up template image.
|
||||
imagedestroy($source);
|
||||
|
||||
// Cut out slices.
|
||||
foreach ($info['slices'] as $file => $coord) {
|
||||
list($x, $y, $width, $height) = $coord;
|
||||
$base = basename($file);
|
||||
$image = $paths['target'] . $base;
|
||||
|
||||
// Cut out slice.
|
||||
if ($file == 'screenshot.png') {
|
||||
$slice = imagecreatetruecolor(150, 90);
|
||||
imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height);
|
||||
variable_set('color_'. $theme .'_screenshot', $image);
|
||||
}
|
||||
else {
|
||||
$slice = imagecreatetruecolor($width, $height);
|
||||
imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
|
||||
}
|
||||
|
||||
// Save image.
|
||||
imagepng($slice, $image);
|
||||
imagedestroy($slice);
|
||||
$paths['files'][] = $image;
|
||||
|
||||
// Set standard file permissions for webserver-generated files
|
||||
@chmod(realpath($image), 0664);
|
||||
|
||||
// Build before/after map of image paths.
|
||||
$paths['map'][$file] = $base;
|
||||
}
|
||||
|
||||
// Clean up target buffer.
|
||||
imagedestroy($target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift a given color, using a reference pair and a target blend color.
|
||||
*
|
||||
* Note: this function is significantly different from the JS version, as it
|
||||
* is written to match the blended images perfectly.
|
||||
*
|
||||
* Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta
|
||||
* then (return == target + (given - target) * delta)
|
||||
*
|
||||
* Loose constraint: Preserve relative positions in saturation and luminance
|
||||
* space.
|
||||
*/
|
||||
function _color_shift($given, $ref1, $ref2, $target) {
|
||||
|
||||
// We assume that ref2 is a blend of ref1 and target and find
|
||||
// delta based on the length of the difference vectors:
|
||||
|
||||
// delta = 1 - |ref2 - ref1| / |white - ref1|
|
||||
$target = _color_unpack($target, true);
|
||||
$ref1 = _color_unpack($ref1, true);
|
||||
$ref2 = _color_unpack($ref2, true);
|
||||
$numerator = 0;
|
||||
$denominator = 0;
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
|
||||
$denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
|
||||
}
|
||||
$delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
|
||||
|
||||
// Calculate the color that ref2 would be if the assumption was true.
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
|
||||
}
|
||||
|
||||
// If the assumption is not true, there is a difference between ref2 and ref3.
|
||||
// We measure this in HSL space. Notation: x' = hsl(x).
|
||||
$ref2 = _color_rgb2hsl($ref2);
|
||||
$ref3 = _color_rgb2hsl($ref3);
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$shift[$i] = $ref2[$i] - $ref3[$i];
|
||||
}
|
||||
|
||||
// Take the given color, and blend it towards the target.
|
||||
$given = _color_unpack($given, true);
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
|
||||
}
|
||||
|
||||
// Finally, we apply the extra shift in HSL space.
|
||||
// Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
|
||||
$result = _color_rgb2hsl($result);
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
|
||||
}
|
||||
$result = _color_hsl2rgb($result);
|
||||
|
||||
// Return hex color.
|
||||
return _color_pack($result, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hex triplet into a GD color.
|
||||
*/
|
||||
function _color_gd($img, $hex) {
|
||||
$c = array_merge(array($img), _color_unpack($hex));
|
||||
return call_user_func_array('imagecolorallocate', $c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend two hex colors and return the GD color.
|
||||
*/
|
||||
function _color_blend($img, $hex1, $hex2, $alpha) {
|
||||
$in1 = _color_unpack($hex1);
|
||||
$in2 = _color_unpack($hex2);
|
||||
$out = array($img);
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
|
||||
}
|
||||
return call_user_func_array('imagecolorallocate', $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hex color into an RGB triplet.
|
||||
*/
|
||||
function _color_unpack($hex, $normalize = false) {
|
||||
if (strlen($hex) == 4) {
|
||||
$hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
|
||||
}
|
||||
$c = hexdec($hex);
|
||||
for ($i = 16; $i >= 0; $i -= 8) {
|
||||
$out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an RGB triplet to a hex color.
|
||||
*/
|
||||
function _color_pack($rgb, $normalize = false) {
|
||||
$out = 0;
|
||||
foreach ($rgb as $k => $v) {
|
||||
$out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
|
||||
}
|
||||
return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a HSL triplet into RGB
|
||||
*/
|
||||
function _color_hsl2rgb($hsl) {
|
||||
$h = $hsl[0];
|
||||
$s = $hsl[1];
|
||||
$l = $hsl[2];
|
||||
$m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
|
||||
$m1 = $l * 2 - $m2;
|
||||
return array(
|
||||
_color_hue2rgb($m1, $m2, $h + 0.33333),
|
||||
_color_hue2rgb($m1, $m2, $h),
|
||||
_color_hue2rgb($m1, $m2, $h - 0.33333),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for _color_hsl2rgb().
|
||||
*/
|
||||
function _color_hue2rgb($m1, $m2, $h) {
|
||||
$h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
|
||||
if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
|
||||
if ($h * 2 < 1) return $m2;
|
||||
if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
|
||||
return $m1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an RGB triplet to HSL.
|
||||
*/
|
||||
function _color_rgb2hsl($rgb) {
|
||||
$r = $rgb[0];
|
||||
$g = $rgb[1];
|
||||
$b = $rgb[2];
|
||||
$min = min($r, min($g, $b));
|
||||
$max = max($r, max($g, $b));
|
||||
$delta = $max - $min;
|
||||
$l = ($min + $max) / 2;
|
||||
$s = 0;
|
||||
if ($l > 0 && $l < 1) {
|
||||
$s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
|
||||
}
|
||||
$h = 0;
|
||||
if ($delta > 0) {
|
||||
if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
|
||||
if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
|
||||
if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
|
||||
$h /= 6;
|
||||
}
|
||||
return array($h, $s, $l);
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 170 B |
Binary file not shown.
Before Width: | Height: | Size: 140 B |
Binary file not shown.
Before Width: | Height: | Size: 251 B |
|
@ -1,172 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Admin page callbacks for the contact module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Categories/list tab.
|
||||
*/
|
||||
function contact_admin_categories() {
|
||||
$result = db_query('SELECT cid, category, recipients, selected FROM {contact} ORDER BY weight, category');
|
||||
$rows = array();
|
||||
while ($category = db_fetch_object($result)) {
|
||||
$rows[] = array(check_plain($category->category), check_plain($category->recipients), ($category->selected ? t('Yes') : t('No')), l(t('edit'), 'admin/build/contact/edit/'. $category->cid), l(t('delete'), 'admin/build/contact/delete/'. $category->cid));
|
||||
}
|
||||
$header = array(t('Category'), t('Recipients'), t('Selected'), array('data' => t('Operations'), 'colspan' => 2));
|
||||
|
||||
return theme('table', $header, $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Category edit page.
|
||||
*/
|
||||
function contact_admin_edit($form_state = array(), $op, $contact = NULL) {
|
||||
|
||||
if (empty($contact) || $op == 'add') {
|
||||
$contact = array(
|
||||
'category' => '',
|
||||
'recipients' => '',
|
||||
'reply' => '',
|
||||
'weight' => 0,
|
||||
'selected' => 0,
|
||||
'cid' => NULL,
|
||||
);
|
||||
}
|
||||
$form['contact_op'] = array('#type' => 'value', '#value' => $op);
|
||||
$form['category'] = array('#type' => 'textfield',
|
||||
'#title' => t('Category'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $contact['category'],
|
||||
'#description' => t("Example: 'website feedback' or 'product information'."),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['recipients'] = array('#type' => 'textarea',
|
||||
'#title' => t('Recipients'),
|
||||
'#default_value' => $contact['recipients'],
|
||||
'#description' => t("Example: 'webmaster@example.com' or 'sales@example.com,support@example.com'. To specify multiple recipients, separate each e-mail address with a comma."),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['reply'] = array('#type' => 'textarea',
|
||||
'#title' => t('Auto-reply'),
|
||||
'#default_value' => $contact['reply'],
|
||||
'#description' => t('Optional auto-reply. Leave empty if you do not want to send the user an auto-reply message.'),
|
||||
);
|
||||
$form['weight'] = array('#type' => 'weight',
|
||||
'#title' => t('Weight'),
|
||||
'#default_value' => $contact['weight'],
|
||||
'#description' => t('When listing categories, those with lighter (smaller) weights get listed before categories with heavier (larger) weights. Categories with equal weights are sorted alphabetically.'),
|
||||
);
|
||||
$form['selected'] = array('#type' => 'select',
|
||||
'#title' => t('Selected'),
|
||||
'#options' => array('0' => t('No'), '1' => t('Yes')),
|
||||
'#default_value' => $contact['selected'],
|
||||
'#description' => t('Set this to <em>Yes</em> if you would like this category to be selected by default.'),
|
||||
);
|
||||
$form['cid'] = array('#type' => 'value',
|
||||
'#value' => $contact['cid'],
|
||||
);
|
||||
$form['submit'] = array('#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the contact category edit page form submission.
|
||||
*/
|
||||
function contact_admin_edit_validate($form, &$form_state) {
|
||||
if (empty($form_state['values']['category'])) {
|
||||
form_set_error('category', t('You must enter a category.'));
|
||||
}
|
||||
if (empty($form_state['values']['recipients'])) {
|
||||
form_set_error('recipients', t('You must enter one or more recipients.'));
|
||||
}
|
||||
else {
|
||||
$recipients = explode(',', $form_state['values']['recipients']);
|
||||
foreach ($recipients as $recipient) {
|
||||
if (!valid_email_address(trim($recipient))) {
|
||||
form_set_error('recipients', t('%recipient is an invalid e-mail address.', array('%recipient' => $recipient)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the contact category edit page form submission.
|
||||
*/
|
||||
function contact_admin_edit_submit($form, &$form_state) {
|
||||
if ($form_state['values']['selected']) {
|
||||
// Unselect all other contact categories.
|
||||
db_query('UPDATE {contact} SET selected = 0');
|
||||
}
|
||||
$recipients = explode(',', $form_state['values']['recipients']);
|
||||
foreach ($recipients as $key => $recipient) {
|
||||
// E-mail address validation has already been done in _validate.
|
||||
$recipients[$key] = trim($recipient);
|
||||
}
|
||||
$form_state['values']['recipients'] = implode(',', $recipients);
|
||||
if (empty($form_state['values']['cid']) || $form_state['values']['contact_op'] == 'add') {
|
||||
drupal_write_record('contact', $form_state['values']);
|
||||
drupal_set_message(t('Category %category has been added.', array('%category' => $form_state['values']['category'])));
|
||||
watchdog('mail', 'Contact form: category %category added.', array('%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/build/contact'));
|
||||
|
||||
}
|
||||
else {
|
||||
drupal_write_record('contact', $form_state['values'], 'cid');
|
||||
drupal_set_message(t('Category %category has been updated.', array('%category' => $form_state['values']['category'])));
|
||||
watchdog('mail', 'Contact form: category %category updated.', array('%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/build/contact'));
|
||||
}
|
||||
|
||||
$form_state['redirect'] = 'admin/build/contact';
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category delete page.
|
||||
*/
|
||||
function contact_admin_delete(&$form_state, $contact) {
|
||||
|
||||
$form['contact'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $contact,
|
||||
);
|
||||
|
||||
return confirm_form($form, t('Are you sure you want to delete %category?', array('%category' => $contact['category'])), 'admin/build/contact', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process category delete form submission.
|
||||
*/
|
||||
function contact_admin_delete_submit($form, &$form_state) {
|
||||
$contact = $form_state['values']['contact'];
|
||||
db_query("DELETE FROM {contact} WHERE cid = %d", $contact['cid']);
|
||||
drupal_set_message(t('Category %category has been deleted.', array('%category' => $contact['category'])));
|
||||
watchdog('mail', 'Contact form: category %category deleted.', array('%category' => $contact['category']), WATCHDOG_NOTICE);
|
||||
|
||||
$form_state['redirect'] = 'admin/build/contact';
|
||||
return;
|
||||
}
|
||||
|
||||
function contact_admin_settings() {
|
||||
$form['contact_form_information'] = array('#type' => 'textarea',
|
||||
'#title' => t('Additional information'),
|
||||
'#default_value' => variable_get('contact_form_information', t('You can leave a message using the contact form below.')),
|
||||
'#description' => t('Information to show on the <a href="@form">contact page</a>. Can be anything from submission guidelines to your postal address or telephone number.', array('@form' => url('contact'))),
|
||||
);
|
||||
$form['contact_hourly_threshold'] = array('#type' => 'select',
|
||||
'#title' => t('Hourly threshold'),
|
||||
'#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50)),
|
||||
'#default_value' => variable_get('contact_hourly_threshold', 3),
|
||||
'#description' => t('The maximum number of contact form submissions a user can perform per hour.'),
|
||||
);
|
||||
$form['contact_default_status'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable personal contact form by default'),
|
||||
'#default_value' => variable_get('contact_default_status', 1),
|
||||
'#description' => t('Default status of the personal contact form for new users.'),
|
||||
);
|
||||
return system_settings_form($form);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Contact
|
||||
description = Enables the use of both personal and site-wide contact forms.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function contact_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('contact');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function contact_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('contact');
|
||||
|
||||
variable_del('contact_default_status');
|
||||
variable_del('contact_form_information');
|
||||
variable_del('contact_hourly_threshold');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function contact_schema() {
|
||||
$schema['contact'] = array(
|
||||
'description' => 'Contact form category settings.',
|
||||
'fields' => array(
|
||||
'cid' => array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique category ID.',
|
||||
),
|
||||
'category' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Category name.',
|
||||
),
|
||||
'recipients' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Comma-separated list of recipient e-mail addresses.',
|
||||
),
|
||||
'reply' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Text of the auto-reply message.',
|
||||
),
|
||||
'weight' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => "The category's weight.",
|
||||
),
|
||||
'selected' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'Flag to indicate whether or not category is selected by default. (1 = Yes, 0 = No)',
|
||||
),
|
||||
),
|
||||
'primary key' => array('cid'),
|
||||
'unique keys' => array(
|
||||
'category' => array('category'),
|
||||
),
|
||||
'indexes' => array(
|
||||
'list' => array('weight', 'category'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables the use of personal and site-wide contact forms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function contact_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#contact':
|
||||
$output = '<p>'. t('The contact module facilitates communication via e-mail, by allowing your site\'s visitors to contact one another (personal contact forms), and by providing a simple way to direct messages to a set of administrator-defined recipients (the <a href="@contact">contact page</a>). With either form, users specify a subject, write their message, and (optionally) have a copy of their message sent to their own e-mail address.', array('@contact' => url('contact'))) .'</p>';
|
||||
$output .= '<p>'. t("Personal contact forms allow users to be contacted via e-mail, while keeping recipient e-mail addresses private. Users may enable or disable their personal contact forms by editing their <em>My account</em> page. If enabled, a <em>Contact</em> tab leading to their personal contact form is available on their user profile. Site administrators have access to all personal contact forms (even if they have been disabled). The <em>Contact</em> tab is only visible when viewing another user's profile (users do not see their own <em>Contact</em> tab).") .'</p>';
|
||||
$output .= '<p>'. t('The <a href="@contact">contact page</a> provides a simple form for visitors to leave comments, feedback, or other requests. Messages are routed by selecting a category from a list of administrator-defined options; each category has its own set of e-mail recipients. Common categories for a business site include, for example, "Website feedback" (messages are forwarded to web site administrators) and "Product information" (messages are forwarded to members of the sales department). The actual e-mail addresses defined within a category are not displayed. Only users in roles with the <em>access site-wide contact form</em> permission may access the <a href="@contact">contact page</a>.', array('@contact' => url('contact'))) .'</p>';
|
||||
$output .= '<p>'. t('A link to your site\'s <a href="@contact">contact page</a> from the main <em>Navigation</em> menu is created, but is disabled by default. Create a similar link on another menu by adding a menu item pointing to the path "contact"', array('@contact' => url('contact'))) .'</p>';
|
||||
$output .= '<p>'. t('Customize the <a href="@contact">contact page</a> with additional information (like physical location, mailing address, and telephone number) using the <a href="@contact-settings">contact form settings page</a>. The <a href="@contact-settings">settings page</a> also provides configuration options for the maximum number of contact form submissions a user may perform per hour, and the default status of users\' personal contact forms.', array('@contact-settings' => url('admin/build/contact/settings'), '@contact' => url('contact'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@contact">Contact module</a>.', array('@contact' => url('http://drupal.org/handbook/modules/contact/', array('absolute' => TRUE)))) .'</p>';
|
||||
return $output;
|
||||
case 'admin/build/contact':
|
||||
$output = '<p>'. t('This page lets you set up <a href="@form">your site-wide contact form</a>. To do so, add one or more categories. You can associate different recipients with each category to route e-mails to different people. For example, you can route website feedback to the webmaster and direct product information requests to the sales department. On the <a href="@settings">settings page</a>, you can customize the information shown above the contact form. This can be useful to provide additional contact information such as your postal address and telephone number.', array('@settings' => url('admin/build/contact/settings'), '@form' => url('contact'))) .'</p>';
|
||||
if (!module_exists('menu')) {
|
||||
$menu_note = t('The menu item can be customized and configured only once the menu module has been <a href="@modules-page">enabled</a>.', array('@modules-page' => url('admin/settings/modules')));
|
||||
}
|
||||
else {
|
||||
$menu_note = '';
|
||||
}
|
||||
$output .= '<p>'. t('The contact module also adds a <a href="@menu-settings">menu item</a> (disabled by default) to the navigation block.', array('@menu-settings' => url('admin/build/menu'))) .' '. $menu_note .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm
|
||||
*/
|
||||
function contact_perm() {
|
||||
return array('access site-wide contact form', 'administer site-wide contact form');
|
||||
}
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function contact_menu() {
|
||||
$items['admin/build/contact'] = array(
|
||||
'title' => 'Contact form',
|
||||
'description' => 'Create a system contact form and set up categories for the form to use.',
|
||||
'page callback' => 'contact_admin_categories',
|
||||
'access arguments' => array('administer site-wide contact form'),
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['admin/build/contact/list'] = array(
|
||||
'title' => 'List',
|
||||
'page callback' => 'contact_admin_categories',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['admin/build/contact/add'] = array(
|
||||
'title' => 'Add category',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('contact_admin_edit', 3),
|
||||
'access arguments' => array('administer site-wide contact form'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 1,
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['admin/build/contact/edit/%contact'] = array(
|
||||
'title' => 'Edit contact category',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('contact_admin_edit', 3, 4),
|
||||
'access arguments' => array('administer site-wide contact form'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['admin/build/contact/delete/%contact'] = array(
|
||||
'title' => 'Delete contact',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('contact_admin_delete', 4),
|
||||
'access arguments' => array('administer site-wide contact form'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['admin/build/contact/settings'] = array(
|
||||
'title' => 'Settings',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('contact_admin_settings'),
|
||||
'access arguments' => array('administer site-wide contact form'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 2,
|
||||
'file' => 'contact.admin.inc',
|
||||
);
|
||||
$items['contact'] = array(
|
||||
'title' => 'Contact',
|
||||
'page callback' => 'contact_site_page',
|
||||
'access arguments' => array('access site-wide contact form'),
|
||||
'type' => MENU_SUGGESTED_ITEM,
|
||||
'file' => 'contact.pages.inc',
|
||||
);
|
||||
$items['user/%user/contact'] = array(
|
||||
'title' => 'Contact',
|
||||
'page callback' => 'contact_user_page',
|
||||
'page arguments' => array(1),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'access callback' => '_contact_user_tab_access',
|
||||
'access arguments' => array(1),
|
||||
'weight' => 2,
|
||||
'file' => 'contact.pages.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access callback for a user's personal contact form.
|
||||
*
|
||||
* @param $account
|
||||
* A user account object.
|
||||
* @return
|
||||
* TRUE if the current user has access to the requested user's contact form,
|
||||
* or FALSE otherwise.
|
||||
*/
|
||||
function _contact_user_tab_access($account) {
|
||||
global $user;
|
||||
|
||||
// Anonymous users cannot use or have contact forms.
|
||||
if (!$user->uid || !$account->uid) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// User administrators should always have access to personal contact forms.
|
||||
if (user_access('administer users')) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Users may not contact themselves.
|
||||
if ($user->uid == $account->uid) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the requested user has disabled their contact form, or this preference
|
||||
// has not yet been saved, do not allow users to contact them.
|
||||
if (empty($account->contact)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the data for a single contact category.
|
||||
*/
|
||||
function contact_load($cid) {
|
||||
$contact = db_fetch_array(db_query("SELECT * FROM {contact} WHERE cid = %d", $cid));
|
||||
return empty($contact) ? FALSE : $contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*
|
||||
* Allows the user the option of enabling/disabling his personal contact form.
|
||||
*/
|
||||
function contact_user($type, &$edit, &$user, $category = NULL) {
|
||||
if ($type == 'form' && $category == 'account') {
|
||||
$form['contact'] = array('#type' => 'fieldset',
|
||||
'#title' => t('Contact settings'),
|
||||
'#weight' => 5,
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
$form['contact']['contact'] = array('#type' => 'checkbox',
|
||||
'#title' => t('Personal contact form'),
|
||||
'#default_value' => !empty($edit['contact']) ? $edit['contact'] : FALSE,
|
||||
'#description' => t('Allow other users to contact you by e-mail via <a href="@url">your personal contact form</a>. Note that while your e-mail address is not made public to other members of the community, privileged users such as site administrators are able to contact you even if you choose not to enable this feature.', array('@url' => url("user/$user->uid/contact"))),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
elseif ($type == 'validate') {
|
||||
return array('contact' => isset($edit['contact']) ? $edit['contact'] : FALSE);
|
||||
}
|
||||
elseif ($type == 'insert') {
|
||||
$edit['contact'] = variable_get('contact_default_status', 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_mail().
|
||||
*/
|
||||
function contact_mail($key, &$message, $params) {
|
||||
$language = $message['language'];
|
||||
switch ($key) {
|
||||
case 'page_mail':
|
||||
case 'page_copy':
|
||||
$contact = $params['contact'];
|
||||
$message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language);
|
||||
$message['body'][] = t("!name sent a message using the contact form at !form.", array('!name' => $params['name'], '!form' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language))), $language->language);
|
||||
$message['body'][] = $params['message'];
|
||||
break;
|
||||
case 'page_autoreply':
|
||||
$contact = $params['contact'];
|
||||
$message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language);
|
||||
$message['body'][] = $contact['reply'];
|
||||
break;
|
||||
case 'user_mail':
|
||||
case 'user_copy':
|
||||
$user = $params['user'];
|
||||
$account = $params['account'];
|
||||
$message['subject'] .= '['. variable_get('site_name', 'Drupal') .'] '. $params['subject'];
|
||||
$message['body'][] = "$account->name,";
|
||||
$message['body'][] = t("!name (!name-url) has sent you a message via your contact form (!form-url) at !site.", array('!name' => $user->name, '!name-url' => url("user/$user->uid", array('absolute' => TRUE, 'language' => $language)), '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), '!site' => variable_get('site_name', 'Drupal')), $language->language);
|
||||
$message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE, 'language' => $language))), $language->language);
|
||||
$message['body'][] = t('Message:', NULL, $language->language);
|
||||
$message['body'][] = $params['message'];
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the contact module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Site-wide contact page.
|
||||
*/
|
||||
function contact_site_page() {
|
||||
global $user;
|
||||
|
||||
if (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3))) {
|
||||
$output = t("You cannot send more than %number messages per hour. Please try again later.", array('%number' => variable_get('contact_hourly_threshold', 3)));
|
||||
}
|
||||
else {
|
||||
$output = drupal_get_form('contact_mail_page');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function contact_mail_page() {
|
||||
global $user;
|
||||
|
||||
$form = $categories = array();
|
||||
|
||||
$result = db_query('SELECT cid, category, selected FROM {contact} ORDER BY weight, category');
|
||||
while ($category = db_fetch_object($result)) {
|
||||
$categories[$category->cid] = $category->category;
|
||||
if ($category->selected) {
|
||||
$default_category = $category->cid;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($categories) > 0) {
|
||||
$form['#token'] = $user->uid ? $user->name . $user->mail : '';
|
||||
$form['contact_information'] = array('#value' => filter_xss_admin(variable_get('contact_form_information', t('You can leave a message using the contact form below.'))));
|
||||
$form['name'] = array('#type' => 'textfield',
|
||||
'#title' => t('Your name'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $user->uid ? $user->name : '',
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['mail'] = array('#type' => 'textfield',
|
||||
'#title' => t('Your e-mail address'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $user->uid ? $user->mail : '',
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['subject'] = array('#type' => 'textfield',
|
||||
'#title' => t('Subject'),
|
||||
'#maxlength' => 255,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
if (count($categories) > 1) {
|
||||
// If there is more than one category available and no default category has been selected,
|
||||
// prepend a default placeholder value.
|
||||
if (!isset($default_category)) {
|
||||
$default_category = t('- Please choose -');
|
||||
$categories = array($default_category) + $categories;
|
||||
}
|
||||
$form['cid'] = array('#type' => 'select',
|
||||
'#title' => t('Category'),
|
||||
'#default_value' => $default_category,
|
||||
'#options' => $categories,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
}
|
||||
else {
|
||||
// If there is only one category, store its cid.
|
||||
$category_keys = array_keys($categories);
|
||||
$form['cid'] = array('#type' => 'value',
|
||||
'#value' => array_shift($category_keys),
|
||||
);
|
||||
}
|
||||
$form['message'] = array('#type' => 'textarea',
|
||||
'#title' => t('Message'),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
// We do not allow anonymous users to send themselves a copy
|
||||
// because it can be abused to spam people.
|
||||
if ($user->uid) {
|
||||
$form['copy'] = array('#type' => 'checkbox',
|
||||
'#title' => t('Send yourself a copy.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form['copy'] = array('#type' => 'value', '#value' => FALSE);
|
||||
}
|
||||
$form['submit'] = array('#type' => 'submit',
|
||||
'#value' => t('Send e-mail'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('The contact form has not been configured. <a href="@add">Add one or more categories</a> to the form.', array('@add' => url('admin/build/contact/add'))), 'error');
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the site-wide contact page form submission.
|
||||
*/
|
||||
function contact_mail_page_validate($form, &$form_state) {
|
||||
if (!$form_state['values']['cid']) {
|
||||
form_set_error('cid', t('You must select a valid category.'));
|
||||
}
|
||||
if (!valid_email_address($form_state['values']['mail'])) {
|
||||
form_set_error('mail', t('You must enter a valid e-mail address.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the site-wide contact page form submission.
|
||||
*/
|
||||
function contact_mail_page_submit($form, &$form_state) {
|
||||
global $language;
|
||||
|
||||
$values = $form_state['values'];
|
||||
|
||||
// E-mail address of the sender: as the form field is a text field,
|
||||
// all instances of \r and \n have been automatically stripped from it.
|
||||
$from = $values['mail'];
|
||||
|
||||
// Load category properties and save form values for email composition.
|
||||
$contact = contact_load($values['cid']);
|
||||
$values['contact'] = $contact;
|
||||
|
||||
// Send the e-mail to the recipients using the site default language.
|
||||
drupal_mail('contact', 'page_mail', $contact['recipients'], language_default(), $values, $from);
|
||||
|
||||
// If the user requests it, send a copy using the current language.
|
||||
if ($values['copy']) {
|
||||
drupal_mail('contact', 'page_copy', $from, $language, $values, $from);
|
||||
}
|
||||
|
||||
// Send an auto-reply if necessary using the current language.
|
||||
if ($contact['reply']) {
|
||||
drupal_mail('contact', 'page_autoreply', $from, $language, $values, $contact['recipients']);
|
||||
}
|
||||
|
||||
flood_register_event('contact');
|
||||
watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $values['name'] ." [$from]", '%category' => $contact['category']));
|
||||
drupal_set_message(t('Your message has been sent.'));
|
||||
|
||||
// Jump to home page rather than back to contact page to avoid
|
||||
// contradictory messages if flood control has been activated.
|
||||
$form_state['redirect'] = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Personal contact page.
|
||||
*/
|
||||
function contact_user_page($account) {
|
||||
global $user;
|
||||
|
||||
if (!valid_email_address($user->mail)) {
|
||||
$output = t('You need to provide a valid e-mail address to contact other users. Please update your <a href="@url">user information</a> and try again.', array('@url' => url("user/$user->uid/edit")));
|
||||
}
|
||||
else if (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3))) {
|
||||
$output = t('You cannot contact more than %number users per hour. Please try again later.', array('%number' => variable_get('contact_hourly_threshold', 3)));
|
||||
}
|
||||
else {
|
||||
drupal_set_title(check_plain($account->name));
|
||||
$output = drupal_get_form('contact_mail_user', $account);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function contact_mail_user(&$form_state, $recipient) {
|
||||
global $user;
|
||||
$form['#token'] = $user->name . $user->mail;
|
||||
$form['recipient'] = array('#type' => 'value', '#value' => $recipient);
|
||||
$form['from'] = array('#type' => 'item',
|
||||
'#title' => t('From'),
|
||||
'#value' => theme('username', $user) .' <'. check_plain($user->mail) .'>',
|
||||
);
|
||||
$form['to'] = array('#type' => 'item',
|
||||
'#title' => t('To'),
|
||||
'#value' => theme('username', $recipient),
|
||||
);
|
||||
$form['subject'] = array('#type' => 'textfield',
|
||||
'#title' => t('Subject'),
|
||||
'#maxlength' => 50,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['message'] = array('#type' => 'textarea',
|
||||
'#title' => t('Message'),
|
||||
'#rows' => 15,
|
||||
'#required' => TRUE,
|
||||
);
|
||||
$form['copy'] = array('#type' => 'checkbox',
|
||||
'#title' => t('Send yourself a copy.'),
|
||||
);
|
||||
$form['submit'] = array('#type' => 'submit',
|
||||
'#value' => t('Send e-mail'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the personal contact page form submission.
|
||||
*/
|
||||
function contact_mail_user_submit($form, &$form_state) {
|
||||
global $user, $language;
|
||||
|
||||
$account = $form_state['values']['recipient'];
|
||||
|
||||
// Send from the current user to the requested user.
|
||||
$to = $account->mail;
|
||||
$from = $user->mail;
|
||||
|
||||
// Save both users and all form values for email composition.
|
||||
$values = $form_state['values'];
|
||||
$values['account'] = $account;
|
||||
$values['user'] = $user;
|
||||
|
||||
// Send the e-mail in the requested user language.
|
||||
drupal_mail('contact', 'user_mail', $to, user_preferred_language($account), $values, $from);
|
||||
|
||||
// Send a copy if requested, using current page language.
|
||||
if ($form_state['values']['copy']) {
|
||||
drupal_mail('contact', 'user_copy', $from, $language, $values, $from);
|
||||
}
|
||||
|
||||
flood_register_event('contact');
|
||||
watchdog('mail', '%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
|
||||
drupal_set_message(t('The message has been sent.'));
|
||||
|
||||
// Back to the requested users profile page.
|
||||
$form_state['redirect'] = "user/$account->uid";
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
#edit-type-wrapper, #edit-severity-wrapper {
|
||||
float: right;
|
||||
padding-right: 0;
|
||||
padding-left: .8em;
|
||||
}
|
|
@ -1,329 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Administrative page callbacks for the dblog module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* dblog module settings form.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see system_settings_form()
|
||||
*/
|
||||
function dblog_admin_settings() {
|
||||
$form['dblog_row_limit'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Discard log entries above the following row limit'),
|
||||
'#default_value' => variable_get('dblog_row_limit', 1000),
|
||||
'#options' => drupal_map_assoc(array(100, 1000, 10000, 100000, 1000000)),
|
||||
'#description' => t('The maximum number of rows to keep in the database log. Older entries will be automatically discarded. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status')))
|
||||
);
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays a listing of log messages.
|
||||
*/
|
||||
function dblog_overview() {
|
||||
$filter = dblog_build_filter_query();
|
||||
$rows = array();
|
||||
$icons = array(
|
||||
WATCHDOG_DEBUG => '',
|
||||
WATCHDOG_INFO => '',
|
||||
WATCHDOG_NOTICE => '',
|
||||
WATCHDOG_WARNING => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')),
|
||||
WATCHDOG_ERROR => theme('image', 'misc/watchdog-error.png', t('error'), t('error')),
|
||||
WATCHDOG_CRITICAL => theme('image', 'misc/watchdog-error.png', t('critical'), t('critical')),
|
||||
WATCHDOG_ALERT => theme('image', 'misc/watchdog-error.png', t('alert'), t('alert')),
|
||||
WATCHDOG_EMERG => theme('image', 'misc/watchdog-error.png', t('emergency'), t('emergency')),
|
||||
);
|
||||
$classes = array(
|
||||
WATCHDOG_DEBUG => 'dblog-debug',
|
||||
WATCHDOG_INFO => 'dblog-info',
|
||||
WATCHDOG_NOTICE => 'dblog-notice',
|
||||
WATCHDOG_WARNING => 'dblog-warning',
|
||||
WATCHDOG_ERROR => 'dblog-error',
|
||||
WATCHDOG_CRITICAL => 'dblog-critical',
|
||||
WATCHDOG_ALERT => 'dblog-alert',
|
||||
WATCHDOG_EMERG => 'dblog-emerg',
|
||||
);
|
||||
|
||||
$output = drupal_get_form('dblog_filter_form');
|
||||
|
||||
$header = array(
|
||||
' ',
|
||||
array('data' => t('Type'), 'field' => 'w.type'),
|
||||
array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc'),
|
||||
t('Message'),
|
||||
array('data' => t('User'), 'field' => 'u.name'),
|
||||
array('data' => t('Operations')),
|
||||
);
|
||||
|
||||
$sql = "SELECT w.wid, w.uid, w.severity, w.type, w.timestamp, w.message, w.variables, w.link, u.name FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid";
|
||||
$tablesort = tablesort_sql($header);
|
||||
if (!empty($filter['where'])) {
|
||||
$result = pager_query($sql ." WHERE ". $filter['where'] . $tablesort, 50, 0, NULL, $filter['args']);
|
||||
}
|
||||
else {
|
||||
$result = pager_query($sql . $tablesort, 50);
|
||||
}
|
||||
|
||||
while ($dblog = db_fetch_object($result)) {
|
||||
$rows[] = array('data' =>
|
||||
array(
|
||||
// Cells
|
||||
$icons[$dblog->severity],
|
||||
t($dblog->type),
|
||||
format_date($dblog->timestamp, 'small'),
|
||||
l(truncate_utf8(_dblog_format_message($dblog), 56, TRUE, TRUE), 'admin/reports/event/'. $dblog->wid, array('html' => TRUE)),
|
||||
theme('username', $dblog),
|
||||
filter_xss($dblog->link),
|
||||
),
|
||||
// Attributes for tr
|
||||
'class' => "dblog-". preg_replace('/[^a-z]/i', '-', $dblog->type) .' '. $classes[$dblog->severity]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$rows) {
|
||||
$rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 6));
|
||||
}
|
||||
|
||||
$output .= theme('table', $header, $rows, array('id' => 'admin-dblog'));
|
||||
$output .= theme('pager', NULL, 50, 0);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; generic function to display a page of the most frequent
|
||||
* dblog events of a specified type.
|
||||
*/
|
||||
function dblog_top($type) {
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'),
|
||||
array('data' => t('Message'), 'field' => 'message')
|
||||
);
|
||||
|
||||
$result = pager_query("SELECT COUNT(wid) AS count, message, variables FROM {watchdog} WHERE type = '%s' GROUP BY message, variables ". tablesort_sql($header), 30, 0, "SELECT COUNT(DISTINCT(message)) FROM {watchdog} WHERE type = '%s'", $type);
|
||||
|
||||
$rows = array();
|
||||
while ($dblog = db_fetch_object($result)) {
|
||||
$rows[] = array($dblog->count, truncate_utf8(_dblog_format_message($dblog), 56, TRUE, TRUE));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 2));
|
||||
}
|
||||
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; displays details about a log message.
|
||||
*/
|
||||
function dblog_event($id) {
|
||||
$severity = watchdog_severity_levels();
|
||||
$output = '';
|
||||
$result = db_query('SELECT w.*, u.name, u.uid FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid WHERE w.wid = %d', $id);
|
||||
if ($dblog = db_fetch_object($result)) {
|
||||
$rows = array(
|
||||
array(
|
||||
array('data' => t('Type'), 'header' => TRUE),
|
||||
t($dblog->type),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Date'), 'header' => TRUE),
|
||||
format_date($dblog->timestamp, 'large'),
|
||||
),
|
||||
array(
|
||||
array('data' => t('User'), 'header' => TRUE),
|
||||
theme('username', $dblog),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Location'), 'header' => TRUE),
|
||||
l($dblog->location, $dblog->location),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Referrer'), 'header' => TRUE),
|
||||
l($dblog->referer, $dblog->referer),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Message'), 'header' => TRUE),
|
||||
_dblog_format_message($dblog),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Severity'), 'header' => TRUE),
|
||||
$severity[$dblog->severity],
|
||||
),
|
||||
array(
|
||||
array('data' => t('Hostname'), 'header' => TRUE),
|
||||
check_plain($dblog->hostname),
|
||||
),
|
||||
array(
|
||||
array('data' => t('Operations'), 'header' => TRUE),
|
||||
$dblog->link,
|
||||
),
|
||||
);
|
||||
$attributes = array('class' => 'dblog-event');
|
||||
$output = theme('table', array(), $rows, $attributes);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query for dblog administration filters based on session.
|
||||
*/
|
||||
function dblog_build_filter_query() {
|
||||
if (empty($_SESSION['dblog_overview_filter'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filters = dblog_filters();
|
||||
|
||||
// Build query
|
||||
$where = $args = array();
|
||||
foreach ($_SESSION['dblog_overview_filter'] as $key => $filter) {
|
||||
$filter_where = array();
|
||||
foreach ($filter as $value) {
|
||||
$filter_where[] = $filters[$key]['where'];
|
||||
$args[] = $value;
|
||||
}
|
||||
if (!empty($filter_where)) {
|
||||
$where[] = '('. implode(' OR ', $filter_where) .')';
|
||||
}
|
||||
}
|
||||
$where = !empty($where) ? implode(' AND ', $where) : '';
|
||||
|
||||
return array(
|
||||
'where' => $where,
|
||||
'args' => $args,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List dblog administration filters that can be applied.
|
||||
*/
|
||||
function dblog_filters() {
|
||||
$filters = array();
|
||||
|
||||
foreach (_dblog_get_message_types() as $type) {
|
||||
$types[$type] = t($type);
|
||||
}
|
||||
|
||||
if (!empty($types)) {
|
||||
$filters['type'] = array(
|
||||
'title' => t('Type'),
|
||||
'where' => "w.type = '%s'",
|
||||
'options' => $types,
|
||||
);
|
||||
}
|
||||
|
||||
$filters['severity'] = array(
|
||||
'title' => t('Severity'),
|
||||
'where' => 'w.severity = %d',
|
||||
'options' => watchdog_severity_levels(),
|
||||
);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a log message for display.
|
||||
*
|
||||
* @param $dblog
|
||||
* An object with at least the message and variables properties
|
||||
*/
|
||||
function _dblog_format_message($dblog) {
|
||||
// Legacy messages and user specified text
|
||||
if ($dblog->variables === 'N;') {
|
||||
return $dblog->message;
|
||||
}
|
||||
// Message to translate with injected variables
|
||||
else {
|
||||
return t($dblog->message, unserialize($dblog->variables));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return form for dblog administration filters.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see dblog_filter_form_submit()
|
||||
* @see dblog_filter_form_validate()
|
||||
*/
|
||||
function dblog_filter_form() {
|
||||
$session = &$_SESSION['dblog_overview_filter'];
|
||||
$session = is_array($session) ? $session : array();
|
||||
$filters = dblog_filters();
|
||||
|
||||
$form['filters'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Filter log messages'),
|
||||
'#theme' => 'dblog_filters',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => empty($session),
|
||||
);
|
||||
foreach ($filters as $key => $filter) {
|
||||
$form['filters']['status'][$key] = array(
|
||||
'#title' => $filter['title'],
|
||||
'#type' => 'select',
|
||||
'#multiple' => TRUE,
|
||||
'#size' => 8,
|
||||
'#options' => $filter['options'],
|
||||
);
|
||||
if (!empty($session[$key])) {
|
||||
$form['filters']['status'][$key]['#default_value'] = $session[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$form['filters']['buttons']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Filter'),
|
||||
);
|
||||
if (!empty($session)) {
|
||||
$form['filters']['buttons']['reset'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Reset')
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate result from dblog administration filter form.
|
||||
*/
|
||||
function dblog_filter_form_validate($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) {
|
||||
form_set_error('type', t('You must select something to filter by.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process result from dblog administration filter form.
|
||||
*/
|
||||
function dblog_filter_form_submit($form, &$form_state) {
|
||||
$op = $form_state['values']['op'];
|
||||
$filters = dblog_filters();
|
||||
switch ($op) {
|
||||
case t('Filter'):
|
||||
foreach ($filters as $name => $filter) {
|
||||
if (isset($form_state['values'][$name])) {
|
||||
$_SESSION['dblog_overview_filter'][$name] = $form_state['values'][$name];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case t('Reset'):
|
||||
$_SESSION['dblog_overview_filter'] = array();
|
||||
break;
|
||||
}
|
||||
return 'admin/reports/dblog';
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
|
||||
#edit-type-wrapper, #edit-severity-wrapper {
|
||||
float: left; /* LTR */
|
||||
padding-right: .8em; /* LTR */
|
||||
margin: 0.1em;
|
||||
/**
|
||||
* In Opera 9, DOM elements with the property of "overflow: auto"
|
||||
* will partially hide its contents with unnecessary scrollbars when
|
||||
* its immediate child is floated without an explicit width set.
|
||||
*/
|
||||
width: 15em;
|
||||
}
|
||||
#dblog-filter-form .form-item select.form-select {
|
||||
width: 100%;
|
||||
}
|
||||
tr.dblog-user {
|
||||
background: #ffd;
|
||||
}
|
||||
tr.dblog-user .active {
|
||||
background: #eed;
|
||||
}
|
||||
tr.dblog-content {
|
||||
background: #ddf;
|
||||
}
|
||||
tr.dblog-content .active {
|
||||
background: #cce;
|
||||
}
|
||||
tr.dblog-page-not-found, tr.dblog-access-denied {
|
||||
background: #dfd;
|
||||
}
|
||||
tr.dblog-page-not-found .active, tr.dblog-access-denied .active {
|
||||
background: #cec;
|
||||
}
|
||||
tr.dblog-error {
|
||||
background: #ffc9c9;
|
||||
}
|
||||
tr.dblog-error .active {
|
||||
background: #eeb9b9;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Database logging
|
||||
description = Logs and records system events to the database.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function dblog_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('dblog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function dblog_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('dblog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function dblog_schema() {
|
||||
$schema['watchdog'] = array(
|
||||
'description' => 'Table that contains logs of all system events.',
|
||||
'fields' => array(
|
||||
'wid' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique watchdog event ID.',
|
||||
),
|
||||
'uid' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {users}.uid of the user who triggered the event.',
|
||||
),
|
||||
'type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 16,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Type of log message, for example "user" or "page not found."',
|
||||
),
|
||||
'message' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Text of log message to be passed into the t() function.',
|
||||
),
|
||||
'variables' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'big',
|
||||
'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.',
|
||||
),
|
||||
'severity' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'The severity level of the event; ranges from 0 (Emergency) to 7 (Debug)',
|
||||
),
|
||||
'link' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Link to view the result of the event.',
|
||||
),
|
||||
'location' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'description' => 'URL of the origin of the event.',
|
||||
),
|
||||
'referer' => array(
|
||||
'type' => 'text',
|
||||
'not null' => FALSE,
|
||||
'description' => 'URL of referring page.',
|
||||
),
|
||||
'hostname' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Hostname of the user who triggered the event.',
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Unix timestamp of when event occurred.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('wid'),
|
||||
'indexes' => array('type' => array('type')),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-6.x-extra
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow longer referrers.
|
||||
*/
|
||||
function dblog_update_6000() {
|
||||
$ret = array();
|
||||
db_change_field($ret, 'watchdog', 'referer', 'referer', array('type' => 'text', 'not null' => FALSE));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-6.x-extra".
|
||||
* The next series of updates should start at 7000.
|
||||
*/
|
|
@ -1,166 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* System monitoring and logging for administrators.
|
||||
*
|
||||
* The dblog module monitors your site and keeps a list of
|
||||
* recorded events containing usage and performance data, errors,
|
||||
* warnings, and similar operational information.
|
||||
*
|
||||
* @see watchdog()
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function dblog_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#dblog':
|
||||
$output = '<p>'. t('The dblog module monitors your system, capturing system events in a log to be reviewed by an authorized individual at a later time. This is useful for site administrators who want a quick overview of activities on their site. The logs also record the sequence of events, so it can be useful for debugging site errors.') .'</p>';
|
||||
$output .= '<p>'. t('The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. Administrators should check the dblog report on a regular basis to ensure their site is working properly.') .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@dblog">Dblog module</a>.', array('@dblog' => 'http://drupal.org/handbook/modules/dblog/')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/reports/dblog':
|
||||
return '<p>'. t('The dblog module monitors your website, capturing system events in a log to be reviewed by an authorized individual at a later time. The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. It is vital to check the dblog report on a regular basis as it is often the only way to tell what is going on.') .'</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme()
|
||||
*/
|
||||
function dblog_theme() {
|
||||
return array(
|
||||
'dblog_filters' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function dblog_menu() {
|
||||
$items['admin/settings/logging/dblog'] = array(
|
||||
'title' => 'Database logging',
|
||||
'description' => 'Settings for logging to the Drupal database logs. This is the most common method for small to medium sites on shared hosting. The logs are viewable from the admin pages.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('dblog_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'dblog.admin.inc',
|
||||
);
|
||||
|
||||
$items['admin/reports/dblog'] = array(
|
||||
'title' => 'Recent log entries',
|
||||
'description' => 'View events that have recently been logged.',
|
||||
'page callback' => 'dblog_overview',
|
||||
'access arguments' => array('access site reports'),
|
||||
'weight' => -1,
|
||||
'file' => 'dblog.admin.inc',
|
||||
);
|
||||
$items['admin/reports/page-not-found'] = array(
|
||||
'title' => "Top 'page not found' errors",
|
||||
'description' => "View 'page not found' errors (404s).",
|
||||
'page callback' => 'dblog_top',
|
||||
'page arguments' => array('page not found'),
|
||||
'access arguments' => array('access site reports'),
|
||||
'file' => 'dblog.admin.inc',
|
||||
);
|
||||
$items['admin/reports/access-denied'] = array(
|
||||
'title' => "Top 'access denied' errors",
|
||||
'description' => "View 'access denied' errors (403s).",
|
||||
'page callback' => 'dblog_top',
|
||||
'page arguments' => array('access denied'),
|
||||
'access arguments' => array('access site reports'),
|
||||
'file' => 'dblog.admin.inc',
|
||||
);
|
||||
$items['admin/reports/event/%'] = array(
|
||||
'title' => 'Details',
|
||||
'page callback' => 'dblog_event',
|
||||
'page arguments' => array(3),
|
||||
'access arguments' => array('access site reports'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'dblog.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
function dblog_init() {
|
||||
if (arg(0) == 'admin' && arg(1) == 'reports') {
|
||||
// Add the CSS for this module
|
||||
drupal_add_css(drupal_get_path('module', 'dblog') .'/dblog.css', 'module', 'all', FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*
|
||||
* Remove expired log messages.
|
||||
*/
|
||||
function dblog_cron() {
|
||||
// Cleanup the watchdog table
|
||||
$max = db_result(db_query('SELECT MAX(wid) FROM {watchdog}'));
|
||||
db_query('DELETE FROM {watchdog} WHERE wid <= %d', $max - variable_get('dblog_row_limit', 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function dblog_user($op, &$edit, &$user) {
|
||||
if ($op == 'delete') {
|
||||
db_query('UPDATE {watchdog} SET uid = 0 WHERE uid = %d', $user->uid);
|
||||
}
|
||||
}
|
||||
|
||||
function _dblog_get_message_types() {
|
||||
$types = array();
|
||||
|
||||
$result = db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type');
|
||||
while ($object = db_fetch_object($result)) {
|
||||
$types[] = $object->type;
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_watchdog().
|
||||
*/
|
||||
function dblog_watchdog($log = array()) {
|
||||
$current_db = db_set_active();
|
||||
db_query("INSERT INTO {watchdog}
|
||||
(uid, type, message, variables, severity, link, location, referer, hostname, timestamp)
|
||||
VALUES
|
||||
(%d, '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', %d)",
|
||||
$log['user']->uid,
|
||||
$log['type'],
|
||||
$log['message'],
|
||||
serialize($log['variables']),
|
||||
$log['severity'],
|
||||
$log['link'],
|
||||
$log['request_uri'],
|
||||
$log['referer'],
|
||||
$log['ip'],
|
||||
$log['timestamp']);
|
||||
|
||||
if ($current_db) {
|
||||
db_set_active($current_db);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme dblog administration filter selector.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_dblog_filters($form) {
|
||||
$output = '';
|
||||
foreach (element_children($form['status']) as $key) {
|
||||
$output .= drupal_render($form['status'][$key]);
|
||||
}
|
||||
$output .= '<div id="dblog-admin-buttons">'. drupal_render($form['buttons']) .'</div>';
|
||||
return $output;
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 223 B |
|
@ -1,39 +0,0 @@
|
|||
|
||||
#edit-openid-identifier {
|
||||
background-image: url("login-bg.png");
|
||||
background-position: 0% 50%;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 20px;
|
||||
}
|
||||
div#edit-openid-identifier-wrapper {
|
||||
display: block;
|
||||
}
|
||||
html.js #user-login-form div#edit-openid-identifier-wrapper,
|
||||
html.js #user-login div#edit-openid-identifier-wrapper {
|
||||
display: none;
|
||||
}
|
||||
html.js #user-login-form li.openid-link,
|
||||
html.js #user-login li.openid-link {
|
||||
display : block;
|
||||
list-style: none;
|
||||
}
|
||||
#user-login-form ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
#user-login ul {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
#user-login ul li {
|
||||
margin: 0;
|
||||
}
|
||||
#user-login-form li.openid-link,
|
||||
#user-login-form li.user-link,
|
||||
#user-login li.openid-link,
|
||||
#user-login li.user-link {
|
||||
display: none;
|
||||
}
|
||||
#user-login-form li.openid-link a,
|
||||
#user-login li.openid-link a {
|
||||
background: transparent url("login-bg.png") no-repeat 0 2px;
|
||||
padding: 0 20px;
|
||||
}
|
|
@ -1,428 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* OpenID utility functions.
|
||||
*/
|
||||
|
||||
// Diffie-Hellman Key Exchange Default Value.
|
||||
define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801'.
|
||||
'966915404479707795314057629378541917580651227423698188993727816152646631'.
|
||||
'438561595825688188889951272158842675419950341258706556549803580104870537'.
|
||||
'681476726513255747040765857479291291572334510643245094715007229621094194'.
|
||||
'349783925984760375594985848253359305585439638443');
|
||||
|
||||
// Constants for Diffie-Hellman key exchange computations.
|
||||
define('OPENID_DH_DEFAULT_GEN', '2');
|
||||
define('OPENID_SHA1_BLOCKSIZE', 64);
|
||||
define('OPENID_RAND_SOURCE', '/dev/urandom');
|
||||
|
||||
// OpenID namespace URLs
|
||||
define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
|
||||
define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
|
||||
define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
|
||||
|
||||
/**
|
||||
* Performs an HTTP 302 redirect (for the 1.x protocol).
|
||||
*/
|
||||
function openid_redirect_http($url, $message) {
|
||||
$query = array();
|
||||
foreach ($message as $key => $val) {
|
||||
$query[] = $key .'='. urlencode($val);
|
||||
}
|
||||
|
||||
$sep = (strpos($url, '?') === FALSE) ? '?' : '&';
|
||||
header('Location: '. $url . $sep . implode('&', $query), TRUE, 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a js auto-submit redirect for (for the 2.x protocol)
|
||||
*/
|
||||
function openid_redirect($url, $message) {
|
||||
$output = '<html><head><title>'. t('OpenID redirect') ."</title></head>\n<body>";
|
||||
$output .= drupal_get_form('openid_redirect_form', $url, $message);
|
||||
$output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>';
|
||||
$output .= "</body></html>\n";
|
||||
print $output;
|
||||
exit;
|
||||
}
|
||||
|
||||
function openid_redirect_form(&$form_state, $url, $message) {
|
||||
$form = array();
|
||||
$form['#action'] = $url;
|
||||
$form['#method'] = "post";
|
||||
foreach ($message as $key => $value) {
|
||||
$form[$key] = array(
|
||||
'#type' => 'hidden',
|
||||
'#name' => $key,
|
||||
'#value' => $value,
|
||||
);
|
||||
}
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#prefix' => '<noscript>',
|
||||
'#suffix' => '</noscript>',
|
||||
'#value' => t('Send'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given identifier is an XRI ID.
|
||||
*/
|
||||
function _openid_is_xri($identifier) {
|
||||
// Strip the xri:// scheme from the identifier if present.
|
||||
if (strpos(strtolower($identifier), 'xri://') !== FALSE) {
|
||||
$identifier = substr($identifier, 6);
|
||||
}
|
||||
|
||||
// Test whether the identifier starts with an XRI global context symbol or (.
|
||||
$firstchar = substr($identifier, 0, 1);
|
||||
if (strpos("=@+$!(", $firstchar) !== FALSE) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given identifier as per spec.
|
||||
*/
|
||||
function _openid_normalize($identifier) {
|
||||
if (_openid_is_xri($identifier)) {
|
||||
return _openid_normalize_xri($identifier);
|
||||
}
|
||||
else {
|
||||
return _openid_normalize_url($identifier);
|
||||
}
|
||||
}
|
||||
|
||||
function _openid_normalize_xri($xri) {
|
||||
$normalized_xri = $xri;
|
||||
if (stristr($xri, 'xri://') !== FALSE) {
|
||||
$normalized_xri = substr($xri, 6);
|
||||
}
|
||||
return $normalized_xri;
|
||||
}
|
||||
|
||||
function _openid_normalize_url($url) {
|
||||
$normalized_url = $url;
|
||||
|
||||
if (stristr($url, '://') === FALSE) {
|
||||
$normalized_url = 'http://'. $url;
|
||||
}
|
||||
|
||||
// Strip the fragment and fragment delimiter if present.
|
||||
$normalized_url = strtok($normalized_url, '#');
|
||||
|
||||
if (substr_count($normalized_url, '/') < 3) {
|
||||
$normalized_url .= '/';
|
||||
}
|
||||
|
||||
return $normalized_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a serialized message packet as per spec: $key:$value\n .
|
||||
*/
|
||||
function _openid_create_message($data) {
|
||||
$serialized = '';
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
|
||||
return null;
|
||||
}
|
||||
$serialized .= "$key:$value\n";
|
||||
}
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a message from _openid_create_message for HTTP Post
|
||||
*/
|
||||
function _openid_encode_message($message) {
|
||||
$encoded_message = '';
|
||||
|
||||
$items = explode("\n", $message);
|
||||
foreach ($items as $item) {
|
||||
$parts = explode(':', $item, 2);
|
||||
|
||||
if (count($parts) == 2) {
|
||||
if ($encoded_message != '') {
|
||||
$encoded_message .= '&';
|
||||
}
|
||||
$encoded_message .= rawurlencode(trim($parts[0])) .'='. rawurlencode(trim($parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return $encoded_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a direct communication message
|
||||
* into an associative array.
|
||||
*/
|
||||
function _openid_parse_message($message) {
|
||||
$parsed_message = array();
|
||||
|
||||
$items = explode("\n", $message);
|
||||
foreach ($items as $item) {
|
||||
$parts = explode(':', $item, 2);
|
||||
|
||||
if (count($parts) == 2) {
|
||||
$parsed_message[$parts[0]] = $parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nonce value - formatted per OpenID spec.
|
||||
*/
|
||||
function _openid_nonce() {
|
||||
// YYYY-MM-DDThh:mm:ssTZD UTC, plus some optional extra unique chars
|
||||
return gmstrftime('%Y-%m-%dT%H:%M:%S%Z') .
|
||||
chr(mt_rand(0, 25) + 65) .
|
||||
chr(mt_rand(0, 25) + 65) .
|
||||
chr(mt_rand(0, 25) + 65) .
|
||||
chr(mt_rand(0, 25) + 65);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the href attribute out of an html link element.
|
||||
*/
|
||||
function _openid_link_href($rel, $html) {
|
||||
$rel = preg_quote($rel);
|
||||
preg_match('|<link\s+rel=["\'](.*)'. $rel .'(.*)["\'](.*)/?>|iUs', $html, $matches);
|
||||
if (isset($matches[3])) {
|
||||
preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
|
||||
return trim($href[1]);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the http-equiv attribute out of an html meta element
|
||||
*/
|
||||
function _openid_meta_httpequiv($equiv, $html) {
|
||||
preg_match('|<meta\s+http-equiv=["\']'. $equiv .'["\'](.*)/?>|iUs', $html, $matches);
|
||||
if (isset($matches[1])) {
|
||||
preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
|
||||
if (isset($content[1])) {
|
||||
return $content[1];
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign certain keys in a message
|
||||
* @param $association - object loaded from openid_association or openid_server_association table
|
||||
* - important fields are ->assoc_type and ->mac_key
|
||||
* @param $message_array - array of entire message about to be sent
|
||||
* @param $keys_to_sign - keys in the message to include in signature (without
|
||||
* 'openid.' appended)
|
||||
*/
|
||||
function _openid_signature($association, $message_array, $keys_to_sign) {
|
||||
$signature = '';
|
||||
$sign_data = array();
|
||||
|
||||
foreach ($keys_to_sign as $key) {
|
||||
if (isset($message_array['openid.'. $key])) {
|
||||
$sign_data[$key] = $message_array['openid.'. $key];
|
||||
}
|
||||
}
|
||||
|
||||
$message = _openid_create_message($sign_data);
|
||||
$secret = base64_decode($association->mac_key);
|
||||
$signature = _openid_hmac($secret, $message);
|
||||
|
||||
return base64_encode($signature);
|
||||
}
|
||||
|
||||
function _openid_hmac($key, $text) {
|
||||
if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
|
||||
$key = _openid_sha1($key, true);
|
||||
}
|
||||
|
||||
$key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
|
||||
$ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
|
||||
$opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
|
||||
$hash1 = _openid_sha1(($key ^ $ipad) . $text, true);
|
||||
$hmac = _openid_sha1(($key ^ $opad) . $hash1, true);
|
||||
|
||||
return $hmac;
|
||||
}
|
||||
|
||||
function _openid_sha1($text) {
|
||||
$hex = sha1($text);
|
||||
$raw = '';
|
||||
for ($i = 0; $i < 40; $i += 2) {
|
||||
$hexcode = substr($hex, $i, 2);
|
||||
$charcode = (int)base_convert($hexcode, 16, 10);
|
||||
$raw .= chr($charcode);
|
||||
}
|
||||
return $raw;
|
||||
}
|
||||
|
||||
function _openid_dh_base64_to_long($str) {
|
||||
$b64 = base64_decode($str);
|
||||
|
||||
return _openid_dh_binary_to_long($b64);
|
||||
}
|
||||
|
||||
function _openid_dh_long_to_base64($str) {
|
||||
return base64_encode(_openid_dh_long_to_binary($str));
|
||||
}
|
||||
|
||||
function _openid_dh_binary_to_long($str) {
|
||||
$bytes = array_merge(unpack('C*', $str));
|
||||
|
||||
$n = 0;
|
||||
foreach ($bytes as $byte) {
|
||||
$n = bcmul($n, pow(2, 8));
|
||||
$n = bcadd($n, $byte);
|
||||
}
|
||||
|
||||
return $n;
|
||||
}
|
||||
|
||||
function _openid_dh_long_to_binary($long) {
|
||||
$cmp = bccomp($long, 0);
|
||||
if ($cmp < 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($cmp == 0) {
|
||||
return "\x00";
|
||||
}
|
||||
|
||||
$bytes = array();
|
||||
|
||||
while (bccomp($long, 0) > 0) {
|
||||
array_unshift($bytes, bcmod($long, 256));
|
||||
$long = bcdiv($long, pow(2, 8));
|
||||
}
|
||||
|
||||
if ($bytes && ($bytes[0] > 127)) {
|
||||
array_unshift($bytes, 0);
|
||||
}
|
||||
|
||||
$string = '';
|
||||
foreach ($bytes as $byte) {
|
||||
$string .= pack('C', $byte);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
function _openid_dh_xorsecret($shared, $secret) {
|
||||
$dh_shared_str = _openid_dh_long_to_binary($shared);
|
||||
$sha1_dh_shared = _openid_sha1($dh_shared_str);
|
||||
$xsecret = "";
|
||||
for ($i = 0; $i < strlen($secret); $i++) {
|
||||
$xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
|
||||
}
|
||||
|
||||
return $xsecret;
|
||||
}
|
||||
|
||||
function _openid_dh_rand($stop) {
|
||||
static $duplicate_cache = array();
|
||||
|
||||
// Used as the key for the duplicate cache
|
||||
$rbytes = _openid_dh_long_to_binary($stop);
|
||||
|
||||
if (array_key_exists($rbytes, $duplicate_cache)) {
|
||||
list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
|
||||
}
|
||||
else {
|
||||
if ($rbytes[0] == "\x00") {
|
||||
$nbytes = strlen($rbytes) - 1;
|
||||
}
|
||||
else {
|
||||
$nbytes = strlen($rbytes);
|
||||
}
|
||||
|
||||
$mxrand = bcpow(256, $nbytes);
|
||||
|
||||
// If we get a number less than this, then it is in the
|
||||
// duplicated range.
|
||||
$duplicate = bcmod($mxrand, $stop);
|
||||
|
||||
if (count($duplicate_cache) > 10) {
|
||||
$duplicate_cache = array();
|
||||
}
|
||||
|
||||
$duplicate_cache[$rbytes] = array($duplicate, $nbytes);
|
||||
}
|
||||
|
||||
do {
|
||||
$bytes = "\x00". drupal_random_bytes($nbytes);
|
||||
$n = _openid_dh_binary_to_long($bytes);
|
||||
// Keep looping if this value is in the low duplicated range.
|
||||
} while (bccomp($n, $duplicate) < 0);
|
||||
|
||||
return bcmod($n, $stop);
|
||||
}
|
||||
|
||||
function _openid_get_bytes($num_bytes) {
|
||||
return drupal_random_bytes($num_bytes);
|
||||
}
|
||||
|
||||
function _openid_response($str = NULL) {
|
||||
$data = array();
|
||||
|
||||
if (isset($_SERVER['REQUEST_METHOD'])) {
|
||||
$data = _openid_get_params($_SERVER['QUERY_STRING']);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$str = file_get_contents('php://input');
|
||||
|
||||
$post = array();
|
||||
if ($str !== false) {
|
||||
$post = _openid_get_params($str);
|
||||
}
|
||||
|
||||
$data = array_merge($data, $post);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function _openid_get_params($str) {
|
||||
$chunks = explode("&", $str);
|
||||
|
||||
$data = array();
|
||||
foreach ($chunks as $chunk) {
|
||||
$parts = explode("=", $chunk, 2);
|
||||
|
||||
if (count($parts) == 2) {
|
||||
list($k, $v) = $parts;
|
||||
$data[$k] = urldecode($v);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide bcpowmod support for PHP4.
|
||||
*/
|
||||
if (!function_exists('bcpowmod')) {
|
||||
function bcpowmod($base, $exp, $mod) {
|
||||
$square = bcmod($base, $mod);
|
||||
$result = 1;
|
||||
while (bccomp($exp, 0) > 0) {
|
||||
if (bcmod($exp, 2)) {
|
||||
$result = bcmod(bcmul($result, $square), $mod);
|
||||
}
|
||||
$square = bcmod(bcmul($square, $square), $mod);
|
||||
$exp = bcdiv($exp, 2);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = OpenID
|
||||
description = "Allows users to log into your site using OpenID."
|
||||
version = VERSION
|
||||
package = Core - optional
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function openid_install() {
|
||||
// Create table.
|
||||
drupal_install_schema('openid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function openid_uninstall() {
|
||||
// Remove table.
|
||||
drupal_uninstall_schema('openid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function openid_schema() {
|
||||
$schema['openid_association'] = array(
|
||||
'description' => 'Stores temporary shared key association information for OpenID authentication.',
|
||||
'fields' => array(
|
||||
'idp_endpoint_uri' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
|
||||
),
|
||||
'assoc_handle' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Used to refer to this association in subsequent messages.',
|
||||
),
|
||||
'assoc_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.',
|
||||
),
|
||||
'session_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".',
|
||||
),
|
||||
'mac_key' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'The MAC key (shared secret) for this association.',
|
||||
),
|
||||
'created' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp for when the association was created.',
|
||||
),
|
||||
'expires_in' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The lifetime, in seconds, of this association.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('idp_endpoint_uri'),
|
||||
'unique keys' => array(
|
||||
'assoc_handle' => array('assoc_handle'),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['openid_nonce'] = array(
|
||||
'description' => 'Stores received openid.response_nonce per OpenID endpoint URL to prevent replay attacks.',
|
||||
'fields' => array(
|
||||
'idp_endpoint_uri' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'URI of the OpenID Provider endpoint.',
|
||||
),
|
||||
'nonce' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'The value of openid.response_nonce'
|
||||
),
|
||||
'expires' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'A Unix timestamp indicating when the entry should expire.',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'nonce' => array('nonce'),
|
||||
'expires' => array('expires'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-6.x-extra
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add the openid_nonce table.
|
||||
*
|
||||
* Implementation of hook_update_N().
|
||||
*/
|
||||
function openid_update_6000() {
|
||||
$ret = array();
|
||||
|
||||
$schema['openid_nonce'] = array(
|
||||
'description' => 'Stores received openid.response_nonce per OpenID endpoint URL to prevent replay attacks.',
|
||||
'fields' => array(
|
||||
'idp_endpoint_uri' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'URI of the OpenID Provider endpoint.',
|
||||
),
|
||||
'nonce' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'The value of openid.response_nonce'
|
||||
),
|
||||
'expires' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'A Unix timestamp indicating when the entry should expire.',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'nonce' => array('nonce'),
|
||||
'expires' => array('expires'),
|
||||
),
|
||||
);
|
||||
|
||||
db_create_table($ret, 'openid_nonce', $schema['openid_nonce']);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind associations to their providers.
|
||||
*/
|
||||
function openid_update_6001() {
|
||||
$ret = array();
|
||||
|
||||
db_drop_table($ret, 'openid_association');
|
||||
|
||||
$schema['openid_association'] = array(
|
||||
'description' => 'Stores temporary shared key association information for OpenID authentication.',
|
||||
'fields' => array(
|
||||
'idp_endpoint_uri' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
|
||||
),
|
||||
'assoc_handle' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Used to refer to this association in subsequent messages.',
|
||||
),
|
||||
'assoc_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.',
|
||||
),
|
||||
'session_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".',
|
||||
),
|
||||
'mac_key' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'description' => 'The MAC key (shared secret) for this association.',
|
||||
),
|
||||
'created' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp for when the association was created.',
|
||||
),
|
||||
'expires_in' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The lifetime, in seconds, of this association.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('idp_endpoint_uri'),
|
||||
'unique keys' => array(
|
||||
'assoc_handle' => array('assoc_handle'),
|
||||
),
|
||||
);
|
||||
|
||||
db_create_table($ret, 'openid_association', $schema['openid_association']);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-6.x-extra".
|
||||
* The next series of updates should start at 7000.
|
||||
*/
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
Drupal.behaviors.openid = function (context) {
|
||||
var $loginElements = $("#edit-name-wrapper, #edit-pass-wrapper, li.openid-link");
|
||||
var $openidElements = $("#edit-openid-identifier-wrapper, li.user-link");
|
||||
|
||||
// This behavior attaches by ID, so is only valid once on a page.
|
||||
if (!$("#edit-openid-identifier.openid-processed").size() && $("#edit-openid-identifier").val()) {
|
||||
$("#edit-openid-identifier").addClass('openid-processed');
|
||||
$loginElements.hide();
|
||||
// Use .css("display", "block") instead of .show() to be Konqueror friendly.
|
||||
$openidElements.css("display", "block");
|
||||
}
|
||||
$("li.openid-link:not(.openid-processed)", context)
|
||||
.addClass('openid-processed')
|
||||
.click( function() {
|
||||
$loginElements.hide();
|
||||
$openidElements.css("display", "block");
|
||||
// Remove possible error message.
|
||||
$("#edit-name, #edit-pass").removeClass("error");
|
||||
$("div.messages.error").hide();
|
||||
// Set focus on OpenID Identifier field.
|
||||
$("#edit-openid-identifier")[0].focus();
|
||||
return false;
|
||||
});
|
||||
$("li.user-link:not(.openid-processed)", context)
|
||||
.addClass('openid-processed')
|
||||
.click(function() {
|
||||
$openidElements.hide();
|
||||
$loginElements.css("display", "block");
|
||||
// Clear OpenID Identifier field and remove possible error message.
|
||||
$("#edit-openid-identifier").val('').removeClass("error");
|
||||
$("div.messages.error").css("display", "block");
|
||||
// Set focus on username field.
|
||||
$("#edit-name")[0].focus();
|
||||
return false;
|
||||
});
|
||||
};
|
|
@ -1,701 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement OpenID Relying Party support for Drupal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu.
|
||||
*/
|
||||
function openid_menu() {
|
||||
$items['openid/authenticate'] = array(
|
||||
'title' => 'OpenID Login',
|
||||
'page callback' => 'openid_authentication_page',
|
||||
'access callback' => 'user_is_anonymous',
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'openid.pages.inc',
|
||||
);
|
||||
$items['user/%user/openid'] = array(
|
||||
'title' => 'OpenID identities',
|
||||
'page callback' => 'openid_user_identities',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => 'user_edit_access',
|
||||
'access arguments' => array(1),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'openid.pages.inc',
|
||||
);
|
||||
$items['user/%user/openid/delete'] = array(
|
||||
'title' => 'Delete OpenID',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('openid_user_delete_form', 1),
|
||||
'access callback' => 'user_edit_access',
|
||||
'access arguments' => array(1),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'openid.pages.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function openid_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'user/%/openid':
|
||||
$output = '<p>'. t('This site supports <a href="@openid-net">OpenID</a>, a secure way to log into many websites using a single username and password. OpenID can reduce the necessity of managing many usernames and passwords for many websites.', array('@openid-net' => 'http://openid.net')) .'</p>';
|
||||
$output .= '<p>'. t('To use OpenID you must first establish an identity on a public or private OpenID server. If you do not have an OpenID and would like one, look into one of the <a href="@openid-providers">free public providers</a>. You can find out more about OpenID at <a href="@openid-net">this website</a>.', array('@openid-providers' => 'http://openid.net/get/', '@openid-net' => 'http://openid.net')) .'</p>';
|
||||
$output .= '<p>'. t('If you already have an OpenID, enter the URL to your OpenID server below (e.g. myusername.openidprovider.com). Next time you login, you will be able to use this URL instead of a regular username and password. You can have multiple OpenID servers if you like; just keep adding them here.') .'</p>';
|
||||
return $output;
|
||||
|
||||
case 'admin/help#openid':
|
||||
$output = '<p>'. t('OpenID is a secure method for logging into many websites with a single username and password. It does not require special software, and it does not share passwords with any site to which it is associated; including your site.') .'</p>';
|
||||
$output .= '<p>'. t('Users can create accounts using their OpenID, assign one or more OpenIDs to an existing account, and log in using an OpenID. This lowers the barrier to registration, which is good for the site, and offers convenience and security to the users. OpenID is not a trust system, so email verification is still necessary. The benefit stems from the fact that users can have a single password that they can use on many websites. This means they can easily update their single password from a centralized location, rather than having to change dozens of passwords individually.') .'</p>';
|
||||
$output .= '<p>'. t('The basic concept is as follows: A user has an account on an OpenID server. This account provides them with a unique URL (such as myusername.openidprovider.com). When the user comes to your site, they are presented with the option of entering this URL. Your site then communicates with the OpenID server, asking it to verify the identity of the user. If the user is logged into their OpenID server, the server communicates back to your site, verifying the user. If they are not logged in, the OpenID server will ask the user for their password. At no point does your site record, or need to record the user\'s password.') .'</p>';
|
||||
$output .= '<p>'. t('More information on OpenID is available at <a href="@openid-net">OpenID.net</a>.', array('@openid-net' => url('http://openid.net'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@handbook">OpenID module</a>.', array('@handbook' => 'http://drupal.org/handbook/modules/openid')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function openid_user($op, &$edit, &$account, $category = NULL) {
|
||||
if ($op == 'insert' && isset($_SESSION['openid']['values'])) {
|
||||
// The user has registered after trying to login via OpenID.
|
||||
if (variable_get('user_email_verification', TRUE)) {
|
||||
drupal_set_message(t('Once you have verified your email address, you may log in via OpenID.'));
|
||||
}
|
||||
unset($_SESSION['openid']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_form_alter : adds OpenID login to the login forms.
|
||||
*/
|
||||
function openid_form_alter(&$form, $form_state, $form_id) {
|
||||
if ($form_id == 'user_login_block' || $form_id == 'user_login') {
|
||||
drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
|
||||
drupal_add_js(drupal_get_path('module', 'openid') .'/openid.js');
|
||||
if (!empty($form_state['post']['openid_identifier'])) {
|
||||
$form['name']['#required'] = FALSE;
|
||||
$form['pass']['#required'] = FALSE;
|
||||
unset($form['#submit']);
|
||||
$form['#validate'] = array('openid_login_validate');
|
||||
}
|
||||
|
||||
$items = array();
|
||||
$items[] = array(
|
||||
'data' => l(t('Log in using OpenID'), '#'),
|
||||
'class' => 'openid-link',
|
||||
);
|
||||
$items[] = array(
|
||||
'data' => l(t('Cancel OpenID login'), '#'),
|
||||
'class' => 'user-link',
|
||||
);
|
||||
|
||||
$form['openid_links'] = array(
|
||||
'#value' => theme('item_list', $items),
|
||||
'#weight' => 1,
|
||||
);
|
||||
|
||||
$form['links']['#weight'] = 2;
|
||||
|
||||
$form['openid_identifier'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Log in using OpenID'),
|
||||
'#size' => ($form_id == 'user_login') ? 58 : 13,
|
||||
'#maxlength' => 255,
|
||||
'#weight' => -1,
|
||||
'#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)),
|
||||
);
|
||||
$form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => user_login_destination())));
|
||||
}
|
||||
elseif ($form_id == 'user_register' && isset($_SESSION['openid']['values'])) {
|
||||
// We were unable to auto-register a new user. Prefill the registration
|
||||
// form with the values we have.
|
||||
$form['name']['#default_value'] = $_SESSION['openid']['values']['name'];
|
||||
$form['mail']['#default_value'] = $_SESSION['openid']['values']['mail'];
|
||||
// If user_email_verification is off, hide the password field and just fill
|
||||
// with random password to avoid confusion.
|
||||
if (!variable_get('user_email_verification', TRUE)) {
|
||||
$form['pass']['#type'] = 'hidden';
|
||||
$form['pass']['#value'] = user_password();
|
||||
}
|
||||
$form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']);
|
||||
$form['openid_display'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Your OpenID'),
|
||||
'#description' => t('This OpenID will be attached to your account after registration.'),
|
||||
'#value' => check_plain($_SESSION['openid']['values']['auth_openid']),
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login form _validate hook
|
||||
*/
|
||||
function openid_login_validate($form, &$form_state) {
|
||||
$return_to = $form_state['values']['openid.return_to'];
|
||||
if (empty($return_to)) {
|
||||
$return_to = url('', array('absolute' => TRUE));
|
||||
}
|
||||
|
||||
openid_begin($form_state['values']['openid_identifier'], $return_to, $form_state['values']);
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial step of OpenID authentication responsible for the following:
|
||||
* - Perform discovery on the claimed OpenID.
|
||||
* - If possible, create an association with the Provider's endpoint.
|
||||
* - Create the authentication request.
|
||||
* - Perform the appropriate redirect.
|
||||
*
|
||||
* @param $claimed_id The OpenID to authenticate
|
||||
* @param $return_to The endpoint to return to from the OpenID Provider
|
||||
*/
|
||||
function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
$claimed_id = _openid_normalize($claimed_id);
|
||||
|
||||
$services = openid_discovery($claimed_id);
|
||||
if (count($services) == 0) {
|
||||
form_set_error('openid_identifier', t('Sorry, that is not a valid OpenID. Please ensure you have spelled your ID correctly.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Store discovered information in the users' session so we don't have to rediscover.
|
||||
$_SESSION['openid']['service'] = $services[0];
|
||||
// Store the claimed id
|
||||
$_SESSION['openid']['claimed_id'] = $claimed_id;
|
||||
// Store the login form values so we can pass them to
|
||||
// user_exteral_login later.
|
||||
$_SESSION['openid']['user_login_values'] = $form_values;
|
||||
|
||||
$op_endpoint = $services[0]['uri'];
|
||||
// If bcmath is present, then create an association
|
||||
$assoc_handle = '';
|
||||
if (function_exists('bcadd')) {
|
||||
$assoc_handle = openid_association($op_endpoint);
|
||||
}
|
||||
|
||||
// Now that there is an association created, move on
|
||||
// to request authentication from the IdP
|
||||
// First check for LocalID. If not found, check for Delegate. Fall
|
||||
// back to $claimed_id if neither is found.
|
||||
if (!empty($services[0]['localid'])) {
|
||||
$identity = $services[0]['localid'];
|
||||
}
|
||||
else if (!empty($services[0]['delegate'])) {
|
||||
$identity = $services[0]['delegate'];
|
||||
}
|
||||
else {
|
||||
$identity = $claimed_id;
|
||||
}
|
||||
|
||||
if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
|
||||
$claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
|
||||
}
|
||||
$authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
|
||||
|
||||
if ($services[0]['version'] == 2) {
|
||||
openid_redirect($op_endpoint, $authn_request);
|
||||
}
|
||||
else {
|
||||
openid_redirect_http($op_endpoint, $authn_request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes OpenID authentication by validating returned data from the OpenID
|
||||
* Provider.
|
||||
*
|
||||
* @param $response Array of returned values from the OpenID Provider.
|
||||
*
|
||||
* @return $response Response values for further processing with
|
||||
* $response['status'] set to one of 'success', 'failed' or 'cancel'.
|
||||
*/
|
||||
function openid_complete($response = array()) {
|
||||
global $base_url;
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
if (count($response) == 0) {
|
||||
$response = _openid_response();
|
||||
}
|
||||
|
||||
// Default to failed response
|
||||
$response['status'] = 'failed';
|
||||
if (isset($_SESSION['openid']['service']['uri']) && isset($_SESSION['openid']['claimed_id'])) {
|
||||
$service = $_SESSION['openid']['service'];
|
||||
$claimed_id = $_SESSION['openid']['claimed_id'];
|
||||
unset($_SESSION['openid']['service']);
|
||||
unset($_SESSION['openid']['claimed_id']);
|
||||
if (isset($response['openid.mode'])) {
|
||||
if ($response['openid.mode'] == 'cancel') {
|
||||
$response['status'] = 'cancel';
|
||||
}
|
||||
else {
|
||||
if (openid_verify_assertion($service, $response)) {
|
||||
// If the returned claimed_id is different from the session claimed_id,
|
||||
// then we need to do discovery and make sure the op_endpoint matches.
|
||||
if ($service['version'] == 2) {
|
||||
// Returned Claimed Identifier could contain unique fragment
|
||||
// identifier to allow identifier recycling so we need to preserve
|
||||
// it in the response.
|
||||
$response_claimed_id = _openid_normalize($response['openid.claimed_id']);
|
||||
|
||||
if ($response_claimed_id != $claimed_id || $response_claimed_id != $response['openid.identity']) {
|
||||
$disco = openid_discovery($response['openid.claimed_id']);
|
||||
|
||||
if ($disco[0]['uri'] != $service['uri']) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (!empty($disco[0]['localid'])) {
|
||||
$identity = $disco[0]['localid'];
|
||||
}
|
||||
else if (!empty($disco[0]['delegate'])) {
|
||||
$identity = $disco[0]['delegate'];
|
||||
}
|
||||
else {
|
||||
$identity = FALSE;
|
||||
}
|
||||
|
||||
// The OP-Local Identifier (if different than the Claimed
|
||||
// Identifier) must be present in the XRDS document.
|
||||
if ($response_claimed_id != $response['openid.identity'] && (!$identity || $identity != $response['openid.identity'])) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$response['openid.claimed_id'] = $claimed_id;
|
||||
}
|
||||
// Verify that openid.return_to matches the current URL (see OpenID
|
||||
// Authentication 2.0, section 11.1).
|
||||
// While OpenID Authentication 1.1, section 4.3 does not mandate
|
||||
// return_to verification, the received return_to should still
|
||||
// match these constraints.
|
||||
$return_to_parts = parse_url($response['openid.return_to']);
|
||||
|
||||
$base_url_parts = parse_url($base_url);
|
||||
$current_parts = parse_url($base_url_parts['scheme'] .'://'. $base_url_parts['host'] . request_uri());
|
||||
|
||||
if ($return_to_parts['scheme'] != $current_parts['scheme'] ||
|
||||
$return_to_parts['host'] != $current_parts['host'] ||
|
||||
$return_to_parts['path'] != $current_parts['path']) {
|
||||
|
||||
return $response;
|
||||
}
|
||||
// Verify that all query parameters in the openid.return_to URL have
|
||||
// the same value in the current URL. In addition, the current URL
|
||||
// contains a number of other parameters added by the OpenID Provider.
|
||||
parse_str(isset($return_to_parts['query']) ? $return_to_parts['query'] : '', $return_to_query_parameters);
|
||||
foreach ($return_to_query_parameters as $name => $value) {
|
||||
if (!array_key_exists($name, $_GET) || $_GET[$name] != $value) {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
$response['status'] = 'success';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform discovery on a claimed ID to determine the OpenID provider endpoint.
|
||||
*
|
||||
* @param $claimed_id The OpenID URL to perform discovery on.
|
||||
*
|
||||
* @return Array of services discovered (including OpenID version, endpoint
|
||||
* URI, etc).
|
||||
*/
|
||||
function openid_discovery($claimed_id) {
|
||||
module_load_include('inc', 'openid');
|
||||
module_load_include('inc', 'openid', 'xrds');
|
||||
|
||||
$services = array();
|
||||
|
||||
$xrds_url = $claimed_id;
|
||||
if (_openid_is_xri($claimed_id)) {
|
||||
$xrds_url = 'http://xri.net/'. $claimed_id;
|
||||
}
|
||||
$url = @parse_url($xrds_url);
|
||||
if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
|
||||
// For regular URLs, try Yadis resolution first, then HTML-based discovery
|
||||
$headers = array('Accept' => 'application/xrds+xml');
|
||||
$result = drupal_http_request($xrds_url, $headers);
|
||||
|
||||
if (!isset($result->error)) {
|
||||
if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
|
||||
// Parse XML document to find URL
|
||||
$services = xrds_parse($result->data);
|
||||
}
|
||||
else {
|
||||
$xrds_url = NULL;
|
||||
if (isset($result->headers['X-XRDS-Location'])) {
|
||||
$xrds_url = $result->headers['X-XRDS-Location'];
|
||||
}
|
||||
else {
|
||||
// Look for meta http-equiv link in HTML head
|
||||
$xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
|
||||
}
|
||||
if (!empty($xrds_url)) {
|
||||
$headers = array('Accept' => 'application/xrds+xml');
|
||||
$xrds_result = drupal_http_request($xrds_url, $headers);
|
||||
if (!isset($xrds_result->error)) {
|
||||
$services = xrds_parse($xrds_result->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for HTML delegation
|
||||
if (count($services) == 0) {
|
||||
// Look for 2.0 links
|
||||
$uri = _openid_link_href('openid2.provider', $result->data);
|
||||
$delegate = _openid_link_href('openid2.local_id', $result->data);
|
||||
$version = 2;
|
||||
|
||||
// 1.0 links
|
||||
if (empty($uri)) {
|
||||
$uri = _openid_link_href('openid.server', $result->data);
|
||||
$delegate = _openid_link_href('openid.delegate', $result->data);
|
||||
$version = 1;
|
||||
}
|
||||
if (!empty($uri)) {
|
||||
$services[] = array('uri' => $uri, 'delegate' => $delegate, 'version' => $version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create a shared secret with the OpenID Provider.
|
||||
*
|
||||
* @param $op_endpoint URL of the OpenID Provider endpoint.
|
||||
*
|
||||
* @return $assoc_handle The association handle.
|
||||
*/
|
||||
function openid_association($op_endpoint) {
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
// Remove Old Associations:
|
||||
db_query("DELETE FROM {openid_association} WHERE created + expires_in < %d", time());
|
||||
|
||||
// Check to see if we have an association for this IdP already
|
||||
$assoc_handle = db_result(db_query("SELECT assoc_handle FROM {openid_association} WHERE idp_endpoint_uri = '%s'", $op_endpoint));
|
||||
if (empty($assoc_handle)) {
|
||||
$mod = OPENID_DH_DEFAULT_MOD;
|
||||
$gen = OPENID_DH_DEFAULT_GEN;
|
||||
$r = _openid_dh_rand($mod);
|
||||
$private = bcadd($r, 1);
|
||||
$public = bcpowmod($gen, $private, $mod);
|
||||
|
||||
// If there is no existing association, then request one
|
||||
$assoc_request = openid_association_request($public);
|
||||
$assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
|
||||
$assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
|
||||
$assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message);
|
||||
if (isset($assoc_result->error)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$assoc_response = _openid_parse_message($assoc_result->data);
|
||||
if (isset($assoc_response['mode']) && $assoc_response['mode'] == 'error') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($assoc_response['session_type'] == 'DH-SHA1') {
|
||||
$spub = _openid_dh_base64_to_long($assoc_response['dh_server_public']);
|
||||
$enc_mac_key = base64_decode($assoc_response['enc_mac_key']);
|
||||
$shared = bcpowmod($spub, $private, $mod);
|
||||
$assoc_response['mac_key'] = base64_encode(_openid_dh_xorsecret($shared, $enc_mac_key));
|
||||
}
|
||||
db_query("INSERT INTO {openid_association} (idp_endpoint_uri, session_type, assoc_handle, assoc_type, expires_in, mac_key, created) VALUES('%s', '%s', '%s', '%s', %d, '%s', %d)",
|
||||
$op_endpoint, $assoc_response['session_type'], $assoc_response['assoc_handle'], $assoc_response['assoc_type'], $assoc_response['expires_in'], $assoc_response['mac_key'], time());
|
||||
|
||||
$assoc_handle = $assoc_response['assoc_handle'];
|
||||
}
|
||||
|
||||
return $assoc_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user or attempt registration.
|
||||
*
|
||||
* @param $response Response values from the OpenID Provider.
|
||||
*/
|
||||
function openid_authentication($response) {
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
$identity = $response['openid.claimed_id'];
|
||||
|
||||
$account = user_external_load($identity);
|
||||
if (isset($account->uid)) {
|
||||
if (!variable_get('user_email_verification', TRUE) || $account->login) {
|
||||
user_external_login($account, $_SESSION['openid']['user_login_values']);
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('You must validate your email address for this account before logging in via OpenID'));
|
||||
}
|
||||
}
|
||||
elseif (variable_get('user_register', 1)) {
|
||||
// Register new user
|
||||
$form_state['redirect'] = NULL;
|
||||
// Only signed SREG keys are included as required by OpenID Simple
|
||||
// Registration Extension 1.0, section 4.
|
||||
$signed_keys = explode(',', $response['openid.signed']);
|
||||
$form_state['values']['name'] = in_array('sreg.nickname', $signed_keys) ? $response['openid.sreg.nickname'] : '';
|
||||
$form_state['values']['mail'] = in_array('sreg.email', $signed_keys) ? $response['openid.sreg.email'] : '';
|
||||
$form_state['values']['pass'] = user_password();
|
||||
$form_state['values']['status'] = variable_get('user_register', 1) == 1;
|
||||
$form_state['values']['response'] = $response;
|
||||
$form_state['values']['auth_openid'] = $identity;
|
||||
|
||||
if (empty($form_state['values']['name']) && empty($form_state['values']['mail'])) {
|
||||
drupal_set_message(t('Please complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'warning');
|
||||
$success = FALSE;
|
||||
}
|
||||
else {
|
||||
$form = drupal_retrieve_form('user_register', $form_state);
|
||||
drupal_prepare_form('user_register', $form, $form_state);
|
||||
drupal_validate_form('user_register', $form, $form_state);
|
||||
$success = !form_get_errors();
|
||||
if (!$success) {
|
||||
drupal_set_message(t('Account registration using the information provided by your OpenID provider failed due to the reasons listed below. Please complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'warning');
|
||||
// Append form validation errors below the above warning.
|
||||
$messages = drupal_get_messages('error');
|
||||
foreach ($messages['error'] as $message) {
|
||||
drupal_set_message( $message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$success) {
|
||||
// We were unable to register a valid new user, redirect to standard
|
||||
// user/register and prefill with the values we received.
|
||||
$_SESSION['openid']['values'] = $form_state['values'];
|
||||
// We'll want to redirect back to the same place.
|
||||
$destination = drupal_get_destination();
|
||||
unset($_REQUEST['destination']);
|
||||
drupal_goto('user/register', $destination);
|
||||
}
|
||||
else {
|
||||
unset($form_state['values']['response']);
|
||||
$account = user_save('', $form_state['values']);
|
||||
// Terminate if an error occured during user_save().
|
||||
if (!$account) {
|
||||
drupal_set_message(t("Error saving user account."), 'error');
|
||||
drupal_goto();
|
||||
}
|
||||
user_external_login($account);
|
||||
}
|
||||
drupal_redirect_form($form, $form_state['redirect']);
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
|
||||
}
|
||||
drupal_goto();
|
||||
}
|
||||
|
||||
function openid_association_request($public) {
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
$request = array(
|
||||
'openid.ns' => OPENID_NS_2_0,
|
||||
'openid.mode' => 'associate',
|
||||
'openid.session_type' => 'DH-SHA1',
|
||||
'openid.assoc_type' => 'HMAC-SHA1'
|
||||
);
|
||||
|
||||
if ($request['openid.session_type'] == 'DH-SHA1' || $request['openid.session_type'] == 'DH-SHA256') {
|
||||
$cpub = _openid_dh_long_to_base64($public);
|
||||
$request['openid.dh_consumer_public'] = $cpub;
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
function openid_authentication_request($claimed_id, $identity, $return_to = '', $assoc_handle = '', $version = 2) {
|
||||
global $base_url;
|
||||
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
$ns = ($version == 2) ? OPENID_NS_2_0 : OPENID_NS_1_0;
|
||||
$request = array(
|
||||
'openid.ns' => $ns,
|
||||
'openid.mode' => 'checkid_setup',
|
||||
'openid.identity' => $identity,
|
||||
'openid.claimed_id' => $claimed_id,
|
||||
'openid.assoc_handle' => $assoc_handle,
|
||||
'openid.return_to' => $return_to,
|
||||
);
|
||||
|
||||
if ($version == 2) {
|
||||
$request['openid.realm'] = $base_url . '/';
|
||||
}
|
||||
else {
|
||||
$request['openid.trust_root'] = $base_url . '/';
|
||||
}
|
||||
|
||||
// Simple Registration
|
||||
$request['openid.sreg.required'] = 'nickname,email';
|
||||
$request['openid.ns.sreg'] = "http://openid.net/extensions/sreg/1.1";
|
||||
|
||||
$request = array_merge($request, module_invoke_all('openid', 'request', $request));
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to verify the response received from the OpenID Provider.
|
||||
*
|
||||
* @param $service
|
||||
* Array describing the OpenID provider.
|
||||
* @param $response
|
||||
* Array of response values from the provider.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
function openid_verify_assertion($service, $response) {
|
||||
module_load_include('inc', 'openid');
|
||||
|
||||
// http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.3
|
||||
// Check the Nonce to protect against replay attacks.
|
||||
if (!openid_verify_assertion_nonce($service, $response)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4
|
||||
// Verify the signatures.
|
||||
$valid = FALSE;
|
||||
$association = db_fetch_object(db_query("SELECT * FROM {openid_association} WHERE idp_endpoint_uri = '%s' AND assoc_handle = '%s'", $service['uri'], $response['openid.assoc_handle']));
|
||||
if ($association && isset($association->session_type)) {
|
||||
// http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.2
|
||||
// Verification using an association.
|
||||
$valid = openid_verify_assertion_signature($service, $association, $response);
|
||||
}
|
||||
else {
|
||||
// http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4.3
|
||||
// Direct verification.
|
||||
$request = $response;
|
||||
$request['openid.mode'] = 'check_authentication';
|
||||
$message = _openid_create_message($request);
|
||||
$headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
|
||||
$result = drupal_http_request($service['uri'], $headers, 'POST', _openid_encode_message($message));
|
||||
if (!isset($result->error)) {
|
||||
$response = _openid_parse_message($result->data);
|
||||
if (strtolower(trim($response['is_valid'])) == 'true') {
|
||||
$valid = TRUE;
|
||||
}
|
||||
else {
|
||||
$valid = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the signature of the response received from the OpenID provider.
|
||||
*
|
||||
* @param $service
|
||||
* Array describing the OpenID provider.
|
||||
* @param $association
|
||||
* Information on the association with the OpenID provider.
|
||||
* @param $response
|
||||
* Array of response values from the provider.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the signature is valid and covers all fields required to be signed.
|
||||
* @see http://openid.net/specs/openid-authentication-2_0.html#rfc.section.11.4
|
||||
*/
|
||||
function openid_verify_assertion_signature($service, $association, $response) {
|
||||
if ($service['version'] == 2) {
|
||||
// OpenID Authentication 2.0, section 10.1:
|
||||
// These keys must always be signed.
|
||||
$mandatory_keys = array('op_endpoint', 'return_to', 'response_nonce', 'assoc_handle');
|
||||
if (isset($response['openid.claimed_id'])) {
|
||||
// If present, these two keys must also be signed. According to the spec,
|
||||
// they are either both present or both absent.
|
||||
$mandatory_keys[] = 'claimed_id';
|
||||
$mandatory_keys[] = 'identity';
|
||||
}
|
||||
}
|
||||
else {
|
||||
// OpenID Authentication 1.1. section 4.3.3.
|
||||
$mandatory_keys = array('identity', 'return_to');
|
||||
}
|
||||
|
||||
$keys_to_sign = explode(',', $response['openid.signed']);
|
||||
|
||||
if (count(array_diff($mandatory_keys, $keys_to_sign)) > 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return _openid_signature($association, $response, $keys_to_sign) == $response['openid.sig'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the nonce has not been used in earlier assertions from the same OpenID provider.
|
||||
*
|
||||
* @param $service
|
||||
* Array describing the OpenID provider.
|
||||
* @param $response
|
||||
* Array of response values from the provider.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the nonce has not expired and has not been used earlier.
|
||||
*/
|
||||
function openid_verify_assertion_nonce($service, $response) {
|
||||
if ($service['version'] != 2) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z/', $response['openid.response_nonce'], $matches)) {
|
||||
list(, $year, $month, $day, $hour, $minutes, $seconds) = $matches;
|
||||
$nonce_timestamp = gmmktime($hour, $minutes, $seconds, $month, $day, $year);
|
||||
}
|
||||
else {
|
||||
watchdog('openid', 'Nonce from @endpoint rejected because it is not correctly formatted, nonce: @nonce.', array('@endpoint' => $service['uri'], '@nonce' => $response['openid.response_nonce']), WATCHDOG_WARNING);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// A nonce with a timestamp to far in the past or future will already have
|
||||
// been removed and cannot be checked for single use anymore.
|
||||
$time = time();
|
||||
$expiry = 900;
|
||||
if ($nonce_timestamp <= $time - $expiry || $nonce_timestamp >= $time + $expiry) {
|
||||
watchdog('openid', 'Nonce received from @endpoint is out of range (time difference: @intervals). Check possible clock skew.', array('@endpoint' => $service['uri'], '@interval' => $time - $nonce_timestamp), WATCHDOG_WARNING);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Record that this nonce was used.
|
||||
db_query("INSERT INTO {openid_nonce} (idp_endpoint_uri, nonce, expires) VALUES ('%s', '%s', %d)", $service['uri'], $response['openid.response_nonce'], $nonce_timestamp + $expiry);
|
||||
|
||||
// Count the number of times this nonce was used.
|
||||
$count_used = db_result(db_query("SELECT COUNT(*) FROM {openid_nonce} WHERE nonce = '%s' AND idp_endpoint_uri = '%s'", $response['openid.response_nonce'], $service['uri']));
|
||||
|
||||
if ($count_used == 1) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
watchdog('openid', 'Nonce replay attempt blocked from @ip, nonce: @nonce.', array('@ip' => ip_address(), '@nonce' => $response['openid.response_nonce']), WATCHDOG_CRITICAL);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired nonces from the database.
|
||||
*
|
||||
* Implementation of hook_cron().
|
||||
*/
|
||||
function openid_cron() {
|
||||
db_query("DELETE FROM {openid_nonce} WHERE expires < %d", time());
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the openid module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback; Process an OpenID authentication.
|
||||
*/
|
||||
function openid_authentication_page() {
|
||||
$result = openid_complete();
|
||||
switch ($result['status']) {
|
||||
case 'success':
|
||||
return openid_authentication($result);
|
||||
case 'failed':
|
||||
drupal_set_message(t('OpenID login failed.'), 'error');
|
||||
break;
|
||||
case 'cancel':
|
||||
drupal_set_message(t('OpenID login cancelled.'));
|
||||
break;
|
||||
}
|
||||
drupal_goto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; Manage OpenID identities for the specified user.
|
||||
*/
|
||||
function openid_user_identities($account) {
|
||||
drupal_set_title(check_plain($account->name));
|
||||
drupal_add_css(drupal_get_path('module', 'openid') .'/openid.css', 'module');
|
||||
|
||||
// Check to see if we got a response
|
||||
$result = openid_complete();
|
||||
if ($result['status'] == 'success') {
|
||||
$identity = $result['openid.claimed_id'];
|
||||
db_query("INSERT INTO {authmap} (uid, authname, module) VALUES (%d, '%s','openid')", $account->uid, $identity);
|
||||
drupal_set_message(t('Successfully added %identity', array('%identity' => $identity)));
|
||||
}
|
||||
|
||||
$header = array(t('OpenID'), t('Operations'));
|
||||
$rows = array();
|
||||
|
||||
$result = db_query("SELECT * FROM {authmap} WHERE module='openid' AND uid=%d", $account->uid);
|
||||
while ($identity = db_fetch_object($result)) {
|
||||
$rows[] = array(check_plain($identity->authname), l(t('Delete'), 'user/'. $account->uid .'/openid/delete/'. $identity->aid));
|
||||
}
|
||||
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= drupal_get_form('openid_user_add');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; Add an OpenID identity.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see openid_user_add_validate()
|
||||
*/
|
||||
function openid_user_add() {
|
||||
$form['openid_identifier'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('OpenID'),
|
||||
);
|
||||
$form['submit'] = array('#type' => 'submit', '#value' => t('Add an OpenID'));
|
||||
return $form;
|
||||
}
|
||||
|
||||
function openid_user_add_validate($form, &$form_state) {
|
||||
// Check for existing entries.
|
||||
$claimed_id = _openid_normalize($form_state['values']['openid_identifier']);
|
||||
if (db_result(db_query("SELECT authname FROM {authmap} WHERE authname='%s'", $claimed_id))) {
|
||||
form_set_error('openid_identifier', t('That OpenID is already in use on this site.'));
|
||||
}
|
||||
}
|
||||
|
||||
function openid_user_add_submit($form, &$form_state) {
|
||||
$return_to = url('user/'. arg(1) .'/openid', array('absolute' => TRUE));
|
||||
openid_begin($form_state['values']['openid_identifier'], $return_to);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Present a confirmation form to delete the specified OpenID identity from the system.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see openid_user_delete_form_submit()
|
||||
*/
|
||||
function openid_user_delete_form($form_state, $account, $aid = 0) {
|
||||
$authname = db_result(db_query('SELECT authname FROM {authmap} WHERE uid = %d AND aid = %d', $account->uid, $aid));
|
||||
|
||||
$form = array();
|
||||
|
||||
$form['uid'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $account->uid,
|
||||
);
|
||||
|
||||
$form['aid'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $aid,
|
||||
);
|
||||
|
||||
return confirm_form($form, t('Are you sure you want to delete the OpenID %authname for %user?', array('%authname' => $authname, '%user' => $account->name)), 'user/'. $account->uid .'/openid');
|
||||
}
|
||||
|
||||
function openid_user_delete_form_submit($form, &$form_state) {
|
||||
db_query("DELETE FROM {authmap} WHERE uid = %d AND aid = %d AND module = 'openid'", $form_state['values']['uid'], $form_state['values']['aid']);
|
||||
if (db_affected_rows()) {
|
||||
drupal_set_message(t('OpenID deleted.'));
|
||||
}
|
||||
$form_state['redirect'] = 'user/'. $form_state['values']['uid'] .'/openid';
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
// Global variables to track parsing state
|
||||
$xrds_open_elements = array();
|
||||
$xrds_services = array();
|
||||
$xrds_current_service = array();
|
||||
|
||||
/**
|
||||
* Main entry point for parsing XRDS documents
|
||||
*/
|
||||
function xrds_parse($xml) {
|
||||
global $xrds_services;
|
||||
|
||||
$parser = xml_parser_create_ns();
|
||||
xml_set_element_handler($parser, '_xrds_element_start', '_xrds_element_end');
|
||||
xml_set_character_data_handler($parser, '_xrds_cdata');
|
||||
|
||||
// Since DOCTYPE declarations from an untrusted source could be malicious, we
|
||||
// stop parsing here and treat the XML as invalid. XRDS documents do not
|
||||
// require, and are not expected to have, a DOCTYPE.
|
||||
if (preg_match('/<!DOCTYPE/i', $xml)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Also stop parsing if there is an unreasonably large number of tags.
|
||||
// substr_count() has much better performance (compared to preg_match_all())
|
||||
// for large payloads but is less accurate, so we check for twice the desired
|
||||
// number of allowed tags (to take into account opening/closing tags as well
|
||||
// as false positives).
|
||||
if (substr_count($xml, '<') > 2 * variable_get('openid_xrds_maximum_tag_count', 30000)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
xml_parse($parser, $xml);
|
||||
xml_parser_free($parser);
|
||||
|
||||
return $xrds_services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser callback functions
|
||||
*/
|
||||
function _xrds_element_start(&$parser, $name, $attribs) {
|
||||
global $xrds_open_elements;
|
||||
|
||||
$xrds_open_elements[] = _xrds_strip_namespace($name);
|
||||
}
|
||||
|
||||
function _xrds_element_end(&$parser, $name) {
|
||||
global $xrds_open_elements, $xrds_services, $xrds_current_service;
|
||||
|
||||
$name = _xrds_strip_namespace($name);
|
||||
if ($name == 'SERVICE') {
|
||||
if (in_array(OPENID_NS_2_0 .'/signon', $xrds_current_service['types']) ||
|
||||
in_array(OPENID_NS_2_0 .'/server', $xrds_current_service['types'])) {
|
||||
$xrds_current_service['version'] = 2;
|
||||
}
|
||||
elseif (in_array(OPENID_NS_1_1, $xrds_current_service['types']) ||
|
||||
in_array(OPENID_NS_1_0, $xrds_current_service['types'])) {
|
||||
$xrds_current_service['version'] = 1;
|
||||
}
|
||||
if (!empty($xrds_current_service['version'])) {
|
||||
$xrds_services[] = $xrds_current_service;
|
||||
}
|
||||
$xrds_current_service = array();
|
||||
}
|
||||
array_pop($xrds_open_elements);
|
||||
}
|
||||
|
||||
function _xrds_cdata(&$parser, $data) {
|
||||
global $xrds_open_elements, $xrds_services, $xrds_current_service;
|
||||
$path = strtoupper(implode('/', $xrds_open_elements));
|
||||
switch ($path) {
|
||||
case 'XRDS/XRD/SERVICE/TYPE':
|
||||
$xrds_current_service['types'][] = $data;
|
||||
break;
|
||||
case 'XRDS/XRD/SERVICE/URI':
|
||||
$xrds_current_service['uri'] = $data;
|
||||
break;
|
||||
case 'XRDS/XRD/SERVICE/DELEGATE':
|
||||
$xrds_current_service['delegate'] = $data;
|
||||
break;
|
||||
case 'XRDS/XRD/SERVICE/LOCALID':
|
||||
$xrds_current_service['localid'] = $data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function _xrds_strip_namespace($name) {
|
||||
// Strip namespacing.
|
||||
$pos = strrpos($name, ':');
|
||||
if ($pos !== FALSE) {
|
||||
$name = substr($name, $pos + 1, strlen($name));
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = PHP filter
|
||||
description = Allows embedded PHP code/snippets to be evaluated.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function php_install() {
|
||||
$format_exists = db_result(db_query("SELECT COUNT(*) FROM {filter_formats} WHERE name = 'PHP code'"));
|
||||
// Add a PHP code input format, if it does not exist. Do this only for the
|
||||
// first install (or if the format has been manually deleted) as there is no
|
||||
// reliable method to identify the format in an uninstall hook or in
|
||||
// subsequent clean installs.
|
||||
if (!$format_exists) {
|
||||
db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('PHP code', '', 0)");
|
||||
$format = db_result(db_query("SELECT MAX(format) FROM {filter_formats}"));
|
||||
|
||||
// Enable the PHP evaluator filter.
|
||||
db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, 'php', 0, 0)", $format);
|
||||
|
||||
drupal_set_message(t('A !php-code input format has been created.', array('!php-code' => l('PHP code', 'admin/settings/filters/'. $format))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_disable().
|
||||
*/
|
||||
function php_disable() {
|
||||
drupal_set_message(t('The PHP module has been disabled. Please note that any existing content that was using the PHP filter will now be visible in plain text. This might pose a security risk by exposing sensitive information, if any, used in the PHP code.'));
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Additional filter for PHP input.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function php_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#php':
|
||||
$output = '<p>'. t('The PHP filter adds the ability to include PHP code in posts. PHP is a general-purpose scripting language widely-used for web development; the content management system used by this website has been developed using PHP.') .'</p>';
|
||||
$output .= '<p>'. t('Through the PHP filter, users with the proper permission may include custom PHP code within a page of the site. While this is a powerful and flexible feature if used by a trusted user with PHP experience, it is a significant and dangerous security risk in the hands of a malicious user. Even a trusted user may accidentally compromise the site by entering malformed or incorrect PHP code. Only the most trusted users should be granted permission to use the PHP filter, and all PHP code added through the PHP filter should be carefully examined before use.') .'</p>';
|
||||
$output .= '<p>'. t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/handbook/customization/php-snippets'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@php">PHP module</a>.', array('@php' => 'http://drupal.org/handbook/modules/php/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_filter_tips().
|
||||
*/
|
||||
function php_filter_tips($delta, $format, $long = false) {
|
||||
global $base_url;
|
||||
if ($delta == 0) {
|
||||
switch ($long) {
|
||||
case 0:
|
||||
return t('You may post PHP code. You should include <?php ?> tags.');
|
||||
case 1:
|
||||
$output = '<h4>'. t('Using custom PHP code') .'</h4>';
|
||||
$output .= '<p>'. t('Custom PHP code may be embedded in some types of site content, including posts and blocks. While embedding PHP code inside a post or block is a powerful and flexible feature when used by a trusted user with PHP experience, it is a significant and dangerous security risk when used improperly. Even a small mistake when posting PHP code may accidentally compromise your site.') .'</p>';
|
||||
$output .= '<p>'. t('If you are unfamiliar with PHP, SQL, or Drupal, avoid using custom PHP code within posts. Experimenting with PHP may corrupt your database, render your site inoperable, or significantly compromise security.') .'</p>';
|
||||
$output .= '<p>'. t('Notes:') .'</p>';
|
||||
$output .= '<ul><li>'. t('Remember to double-check each line for syntax and logic errors <strong>before</strong> saving.') .'</li>';
|
||||
$output .= '<li>'. t('Statements must be correctly terminated with semicolons.') .'</li>';
|
||||
$output .= '<li>'. t('Global variables used within your PHP code retain their values after your script executes.') .'</li>';
|
||||
$output .= '<li>'. t('<code>register_globals</code> is <strong>turned off</strong>. If you need to use forms, understand and use the functions in <a href="@formapi">the Drupal Form API</a>.', array('@formapi' => url('http://api.drupal.org/api/group/form_api/6'))) .'</li>';
|
||||
$output .= '<li>'. t('Use a <code>print</code> or <code>return</code> statement in your code to output content.') .'</li>';
|
||||
$output .= '<li>'. t('Develop and test your PHP code using a separate test script and sample database before deploying on a production site.') .'</li>';
|
||||
$output .= '<li>'. t('Consider including your custom PHP code within a site-specific module or <code>template.php</code> file rather than embedding it directly into a post or block.') .'</li>';
|
||||
$output .= '<li>'. t('Be aware that the ability to embed PHP code within content is provided by the PHP Filter module. If this module is disabled or deleted, then blocks and posts with embedded PHP may display, rather than execute, the PHP code.') .'</li></ul>';
|
||||
$output .= '<p>'. t('A basic example: <em>Creating a "Welcome" block that greets visitors with a simple message.</em>') .'</p>';
|
||||
$output .= '<ul><li>'. t('<p>Add a custom block to your site, named "Welcome". With its input format set to "PHP code" (or another format supporting PHP input), add the following in the Block body:</p>
|
||||
<pre>
|
||||
print t(\'Welcome visitor! Thank you for visiting.\');
|
||||
</pre>') .'</li>';
|
||||
$output .= '<li>'. t('<p>To display the name of a registered user, use this instead:</p>
|
||||
<pre>
|
||||
global $user;
|
||||
if ($user->uid) {
|
||||
print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => $user->name));
|
||||
}
|
||||
else {
|
||||
print t(\'Welcome visitor! Thank you for visiting.\');
|
||||
}
|
||||
</pre>') .'</li></ul>';
|
||||
$output .= '<p>'. t('<a href="@drupal">Drupal.org</a> offers <a href="@php-snippets">some example PHP snippets</a>, or you can create your own with some PHP experience and knowledge of the Drupal system.', array('@drupal' => url('http://drupal.org'), '@php-snippets' => url('http://drupal.org/handbook/customization/php-snippets'))) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_filter(). Contains a basic PHP evaluator.
|
||||
*
|
||||
* Executes PHP code. Use with care.
|
||||
*/
|
||||
function php_filter($op, $delta = 0, $format = -1, $text = '') {
|
||||
switch ($op) {
|
||||
case 'list':
|
||||
return array(0 => t('PHP evaluator'));
|
||||
case 'no cache':
|
||||
// No caching for the PHP evaluator.
|
||||
return $delta == 0;
|
||||
case 'description':
|
||||
return t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!');
|
||||
case 'process':
|
||||
return drupal_eval($text);
|
||||
default:
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
name = Ping
|
||||
description = Alerts other sites when your site has been updated.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Alerts other sites that your site has been updated.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function ping_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#ping':
|
||||
$output = '<p>'. t('The ping module is useful for notifying interested sites that your site has changed. It automatically sends notifications, or "pings", to the <a href="@external-http-pingomatic-com">pingomatic</a> service about new or updated content. In turn, <a href="@external-http-pingomatic-com">pingomatic</a> notifies other popular services, including weblogs.com, Technorati, blo.gs, BlogRolling, Feedster.com, and Moreover.', array('@external-http-pingomatic-com' => 'http://pingomatic.com/')) .'</p>';
|
||||
$output .= '<p>'. t('The ping module requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@ping">Ping module</a>.', array('@ping' => 'http://drupal.org/handbook/modules/ping/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*
|
||||
* Fire off notifications of updates to remote sites.
|
||||
*/
|
||||
function ping_cron() {
|
||||
global $base_url;
|
||||
|
||||
if (variable_get('site_name', 0)) {
|
||||
$cron_last = variable_get('cron_last', time());
|
||||
// Query changed first since usually changed >= created.
|
||||
if (db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND changed > %d', $cron_last)) || db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1 AND created > %d', $cron_last))) {
|
||||
_ping_notify(variable_get('site_name', ''), $base_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call hook_ping() in all modules to notify remote sites that there is
|
||||
* new content at this one.
|
||||
*/
|
||||
function _ping_notify($name, $url) {
|
||||
module_invoke_all('ping', $name, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_ping().
|
||||
*
|
||||
* Notifies pingomatic.com, blo.gs, and technorati.com of changes at this site.
|
||||
*/
|
||||
function ping_ping($name = '', $url = '') {
|
||||
|
||||
$result = xmlrpc('http://rpc.pingomatic.com', 'weblogUpdates.ping', $name, $url);
|
||||
|
||||
if ($result === FALSE) {
|
||||
watchdog('directory ping', 'Failed to notify pingomatic.com (site).', array(), WATCHDOG_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file poll-bar-block.tpl.php
|
||||
* Display the bar for a single choice in a poll
|
||||
*
|
||||
* Variables available:
|
||||
* - $title: The title of the poll.
|
||||
* - $votes: The number of votes for this choice
|
||||
* - $total_votes: The number of votes for this choice
|
||||
* - $percentage: The percentage of votes for this choice.
|
||||
* - $vote: The choice number of the current user's vote.
|
||||
* - $voted: Set to TRUE if the user voted for this choice.
|
||||
*
|
||||
* @see template_preprocess_poll_bar()
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="text"><?php print $title; ?></div>
|
||||
<div class="bar">
|
||||
<div style="width: <?php print $percentage; ?>%;" class="foreground"></div>
|
||||
</div>
|
||||
<div class="percent">
|
||||
<?php print $percentage; ?>%
|
||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file poll-bar.tpl.php
|
||||
* Display the bar for a single choice in a poll
|
||||
*
|
||||
* Variables available:
|
||||
* - $title: The title of the poll.
|
||||
* - $votes: The number of votes for this choice
|
||||
* - $total_votes: The number of votes for this choice
|
||||
* - $percentage: The percentage of votes for this choice.
|
||||
* - $vote: The choice number of the current user's vote.
|
||||
* - $voted: Set to TRUE if the user voted for this choice.
|
||||
*
|
||||
* @see template_preprocess_poll_bar()
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="text"><?php print $title; ?></div>
|
||||
<div class="bar">
|
||||
<div style="width: <?php print $percentage; ?>%;" class="foreground"></div>
|
||||
</div>
|
||||
<div class="percent">
|
||||
<?php print $percentage; ?>% (<?php print format_plural($votes, '1 vote', '@count votes'); ?>)
|
||||
</div>
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @file poll-results-block.tpl.php
|
||||
* Display the poll results in a block.
|
||||
*
|
||||
* Variables available:
|
||||
* - $title: The title of the poll.
|
||||
* - $results: The results of the poll.
|
||||
* - $votes: The total results in the poll.
|
||||
* - $links: Links in the poll.
|
||||
* - $nid: The nid of the poll
|
||||
* - $cancel_form: A form to cancel the user's vote, if allowed.
|
||||
* - $raw_links: The raw array of links. Should be run through theme('links')
|
||||
* if used.
|
||||
* - $vote: The choice number of the current user's vote.
|
||||
*
|
||||
* @see template_preprocess_poll_results()
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="poll">
|
||||
<div class="title"><?php print $title ?></div>
|
||||
<?php print $results ?>
|
||||
<div class="total">
|
||||
<?php print t('Total votes: @votes', array('@votes' => $votes)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="links"><?php print $links; ?></div>
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file poll-results-block.tpl.php
|
||||
* Display the poll results in a block.
|
||||
*
|
||||
* Variables available:
|
||||
* - $title: The title of the poll.
|
||||
* - $results: The results of the poll.
|
||||
* - $votes: The total results in the poll.
|
||||
* - $links: Links in the poll.
|
||||
* - $nid: The nid of the poll
|
||||
* - $cancel_form: A form to cancel the user's vote, if allowed.
|
||||
* - $raw_links: The raw array of links.
|
||||
* - $vote: The choice number of the current user's vote.
|
||||
*
|
||||
* @see template_preprocess_poll_results()
|
||||
*/
|
||||
?>
|
||||
<div class="poll">
|
||||
<?php print $results; ?>
|
||||
<div class="total">
|
||||
<?php print t('Total votes: @votes', array('@votes' => $votes)); ?>
|
||||
</div>
|
||||
<?php if (!empty($cancel_form)): ?>
|
||||
<?php print $cancel_form; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
.poll .bar .foreground {
|
||||
float: right;
|
||||
}
|
||||
.poll .percent {
|
||||
text-align: left;
|
||||
}
|
||||
.poll .vote-form .choices {
|
||||
text-align: right;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file poll-vote.tpl.php
|
||||
* Voting form for a poll.
|
||||
*
|
||||
* - $choice: The radio buttons for the choices in the poll.
|
||||
* - $title: The title of the poll.
|
||||
* - $block: True if this is being displayed as a block.
|
||||
* - $vote: The vote button
|
||||
* - $rest: Anything else in the form that may have been added via
|
||||
* form_alter hooks.
|
||||
*
|
||||
* @see template_preprocess_poll_vote()
|
||||
*/
|
||||
?>
|
||||
<div class="poll">
|
||||
<div class="vote-form">
|
||||
<div class="choices">
|
||||
<?php if ($block): ?>
|
||||
<div class="title"><?php print $title; ?>:</div>
|
||||
<?php endif; ?>
|
||||
<?php print $choice; ?>
|
||||
</div>
|
||||
<?php print $vote; ?>
|
||||
</div>
|
||||
<?php // This is the 'rest' of the form, in case items have been added. ?>
|
||||
<?php print $rest ?>
|
||||
</div>
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
.poll .bar {
|
||||
height: 1em;
|
||||
margin: 1px 0;
|
||||
background-color: #ddd;
|
||||
}
|
||||
.poll .bar .foreground {
|
||||
background-color: #000;
|
||||
height: 1em;
|
||||
float: left; /* LTR */
|
||||
}
|
||||
.poll .links {
|
||||
text-align: center;
|
||||
}
|
||||
.poll .percent {
|
||||
text-align: right; /* LTR */
|
||||
}
|
||||
.poll .total {
|
||||
text-align: center;
|
||||
}
|
||||
.poll .vote-form {
|
||||
text-align: center;
|
||||
}
|
||||
.poll .vote-form .choices {
|
||||
text-align: left; /* LTR */
|
||||
margin: 0 auto;
|
||||
display: table;
|
||||
}
|
||||
.poll .vote-form .choices .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.node-form #edit-poll-more {
|
||||
margin: 0;
|
||||
}
|
||||
td.poll-chtext {
|
||||
width: 80%;
|
||||
}
|
||||
td.poll-chvotes .form-text {
|
||||
width: 85%;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Poll
|
||||
description = Allows your site to capture votes on different topics in the form of multiple choice questions.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function poll_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('poll');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function poll_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('poll');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function poll_schema() {
|
||||
$schema['poll'] = array(
|
||||
'description' => 'Stores poll-specific information for poll nodes.',
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => "The poll's {node}.nid."
|
||||
),
|
||||
'runtime' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The number of seconds past {node}.created during which the poll is open.'
|
||||
),
|
||||
'active' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Boolean indicating whether or not the poll is open.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('nid'),
|
||||
);
|
||||
|
||||
$schema['poll_choices'] = array(
|
||||
'description' => 'Stores information about all choices for all {poll}s.',
|
||||
'fields' => array(
|
||||
'chid' => array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Unique identifier for a poll choice.',
|
||||
),
|
||||
'nid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {node}.nid this choice belongs to.',
|
||||
),
|
||||
'chtext' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The text for this choice.',
|
||||
),
|
||||
'chvotes' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The total number of votes this choice has received by all users.',
|
||||
),
|
||||
'chorder' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The sort order of this choice among all choices for the same node.',
|
||||
)
|
||||
),
|
||||
'indexes' => array(
|
||||
'nid' => array('nid')
|
||||
),
|
||||
'primary key' => array('chid'),
|
||||
);
|
||||
|
||||
$schema['poll_votes'] = array(
|
||||
'description' => 'Stores per-{users} votes for each {poll}.',
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The {poll} node this vote is for.',
|
||||
),
|
||||
'uid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {users}.uid this vote is from unless the voter was anonymous.',
|
||||
),
|
||||
'chorder' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => -1,
|
||||
'description' => "The {users}'s vote for this poll.",
|
||||
),
|
||||
'hostname' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The IP address this vote is from unless the voter was logged in.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('nid', 'uid', 'hostname'),
|
||||
'indexes' => array(
|
||||
'hostname' => array('hostname'),
|
||||
'uid' => array('uid'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
|
@ -1,781 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables your site to capture votes on different topics in the form of multiple
|
||||
* choice questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function poll_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#poll':
|
||||
$output = '<p>'. t('The poll module can be used to create simple polls for site users. A poll is a simple, multiple choice questionnaire which displays the cumulative results of the answers to the poll. Having polls on the site is a good way to receive feedback from community members.') .'</p>';
|
||||
$output .= '<p>'. t('When creating a poll, enter the question being posed, as well as the potential choices (and beginning vote counts for each choice). The status and duration (length of time the poll remains active for new votes) can also be specified. Use the <a href="@poll">poll</a> menu item to view all current polls. To vote in or view the results of a specific poll, click on the poll itself.', array('@poll' => url('poll'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@poll">Poll module</a>.', array('@poll' => 'http://drupal.org/handbook/modules/poll/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_init().
|
||||
*/
|
||||
function poll_init() {
|
||||
drupal_add_css(drupal_get_path('module', 'poll') .'/poll.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme()
|
||||
*/
|
||||
function poll_theme() {
|
||||
return array(
|
||||
'poll_vote' => array(
|
||||
'template' => 'poll-vote',
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
'poll_choices' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
'poll_results' => array(
|
||||
'template' => 'poll-results',
|
||||
'arguments' => array('raw_title' => NULL, 'results' => NULL, 'votes' => NULL, 'raw_links' => NULL, 'block' => NULL, 'nid' => NULL, 'vote' => NULL),
|
||||
),
|
||||
'poll_bar' => array(
|
||||
'template' => 'poll-bar',
|
||||
'arguments' => array('title' => NULL, 'votes' => NULL, 'total_votes' => NULL, 'vote' => NULL, 'block' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function poll_perm() {
|
||||
return array('create poll content', 'delete own poll content', 'delete any poll content', 'edit any poll content', 'edit own poll content', 'vote on polls', 'cancel own vote', 'inspect all votes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_access().
|
||||
*/
|
||||
function poll_access($op, $node, $account) {
|
||||
switch ($op) {
|
||||
case 'create':
|
||||
return user_access('create poll content', $account) ? TRUE : NULL;
|
||||
case 'update':
|
||||
return user_access('edit any poll content', $account) || (user_access('edit own poll content', $account) && ($node->uid == $account->uid)) ? TRUE : NULL;
|
||||
case 'delete':
|
||||
return user_access('delete any poll content', $account) || (user_access('delete own poll content', $account) && ($node->uid == $account->uid)) ? TRUE : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function poll_menu() {
|
||||
$items['poll'] = array(
|
||||
'title' => 'Polls',
|
||||
'page callback' => 'poll_page',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_SUGGESTED_ITEM,
|
||||
'file' => 'poll.pages.inc',
|
||||
);
|
||||
|
||||
$items['node/%node/votes'] = array(
|
||||
'title' => 'Votes',
|
||||
'page callback' => 'poll_votes',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => '_poll_menu_access',
|
||||
'access arguments' => array(1, 'inspect all votes', FALSE),
|
||||
'weight' => 3,
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'poll.pages.inc',
|
||||
);
|
||||
|
||||
$items['node/%node/results'] = array(
|
||||
'title' => 'Results',
|
||||
'page callback' => 'poll_results',
|
||||
'page arguments' => array(1),
|
||||
'access callback' => '_poll_menu_access',
|
||||
'access arguments' => array(1, 'access content', TRUE),
|
||||
'weight' => 3,
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'poll.pages.inc',
|
||||
);
|
||||
|
||||
$items['poll/js'] = array(
|
||||
'title' => 'Javascript Choice Form',
|
||||
'page callback' => 'poll_choice_js',
|
||||
'access arguments' => array('access content'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function to see if a node is acceptable for poll menu items.
|
||||
*/
|
||||
function _poll_menu_access($node, $perm, $inspect_allowvotes) {
|
||||
return user_access($perm) && ($node->type == 'poll') && ($node->allowvotes || !$inspect_allowvotes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_block().
|
||||
*
|
||||
* Generates a block containing the latest poll.
|
||||
*/
|
||||
function poll_block($op = 'list', $delta = 0) {
|
||||
if ($op == 'list') {
|
||||
$blocks[0]['info'] = t('Most recent poll');
|
||||
return $blocks;
|
||||
}
|
||||
else if ($op == 'view' && user_access('access content')) {
|
||||
// Retrieve the latest poll.
|
||||
$sql = db_rewrite_sql("SELECT MAX(n.created) FROM {node} n INNER JOIN {poll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1");
|
||||
$timestamp = db_result(db_query($sql));
|
||||
if ($timestamp) {
|
||||
$poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'status' => 1));
|
||||
|
||||
if ($poll->nid) {
|
||||
$poll = poll_view($poll, TRUE, FALSE, TRUE);
|
||||
}
|
||||
}
|
||||
$block['subject'] = t('Poll');
|
||||
$block['content'] = drupal_render($poll->content);
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*
|
||||
* Closes polls that have exceeded their allowed runtime.
|
||||
*/
|
||||
function poll_cron() {
|
||||
$result = db_query('SELECT p.nid FROM {poll} p INNER JOIN {node} n ON p.nid = n.nid WHERE (n.created + p.runtime) < '. time() .' AND p.active = 1 AND p.runtime != 0');
|
||||
while ($poll = db_fetch_object($result)) {
|
||||
db_query("UPDATE {poll} SET active = 0 WHERE nid = %d", $poll->nid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_node_info().
|
||||
*/
|
||||
function poll_node_info() {
|
||||
return array(
|
||||
'poll' => array(
|
||||
'name' => t('Poll'),
|
||||
'module' => 'poll',
|
||||
'description' => t('A <em>poll</em> is a question with a set of possible responses. A <em>poll</em>, once created, automatically provides a simple running count of the number of votes received for each response.'),
|
||||
'title_label' => t('Question'),
|
||||
'has_body' => FALSE,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_form().
|
||||
*/
|
||||
function poll_form(&$node, $form_state) {
|
||||
global $user;
|
||||
|
||||
$admin = user_access('administer nodes') || user_access('edit any poll content') || (user_access('edit own poll content') && $user->uid == $node->uid);
|
||||
|
||||
$type = node_get_types('type', $node);
|
||||
|
||||
$form = array(
|
||||
'#cache' => TRUE,
|
||||
);
|
||||
|
||||
$form['title'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => check_plain($type->title_label),
|
||||
'#required' => TRUE,
|
||||
'#default_value' => $node->title,
|
||||
'#weight' => -5,
|
||||
);
|
||||
|
||||
if (isset($form_state['choice_count'])) {
|
||||
$choice_count = $form_state['choice_count'];
|
||||
}
|
||||
else {
|
||||
$choice_count = max(2, empty($node->choice) ? 2 : count($node->choice));
|
||||
}
|
||||
|
||||
// Add a wrapper for the choices and more button.
|
||||
$form['choice_wrapper'] = array(
|
||||
'#tree' => FALSE,
|
||||
'#weight' => -4,
|
||||
'#prefix' => '<div class="clear-block" id="poll-choice-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
|
||||
// Container for just the poll choices.
|
||||
$form['choice_wrapper']['choice'] = array(
|
||||
'#prefix' => '<div id="poll-choices">',
|
||||
'#suffix' => '</div>',
|
||||
'#theme' => 'poll_choices',
|
||||
);
|
||||
|
||||
// Add the current choices to the form.
|
||||
for ($delta = 0; $delta < $choice_count; $delta++) {
|
||||
$text = isset($node->choice[$delta]['chtext']) ? $node->choice[$delta]['chtext'] : '';
|
||||
$votes = isset($node->choice[$delta]['chvotes']) ? $node->choice[$delta]['chvotes'] : 0;
|
||||
|
||||
$form['choice_wrapper']['choice'][$delta] = _poll_choice_form($delta, $text, $votes);
|
||||
}
|
||||
|
||||
// We name our button 'poll_more' to avoid conflicts with other modules using
|
||||
// AHAH-enabled buttons with the id 'more'.
|
||||
$form['choice_wrapper']['poll_more'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('More choices'),
|
||||
'#description' => t("If the amount of boxes above isn't enough, click here to add more choices."),
|
||||
'#weight' => 1,
|
||||
'#submit' => array('poll_more_choices_submit'), // If no javascript action.
|
||||
'#ahah' => array(
|
||||
'path' => 'poll/js',
|
||||
'wrapper' => 'poll-choices',
|
||||
'method' => 'replace',
|
||||
'effect' => 'fade',
|
||||
),
|
||||
);
|
||||
|
||||
// Poll attributes
|
||||
$_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
|
||||
$_active = array(0 => t('Closed'), 1 => t('Active'));
|
||||
|
||||
if ($admin) {
|
||||
$form['settings'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#collapsible' => TRUE,
|
||||
'#title' => t('Poll settings'),
|
||||
'#weight' => -3,
|
||||
);
|
||||
|
||||
$form['settings']['active'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Poll status'),
|
||||
'#default_value' => isset($node->active) ? $node->active : 1,
|
||||
'#options' => $_active,
|
||||
'#description' => t('When a poll is closed, visitors can no longer vote for it.')
|
||||
);
|
||||
}
|
||||
$form['settings']['runtime'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Poll duration'),
|
||||
'#default_value' => isset($node->runtime) ? $node->runtime : 0,
|
||||
'#options' => $_duration,
|
||||
'#description' => t('After this period, the poll will be closed automatically.'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler to add more choices to a poll form. This handler is used when
|
||||
* javascript is not available. It makes changes to the form state and the
|
||||
* entire form is rebuilt during the page reload.
|
||||
*/
|
||||
function poll_more_choices_submit($form, &$form_state) {
|
||||
// Set the form to rebuild and run submit handlers.
|
||||
node_form_submit_build_node($form, $form_state);
|
||||
|
||||
// Make the changes we want to the form state.
|
||||
if ($form_state['values']['poll_more']) {
|
||||
$n = $_GET['q'] == 'poll/js' ? 1 : 5;
|
||||
$form_state['choice_count'] = count($form_state['values']['choice']) + $n;
|
||||
}
|
||||
}
|
||||
|
||||
function _poll_choice_form($delta, $value = '', $votes = 0) {
|
||||
$form = array(
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
|
||||
// We'll manually set the #parents property of these fields so that
|
||||
// their values appear in the $form_state['values']['choice'] array.
|
||||
$form['chtext'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Choice @n', array('@n' => ($delta + 1))),
|
||||
'#default_value' => $value,
|
||||
'#parents' => array('choice', $delta, 'chtext'),
|
||||
);
|
||||
$form['chvotes'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Votes for choice @n', array('@n' => ($delta + 1))),
|
||||
'#default_value' => $votes,
|
||||
'#size' => 5,
|
||||
'#maxlength' => 7,
|
||||
'#parents' => array('choice', $delta, 'chvotes'),
|
||||
'#access' => user_access('administer nodes'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback for AHAH additions.
|
||||
*/
|
||||
function poll_choice_js() {
|
||||
include_once 'modules/node/node.pages.inc';
|
||||
$form_state = array('storage' => NULL, 'submitted' => FALSE);
|
||||
$form_build_id = $_POST['form_build_id'];
|
||||
// Get the form from the cache.
|
||||
$form = form_get_cache($form_build_id, $form_state);
|
||||
$args = $form['#parameters'];
|
||||
$form_id = array_shift($args);
|
||||
// We will run some of the submit handlers so we need to disable redirecting.
|
||||
$form['#redirect'] = FALSE;
|
||||
// We need to process the form, prepare for that by setting a few internals
|
||||
// variables.
|
||||
$form['#post'] = $_POST;
|
||||
$form['#programmed'] = FALSE;
|
||||
$form_state['post'] = $_POST;
|
||||
// Build, validate and if possible, submit the form.
|
||||
drupal_process_form($form_id, $form, $form_state);
|
||||
// This call recreates the form relying solely on the form_state that the
|
||||
// drupal_process_form set up.
|
||||
$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
|
||||
// Render the new output.
|
||||
$choice_form = $form['choice_wrapper']['choice'];
|
||||
unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
|
||||
$output = theme('status_messages') . drupal_render($choice_form);
|
||||
|
||||
drupal_json(array('status' => TRUE, 'data' => $output));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renumbers fields and creates a teaser when a poll node is submitted.
|
||||
*/
|
||||
function poll_node_form_submit(&$form, &$form_state) {
|
||||
// Renumber fields
|
||||
$form_state['values']['choice'] = array_values($form_state['values']['choice']);
|
||||
$form_state['values']['teaser'] = poll_teaser((object)$form_state['values']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_validate().
|
||||
*/
|
||||
function poll_validate($node) {
|
||||
if (isset($node->title)) {
|
||||
// Check for at least two options and validate amount of votes:
|
||||
$realchoices = 0;
|
||||
// Renumber fields
|
||||
$node->choice = array_values($node->choice);
|
||||
foreach ($node->choice as $i => $choice) {
|
||||
if ($choice['chtext'] != '') {
|
||||
$realchoices++;
|
||||
}
|
||||
if (isset($choice['chvotes']) && $choice['chvotes'] < 0) {
|
||||
form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($realchoices < 2) {
|
||||
form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_load().
|
||||
*/
|
||||
function poll_load($node) {
|
||||
global $user;
|
||||
|
||||
$poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
|
||||
|
||||
// Load the appropriate choices into the $poll object.
|
||||
$result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
|
||||
while ($choice = db_fetch_array($result)) {
|
||||
$poll->choice[$choice['chorder']] = $choice;
|
||||
}
|
||||
|
||||
// Determine whether or not this user is allowed to vote.
|
||||
$poll->allowvotes = FALSE;
|
||||
if (user_access('vote on polls') && $poll->active) {
|
||||
if ($user->uid) {
|
||||
$result = db_fetch_object(db_query('SELECT chorder FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
|
||||
}
|
||||
else {
|
||||
$result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
|
||||
}
|
||||
if (isset($result->chorder)) {
|
||||
$poll->vote = $result->chorder;
|
||||
}
|
||||
else {
|
||||
$poll->vote = -1;
|
||||
$poll->allowvotes = TRUE;
|
||||
}
|
||||
}
|
||||
return $poll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_insert().
|
||||
*/
|
||||
function poll_insert($node) {
|
||||
if (!user_access('administer nodes')) {
|
||||
// Make sure all votes are 0 initially
|
||||
foreach ($node->choice as $i => $choice) {
|
||||
$node->choice[$i]['chvotes'] = 0;
|
||||
}
|
||||
$node->active = 1;
|
||||
}
|
||||
|
||||
db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
|
||||
|
||||
$i = 0;
|
||||
foreach ($node->choice as $choice) {
|
||||
if ($choice['chtext'] != '') {
|
||||
db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_update().
|
||||
*/
|
||||
function poll_update($node) {
|
||||
// Update poll settings.
|
||||
db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
|
||||
|
||||
// Clean poll choices.
|
||||
db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
|
||||
|
||||
// Poll choices come in the same order with the same numbers as they are in
|
||||
// the database, but some might have an empty title, which signifies that
|
||||
// they should be removed. We remove all votes to the removed options, so
|
||||
// people who voted on them can vote again.
|
||||
$new_chorder = 0;
|
||||
foreach ($node->choice as $old_chorder => $choice) {
|
||||
$chvotes = isset($choice['chvotes']) ? (int)$choice['chvotes'] : 0;
|
||||
$chtext = $choice['chtext'];
|
||||
|
||||
if (!empty($chtext)) {
|
||||
db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $new_chorder);
|
||||
if ($new_chorder != $old_chorder) {
|
||||
// We can only remove items in the middle, not add, so
|
||||
// new_chorder is always <= old_chorder, making this safe.
|
||||
db_query("UPDATE {poll_votes} SET chorder = %d WHERE nid = %d AND chorder = %d", $new_chorder, $node->nid, $old_chorder);
|
||||
}
|
||||
$new_chorder++;
|
||||
}
|
||||
else {
|
||||
db_query("DELETE FROM {poll_votes} WHERE nid = %d AND chorder = %d", $node->nid, $old_chorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_delete().
|
||||
*/
|
||||
function poll_delete($node) {
|
||||
db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
|
||||
db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
|
||||
db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_view().
|
||||
*
|
||||
* @param $block
|
||||
* An extra parameter that adapts the hook to display a block-ready
|
||||
* rendering of the poll.
|
||||
*/
|
||||
function poll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
|
||||
global $user;
|
||||
$output = '';
|
||||
|
||||
// Special display for side-block
|
||||
if ($block) {
|
||||
// No 'read more' link
|
||||
$node->readmore = FALSE;
|
||||
|
||||
$links = module_invoke_all('link', 'node', $node, 1);
|
||||
$links[] = array('title' => t('Older polls'), 'href' => 'poll', 'attributes' => array('title' => t('View the list of polls on this site.')));
|
||||
if ($node->allowvotes && $block) {
|
||||
$links[] = array('title' => t('Results'), 'href' => 'node/'. $node->nid .'/results', 'attributes' => array('title' => t('View the current poll results.')));
|
||||
}
|
||||
|
||||
$node->links = $links;
|
||||
}
|
||||
|
||||
if (!empty($node->allowvotes) && ($block || empty($node->show_results))) {
|
||||
$node->content['body'] = array(
|
||||
'#value' => drupal_get_form('poll_view_voting', $node, $block),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$node->content['body'] = array(
|
||||
'#value' => poll_view_results($node, $teaser, $page, $block),
|
||||
);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple teaser that lists all the choices.
|
||||
*
|
||||
* This is primarily used for RSS.
|
||||
*/
|
||||
function poll_teaser($node) {
|
||||
$teaser = NULL;
|
||||
if (is_array($node->choice)) {
|
||||
foreach ($node->choice as $k => $choice) {
|
||||
if ($choice['chtext'] != '') {
|
||||
$teaser .= '* '. check_plain($choice['chtext']) ."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $teaser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the voting form for a poll.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see poll_vote()
|
||||
* @see phptemplate_preprocess_poll_vote()
|
||||
*/
|
||||
function poll_view_voting(&$form_state, $node, $block) {
|
||||
if ($node->choice) {
|
||||
$list = array();
|
||||
foreach ($node->choice as $i => $choice) {
|
||||
$list[$i] = check_plain($choice['chtext']);
|
||||
}
|
||||
$form['choice'] = array(
|
||||
'#type' => 'radios',
|
||||
'#default_value' => -1,
|
||||
'#options' => $list,
|
||||
);
|
||||
}
|
||||
|
||||
$form['vote'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Vote'),
|
||||
'#submit' => array('poll_vote'),
|
||||
);
|
||||
|
||||
// Store the node so we can get to it in submit functions.
|
||||
$form['#node'] = $node;
|
||||
$form['#block'] = $block;
|
||||
|
||||
// Set form caching because we could have multiple of these forms on
|
||||
// the same page, and we want to ensure the right one gets picked.
|
||||
$form['#cache'] = TRUE;
|
||||
|
||||
// Provide a more cleanly named voting form theme.
|
||||
$form['#theme'] = 'poll_vote';
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation function for processing votes
|
||||
*/
|
||||
function poll_view_voting_validate($form, &$form_state) {
|
||||
if ($form_state['values']['choice'] == -1) {
|
||||
form_set_error( 'choice', t('Your vote could not be recorded because you did not select any of the choices.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for processing a vote
|
||||
*/
|
||||
function poll_vote($form, &$form_state) {
|
||||
$node = $form['#node'];
|
||||
$choice = $form_state['values']['choice'];
|
||||
|
||||
global $user;
|
||||
if ($user->uid) {
|
||||
db_query('INSERT INTO {poll_votes} (nid, chorder, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
|
||||
}
|
||||
else {
|
||||
db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, ip_address());
|
||||
}
|
||||
|
||||
// Add one to the votes.
|
||||
db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
|
||||
|
||||
cache_clear_all();
|
||||
drupal_set_message(t('Your vote was recorded.'));
|
||||
|
||||
// Return the user to whatever page they voted from.
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes the voting form for a poll.
|
||||
*
|
||||
* Inputs: $form
|
||||
*/
|
||||
function template_preprocess_poll_vote(&$variables) {
|
||||
$form = $variables['form'];
|
||||
$variables['choice'] = drupal_render($form['choice']);
|
||||
$variables['title'] = check_plain($form['#node']->title);
|
||||
$variables['vote'] = drupal_render($form['vote']);
|
||||
$variables['rest'] = drupal_render($form);
|
||||
$variables['block'] = $form['#block'];
|
||||
// If this is a block, allow a different tpl.php to be used.
|
||||
if ($variables['block']) {
|
||||
$variables['template_files'][] = 'poll-vote-block';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a graphical representation of the results of a poll.
|
||||
*/
|
||||
function poll_view_results(&$node, $teaser, $page, $block) {
|
||||
// Count the votes and find the maximum
|
||||
$total_votes = 0;
|
||||
$max_votes = 0;
|
||||
foreach ($node->choice as $choice) {
|
||||
if (isset($choice['chvotes'])) {
|
||||
$total_votes += $choice['chvotes'];
|
||||
$max_votes = max($max_votes, $choice['chvotes']);
|
||||
}
|
||||
}
|
||||
|
||||
$poll_results = '';
|
||||
foreach ($node->choice as $i => $choice) {
|
||||
if (!empty($choice['chtext'])) {
|
||||
$chvotes = isset($choice['chvotes']) ? $choice['chvotes'] : NULL;
|
||||
$poll_results .= theme('poll_bar', $choice['chtext'], $chvotes, $total_votes, isset($node->vote) && $node->vote == $i, $block);
|
||||
}
|
||||
}
|
||||
|
||||
return theme('poll_results', $node->title, $poll_results, $total_votes, isset($node->links) ? $node->links : array(), $block, $node->nid, isset($node->vote) ? $node->vote : NULL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Theme the admin poll form for choices.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_poll_choices($form) {
|
||||
// Change the button title to reflect the behavior when using JavaScript.
|
||||
drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-poll-more").val("'. t('Add another choice') .'"); }); }', 'inline');
|
||||
|
||||
$rows = array();
|
||||
$headers = array(
|
||||
t('Choice'),
|
||||
t('Vote count'),
|
||||
);
|
||||
|
||||
foreach (element_children($form) as $key) {
|
||||
// No need to print the field title every time.
|
||||
unset($form[$key]['chtext']['#title'], $form[$key]['chvotes']['#title']);
|
||||
|
||||
// Build the table row.
|
||||
$row = array(
|
||||
'data' => array(
|
||||
array('data' => drupal_render($form[$key]['chtext']), 'class' => 'poll-chtext'),
|
||||
array('data' => drupal_render($form[$key]['chvotes']), 'class' => 'poll-chvotes'),
|
||||
),
|
||||
);
|
||||
|
||||
// Add additional attributes to the row, such as a class for this row.
|
||||
if (isset($form[$key]['#attributes'])) {
|
||||
$row = array_merge($row, $form[$key]['#attributes']);
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$output = theme('table', $headers, $rows);
|
||||
$output .= drupal_render($form);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess the poll_results theme hook.
|
||||
*
|
||||
* Inputs: $raw_title, $results, $votes, $raw_links, $block, $nid, $vote. The
|
||||
* $raw_* inputs to this are naturally unsafe; often safe versions are
|
||||
* made to simply overwrite the raw version, but in this case it seems likely
|
||||
* that the title and the links may be overridden by the theme layer, so they
|
||||
* are left in with a different name for that purpose.
|
||||
*
|
||||
* @see poll-results.tpl.php
|
||||
* @see poll-results-block.tpl.php
|
||||
* @see theme_poll_results()
|
||||
*/
|
||||
function template_preprocess_poll_results(&$variables) {
|
||||
$variables['links'] = theme('links', $variables['raw_links']);
|
||||
if (isset($variables['vote']) && $variables['vote'] > -1 && user_access('cancel own vote')) {
|
||||
$variables['cancel_form'] = drupal_get_form('poll_cancel_form', $variables['nid']);
|
||||
}
|
||||
$variables['title'] = check_plain($variables['raw_title']);
|
||||
|
||||
// If this is a block, allow a different tpl.php to be used.
|
||||
if ($variables['block']) {
|
||||
$variables['template_files'][] = 'poll-results-block';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess the poll_bar theme hook.
|
||||
*
|
||||
* Inputs: $title, $votes, $total_votes, $voted, $block
|
||||
*
|
||||
* @see poll-bar.tpl.php
|
||||
* @see poll-bar-block.tpl.php
|
||||
* @see theme_poll_bar()
|
||||
*/
|
||||
function template_preprocess_poll_bar(&$variables) {
|
||||
if ($variables['block']) {
|
||||
$variables['template_files'][] = 'poll-bar-block';
|
||||
}
|
||||
$variables['title'] = check_plain($variables['title']);
|
||||
$variables['percentage'] = round($variables['votes'] * 100 / max($variables['total_votes'], 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cancel form for a poll.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see poll_cancel()
|
||||
*/
|
||||
function poll_cancel_form(&$form_state, $nid) {
|
||||
// Store the nid so we can get to it in submit functions.
|
||||
$form['#nid'] = $nid;
|
||||
|
||||
$form['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Cancel your vote'),
|
||||
'#submit' => array('poll_cancel')
|
||||
);
|
||||
|
||||
$form['#cache'] = TRUE;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for poll_cancel_form
|
||||
*/
|
||||
function poll_cancel($form, &$form_state) {
|
||||
$node = node_load($form['#nid']);
|
||||
global $user;
|
||||
|
||||
if ($user->uid) {
|
||||
db_query('DELETE FROM {poll_votes} WHERE nid = %d and uid = %d', $node->nid, $user->uid);
|
||||
}
|
||||
else {
|
||||
db_query("DELETE FROM {poll_votes} WHERE nid = %d and hostname = '%s'", $node->nid, ip_address());
|
||||
}
|
||||
|
||||
// Subtract from the votes.
|
||||
db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE nid = %d AND chorder = %d", $node->nid, $node->vote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function poll_user($op, &$edit, &$user) {
|
||||
if ($op == 'delete') {
|
||||
db_query('UPDATE {poll_votes} SET uid = 0 WHERE uid = %d', $user->uid);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the poll module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback to provide a simple list of all polls available.
|
||||
*/
|
||||
function poll_page() {
|
||||
// List all polls.
|
||||
$sql = db_rewrite_sql("SELECT n.nid, n.title, p.active, n.created, SUM(c.chvotes) AS votes FROM {node} n INNER JOIN {poll} p ON n.nid = p.nid INNER JOIN {poll_choices} c ON n.nid = c.nid WHERE n.status = 1 GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC");
|
||||
// Count all polls for the pager.
|
||||
$count_sql = db_rewrite_sql('SELECT COUNT(*) FROM {node} n INNER JOIN {poll} p ON n.nid = p.nid WHERE n.status = 1');
|
||||
$result = pager_query($sql, 15, 0, $count_sql);
|
||||
$output = '<ul>';
|
||||
while ($node = db_fetch_object($result)) {
|
||||
$output .= '<li>'. l($node->title, "node/$node->nid") .' - '. format_plural($node->votes, '1 vote', '@count votes') .' - '. ($node->active ? t('open') : t('closed')) .'</li>';
|
||||
}
|
||||
$output .= '</ul>';
|
||||
$output .= theme("pager", NULL, 15);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the 'votes' tab for polls you can see other votes on
|
||||
*/
|
||||
function poll_votes($node) {
|
||||
drupal_set_title(check_plain($node->title));
|
||||
$output = t('This table lists all the recorded votes for this poll. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.');
|
||||
|
||||
$header[] = array('data' => t('Visitor'), 'field' => 'u.name');
|
||||
$header[] = array('data' => t('Vote'), 'field' => 'pv.chorder');
|
||||
|
||||
$result = pager_query("SELECT pv.chorder, pv.uid, pv.hostname, u.name FROM {poll_votes} pv LEFT JOIN {users} u ON pv.uid = u.uid WHERE pv.nid = %d". tablesort_sql($header), 20, 0, NULL, $node->nid);
|
||||
$rows = array();
|
||||
while ($vote = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
$vote->name ? theme('username', $vote) : check_plain($vote->hostname),
|
||||
check_plain($node->choice[$vote->chorder]['chtext']));
|
||||
}
|
||||
$output .= theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 20, 0);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the 'results' tab for polls you can vote on
|
||||
*/
|
||||
function poll_results($node) {
|
||||
drupal_set_title(check_plain($node->title));
|
||||
$node->show_results = TRUE;
|
||||
return node_show($node, 0);
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Admin page callbacks for the statistics module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback; presents the "recent hits" page.
|
||||
*/
|
||||
function statistics_recent_hits() {
|
||||
$header = array(
|
||||
array('data' => t('Timestamp'), 'field' => 'a.timestamp', 'sort' => 'desc'),
|
||||
array('data' => t('Page'), 'field' => 'a.path'),
|
||||
array('data' => t('User'), 'field' => 'u.name'),
|
||||
array('data' => t('Operations'))
|
||||
);
|
||||
|
||||
$sql = 'SELECT a.aid, a.path, a.title, a.uid, u.name, a.timestamp FROM {accesslog} a LEFT JOIN {users} u ON u.uid = a.uid'. tablesort_sql($header);
|
||||
|
||||
$result = pager_query($sql, 30);
|
||||
$rows = array();
|
||||
while ($log = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
|
||||
_statistics_format_item($log->title, $log->path),
|
||||
theme('username', $log),
|
||||
l(t('details'), "admin/reports/access/$log->aid"));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
|
||||
}
|
||||
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; presents the "top pages" page.
|
||||
*/
|
||||
function statistics_top_pages() {
|
||||
// MAX(title) avoids having empty node titles which otherwise causes duplicates in the top pages list
|
||||
$sql = "SELECT COUNT(path) AS hits, path, MAX(title) AS title, AVG(timer) AS average_time, SUM(timer) AS total_time FROM {accesslog} GROUP BY path";
|
||||
$sql_cnt = "SELECT COUNT(DISTINCT(path)) FROM {accesslog}";
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
|
||||
array('data' => t('Page'), 'field' => 'path'),
|
||||
array('data' => t('Average page generation time'), 'field' => 'average_time'),
|
||||
array('data' => t('Total page generation time'), 'field' => 'total_time')
|
||||
);
|
||||
$sql .= tablesort_sql($header);
|
||||
$result = pager_query($sql, 30, 0, $sql_cnt);
|
||||
|
||||
$rows = array();
|
||||
while ($page = db_fetch_object($result)) {
|
||||
$rows[] = array($page->hits, _statistics_format_item($page->title, $page->path), t('%time ms', array('%time' => round($page->average_time))), format_interval(round($page->total_time / 1000)));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
|
||||
}
|
||||
|
||||
drupal_set_title(t('Top pages in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; presents the "top visitors" page.
|
||||
*/
|
||||
function statistics_top_visitors() {
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
|
||||
array('data' => t('Visitor'), 'field' => 'u.name'),
|
||||
array('data' => t('Total page generation time'), 'field' => 'total'),
|
||||
array('data' => t('Operations'))
|
||||
);
|
||||
|
||||
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname, a.uid, u.name, ac.aid". tablesort_sql($header);
|
||||
$sql_cnt = "SELECT COUNT(*) FROM (SELECT DISTINCT uid, hostname FROM {accesslog}) AS unique_visits";
|
||||
$result = pager_query($sql, 30, 0, $sql_cnt);
|
||||
|
||||
$rows = array();
|
||||
while ($account = db_fetch_object($result)) {
|
||||
$qs = drupal_get_destination();
|
||||
$ban_link = $account->aid ? l(t('unban'), "admin/user/rules/delete/$account->aid", array('query' => $qs)) : l(t('ban'), "admin/user/rules/add/$account->hostname/host", array('query' => $qs));
|
||||
$rows[] = array($account->hits, ($account->uid ? theme('username', $account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link);
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
|
||||
}
|
||||
|
||||
drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; presents the "referrer" page.
|
||||
*/
|
||||
function statistics_top_referrers() {
|
||||
$query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE '%%%s%%' AND url <> '' GROUP BY url";
|
||||
$query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE '%%%s%%'";
|
||||
drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
|
||||
array('data' => t('Url'), 'field' => 'url'),
|
||||
array('data' => t('Last visit'), 'field' => 'last'),
|
||||
);
|
||||
|
||||
$query .= tablesort_sql($header);
|
||||
$result = pager_query($query, 30, 0, $query_cnt, $_SERVER['HTTP_HOST']);
|
||||
|
||||
$rows = array();
|
||||
while ($referrer = db_fetch_object($result)) {
|
||||
$rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval(time() - $referrer->last))));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3));
|
||||
}
|
||||
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback; Displays recent page accesses.
|
||||
*/
|
||||
function statistics_access_log($aid) {
|
||||
$result = db_query('SELECT a.*, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE aid = %d', $aid);
|
||||
if ($access = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
array('data' => t('URL'), 'header' => TRUE),
|
||||
l(url($access->path, array('absolute' => TRUE)), $access->path)
|
||||
);
|
||||
// It is safe to avoid filtering $access->title through check_plain because
|
||||
// it comes from drupal_get_title().
|
||||
$rows[] = array(
|
||||
array('data' => t('Title'), 'header' => TRUE),
|
||||
$access->title
|
||||
);
|
||||
$rows[] = array(
|
||||
array('data' => t('Referrer'), 'header' => TRUE),
|
||||
($access->url ? l($access->url, $access->url) : '')
|
||||
);
|
||||
$rows[] = array(
|
||||
array('data' => t('Date'), 'header' => TRUE),
|
||||
format_date($access->timestamp, 'large')
|
||||
);
|
||||
$rows[] = array(
|
||||
array('data' => t('User'), 'header' => TRUE),
|
||||
theme('username', $access)
|
||||
);
|
||||
$rows[] = array(
|
||||
array('data' => t('Hostname'), 'header' => TRUE),
|
||||
check_plain($access->hostname)
|
||||
);
|
||||
|
||||
return theme('table', array(), $rows);
|
||||
}
|
||||
else {
|
||||
drupal_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form builder; Configure access logging.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see system_settings_form()
|
||||
*/
|
||||
function statistics_access_logging_settings() {
|
||||
// Access log settings:
|
||||
$options = array('1' => t('Enabled'), '0' => t('Disabled'));
|
||||
$form['access'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Access log settings'));
|
||||
$form['access']['statistics_enable_access_log'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Enable access log'),
|
||||
'#default_value' => variable_get('statistics_enable_access_log', 0),
|
||||
'#options' => $options,
|
||||
'#description' => t('Log each page access. Required for referrer statistics.'));
|
||||
$period = array('0' => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
|
||||
$form['access']['statistics_flush_accesslog_timer'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Discard access logs older than'),
|
||||
'#default_value' => variable_get('statistics_flush_accesslog_timer', 259200),
|
||||
'#options' => $period,
|
||||
'#description' => t('Older access log entries (including referrer statistics) will be automatically discarded. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))));
|
||||
|
||||
// count content views settings
|
||||
$form['content'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Content viewing counter settings'));
|
||||
$form['content']['statistics_count_content_views'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Count content views'),
|
||||
'#default_value' => variable_get('statistics_count_content_views', 0),
|
||||
'#options' => $options,
|
||||
'#description' => t('Increment a counter each time content is viewed.'));
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Statistics
|
||||
description = Logs access statistics for your site.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function statistics_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('statistics');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes session ID field to VARCHAR(64) to add support for SHA-1 hashes.
|
||||
*/
|
||||
function statistics_update_1000() {
|
||||
$ret = array();
|
||||
|
||||
switch ($GLOBALS['db_type']) {
|
||||
case 'mysql':
|
||||
case 'mysqli':
|
||||
$ret[] = update_sql("ALTER TABLE {accesslog} CHANGE COLUMN sid sid varchar(64) NOT NULL default ''");
|
||||
break;
|
||||
case 'pgsql':
|
||||
db_change_column($ret, 'accesslog', 'sid', 'sid', 'varchar(64)', array('not null' => TRUE, 'default' => "''"));
|
||||
break;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function statistics_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('statistics');
|
||||
|
||||
variable_del('statistics_count_content_views');
|
||||
variable_del('statistics_enable_access_log');
|
||||
variable_del('statistics_flush_accesslog_timer');
|
||||
variable_del('statistics_day_timestamp');
|
||||
variable_del('statistics_block_top_day_num');
|
||||
variable_del('statistics_block_top_all_num');
|
||||
variable_del('statistics_block_top_last_num');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function statistics_schema() {
|
||||
$schema['accesslog'] = array(
|
||||
'description' => 'Stores site access information for statistics.',
|
||||
'fields' => array(
|
||||
'aid' => array(
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Primary Key: Unique accesslog ID.',
|
||||
),
|
||||
'sid' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 64,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Browser session ID of user that visited page.',
|
||||
),
|
||||
'title' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Title of page visited.',
|
||||
),
|
||||
'path' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Internal path to page visited (relative to Drupal root.)',
|
||||
),
|
||||
'url' => array(
|
||||
'type' => 'text',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Referrer URI.',
|
||||
),
|
||||
'hostname' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hostname of user that visited the page.',
|
||||
),
|
||||
'uid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => FALSE,
|
||||
'default' => 0,
|
||||
'description' => 'User {users}.uid that visited the page.',
|
||||
),
|
||||
'timer' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Time in milliseconds that the page took to load.',
|
||||
),
|
||||
'timestamp' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Timestamp of when the page was visited.',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'accesslog_timestamp' => array('timestamp'),
|
||||
'uid' => array('uid'),
|
||||
),
|
||||
'primary key' => array('aid'),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-6.x-extra
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow longer referrers.
|
||||
*/
|
||||
function statistics_update_6000() {
|
||||
$ret = array();
|
||||
db_change_field($ret, 'accesslog', 'url', 'url', array('type' => 'text', 'not null' => FALSE));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-6.x-extra".
|
||||
* The next series of updates should start at 7000.
|
||||
*/
|
|
@ -1,322 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Logs access statistics for your site.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function statistics_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#statistics':
|
||||
$output = '<p>'. t('The statistics module keeps track of numerous site usage statistics, including the number of times, and from where, each of your posts is viewed. These statistics are useful in determining how users are interacting with each other and with your site, and are required for the display of some Drupal blocks.') .'</p>';
|
||||
$output .= '<p>'. t('The statistics module provides:') .'</p>';
|
||||
$output .= '<ul><li>'. t('a counter for each post on your site that increments each time the post is viewed. (Enable <em>Count content views</em> on the <a href="@accesslog">access log settings page</a>, and determine if the post access counters should be visible to any user roles on the <a href="@permissions">permissions page</a>.)', array('@accesslog' => url('admin/reports/settings'), '@permissions' => url('admin/user/permissions'))) .'</li>';
|
||||
$output .= '<li>'. t('a <a href="@recent-hits">recent hits</a> log that displays information about the latest activity on your site, including the URL and title of the page accessed, the user name (if available) and IP address of the accessing party.', array('@recent-hits' => url('admin/reports/hits'))) .'</li>';
|
||||
$output .= '<li>'. t('a <a href="@top-referrers">top referrers</a> log that displays the referring parties for your site visits (where your visitors came from).', array('@top-referrers' => url('admin/reports/referrers'))) .'</li>';
|
||||
$output .= '<li>'. t('a <a href="@top-pages">top pages</a> log that displays site content in descending order by number of views.', array('@top-pages' => url('admin/reports/pages'))) .'</li>';
|
||||
$output .= '<li>'. t('a <a href="@top-visitors">top visitors</a> log that displays the most active users on your site.', array('@top-visitors' => url('admin/reports/visitors'))) .'</li>';
|
||||
$output .= '<li>'. t('a <em>Popular content</em> block that displays the day\'s most viewed content, the all-time most viewed content, and the last content viewed. (Enable the <em>Popular content</em> block on the <a href="@blocks">blocks administration page</a>.)', array('@blocks' => url('admin/build/block'))) .'</li></ul>';
|
||||
$output .= '<p>'. t('Configuring the statistics module') .'</p>';
|
||||
$output .= '<ul><li>'. t('When the <em>Enable access log</em> setting on the <a href="@accesslog">access log settings page</a> is enabled, data about every page accessed (including the remote host\'s IP address, referrer, node accessed, and user name) is stored in the access log. The access log must be enabled for the <a href="@recent-hits">recent hits</a>, <a href="@top-referrers">top referrers</a>, <a href="@top-pages">top pages</a>, and <a href="@top-visitors">top visitors</a> log pages to function. Enabling the access log adds one additional database call per page displayed by Drupal.', array('@accesslog' => url('admin/reports/settings'), '@recent-hits' => url('admin/reports/hits'), '@top-referrers' => url('admin/reports/referrers'), '@top-pages' => url('admin/reports/pages'), '@top-visitors' => url('admin/reports/visitors'))) .'</li>';
|
||||
$output .= '<li>'. t('The <em>Discard access logs older than</em> setting on the <a href="@accesslog">access log settings page</a> specifies the length of time entries are retained in the access log before they are deleted. Automatic access log entry deletion requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@accesslog' => url('admin/reports/settings'), '@cron' => url('admin/reports/status'))) .'</li>';
|
||||
$output .= '<li>'. t('The <em>Count content views</em> setting on the <a href="@accesslog">access log settings page</a> enables a counter for each post on your site that increments each time the post is viewed. This option must be enabled to provide post-specific access counts. Enabling this option adds one additional database call per each post displayed by Drupal.', array('@accesslog' => url('admin/reports/settings'))) .'</li></ul>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@statistics">Statistics module</a>.', array('@statistics' => 'http://drupal.org/handbook/modules/statistics/')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/reports/settings':
|
||||
return '<p>'. t('Settings for the statistical information that Drupal will keep about the site. See <a href="@statistics">site statistics</a> for the actual information.', array('@statistics' => url('admin/reports/hits'))) .'</p>';
|
||||
case 'admin/reports/hits':
|
||||
return '<p>'. t("This page displays the site's most recent hits.") .'</p>';
|
||||
case 'admin/reports/referrers':
|
||||
return '<p>'. t('This page displays all external referrers, or external references to your website.') .'</p>';
|
||||
case 'admin/reports/visitors':
|
||||
return '<p>'. t("When you ban a visitor, you prevent the visitor's IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. This is most commonly used to block resource-intensive bots or web crawlers.") .'</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_exit().
|
||||
*
|
||||
* This is where statistics are gathered on page accesses.
|
||||
*/
|
||||
function statistics_exit() {
|
||||
global $user, $recent_activity;
|
||||
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
|
||||
|
||||
if (variable_get('statistics_count_content_views', 0)) {
|
||||
// We are counting content views.
|
||||
if ((arg(0) == 'node') && is_numeric(arg(1)) && arg(2) == '') {
|
||||
// A node has been viewed, so update the node's counters.
|
||||
db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1));
|
||||
// If we affected 0 rows, this is the first time viewing the node.
|
||||
if (!db_affected_rows()) {
|
||||
// We must create a new row to store counters for the new node.
|
||||
db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', arg(1), time());
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) {
|
||||
// Log this page access.
|
||||
db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), $_GET['q'], referer_uri(), ip_address(), $user->uid, session_id(), timer_read('page'), time());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function statistics_perm() {
|
||||
return array('access statistics', 'view post access counter');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_link().
|
||||
*/
|
||||
function statistics_link($type, $node = NULL, $teaser = FALSE) {
|
||||
global $id;
|
||||
$links = array();
|
||||
|
||||
if ($type == 'node' && user_access('view post access counter')) {
|
||||
$statistics = statistics_get($node->nid);
|
||||
if ($statistics) {
|
||||
$links['statistics_counter']['title'] = format_plural($statistics['totalcount'], '1 read', '@count reads');
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function statistics_menu() {
|
||||
$items['admin/reports/hits'] = array(
|
||||
'title' => 'Recent hits',
|
||||
'description' => 'View pages that have recently been visited.',
|
||||
'page callback' => 'statistics_recent_hits',
|
||||
'access arguments' => array('access statistics'),
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['admin/reports/pages'] = array(
|
||||
'title' => 'Top pages',
|
||||
'description' => 'View pages that have been hit frequently.',
|
||||
'page callback' => 'statistics_top_pages',
|
||||
'access arguments' => array('access statistics'),
|
||||
'weight' => 1,
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['admin/reports/visitors'] = array(
|
||||
'title' => 'Top visitors',
|
||||
'description' => 'View visitors that hit many pages.',
|
||||
'page callback' => 'statistics_top_visitors',
|
||||
'access arguments' => array('access statistics'),
|
||||
'weight' => 2,
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['admin/reports/referrers'] = array(
|
||||
'title' => 'Top referrers',
|
||||
'description' => 'View top referrers.',
|
||||
'page callback' => 'statistics_top_referrers',
|
||||
'access arguments' => array('access statistics'),
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['admin/reports/access/%'] = array(
|
||||
'title' => 'Details',
|
||||
'description' => 'View access log.',
|
||||
'page callback' => 'statistics_access_log',
|
||||
'page arguments' => array(3),
|
||||
'access arguments' => array('access statistics'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['admin/reports/settings'] = array(
|
||||
'title' => 'Access log settings',
|
||||
'description' => 'Control details about what and how your site logs.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('statistics_access_logging_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_NORMAL_ITEM,
|
||||
'weight' => 3,
|
||||
'file' => 'statistics.admin.inc',
|
||||
);
|
||||
$items['user/%user/track/navigation'] = array(
|
||||
'title' => 'Track page visits',
|
||||
'page callback' => 'statistics_user_tracker',
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('access statistics'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 2,
|
||||
'file' => 'statistics.pages.inc',
|
||||
);
|
||||
$items['node/%node/track'] = array(
|
||||
'title' => 'Track',
|
||||
'page callback' => 'statistics_node_tracker',
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('access statistics'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 2,
|
||||
'file' => 'statistics.pages.inc',
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function statistics_user($op, &$edit, &$user) {
|
||||
if ($op == 'delete') {
|
||||
db_query('UPDATE {accesslog} SET uid = 0 WHERE uid = %d', $user->uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*/
|
||||
function statistics_cron() {
|
||||
$statistics_timestamp = variable_get('statistics_day_timestamp', '');
|
||||
|
||||
if ((time() - $statistics_timestamp) >= 86400) {
|
||||
// Reset day counts.
|
||||
db_query('UPDATE {node_counter} SET daycount = 0');
|
||||
variable_set('statistics_day_timestamp', time());
|
||||
}
|
||||
|
||||
// Clean up expired access logs.
|
||||
if (variable_get('statistics_flush_accesslog_timer', 259200) > 0) {
|
||||
db_query('DELETE FROM {accesslog} WHERE timestamp < %d', time() - variable_get('statistics_flush_accesslog_timer', 259200));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all time or today top or last viewed node(s).
|
||||
*
|
||||
* @param $dbfield
|
||||
* one of
|
||||
* - 'totalcount': top viewed content of all time.
|
||||
* - 'daycount': top viewed content for today.
|
||||
* - 'timestamp': last viewed node.
|
||||
*
|
||||
* @param $dbrows
|
||||
* number of rows to be returned.
|
||||
*
|
||||
* @return
|
||||
* A query result containing n.nid, n.title, u.uid, u.name of the selected node(s)
|
||||
* or FALSE if the query could not be executed correctly.
|
||||
*/
|
||||
function statistics_title_list($dbfield, $dbrows) {
|
||||
if (in_array($dbfield, array('totalcount', 'daycount', 'timestamp'))) {
|
||||
return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node} n INNER JOIN {node_counter} s ON n.nid = s.nid INNER JOIN {users} u ON n.uid = u.uid WHERE s.". $dbfield ." != 0 AND n.status = 1 ORDER BY s.". $dbfield ." DESC"), 0, $dbrows);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a node's "view statistics".
|
||||
*
|
||||
* @param $nid
|
||||
* node ID
|
||||
*
|
||||
* @return
|
||||
* An array with three entries: [0]=totalcount, [1]=daycount, [2]=timestamp
|
||||
* - totalcount: count of the total number of times that node has been viewed.
|
||||
* - daycount: count of the total number of times that node has been viewed "today".
|
||||
* For the daycount to be reset, cron must be enabled.
|
||||
* - timestamp: timestamp of when that node was last viewed.
|
||||
*/
|
||||
function statistics_get($nid) {
|
||||
|
||||
if ($nid > 0) {
|
||||
// Retrieve an array with both totalcount and daycount.
|
||||
$statistics = db_fetch_array(db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = %d', $nid));
|
||||
}
|
||||
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_block().
|
||||
*/
|
||||
function statistics_block($op = 'list', $delta = 0, $edit = array()) {
|
||||
switch ($op) {
|
||||
case 'list':
|
||||
if (variable_get('statistics_count_content_views', 0)) {
|
||||
$blocks[0]['info'] = t('Popular content');
|
||||
// Too dynamic to cache.
|
||||
$blocks[0]['cache'] = BLOCK_NO_CACHE;
|
||||
return $blocks;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'configure':
|
||||
// Popular content block settings
|
||||
$numbers = array('0' => t('Disabled')) + drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40));
|
||||
$form['statistics_block_top_day_num'] = array('#type' => 'select', '#title' => t("Number of day's top views to display"), '#default_value' => variable_get('statistics_block_top_day_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "day" list.'));
|
||||
$form['statistics_block_top_all_num'] = array('#type' => 'select', '#title' => t('Number of all time views to display'), '#default_value' => variable_get('statistics_block_top_all_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "all time" list.'));
|
||||
$form['statistics_block_top_last_num'] = array('#type' => 'select', '#title' => t('Number of most recent views to display'), '#default_value' => variable_get('statistics_block_top_last_num', 0), '#options' => $numbers, '#description' => t('How many content items to display in "recently viewed" list.'));
|
||||
return $form;
|
||||
|
||||
case 'save':
|
||||
variable_set('statistics_block_top_day_num', $edit['statistics_block_top_day_num']);
|
||||
variable_set('statistics_block_top_all_num', $edit['statistics_block_top_all_num']);
|
||||
variable_set('statistics_block_top_last_num', $edit['statistics_block_top_last_num']);
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
if (user_access('access content')) {
|
||||
$content = array();
|
||||
|
||||
$daytop = variable_get('statistics_block_top_day_num', 0);
|
||||
if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && ($node_title_list = node_title_list($result, t("Today's:")))) {
|
||||
$content[] = $node_title_list;
|
||||
}
|
||||
|
||||
$alltimetop = variable_get('statistics_block_top_all_num', 0);
|
||||
if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && ($node_title_list = node_title_list($result, t('All time:')))) {
|
||||
$content[] = $node_title_list;
|
||||
}
|
||||
|
||||
$lasttop = variable_get('statistics_block_top_last_num', 0);
|
||||
if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && ($node_title_list = node_title_list($result, t('Last viewed:')))) {
|
||||
$content[] = $node_title_list;
|
||||
}
|
||||
|
||||
if (count($content)) {
|
||||
$block['content'] = implode('<br />', $content);
|
||||
$block['subject'] = t('Popular content');
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It is possible to adjust the width of columns generated by the
|
||||
* statistics module.
|
||||
*/
|
||||
function _statistics_link($path, $width = 35) {
|
||||
$title = drupal_get_path_alias($path);
|
||||
$title = truncate_utf8($title, $width, FALSE, TRUE);
|
||||
return l($title, $path);
|
||||
}
|
||||
|
||||
function _statistics_format_item($title, $path) {
|
||||
$path = ($path ? $path : '/');
|
||||
$output = ($title ? "$title<br />" : '');
|
||||
$output .= _statistics_link($path);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_nodeapi().
|
||||
*/
|
||||
function statistics_nodeapi(&$node, $op, $arg = 0) {
|
||||
switch ($op) {
|
||||
case 'delete':
|
||||
// clean up statistics table when node is deleted
|
||||
db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the statistics module.
|
||||
*/
|
||||
|
||||
function statistics_node_tracker() {
|
||||
if ($node = node_load(arg(1))) {
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Time'), 'field' => 'a.timestamp', 'sort' => 'desc'),
|
||||
array('data' => t('Referrer'), 'field' => 'a.url'),
|
||||
array('data' => t('User'), 'field' => 'u.name'),
|
||||
array('data' => t('Operations')));
|
||||
|
||||
$result = pager_query("SELECT a.aid, a.timestamp, a.url, a.uid, u.name FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid WHERE a.path = 'node/%d' OR a.path LIKE 'node/%d/%%'". tablesort_sql($header), 30, 0, NULL, $node->nid, $node->nid);
|
||||
$rows = array();
|
||||
while ($log = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
|
||||
_statistics_link($log->url),
|
||||
theme('username', $log),
|
||||
l(t('details'), "admin/reports/access/$log->aid"));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
|
||||
}
|
||||
|
||||
drupal_set_title(check_plain($node->title));
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
else {
|
||||
drupal_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
function statistics_user_tracker() {
|
||||
if ($account = user_load(array('uid' => arg(1)))) {
|
||||
|
||||
$header = array(
|
||||
array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'),
|
||||
array('data' => t('Page'), 'field' => 'path'),
|
||||
array('data' => t('Operations')));
|
||||
|
||||
$result = pager_query('SELECT aid, timestamp, path, title FROM {accesslog} WHERE uid = %d'. tablesort_sql($header), 30, 0, NULL, $account->uid);
|
||||
$rows = array();
|
||||
while ($log = db_fetch_object($result)) {
|
||||
$rows[] = array(
|
||||
array('data' => format_date($log->timestamp, 'small'), 'class' => 'nowrap'),
|
||||
_statistics_format_item($log->title, $log->path),
|
||||
l(t('details'), "admin/reports/access/$log->aid"));
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3));
|
||||
}
|
||||
|
||||
drupal_set_title(check_plain($account->name));
|
||||
$output = theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 30, 0);
|
||||
return $output;
|
||||
}
|
||||
else {
|
||||
drupal_not_found();
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Admin page callbacks for the throttle module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Form builder; Configure the throttle system.
|
||||
*
|
||||
* @ingroup forms
|
||||
* @see system_settings_form()
|
||||
* @see throttle_admin_settings_validate()
|
||||
*/
|
||||
function throttle_admin_settings() {
|
||||
$probabilities = array(0 => '100%', 1 => '50%', 2 => '33.3%', 3 => '25%', 4 => '20%', 5 => '16.6%', 7 => '12.5%', 9 => '10%', 19 => '5%', 99 => '1%', 199 => '.5%', 399 => '.25%', 989 => '.1%');
|
||||
|
||||
$form['throttle_anonymous'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Auto-throttle on anonymous users'),
|
||||
'#default_value' => variable_get('throttle_anonymous', 0),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 6,
|
||||
'#description' => t('The congestion control throttle can be automatically enabled when the number of anonymous users currently visiting your site exceeds the specified threshold. For example, to start the throttle when your site has 250 anonymous users online at once, enter \'250\' in this field. Leave this value blank or set to "0" if you do not wish to auto-throttle on anonymous users. You can inspect the current number of anonymous users using the "Who\'s online" block.')
|
||||
);
|
||||
$form['throttle_user'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Auto-throttle on authenticated users'),
|
||||
'#default_value' => variable_get('throttle_user', 0),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 6,
|
||||
'#description' => t('The congestion control throttle can be automatically enabled when the number of authenticated users currently visiting your site exceeds the specified threshold. For example, to start the throttle when your site has 50 registered users online at once, enter \'50\' in this field. Leave this value blank or set to "0" if you do not wish to auto-throttle on authenticated users. You can inspect the current number of authenticated users using the "Who\'s online" block.')
|
||||
);
|
||||
$form['throttle_probability_limiter'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Auto-throttle probability limiter'),
|
||||
'#default_value' => variable_get('throttle_probability_limiter', 9),
|
||||
'#options' => $probabilities,
|
||||
'#description' => t('The auto-throttle probability limiter is an efficiency mechanism to statistically reduce the overhead of the auto-throttle. The limiter is expressed as a percentage of page views, so for example if set to the default of 10% we only perform the extra database queries to update the throttle status 1 out of every 10 page views. The busier your site, the lower you should set the limiter value.')
|
||||
);
|
||||
|
||||
$form['#validate'] = array('throttle_admin_settings_validate');
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
||||
|
||||
function throttle_admin_settings_validate($form, &$form_state) {
|
||||
if (!is_numeric($form_state['values']['throttle_anonymous']) || $form_state['values']['throttle_anonymous'] < 0) {
|
||||
form_set_error('throttle_anonymous', t("%value is not a valid auto-throttle setting. Please enter a positive numeric value.", array('%value' => $form_state['values']['throttle_anonymous'])));
|
||||
}
|
||||
if (!is_numeric($form_state['values']['throttle_user']) || $form_state['values']['throttle_user'] < 0) {
|
||||
form_set_error('throttle_user', t("%value is not a valid auto-throttle setting. Please enter a positive numeric value.", array('%value' => $form_state['values']['throttle_user'])));
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Throttle
|
||||
description = Handles the auto-throttling mechanism, to control site congestion.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Allows configuration of congestion control auto-throttle mechanism.
|
||||
*/
|
||||
|
||||
function throttle_menu() {
|
||||
$items['admin/settings/throttle'] = array(
|
||||
'title' => 'Throttle',
|
||||
'description' => 'Control how your site cuts out content during heavy load.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('throttle_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'throttle.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the current load on the site.
|
||||
*
|
||||
* Call the throttle_status() function from your own modules, themes, blocks,
|
||||
* etc. as follows:
|
||||
*
|
||||
* $throttle = module_invoke('throttle', 'status');
|
||||
*
|
||||
* to determine the current throttle status. Use module_invoke() so the
|
||||
* call will still work if the throttle module is disabled. For example, in
|
||||
* your theme you might choose to disable pictures when your site is too busy
|
||||
* (reducing bandwidth), or in your modules you might choose to disable
|
||||
* some complicated logic when your site is too busy (reducing CPU utilization).
|
||||
*
|
||||
* @return
|
||||
* 0 or 1. 0 means that the throttle is currently disabled. 1 means that
|
||||
* the throttle is currently enabled. When the throttle is enabled, CPU
|
||||
* and bandwidth intensive functionality should be disabled.
|
||||
*/
|
||||
function throttle_status() {
|
||||
return variable_get('throttle_level', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_exit().
|
||||
*
|
||||
* Changes the current throttle level based on page hits.
|
||||
*/
|
||||
function throttle_exit() {
|
||||
// The following logic determines what the current throttle level should
|
||||
// be, and can be disabled by the admin. If enabled, the mt_rand() function
|
||||
// returns a number between 0 and N, N being specified by the admin. If
|
||||
// 0 is returned, the throttle logic is run, adding two additional database
|
||||
// queries. Otherwise, the following logic is skipped. This mechanism is
|
||||
// referred to in the admin page as the 'probability limiter', roughly
|
||||
// limiting throttle related database calls to 1 in N.
|
||||
if (!mt_rand(0, variable_get('throttle_probability_limiter', 9))) {
|
||||
|
||||
// Count users with activity in the past n seconds.
|
||||
// This value is defined in the user module Who's Online block.
|
||||
$time_period = variable_get('user_block_seconds_online', 900);
|
||||
|
||||
// When determining throttle status in your own module or theme, use
|
||||
// $throttle = module_invoke('throttle', 'status');
|
||||
// as that will still work when throttle.module is disabled.
|
||||
// Clearly here the module is enabled so we call throttle_status() directly.
|
||||
$throttle = throttle_status();
|
||||
|
||||
if ($max_guests = variable_get('throttle_anonymous', 0)) {
|
||||
$guests = sess_count(time() - $time_period, TRUE);
|
||||
}
|
||||
else {
|
||||
$guests = 0;
|
||||
}
|
||||
if ($max_users = variable_get('throttle_user', 0)) {
|
||||
$users = sess_count(time() - $time_period, FALSE);
|
||||
}
|
||||
else {
|
||||
$users = 0;
|
||||
}
|
||||
|
||||
// update the throttle status
|
||||
$message = '';
|
||||
if ($max_users && $users > $max_users) {
|
||||
if (!$throttle) {
|
||||
variable_set('throttle_level', 1);
|
||||
$message = format_plural($users,
|
||||
'1 user accessing site; throttle enabled.',
|
||||
'@count users accessing site; throttle enabled.');
|
||||
}
|
||||
}
|
||||
elseif ($max_guests && $guests > $max_guests) {
|
||||
if (!$throttle) {
|
||||
variable_set('throttle_level', 1);
|
||||
$message = format_plural($guests,
|
||||
'1 guest accessing site; throttle enabled.',
|
||||
'@count guests accessing site; throttle enabled.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($throttle) {
|
||||
variable_set('throttle_level', 0);
|
||||
// Note: unorthodox format_plural() usage due to Gettext plural limitations.
|
||||
$message = format_plural($users, '1 user', '@count users') .', ';
|
||||
$message .= format_plural($guests, '1 guest accessing site; throttle disabled', '@count guests accessing site; throttle disabled');
|
||||
}
|
||||
}
|
||||
if ($message) {
|
||||
cache_clear_all();
|
||||
watchdog('throttle', 'Throttle: %message', array('%message' => $message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function throttle_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#throttle':
|
||||
$output = '<p>'. t('The throttle module provides a congestion control mechanism that automatically adjusts to a surge in incoming traffic. If your site is referenced by a popular website, or experiences a "Denial of Service" (DoS) attack, your webserver might become overwhelmed. The throttle mechanism is utilized by modules to temporarily disable CPU-intensive functionality, increasing performance. For instance, via the throttle module, modules may choose to disable resource-intensive blocks or the code within the site theme may temporarily disable user pictures in posts.') .'</p>';
|
||||
$output .= '<p>'. t('The congestion control throttle can be automatically enabled when the number of anonymous or authenticated users currently visiting the site exceeds a specified threshold.') .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@throttle">Throttle module</a>.', array('@throttle' => 'http://drupal.org/handbook/modules/throttle/')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/settings/throttle':
|
||||
return '<p>'. t('The throttle module provides a congestion control mechanism that automatically adjusts to a surge in incoming traffic. If your site is referenced by a popular website, or experiences a "Denial of Service" (DoS) attack, your webserver might become overwhelmed. The throttle mechanism is utilized by modules to temporarily disable CPU-intensive functionality, increasing performance.') .'</p>';
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
#tracker td.replies {
|
||||
text-align: center;
|
||||
}
|
||||
#tracker table {
|
||||
width: 100%;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
name = Tracker
|
||||
description = Enables tracking of recent posts for users.
|
||||
dependencies[] = comment
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables tracking of recent posts for users.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function tracker_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#tracker':
|
||||
$output = '<p>'. t('The tracker module displays the most recently added or updated content on your site, and provides user-level tracking to follow the contributions of particular authors.') .'</p>';
|
||||
$output .= '<p>'. t("The <em>Recent posts</em> page is available via a link in the navigation menu block and displays new and recently-updated content (including the content type, the title, the author's name, number of comments, and time of last update) in reverse chronological order. Posts are marked updated when changes occur in the text, or when new comments are added. To use the tracker module to follow a specific user's contributions, select the <em>Track</em> tab from the user's profile page.") .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@tracker">Tracker module</a>.', array('@tracker' => 'http://drupal.org/handbook/modules/tracker/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function tracker_menu() {
|
||||
$items['tracker'] = array(
|
||||
'title' => 'Recent posts',
|
||||
'page callback' => 'tracker_page',
|
||||
'access arguments' => array('access content'),
|
||||
'weight' => 1,
|
||||
'file' => 'tracker.pages.inc',
|
||||
);
|
||||
$items['tracker/all'] = array(
|
||||
'title' => 'All recent posts',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
$items['tracker/%user_uid_optional'] = array(
|
||||
'title' => 'My recent posts',
|
||||
'access callback' => '_tracker_myrecent_access',
|
||||
'access arguments' => array(1),
|
||||
'page arguments' => array(1),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
|
||||
$items['user/%user/track'] = array(
|
||||
'title' => 'Track',
|
||||
'page callback' => 'tracker_page',
|
||||
'page arguments' => array(1, TRUE),
|
||||
'access callback' => '_tracker_user_access',
|
||||
'access arguments' => array(1),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'tracker.pages.inc',
|
||||
);
|
||||
$items['user/%user/track/posts'] = array(
|
||||
'title' => 'Track posts',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback for tracker/%user_uid_optional
|
||||
*/
|
||||
function _tracker_myrecent_access($account) {
|
||||
// This path is only allowed for authenticated users looking at their own posts.
|
||||
return $account->uid && ($GLOBALS['user']->uid == $account->uid) && user_access('access content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback for user/%user/track
|
||||
*/
|
||||
function _tracker_user_access($account) {
|
||||
return user_view_access($account) && user_access('access content');
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the tracker module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Menu callback. Prints a listing of active nodes on the site.
|
||||
*/
|
||||
function tracker_page($account = NULL, $set_title = FALSE) {
|
||||
// Add CSS
|
||||
drupal_add_css(drupal_get_path('module', 'tracker') .'/tracker.css', 'module', 'all', FALSE);
|
||||
|
||||
if ($account) {
|
||||
if ($set_title) {
|
||||
// When viewed from user/%user/track, display the name of the user
|
||||
// as page title -- the tab title remains Track so this needs to be done
|
||||
// here and not in the menu definiton.
|
||||
drupal_set_title(check_plain($account->name));
|
||||
}
|
||||
// TODO: These queries are very expensive, see http://drupal.org/node/105639
|
||||
$sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d) ORDER BY last_updated DESC';
|
||||
$sql = db_rewrite_sql($sql);
|
||||
$sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND (n.uid = %d OR c.uid = %d)';
|
||||
$sql_count = db_rewrite_sql($sql_count);
|
||||
$result = pager_query($sql, 25, 0, $sql_count, COMMENT_PUBLISHED, $account->uid, $account->uid);
|
||||
}
|
||||
else {
|
||||
$sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 ORDER BY last_updated DESC';
|
||||
$sql = db_rewrite_sql($sql);
|
||||
$sql_count = 'SELECT COUNT(n.nid) FROM {node} n WHERE n.status = 1';
|
||||
$sql_count = db_rewrite_sql($sql_count);
|
||||
$result = pager_query($sql, 25, 0, $sql_count);
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
while ($node = db_fetch_object($result)) {
|
||||
// Determine the number of comments:
|
||||
$comments = 0;
|
||||
if ($node->comment_count) {
|
||||
$comments = $node->comment_count;
|
||||
|
||||
if ($new = comment_num_new($node->nid)) {
|
||||
$comments .= '<br />';
|
||||
$comments .= l(format_plural($new, '1 new', '@count new'), "node/$node->nid", array('query' => comment_new_page_count($node->comment_count, $new, $node), 'fragment' => 'new'));
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
check_plain(node_get_types('name', $node->type)),
|
||||
l($node->title, "node/$node->nid") .' '. theme('mark', node_mark($node->nid, $node->changed)),
|
||||
theme('username', $node),
|
||||
array('class' => 'replies', 'data' => $comments),
|
||||
t('!time ago', array('!time' => format_interval(time() - $node->last_updated)))
|
||||
);
|
||||
}
|
||||
|
||||
if (!$rows) {
|
||||
$rows[] = array(array('data' => t('No posts available.'), 'colspan' => '5'));
|
||||
}
|
||||
|
||||
$header = array(t('Type'), t('Post'), t('Author'), t('Replies'), t('Last updated'));
|
||||
|
||||
$output = '<div id="tracker">';
|
||||
$output .= theme('table', $header, $rows);
|
||||
$output .= theme('pager', NULL, 25, 0);
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Admin page callbacks for the trigger module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build the form that allows users to assign actions to hooks.
|
||||
*
|
||||
* @param $type
|
||||
* Name of hook.
|
||||
* @return
|
||||
* HTML form.
|
||||
*/
|
||||
function trigger_assign($type = NULL) {
|
||||
// If no type is specified we default to node actions, since they
|
||||
// are the most common.
|
||||
if (!isset($type)) {
|
||||
drupal_goto('admin/build/trigger/node');
|
||||
}
|
||||
if ($type == 'node') {
|
||||
$type = 'nodeapi';
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$hooks = module_invoke_all('hook_info');
|
||||
foreach ($hooks as $module => $hook) {
|
||||
if (isset($hook[$type])) {
|
||||
foreach ($hook[$type] as $op => $description) {
|
||||
$form_id = 'trigger_'. $type .'_'. $op .'_assign_form';
|
||||
$output .= drupal_get_form($form_id, $type, $op, $description['runs when']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm removal of an assigned action.
|
||||
*
|
||||
* @param $hook
|
||||
* @param $op
|
||||
* @param $aid
|
||||
* The action ID.
|
||||
* @ingroup forms
|
||||
* @see trigger_unassign_submit()
|
||||
*/
|
||||
function trigger_unassign($form_state, $hook = NULL, $op = NULL, $aid = NULL) {
|
||||
if (!($hook && $op && $aid)) {
|
||||
drupal_goto('admin/build/trigger/assign');
|
||||
}
|
||||
|
||||
$form['hook'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $hook,
|
||||
);
|
||||
$form['operation'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $op,
|
||||
);
|
||||
$form['aid'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $aid,
|
||||
);
|
||||
|
||||
$action = actions_function_lookup($aid);
|
||||
$actions = actions_get_all_actions();
|
||||
|
||||
$destination = 'admin/build/trigger/'. ($hook == 'nodeapi' ? 'node' : $hook);
|
||||
|
||||
return confirm_form($form,
|
||||
t('Are you sure you want to unassign the action %title?', array('%title' => $actions[$action]['description'])),
|
||||
$destination,
|
||||
t('You can assign it again later if you wish.'),
|
||||
t('Unassign'), t('Cancel')
|
||||
);
|
||||
}
|
||||
|
||||
function trigger_unassign_submit($form, &$form_state) {
|
||||
$form_values = $form_state['values'];
|
||||
if ($form_values['confirm'] == 1) {
|
||||
$aid = actions_function_lookup($form_values['aid']);
|
||||
db_query("DELETE FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid);
|
||||
$actions = actions_get_all_actions();
|
||||
watchdog('actions', 'Action %action has been unassigned.', array('%action' => $actions[$aid]['description']));
|
||||
drupal_set_message(t('Action %action has been unassigned.', array('%action' => $actions[$aid]['description'])));
|
||||
$hook = $form_values['hook'] == 'nodeapi' ? 'node' : $form_values['hook'];
|
||||
$form_state['redirect'] = 'admin/build/trigger/'. $hook;
|
||||
}
|
||||
else {
|
||||
drupal_goto('admin/build/trigger');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the form definition for assigning an action to a hook-op combination.
|
||||
*
|
||||
* @param $form_state
|
||||
* Information about the current form.
|
||||
* @param $hook
|
||||
* The name of the hook, e.g., 'nodeapi'.
|
||||
* @param $op
|
||||
* The name of the hook operation, e.g., 'insert'.
|
||||
* @param $description
|
||||
* A plain English description of what this hook operation does.
|
||||
* @return
|
||||
*
|
||||
* @ingoup forms
|
||||
* @see trigger_assign_form_validate()
|
||||
* @see trigger_assign_form_submit()
|
||||
*/
|
||||
function trigger_assign_form($form_state, $hook, $op, $description) {
|
||||
$form['hook'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $hook,
|
||||
);
|
||||
$form['operation'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $op,
|
||||
);
|
||||
// All of these forms use the same validate and submit functions.
|
||||
$form['#validate'][] = 'trigger_assign_form_validate';
|
||||
$form['#submit'][] = 'trigger_assign_form_submit';
|
||||
|
||||
$options = array();
|
||||
$functions = array();
|
||||
// Restrict the options list to actions that declare support for this hook-op
|
||||
// combination.
|
||||
foreach (actions_list() as $func => $metadata) {
|
||||
if (isset($metadata['hooks']['any']) || (isset($metadata['hooks'][$hook]) && is_array($metadata['hooks'][$hook]) && (in_array($op, $metadata['hooks'][$hook])))) {
|
||||
$functions[] = $func;
|
||||
}
|
||||
}
|
||||
foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
|
||||
if (in_array($action['callback'], $functions)) {
|
||||
$options[$action['type']][$aid] = $action['description'];
|
||||
}
|
||||
}
|
||||
|
||||
$form[$op] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Trigger: ') . $description,
|
||||
'#theme' => 'trigger_display'
|
||||
);
|
||||
// Retrieve actions that are already assigned to this hook-op combination.
|
||||
$actions = _trigger_get_hook_actions($hook, $op);
|
||||
$form[$op]['assigned']['#type'] = 'value';
|
||||
$form[$op]['assigned']['#value'] = array();
|
||||
foreach ($actions as $aid => $description) {
|
||||
$form[$op]['assigned']['#value'][$aid] = array(
|
||||
'description' => $description,
|
||||
'link' => l(t('unassign'), "admin/build/trigger/unassign/$hook/$op/". md5($aid))
|
||||
);
|
||||
}
|
||||
|
||||
$form[$op]['parent'] = array(
|
||||
'#prefix' => "<div class='container-inline'>",
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
// List possible actions that may be assigned.
|
||||
if (count($options) != 0) {
|
||||
array_unshift($options, t('Choose an action'));
|
||||
$form[$op]['parent']['aid'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
);
|
||||
$form[$op]['parent']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Assign')
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form[$op]['none'] = array(
|
||||
'#value' => t('No available actions for this trigger.')
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation function for trigger_assign_form().
|
||||
*
|
||||
* Makes sure that the user is not re-assigning an action to an event.
|
||||
*/
|
||||
function trigger_assign_form_validate($form, $form_state) {
|
||||
$form_values = $form_state['values'];
|
||||
if (!empty($form_values['aid'])) {
|
||||
$aid = actions_function_lookup($form_values['aid']);
|
||||
if (db_result(db_query("SELECT aid FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = '%s'", $form_values['hook'], $form_values['operation'], $aid))) {
|
||||
form_set_error($form_values['operation'], t('The action you chose is already assigned to that trigger.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit function for trigger_assign_form().
|
||||
*/
|
||||
function trigger_assign_form_submit($form, $form_state) {
|
||||
$form_values = $form_state['values'];
|
||||
|
||||
if (!empty($form_values['aid'])) {
|
||||
$aid = actions_function_lookup($form_values['aid']);
|
||||
$weight = db_result(db_query("SELECT MAX(weight) FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s'", $form_values['hook'], $form_values['operation']));
|
||||
db_query("INSERT INTO {trigger_assignments} values ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], $aid, $weight + 1);
|
||||
// If this action changes a node property, we need to save the node
|
||||
// so the change will persist.
|
||||
$actions = actions_list();
|
||||
if (isset($actions[$aid]['behavior']) && in_array('changes_node_property', $actions[$aid]['behavior']) && ($form_values['operation'] != 'presave')) {
|
||||
// Delete previous node_save_action if it exists, and re-add a new one at a higher weight.
|
||||
$save_post_action_assigned = db_result(db_query("SELECT aid FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']));
|
||||
if ($save_post_action_assigned) {
|
||||
db_query("DELETE FROM {trigger_assignments} WHERE hook = '%s' AND op = '%s' AND aid = 'node_save_action'", $form_values['hook'], $form_values['operation']);
|
||||
}
|
||||
db_query("INSERT INTO {trigger_assignments} VALUES ('%s', '%s', '%s', %d)", $form_values['hook'], $form_values['operation'], 'node_save_action', $weight + 2);
|
||||
if (!$save_post_action_assigned) {
|
||||
drupal_set_message(t('You have added an action that changes a the property of a post. A Save post action has been added so that the property change will be saved.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display actions assigned to this hook-op combination in a table.
|
||||
*
|
||||
* @param array $element
|
||||
* The fieldset including all assigned actions.
|
||||
* @return
|
||||
* The rendered form with the table prepended.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_trigger_display($element) {
|
||||
$header = array();
|
||||
$rows = array();
|
||||
if (count($element['assigned']['#value'])) {
|
||||
$header = array(array('data' => t('Name')), array('data' => t('Operation')));
|
||||
$rows = array();
|
||||
foreach ($element['assigned']['#value'] as $aid => $info) {
|
||||
$rows[] = array(
|
||||
filter_xss_admin($info['description']),
|
||||
$info['link']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($rows)) {
|
||||
$output = theme('table', $header, $rows) . drupal_render($element);
|
||||
}
|
||||
else {
|
||||
$output = drupal_render($element);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the actions that have already been defined for this
|
||||
* type-hook-op combination.
|
||||
*
|
||||
* @param $type
|
||||
* One of 'node', 'user', 'comment'.
|
||||
* @param $hook
|
||||
* The name of the hook for which actions have been assigned,
|
||||
* e.g. 'nodeapi'.
|
||||
* @param $op
|
||||
* The hook operation for which the actions have been assigned,
|
||||
* e.g., 'view'.
|
||||
* @return
|
||||
* An array of action descriptions keyed by action IDs.
|
||||
*/
|
||||
function _trigger_get_hook_actions($hook, $op, $type = NULL) {
|
||||
$actions = array();
|
||||
if ($type) {
|
||||
$result = db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE a.type = '%s' AND h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $type, $hook, $op);
|
||||
}
|
||||
else {
|
||||
$result = db_query("SELECT h.aid, a.description FROM {trigger_assignments} h LEFT JOIN {actions} a on a.aid = h.aid WHERE h.hook = '%s' AND h.op = '%s' ORDER BY h.weight", $hook, $op);
|
||||
}
|
||||
while ($action = db_fetch_object($result)) {
|
||||
$actions[$action->aid] = $action->description;
|
||||
}
|
||||
return $actions;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Trigger
|
||||
description = Enables actions to be fired on certain system events, such as when new content is created.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function trigger_install() {
|
||||
// Create tables.
|
||||
drupal_install_schema('trigger');
|
||||
|
||||
// Do initial synchronization of actions in code and the database.
|
||||
actions_synchronize(actions_list());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function trigger_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('trigger');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function trigger_schema() {
|
||||
$schema['trigger_assignments'] = array(
|
||||
'description' => 'Maps trigger to hook and operation assignments from trigger.module.',
|
||||
'fields' => array(
|
||||
'hook' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Primary Key: The name of the internal Drupal hook upon which an action is firing; for example, nodeapi.',
|
||||
),
|
||||
'op' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Primary Key: The specific operation of the hook upon which an action is firing: for example, presave.',
|
||||
),
|
||||
'aid' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => "Primary Key: Action's {actions}.aid.",
|
||||
),
|
||||
'weight' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The weight of the trigger assignment in relation to other triggers.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('hook', 'op', 'aid'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
|
@ -1,435 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables functions to be stored and executed at a later time when
|
||||
* triggered by other modules or by one of Drupal's core API hooks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function trigger_help($path, $arg) {
|
||||
$explanation = '<p>'. t('Triggers are system events, such as when new content is added or when a user logs in. Trigger module combines these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/settings/actions'))) .'</p>';
|
||||
switch ($path) {
|
||||
case 'admin/build/trigger/comment':
|
||||
return $explanation .'<p>'. t('Below you can assign actions to run when certain comment-related triggers happen. For example, you could promote a post to the front page when a comment is added.') .'</p>';
|
||||
case 'admin/build/trigger/node':
|
||||
return $explanation .'<p>'. t('Below you can assign actions to run when certain content-related triggers happen. For example, you could send an e-mail to an administrator when a post is created or updated.') .'</p>';
|
||||
case 'admin/build/trigger/cron':
|
||||
return $explanation .'<p>'. t('Below you can assign actions to run during each pass of a <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) .'</p>';
|
||||
case 'admin/build/trigger/taxonomy':
|
||||
return $explanation .'<p>'. t('Below you can assign actions to run when certain taxonomy-related triggers happen. For example, you could send an e-mail to an administrator when a term is deleted.') .'</p>';
|
||||
case 'admin/build/trigger/user':
|
||||
return $explanation .'<p>'. t("Below you can assign actions to run when certain user-related triggers happen. For example, you could send an e-mail to an administrator when a user account is deleted.") .'</p>';
|
||||
case 'admin/help#trigger':
|
||||
$output = '<p>'. t('The Trigger module provides the ability to trigger <a href="@actions">actions</a> upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/settings/actions'))) .'</p>';
|
||||
$output .= '<p>'. t('The combination of actions and triggers can perform many useful tasks, such as e-mailing an administrator if a user account is deleted, or automatically unpublishing comments that contain certain words. By default, there are five "contexts" of events (Comments, Content, Cron, Taxonomy, and Users), but more may be added by additional modules.') .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@trigger">Trigger module</a>.', array('@trigger' => 'http://drupal.org/handbook/modules/trigger/')) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function trigger_menu() {
|
||||
$items['admin/build/trigger'] = array(
|
||||
'title' => 'Triggers',
|
||||
'description' => 'Tell Drupal when to execute actions.',
|
||||
'page callback' => 'trigger_assign',
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array('node'),
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
// We don't use a menu wildcard here because these are tabs,
|
||||
// not invisible items.
|
||||
$items['admin/build/trigger/node'] = array(
|
||||
'title' => 'Content',
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array('node'),
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array('node'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
$items['admin/build/trigger/user'] = array(
|
||||
'title' => 'Users',
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array('user'),
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array('user'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
$items['admin/build/trigger/comment'] = array(
|
||||
'title' => 'Comments',
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array('comment'),
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array('comment'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
$items['admin/build/trigger/taxonomy'] = array(
|
||||
'title' => 'Taxonomy',
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array('taxonomy'),
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array('taxonomy'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
$items['admin/build/trigger/cron'] = array(
|
||||
'title' => 'Cron',
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array('cron'),
|
||||
'access arguments' => array('administer actions'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
|
||||
// We want contributed modules to be able to describe
|
||||
// their hooks and have actions assignable to them.
|
||||
$hooks = module_invoke_all('hook_info');
|
||||
foreach ($hooks as $module => $hook) {
|
||||
// We've already done these.
|
||||
if (in_array($module, array('node', 'comment', 'user', 'system', 'taxonomy'))) {
|
||||
continue;
|
||||
}
|
||||
$info = db_result(db_query("SELECT info FROM {system} WHERE name = '%s'", $module));
|
||||
$info = unserialize($info);
|
||||
$nice_name = $info['name'];
|
||||
$items["admin/build/trigger/$module"] = array(
|
||||
'title' => $nice_name,
|
||||
'page callback' => 'trigger_assign',
|
||||
'page arguments' => array($module),
|
||||
'access callback' => 'trigger_access_check',
|
||||
'access arguments' => array($module),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
}
|
||||
$items['admin/build/trigger/unassign'] = array(
|
||||
'title' => 'Unassign',
|
||||
'description' => 'Unassign an action from a trigger.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('trigger_unassign'),
|
||||
'access arguments' => array('administer actions'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'trigger.admin.inc',
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback for menu system.
|
||||
*/
|
||||
function trigger_access_check($module) {
|
||||
return (module_exists($module) && user_access('administer actions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the aids of actions to be executed for a hook-op combination.
|
||||
*
|
||||
* @param $hook
|
||||
* The name of the hook being fired.
|
||||
* @param $op
|
||||
* The name of the operation being executed. Defaults to an empty string
|
||||
* because some hooks (e.g., hook_cron()) do not have operations.
|
||||
* @return
|
||||
* An array of action IDs.
|
||||
*/
|
||||
function _trigger_get_hook_aids($hook, $op = '') {
|
||||
$aids = array();
|
||||
$result = db_query("SELECT aa.aid, a.type FROM {trigger_assignments} aa LEFT JOIN {actions} a ON aa.aid = a.aid WHERE aa.hook = '%s' AND aa.op = '%s' ORDER BY weight", $hook, $op);
|
||||
while ($action = db_fetch_object($result)) {
|
||||
$aids[$action->aid]['type'] = $action->type;
|
||||
}
|
||||
return $aids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme().
|
||||
*/
|
||||
function trigger_theme() {
|
||||
return array(
|
||||
'trigger_display' => array(
|
||||
'arguments' => array('element'),
|
||||
'file' => 'trigger.admin.inc',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_forms(). We reuse code by using the
|
||||
* same assignment form definition for each node-op combination.
|
||||
*/
|
||||
function trigger_forms() {
|
||||
$hooks = module_invoke_all('hook_info');
|
||||
foreach ($hooks as $module => $info) {
|
||||
foreach ($hooks[$module] as $hook => $ops) {
|
||||
foreach ($ops as $op => $description) {
|
||||
$forms['trigger_'. $hook .'_'. $op .'_assign_form'] = array('callback' => 'trigger_assign_form');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* When an action is called in a context that does not match its type,
|
||||
* the object that the action expects must be retrieved. For example, when
|
||||
* an action that works on users is called during the node hook, the
|
||||
* user object is not available since the node hook doesn't pass it.
|
||||
* So here we load the object the action expects.
|
||||
*
|
||||
* @param $type
|
||||
* The type of action that is about to be called.
|
||||
* @param $node
|
||||
* The node that was passed via the nodeapi hook.
|
||||
* @return
|
||||
* The object expected by the action that is about to be called.
|
||||
*/
|
||||
function _trigger_normalize_node_context($type, $node) {
|
||||
switch ($type) {
|
||||
// If an action that works on comments is being called in a node context,
|
||||
// the action is expecting a comment object. But we do not know which comment
|
||||
// to give it. The first? The most recent? All of them? So comment actions
|
||||
// in a node context are not supported.
|
||||
|
||||
// An action that works on users is being called in a node context.
|
||||
// Load the user object of the node's author.
|
||||
case 'user':
|
||||
return user_load(array('uid' => $node->uid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_nodeapi().
|
||||
*/
|
||||
function trigger_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
|
||||
// Keep objects for reuse so that changes actions make to objects can persist.
|
||||
static $objects;
|
||||
// Prevent recursion by tracking which operations have already been called.
|
||||
static $recursion;
|
||||
// Support a subset of operations.
|
||||
if (!in_array($op, array('view', 'update', 'presave', 'insert', 'delete')) || isset($recursion[$op])) {
|
||||
return;
|
||||
}
|
||||
$recursion[$op] = TRUE;
|
||||
|
||||
$aids = _trigger_get_hook_aids('nodeapi', $op);
|
||||
if (!$aids) {
|
||||
return;
|
||||
}
|
||||
$context = array(
|
||||
'hook' => 'nodeapi',
|
||||
'op' => $op,
|
||||
);
|
||||
|
||||
// We need to get the expected object if the action's type is not 'node'.
|
||||
// We keep the object in $objects so we can reuse it if we have multiple actions
|
||||
// that make changes to an object.
|
||||
foreach ($aids as $aid => $action_info) {
|
||||
if ($action_info['type'] != 'node') {
|
||||
if (!isset($objects[$action_info['type']])) {
|
||||
$objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
|
||||
}
|
||||
// Since we know about the node, we pass that info along to the action.
|
||||
$context['node'] = $node;
|
||||
$result = actions_do($aid, $objects[$action_info['type']], $context, $a3, $a4);
|
||||
}
|
||||
else {
|
||||
actions_do($aid, $node, $context, $a3, $a4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When an action is called in a context that does not match its type,
|
||||
* the object that the action expects must be retrieved. For example, when
|
||||
* an action that works on nodes is called during the comment hook, the
|
||||
* node object is not available since the comment hook doesn't pass it.
|
||||
* So here we load the object the action expects.
|
||||
*
|
||||
* @param $type
|
||||
* The type of action that is about to be called.
|
||||
* @param $comment
|
||||
* The comment that was passed via the comment hook.
|
||||
* @return
|
||||
* The object expected by the action that is about to be called.
|
||||
*/
|
||||
function _trigger_normalize_comment_context($type, $comment) {
|
||||
switch ($type) {
|
||||
// An action that works with nodes is being called in a comment context.
|
||||
case 'node':
|
||||
return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
|
||||
|
||||
// An action that works on users is being called in a comment context.
|
||||
case 'user':
|
||||
return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_comment().
|
||||
*/
|
||||
function trigger_comment($a1, $op) {
|
||||
// Keep objects for reuse so that changes actions make to objects can persist.
|
||||
static $objects;
|
||||
// We support a subset of operations.
|
||||
if (!in_array($op, array('insert', 'update', 'delete', 'view'))) {
|
||||
return;
|
||||
}
|
||||
$aids = _trigger_get_hook_aids('comment', $op);
|
||||
$context = array(
|
||||
'hook' => 'comment',
|
||||
'op' => $op,
|
||||
);
|
||||
// We need to get the expected object if the action's type is not 'comment'.
|
||||
// We keep the object in $objects so we can reuse it if we have multiple actions
|
||||
// that make changes to an object.
|
||||
foreach ($aids as $aid => $action_info) {
|
||||
if ($action_info['type'] != 'comment') {
|
||||
if (!isset($objects[$action_info['type']])) {
|
||||
$objects[$action_info['type']] = _trigger_normalize_comment_context($action_info['type'], $a1);
|
||||
}
|
||||
// Since we know about the comment, we pass it along to the action
|
||||
// in case it wants to peek at it.
|
||||
$context['comment'] = (object) $a1;
|
||||
actions_do($aid, $objects[$action_info['type']], $context);
|
||||
}
|
||||
else {
|
||||
$comment = (object) $a1;
|
||||
actions_do($aid, $comment, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*/
|
||||
function trigger_cron() {
|
||||
$aids = _trigger_get_hook_aids('cron', 'run');
|
||||
$context = array(
|
||||
'hook' => 'cron',
|
||||
'op' => 'run',
|
||||
);
|
||||
// Cron does not act on any specific object.
|
||||
$object = NULL;
|
||||
actions_do(array_keys($aids), $object, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an action is called in a context that does not match its type,
|
||||
* the object that the action expects must be retrieved. For example, when
|
||||
* an action that works on nodes is called during the user hook, the
|
||||
* node object is not available since the user hook doesn't pass it.
|
||||
* So here we load the object the action expects.
|
||||
*
|
||||
* @param $type
|
||||
* The type of action that is about to be called.
|
||||
* @param $account
|
||||
* The account object that was passed via the user hook.
|
||||
* @return
|
||||
* The object expected by the action that is about to be called.
|
||||
*/
|
||||
function _trigger_normalize_user_context($type, $account) {
|
||||
switch ($type) {
|
||||
// If an action that works on comments is being called in a user context,
|
||||
// the action is expecting a comment object. But we have no way of
|
||||
// determining the appropriate comment object to pass. So comment
|
||||
// actions in a user context are not supported.
|
||||
|
||||
// An action that works with nodes is being called in a user context.
|
||||
// If a single node is being viewed, return the node.
|
||||
case 'node':
|
||||
// If we are viewing an individual node, return the node.
|
||||
if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
|
||||
return node_load(array('nid' => arg(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_user().
|
||||
*/
|
||||
function trigger_user($op, &$edit, &$account, $category = NULL) {
|
||||
// Keep objects for reuse so that changes actions make to objects can persist.
|
||||
static $objects;
|
||||
// We support a subset of operations.
|
||||
if (!in_array($op, array('login', 'logout', 'insert', 'update', 'delete', 'view'))) {
|
||||
return;
|
||||
}
|
||||
$aids = _trigger_get_hook_aids('user', $op);
|
||||
$context = array(
|
||||
'hook' => 'user',
|
||||
'op' => $op,
|
||||
'form_values' => &$edit,
|
||||
);
|
||||
foreach ($aids as $aid => $action_info) {
|
||||
if ($action_info['type'] != 'user') {
|
||||
if (!isset($objects[$action_info['type']])) {
|
||||
$objects[$action_info['type']] = _trigger_normalize_user_context($action_info['type'], $account);
|
||||
}
|
||||
$context['account'] = $account;
|
||||
actions_do($aid, $objects[$action_info['type']], $context);
|
||||
}
|
||||
else {
|
||||
actions_do($aid, $account, $context, $category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_taxonomy().
|
||||
*/
|
||||
function trigger_taxonomy($op, $type, $array) {
|
||||
if ($type != 'term') {
|
||||
return;
|
||||
}
|
||||
$aids = _trigger_get_hook_aids('taxonomy', $op);
|
||||
$context = array(
|
||||
'hook' => 'taxonomy',
|
||||
'op' => $op
|
||||
);
|
||||
foreach ($aids as $aid => $action_info) {
|
||||
$taxonomy_object = (object) $array;
|
||||
actions_do($aid, $taxonomy_object, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Often we generate a select field of all actions. This function
|
||||
* generates the options for that select.
|
||||
*
|
||||
* @param $type
|
||||
* One of 'node', 'user', 'comment'.
|
||||
* @return
|
||||
* Array keyed by action ID.
|
||||
*/
|
||||
function trigger_options($type = 'all') {
|
||||
$options = array(t('Choose an action'));
|
||||
foreach (actions_actions_map(actions_get_all_actions()) as $aid => $action) {
|
||||
$options[$action['type']][$aid] = $action['description'];
|
||||
}
|
||||
|
||||
if ($type == 'all') {
|
||||
return $options;
|
||||
}
|
||||
else {
|
||||
return $options[$type];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_actions_delete().
|
||||
*
|
||||
* Remove all trigger entries for the given action, when deleted.
|
||||
*/
|
||||
function trigger_actions_delete($aid) {
|
||||
db_query("DELETE FROM {trigger_assignments} WHERE aid = '%s'", $aid);
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
.update .project {
|
||||
padding-right: .25em;
|
||||
}
|
||||
|
||||
.update .version-status {
|
||||
float: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.update .version-status .icon {
|
||||
padding-right: .5em;
|
||||
}
|
||||
|
||||
.update table.version .version-title {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.update table.version .version-details {
|
||||
padding-left: .5em;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.update table.version .version-links {
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.update .check-manually {
|
||||
padding-right: 1em;
|
||||
}
|
|
@ -1,701 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Code required only when comparing available updates to existing data.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch an array of installed and enabled projects.
|
||||
*
|
||||
* This is only responsible for generating an array of projects (taking into
|
||||
* account projects that include more than one module or theme). Other
|
||||
* information like the specific version and install type (official release,
|
||||
* dev snapshot, etc) is handled later in update_process_project_info() since
|
||||
* that logic is only required when preparing the status report, not for
|
||||
* fetching the available release data.
|
||||
*
|
||||
* This array is fairly expensive to construct, since it involves a lot of
|
||||
* disk I/O, so we cache the results into the {cache_update} table using the
|
||||
* 'update_project_projects' cache ID. However, since this is not the data
|
||||
* about available updates fetched from the network, it is ok to invalidate it
|
||||
* somewhat quickly. If we keep this data for very long, site administrators
|
||||
* are more likely to see incorrect results if they upgrade to a newer version
|
||||
* of a module or theme but do not visit certain pages that automatically
|
||||
* clear this cache.
|
||||
*
|
||||
* @see update_process_project_info()
|
||||
* @see update_calculate_project_data()
|
||||
* @see update_project_cache()
|
||||
*/
|
||||
function update_get_projects() {
|
||||
static $projects = array();
|
||||
if (empty($projects)) {
|
||||
// Retrieve the projects from cache, if present.
|
||||
$projects = update_project_cache('update_project_projects');
|
||||
if (empty($projects)) {
|
||||
// Still empty, so we have to rebuild the cache.
|
||||
_update_process_info_list($projects, module_rebuild_cache(), 'module');
|
||||
_update_process_info_list($projects, system_theme_data(), 'theme');
|
||||
// Allow other modules to alter projects before fetching and comparing.
|
||||
drupal_alter('update_projects', $projects);
|
||||
// Cache the site's project data for at most 1 hour.
|
||||
_update_cache_set('update_project_projects', $projects, time() + 3600);
|
||||
}
|
||||
}
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate an array of project data.
|
||||
*/
|
||||
function _update_process_info_list(&$projects, $list, $project_type) {
|
||||
foreach ($list as $file) {
|
||||
// A disabled base theme of an enabled sub-theme still has all of its code
|
||||
// run by the sub-theme, so we include it in our "enabled" projects list.
|
||||
if (!$file->status && !empty($file->sub_themes)) {
|
||||
foreach ($file->sub_themes as $key => $name) {
|
||||
// Build a list of enabled sub-themes.
|
||||
if ($list[$key]->status) {
|
||||
$file->enabled_sub_themes[$key] = $name;
|
||||
}
|
||||
}
|
||||
// If there are no enabled subthemes, we should ingore this theme and go
|
||||
// on to the next one.
|
||||
if (empty($file->enabled_sub_themes)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elseif (empty($file->status)) {
|
||||
// Skip disabled modules or themes.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if the .info file is broken.
|
||||
if (empty($file->info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the .info doesn't define the 'project', try to figure it out.
|
||||
if (!isset($file->info['project'])) {
|
||||
$file->info['project'] = update_get_project_name($file);
|
||||
}
|
||||
|
||||
// If we still don't know the 'project', give up.
|
||||
if (empty($file->info['project'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't already know it, grab the change time on the .info file
|
||||
// itself. Note: we need to use the ctime, not the mtime (modification
|
||||
// time) since many (all?) tar implementations will go out of their way to
|
||||
// set the mtime on the files it creates to the timestamps recorded in the
|
||||
// tarball. We want to see the last time the file was changed on disk,
|
||||
// which is left alone by tar and correctly set to the time the .info file
|
||||
// was unpacked.
|
||||
if (!isset($file->info['_info_file_ctime'])) {
|
||||
$info_filename = dirname($file->filename) .'/'. $file->name .'.info';
|
||||
$file->info['_info_file_ctime'] = filectime($info_filename);
|
||||
}
|
||||
|
||||
if (!isset($file->info['datestamp'])) {
|
||||
$file->info['datestamp'] = 0;
|
||||
}
|
||||
|
||||
$project_name = $file->info['project'];
|
||||
|
||||
// Add a list of sub-themes that "depend on" the project and a list of base
|
||||
// themes that are "required by" the project.
|
||||
if ($project_name == 'drupal') {
|
||||
// Drupal core is always required, so this extra info would be noise.
|
||||
$sub_themes = array();
|
||||
$base_themes = array();
|
||||
}
|
||||
else {
|
||||
// Add list of enabled sub-themes.
|
||||
$sub_themes = !empty($file->enabled_sub_themes) ? $file->enabled_sub_themes : array();
|
||||
// Add list of base themes.
|
||||
$base_themes = !empty($file->base_themes) ? $file->base_themes : array();
|
||||
}
|
||||
|
||||
if (!isset($projects[$project_name])) {
|
||||
// Only process this if we haven't done this project, since a single
|
||||
// project can have multiple modules or themes.
|
||||
$projects[$project_name] = array(
|
||||
'name' => $project_name,
|
||||
// Only save attributes from the .info file we care about so we do not
|
||||
// bloat our RAM usage needlessly.
|
||||
'info' => update_filter_project_info($file->info),
|
||||
'datestamp' => $file->info['datestamp'],
|
||||
'includes' => array($file->name => $file->info['name']),
|
||||
'project_type' => $project_name == 'drupal' ? 'core' : $project_type,
|
||||
'sub_themes' => $sub_themes,
|
||||
'base_themes' => $base_themes,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$projects[$project_name]['includes'][$file->name] = $file->info['name'];
|
||||
$projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
|
||||
$projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
|
||||
$projects[$project_name]['sub_themes'] = array_merge($projects[$project_name]['sub_themes'], $sub_themes);
|
||||
$projects[$project_name]['base_themes'] = array_merge($projects[$project_name]['base_themes'], $base_themes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a $file object (as returned by system_get_files_database()), figure
|
||||
* out what project it belongs to.
|
||||
*
|
||||
* @see system_get_files_database()
|
||||
*/
|
||||
function update_get_project_name($file) {
|
||||
$project_name = '';
|
||||
if (isset($file->info['project'])) {
|
||||
$project_name = $file->info['project'];
|
||||
}
|
||||
elseif (isset($file->info['package']) && (strpos($file->info['package'], 'Core -') !== FALSE)) {
|
||||
$project_name = 'drupal';
|
||||
}
|
||||
elseif (in_array($file->name, array('bluemarine', 'chameleon', 'garland', 'marvin', 'pushbutton'))) {
|
||||
// Unfortunately, there's no way to tell if a theme is part of core,
|
||||
// so we must hard-code a list here.
|
||||
$project_name = 'drupal';
|
||||
}
|
||||
return $project_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the list of projects on the system to figure out the currently
|
||||
* installed versions, and other information that is required before we can
|
||||
* compare against the available releases to produce the status report.
|
||||
*
|
||||
* @param $projects
|
||||
* Array of project information from update_get_projects().
|
||||
*/
|
||||
function update_process_project_info(&$projects) {
|
||||
foreach ($projects as $key => $project) {
|
||||
// Assume an official release until we see otherwise.
|
||||
$install_type = 'official';
|
||||
|
||||
$info = $project['info'];
|
||||
|
||||
if (isset($info['version'])) {
|
||||
// Check for development snapshots
|
||||
if (preg_match('@(dev|HEAD)@', $info['version'])) {
|
||||
$install_type = 'dev';
|
||||
}
|
||||
|
||||
// Figure out what the currently installed major version is. We need
|
||||
// to handle both contribution (e.g. "5.x-1.3", major = 1) and core
|
||||
// (e.g. "5.1", major = 5) version strings.
|
||||
$matches = array();
|
||||
if (preg_match('/^(\d+\.x-)?(\d+)\..*$/', $info['version'], $matches)) {
|
||||
$info['major'] = $matches[2];
|
||||
}
|
||||
elseif (!isset($info['major'])) {
|
||||
// This would only happen for version strings that don't follow the
|
||||
// drupal.org convention. We let contribs define "major" in their
|
||||
// .info in this case, and only if that's missing would we hit this.
|
||||
$info['major'] = -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No version info available at all.
|
||||
$install_type = 'unknown';
|
||||
$info['version'] = t('Unknown');
|
||||
$info['major'] = -1;
|
||||
}
|
||||
|
||||
// Finally, save the results we care about into the $projects array.
|
||||
$projects[$key]['existing_version'] = $info['version'];
|
||||
$projects[$key]['existing_major'] = $info['major'];
|
||||
$projects[$key]['install_type'] = $install_type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the installed projects and the available release data retrieved from
|
||||
* remote servers, calculate the current status.
|
||||
*
|
||||
* This function is the heart of the update status feature. It iterates over
|
||||
* every currently installed project. For each one, it first checks if the
|
||||
* project has been flagged with a special status like "unsupported" or
|
||||
* "insecure", or if the project node itself has been unpublished. In any of
|
||||
* those cases, the project is marked with an error and the next project is
|
||||
* considered.
|
||||
*
|
||||
* If the project itself is valid, the function decides what major release
|
||||
* series to consider. The project defines what the currently supported major
|
||||
* versions are for each version of core, so the first step is to make sure
|
||||
* the current version is still supported. If so, that's the target version.
|
||||
* If the current version is unsupported, the project maintainer's recommended
|
||||
* major version is used. There's also a check to make sure that this function
|
||||
* never recommends an earlier release than the currently installed major
|
||||
* version.
|
||||
*
|
||||
* Given a target major version, it scans the available releases looking for
|
||||
* the specific release to recommend (avoiding beta releases and development
|
||||
* snapshots if possible). This is complicated to describe, but an example
|
||||
* will help clarify. For the target major version, find the highest patch
|
||||
* level. If there is a release at that patch level with no extra ("beta",
|
||||
* etc), then we recommend the release at that patch level with the most
|
||||
* recent release date. If every release at that patch level has extra (only
|
||||
* betas), then recommend the latest release from the previous patch
|
||||
* level. For example:
|
||||
*
|
||||
* 1.6-bugfix <-- recommended version because 1.6 already exists.
|
||||
* 1.6
|
||||
*
|
||||
* or
|
||||
*
|
||||
* 1.6-beta
|
||||
* 1.5 <-- recommended version because no 1.6 exists.
|
||||
* 1.4
|
||||
*
|
||||
* It also looks for the latest release from the same major version, even a
|
||||
* beta release, to display to the user as the "Latest version" option.
|
||||
* Additionally, it finds the latest official release from any higher major
|
||||
* versions that have been released to provide a set of "Also available"
|
||||
* options.
|
||||
*
|
||||
* Finally, and most importantly, it keeps scanning the release history until
|
||||
* it gets to the currently installed release, searching for anything marked
|
||||
* as a security update. If any security updates have been found between the
|
||||
* recommended release and the installed version, all of the releases that
|
||||
* included a security fix are recorded so that the site administrator can be
|
||||
* warned their site is insecure, and links pointing to the release notes for
|
||||
* each security update can be included (which, in turn, will link to the
|
||||
* official security announcements for each vulnerability).
|
||||
*
|
||||
* This function relies on the fact that the .xml release history data comes
|
||||
* sorted based on major version and patch level, then finally by release date
|
||||
* if there are multiple releases such as betas from the same major.patch
|
||||
* version (e.g. 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
|
||||
* snapshots for a given major version are always listed last.
|
||||
*
|
||||
* The results of this function are expensive to compute, especially on sites
|
||||
* with lots of modules or themes, since it involves a lot of comparisons and
|
||||
* other operations. Therefore, we cache the results into the {cache_update}
|
||||
* table using the 'update_project_data' cache ID. However, since this is not
|
||||
* the data about available updates fetched from the network, it is ok to
|
||||
* invalidate it somewhat quickly. If we keep this data for very long, site
|
||||
* administrators are more likely to see incorrect results if they upgrade to
|
||||
* a newer version of a module or theme but do not visit certain pages that
|
||||
* automatically clear this cache.
|
||||
*
|
||||
* @param $available
|
||||
* Array of data about available project releases.
|
||||
*
|
||||
* @see update_get_available()
|
||||
* @see update_get_projects()
|
||||
* @see update_process_project_info()
|
||||
* @see update_project_cache()
|
||||
*/
|
||||
function update_calculate_project_data($available) {
|
||||
// Retrieve the projects from cache, if present.
|
||||
$projects = update_project_cache('update_project_data');
|
||||
// If $projects is empty, then the cache must be rebuilt.
|
||||
// Otherwise, return the cached data and skip the rest of the function.
|
||||
if (!empty($projects)) {
|
||||
return $projects;
|
||||
}
|
||||
$projects = update_get_projects();
|
||||
update_process_project_info($projects);
|
||||
foreach ($projects as $project => $project_info) {
|
||||
if (isset($available[$project])) {
|
||||
|
||||
// If the project status is marked as something bad, there's nothing
|
||||
// else to consider.
|
||||
if (isset($available[$project]['project_status'])) {
|
||||
switch ($available[$project]['project_status']) {
|
||||
case 'insecure':
|
||||
$projects[$project]['status'] = UPDATE_NOT_SECURE;
|
||||
if (empty($projects[$project]['extra'])) {
|
||||
$projects[$project]['extra'] = array();
|
||||
}
|
||||
$projects[$project]['extra'][] = array(
|
||||
'class' => 'project-not-secure',
|
||||
'label' => t('Project not secure'),
|
||||
'data' => t('This project has been labeled insecure by the Drupal security team, and is no longer available for download. Immediately disabling everything included by this project is strongly recommended!'),
|
||||
);
|
||||
break;
|
||||
case 'unpublished':
|
||||
case 'revoked':
|
||||
$projects[$project]['status'] = UPDATE_REVOKED;
|
||||
if (empty($projects[$project]['extra'])) {
|
||||
$projects[$project]['extra'] = array();
|
||||
}
|
||||
$projects[$project]['extra'][] = array(
|
||||
'class' => 'project-revoked',
|
||||
'label' => t('Project revoked'),
|
||||
'data' => t('This project has been revoked, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
|
||||
);
|
||||
break;
|
||||
case 'unsupported':
|
||||
$projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
|
||||
if (empty($projects[$project]['extra'])) {
|
||||
$projects[$project]['extra'] = array();
|
||||
}
|
||||
$projects[$project]['extra'][] = array(
|
||||
'class' => 'project-not-supported',
|
||||
'label' => t('Project not supported'),
|
||||
'data' => t('This project is no longer supported, and is no longer available for download. Disabling everything included by this project is strongly recommended!'),
|
||||
);
|
||||
break;
|
||||
case 'not-fetched':
|
||||
$projects[$project]['status'] = UPDATE_NOT_FETCHED;
|
||||
$projects[$project]['reason'] = t('Failed to fetch available update data');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Assume anything else (e.g. 'published') is valid and we should
|
||||
// perform the rest of the logic in this function.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($projects[$project]['status'])) {
|
||||
// We already know the status for this project, so there's nothing
|
||||
// else to compute. Just record everything else we fetched from the
|
||||
// XML file into our projects array and move to the next project.
|
||||
$projects[$project] += $available[$project];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Figure out the target major version.
|
||||
$existing_major = $project_info['existing_major'];
|
||||
$supported_majors = array();
|
||||
if (isset($available[$project]['supported_majors'])) {
|
||||
$supported_majors = explode(',', $available[$project]['supported_majors']);
|
||||
}
|
||||
elseif (isset($available[$project]['default_major'])) {
|
||||
// Older release history XML file without supported or recommended.
|
||||
$supported_majors[] = $available[$project]['default_major'];
|
||||
}
|
||||
|
||||
if (in_array($existing_major, $supported_majors)) {
|
||||
// Still supported, stay at the current major version.
|
||||
$target_major = $existing_major;
|
||||
}
|
||||
elseif (isset($available[$project]['recommended_major'])) {
|
||||
// Since 'recommended_major' is defined, we know this is the new XML
|
||||
// format. Therefore, we know the current release is unsupported since
|
||||
// its major version was not in the 'supported_majors' list. We should
|
||||
// find the best release from the recommended major version.
|
||||
$target_major = $available[$project]['recommended_major'];
|
||||
$projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
|
||||
}
|
||||
elseif (isset($available[$project]['default_major'])) {
|
||||
// Older release history XML file without recommended, so recommend
|
||||
// the currently defined "default_major" version.
|
||||
$target_major = $available[$project]['default_major'];
|
||||
}
|
||||
else {
|
||||
// Malformed XML file? Stick with the current version.
|
||||
$target_major = $existing_major;
|
||||
}
|
||||
|
||||
// Make sure we never tell the admin to downgrade. If we recommended an
|
||||
// earlier version than the one they're running, they'd face an
|
||||
// impossible data migration problem, since Drupal never supports a DB
|
||||
// downgrade path. In the unfortunate case that what they're running is
|
||||
// unsupported, and there's nothing newer for them to upgrade to, we
|
||||
// can't print out a "Recommended version", but just have to tell them
|
||||
// what they have is unsupported and let them figure it out.
|
||||
$target_major = max($existing_major, $target_major);
|
||||
|
||||
$version_patch_changed = '';
|
||||
$patch = '';
|
||||
|
||||
// Defend ourselves from XML history files that contain no releases.
|
||||
if (empty($available[$project]['releases'])) {
|
||||
$projects[$project]['status'] = UPDATE_UNKNOWN;
|
||||
$projects[$project]['reason'] = t('No available releases found');
|
||||
continue;
|
||||
}
|
||||
foreach ($available[$project]['releases'] as $version => $release) {
|
||||
// First, if this is the existing release, check a few conditions.
|
||||
if ($projects[$project]['existing_version'] === $version) {
|
||||
if (isset($release['terms']['Release type']) &&
|
||||
in_array('Insecure', $release['terms']['Release type'])) {
|
||||
$projects[$project]['status'] = UPDATE_NOT_SECURE;
|
||||
}
|
||||
elseif ($release['status'] == 'unpublished') {
|
||||
$projects[$project]['status'] = UPDATE_REVOKED;
|
||||
if (empty($projects[$project]['extra'])) {
|
||||
$projects[$project]['extra'] = array();
|
||||
}
|
||||
$projects[$project]['extra'][] = array(
|
||||
'class' => 'release-revoked',
|
||||
'label' => t('Release revoked'),
|
||||
'data' => t('Your currently installed release has been revoked, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
|
||||
);
|
||||
}
|
||||
elseif (isset($release['terms']['Release type']) &&
|
||||
in_array('Unsupported', $release['terms']['Release type'])) {
|
||||
$projects[$project]['status'] = UPDATE_NOT_SUPPORTED;
|
||||
if (empty($projects[$project]['extra'])) {
|
||||
$projects[$project]['extra'] = array();
|
||||
}
|
||||
$projects[$project]['extra'][] = array(
|
||||
'class' => 'release-not-supported',
|
||||
'label' => t('Release not supported'),
|
||||
'data' => t('Your currently installed release is now unsupported, and is no longer available for download. Disabling everything included in this release or upgrading is strongly recommended!'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, ignore unpublished, insecure, or unsupported releases.
|
||||
if ($release['status'] == 'unpublished' ||
|
||||
(isset($release['terms']['Release type']) &&
|
||||
(in_array('Insecure', $release['terms']['Release type']) ||
|
||||
in_array('Unsupported', $release['terms']['Release type'])))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if this is a higher major version than our target and yet still
|
||||
// supported. If so, record it as an "Also available" release.
|
||||
if ($release['version_major'] > $target_major) {
|
||||
if (in_array($release['version_major'], $supported_majors)) {
|
||||
if (!isset($available[$project]['also'])) {
|
||||
$available[$project]['also'] = array();
|
||||
}
|
||||
if (!isset($available[$project]['also'][$release['version_major']])) {
|
||||
$available[$project]['also'][$release['version_major']] = $version;
|
||||
}
|
||||
}
|
||||
// Otherwise, this release can't matter to us, since it's neither
|
||||
// from the release series we're currently using nor the recommended
|
||||
// release. We don't even care about security updates for this
|
||||
// branch, since if a project maintainer puts out a security release
|
||||
// at a higher major version and not at the lower major version,
|
||||
// they must remove the lower version from the supported major
|
||||
// versions at the same time, in which case we won't hit this code.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for the 'latest version' if we haven't found it yet. Latest is
|
||||
// defined as the most recent version for the target major version.
|
||||
if (!isset($available[$project]['latest_version'])
|
||||
&& $release['version_major'] == $target_major) {
|
||||
$available[$project]['latest_version'] = $version;
|
||||
}
|
||||
|
||||
// Look for the development snapshot release for this branch.
|
||||
if (!isset($available[$project]['dev_version'])
|
||||
&& $release['version_major'] == $target_major
|
||||
&& isset($release['version_extra'])
|
||||
&& $release['version_extra'] == 'dev') {
|
||||
$available[$project]['dev_version'] = $version;
|
||||
}
|
||||
|
||||
// Look for the 'recommended' version if we haven't found it yet (see
|
||||
// phpdoc at the top of this function for the definition).
|
||||
if (!isset($available[$project]['recommended'])
|
||||
&& $release['version_major'] == $target_major
|
||||
&& isset($release['version_patch'])) {
|
||||
if ($patch != $release['version_patch']) {
|
||||
$patch = $release['version_patch'];
|
||||
$version_patch_changed = $release['version'];
|
||||
}
|
||||
if (empty($release['version_extra']) && $patch == $release['version_patch']) {
|
||||
$available[$project]['recommended'] = $version_patch_changed;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop searching once we hit the currently installed version.
|
||||
if ($projects[$project]['existing_version'] === $version) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're running a dev snapshot and have a timestamp, stop
|
||||
// searching for security updates once we hit an official release
|
||||
// older than what we've got. Allow 100 seconds of leeway to handle
|
||||
// differences between the datestamp in the .info file and the
|
||||
// timestamp of the tarball itself (which are usually off by 1 or 2
|
||||
// seconds) so that we don't flag that as a new release.
|
||||
if ($projects[$project]['install_type'] == 'dev') {
|
||||
if (empty($projects[$project]['datestamp'])) {
|
||||
// We don't have current timestamp info, so we can't know.
|
||||
continue;
|
||||
}
|
||||
elseif (isset($release['date']) && ($projects[$project]['datestamp'] + 100 > $release['date'])) {
|
||||
// We're newer than this, so we can skip it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// See if this release is a security update.
|
||||
if (isset($release['terms']['Release type'])
|
||||
&& in_array('Security update', $release['terms']['Release type'])) {
|
||||
$projects[$project]['security updates'][] = $release;
|
||||
}
|
||||
}
|
||||
|
||||
// If we were unable to find a recommended version, then make the latest
|
||||
// version the recommended version if possible.
|
||||
if (!isset($available[$project]['recommended']) && isset($available[$project]['latest_version'])) {
|
||||
$available[$project]['recommended'] = $available[$project]['latest_version'];
|
||||
}
|
||||
|
||||
// Stash the info about available releases into our $projects array.
|
||||
$projects[$project] += $available[$project];
|
||||
|
||||
//
|
||||
// Check to see if we need an update or not.
|
||||
//
|
||||
|
||||
if (!empty($projects[$project]['security updates'])) {
|
||||
// If we found security updates, that always trumps any other status.
|
||||
$projects[$project]['status'] = UPDATE_NOT_SECURE;
|
||||
}
|
||||
|
||||
if (isset($projects[$project]['status'])) {
|
||||
// If we already know the status, we're done.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't know what to recommend, there's nothing we can report.
|
||||
// Bail out early.
|
||||
if (!isset($projects[$project]['recommended'])) {
|
||||
$projects[$project]['status'] = UPDATE_UNKNOWN;
|
||||
$projects[$project]['reason'] = t('No available releases found');
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're running a dev snapshot, compare the date of the dev snapshot
|
||||
// with the latest official version, and record the absolute latest in
|
||||
// 'latest_dev' so we can correctly decide if there's a newer release
|
||||
// than our current snapshot.
|
||||
if ($projects[$project]['install_type'] == 'dev') {
|
||||
if (isset($available[$project]['dev_version']) && $available[$project]['releases'][$available[$project]['dev_version']]['date'] > $available[$project]['releases'][$available[$project]['latest_version']]['date']) {
|
||||
$projects[$project]['latest_dev'] = $available[$project]['dev_version'];
|
||||
}
|
||||
else {
|
||||
$projects[$project]['latest_dev'] = $available[$project]['latest_version'];
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out the status, based on what we've seen and the install type.
|
||||
switch ($projects[$project]['install_type']) {
|
||||
case 'official':
|
||||
if ($projects[$project]['existing_version'] === $projects[$project]['recommended'] || $projects[$project]['existing_version'] === $projects[$project]['latest_version']) {
|
||||
$projects[$project]['status'] = UPDATE_CURRENT;
|
||||
}
|
||||
else {
|
||||
$projects[$project]['status'] = UPDATE_NOT_CURRENT;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'dev':
|
||||
$latest = $available[$project]['releases'][$projects[$project]['latest_dev']];
|
||||
if (empty($projects[$project]['datestamp'])) {
|
||||
$projects[$project]['status'] = UPDATE_NOT_CHECKED;
|
||||
$projects[$project]['reason'] = t('Unknown release date');
|
||||
}
|
||||
elseif (($projects[$project]['datestamp'] + 100 > $latest['date'])) {
|
||||
$projects[$project]['status'] = UPDATE_CURRENT;
|
||||
}
|
||||
else {
|
||||
$projects[$project]['status'] = UPDATE_NOT_CURRENT;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$projects[$project]['status'] = UPDATE_UNKNOWN;
|
||||
$projects[$project]['reason'] = t('Invalid info');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$projects[$project]['status'] = UPDATE_UNKNOWN;
|
||||
$projects[$project]['reason'] = t('No available releases found');
|
||||
}
|
||||
}
|
||||
// Give other modules a chance to alter the status (for example, to allow a
|
||||
// contrib module to provide fine-grained settings to ignore specific
|
||||
// projects or releases).
|
||||
drupal_alter('update_status', $projects);
|
||||
|
||||
// Cache the site's update status for at most 1 hour.
|
||||
_update_cache_set('update_project_data', $projects, time() + 3600);
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve data from {cache_update} or empty the cache when necessary.
|
||||
*
|
||||
* Two very expensive arrays computed by this module are the list of all
|
||||
* installed modules and themes (and .info data, project associations, etc),
|
||||
* and the current status of the site relative to the currently available
|
||||
* releases. These two arrays are cached in the {cache_update} table and used
|
||||
* whenever possible. The cache is cleared whenever the administrator visits
|
||||
* the status report, available updates report, or the module or theme
|
||||
* administration pages, since we should always recompute the most current
|
||||
* values on any of those pages.
|
||||
*
|
||||
* Note: while both of these arrays are expensive to compute (in terms of disk
|
||||
* I/O and some fairly heavy CPU processing), neither of these is the actual
|
||||
* data about available updates that we have to fetch over the network from
|
||||
* updates.drupal.org. That information is stored with the
|
||||
* 'update_available_releases' cache ID -- it needs to persist longer than 1
|
||||
* hour and never get invalidated just by visiting a page on the site.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache id of data to return from the cache. Valid options are
|
||||
* 'update_project_data' and 'update_project_projects'.
|
||||
*
|
||||
* @return
|
||||
* The cached value of the $projects array generated by
|
||||
* update_calculate_project_data() or update_get_projects(), or an empty
|
||||
* array when the cache is cleared.
|
||||
*/
|
||||
function update_project_cache($cid) {
|
||||
$projects = array();
|
||||
|
||||
// On certain paths, we should clear the cache and recompute the projects or
|
||||
// update status of the site to avoid presenting stale information.
|
||||
$q = $_GET['q'];
|
||||
$paths = array('admin/build/modules', 'admin/build/themes', 'admin/reports', 'admin/reports/updates', 'admin/reports/status', 'admin/reports/updates/check');
|
||||
if (in_array($q, $paths)) {
|
||||
_update_cache_clear($cid);
|
||||
}
|
||||
else {
|
||||
$cache = _update_cache_get($cid);
|
||||
if (!empty($cache->data) && $cache->expire > time()) {
|
||||
$projects = $cache->data;
|
||||
}
|
||||
}
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the project .info data to only save attributes we need.
|
||||
*
|
||||
* @param array $info
|
||||
* Array of .info file data as returned by drupal_parse_info_file().
|
||||
*
|
||||
* @return
|
||||
* Array of .info file data we need for the Update manager.
|
||||
*
|
||||
* @see _update_process_info_list()
|
||||
*/
|
||||
function update_filter_project_info($info) {
|
||||
$whitelist = array(
|
||||
'_info_file_ctime',
|
||||
'datestamp',
|
||||
'major',
|
||||
'name',
|
||||
'package',
|
||||
'project',
|
||||
'project status url',
|
||||
'version',
|
||||
);
|
||||
$whitelist = array_flip($whitelist);
|
||||
foreach ($info as $key => $value) {
|
||||
if (!isset($whitelist[$key])) {
|
||||
unset($info[$key]);
|
||||
}
|
||||
}
|
||||
return $info;
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
|
||||
.update .project {
|
||||
font-weight: bold;
|
||||
font-size: 110%;
|
||||
padding-left: .25em; /* LTR */
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.update .version-status {
|
||||
float: right; /* LTR */
|
||||
padding-right: 10px; /* LTR */
|
||||
font-size: 110%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.update .version-status .icon {
|
||||
padding-left: .5em; /* LTR */
|
||||
}
|
||||
|
||||
.update .version-date {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.update .info {
|
||||
margin: 0;
|
||||
padding: 1em 1em .25em 1em;
|
||||
}
|
||||
|
||||
.update tr td {
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.update tr.error {
|
||||
background: #fcc;
|
||||
}
|
||||
|
||||
.update tr.error .version-recommended {
|
||||
background: #fdd;
|
||||
}
|
||||
|
||||
.update tr.ok {
|
||||
background: #dfd;
|
||||
}
|
||||
|
||||
.update tr.warning {
|
||||
background: #ffd;
|
||||
}
|
||||
|
||||
.update tr.warning .version-recommended {
|
||||
background: #ffe;
|
||||
}
|
||||
|
||||
.current-version, .new-version {
|
||||
direction: ltr; /* Note: version numbers should always be LTR. */
|
||||
}
|
||||
|
||||
.update tr.unknown {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
table.update,
|
||||
.update table.version {
|
||||
width: 100%;
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
.update table.version tbody {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.update table.version tr,
|
||||
.update table.version td {
|
||||
line-height: .9em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.update table.version .version-title {
|
||||
padding-left: 1em; /* LTR */
|
||||
width: 14em;
|
||||
}
|
||||
|
||||
.update table.version .version-details {
|
||||
padding-right: .5em; /* LTR */
|
||||
}
|
||||
|
||||
.update table.version .version-links {
|
||||
text-align: right; /* LTR */
|
||||
padding-right: 1em; /* LTR */
|
||||
}
|
||||
|
||||
.update table.version-security .version-title {
|
||||
color: #970F00;
|
||||
}
|
||||
|
||||
.update table.version-recommended-strong .version-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.update .security-error {
|
||||
font-weight: bold;
|
||||
color: #970F00;
|
||||
}
|
||||
|
||||
.update .check-manually {
|
||||
padding-left: 1em; /* LTR */
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Code required only when fetching information about available updates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback to manually check the update status without cron.
|
||||
*/
|
||||
function update_manual_status() {
|
||||
if (_update_refresh()) {
|
||||
drupal_set_message(t('Attempted to fetch information about all available new releases and updates.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('Unable to fetch any information about available new releases and updates.'), 'error');
|
||||
}
|
||||
drupal_goto('admin/reports/updates');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch project info via XML from a central server.
|
||||
*/
|
||||
function _update_refresh() {
|
||||
static $fail = array();
|
||||
global $base_url;
|
||||
module_load_include('inc', 'update', 'update.compare');
|
||||
|
||||
// Since we're fetching new available update data, we want to clear
|
||||
// our cache of both the projects we care about, and the current update
|
||||
// status of the site. We do *not* want to clear the cache of available
|
||||
// releases just yet, since that data (even if it's stale) can be useful
|
||||
// during update_get_projects(); for example, to modules that implement
|
||||
// hook_system_info_alter() such as cvs_deploy.
|
||||
_update_cache_clear('update_project_projects');
|
||||
_update_cache_clear('update_project_data');
|
||||
|
||||
$available = array();
|
||||
$data = array();
|
||||
$site_key = md5($base_url . drupal_get_private_key());
|
||||
$projects = update_get_projects();
|
||||
|
||||
// Now that we have the list of projects, we should also clear our cache of
|
||||
// available release data, since even if we fail to fetch new data, we need
|
||||
// to clear out the stale data at this point.
|
||||
_update_cache_clear('update_available_releases');
|
||||
$max_fetch_attempts = variable_get('update_max_fetch_attempts', UPDATE_MAX_FETCH_ATTEMPTS);
|
||||
|
||||
foreach ($projects as $key => $project) {
|
||||
$url = _update_build_fetch_url($project, $site_key);
|
||||
$fetch_url_base = _update_get_fetch_url_base($project);
|
||||
if (empty($fail[$fetch_url_base]) || count($fail[$fetch_url_base]) < $max_fetch_attempts) {
|
||||
$xml = drupal_http_request($url);
|
||||
if (isset($xml->data)) {
|
||||
$data[] = $xml->data;
|
||||
}
|
||||
else {
|
||||
// Connection likely broken; prepare to give up.
|
||||
$fail[$fetch_url_base][$key] = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Didn't bother trying to fetch.
|
||||
$fail[$fetch_url_base][$key] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
$parser = new update_xml_parser;
|
||||
$available = $parser->parse($data);
|
||||
}
|
||||
if (!empty($available) && is_array($available)) {
|
||||
// Record the projects where we failed to fetch data.
|
||||
foreach ($fail as $fetch_url_base => $failures) {
|
||||
foreach ($failures as $key => $value) {
|
||||
$available[$key]['project_status'] = 'not-fetched';
|
||||
}
|
||||
}
|
||||
$frequency = variable_get('update_check_frequency', 1);
|
||||
_update_cache_set('update_available_releases', $available, time() + (60 * 60 * 24 * $frequency));
|
||||
watchdog('update', 'Attempted to fetch information about all available new releases and updates.', array(), WATCHDOG_NOTICE, l(t('view'), 'admin/reports/updates'));
|
||||
}
|
||||
else {
|
||||
watchdog('update', 'Unable to fetch any information about available new releases and updates.', array(), WATCHDOG_ERROR, l(t('view'), 'admin/reports/updates'));
|
||||
}
|
||||
// Whether this worked or not, we did just (try to) check for updates.
|
||||
variable_set('update_last_check', time());
|
||||
return $available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the URL to fetch information about project updates.
|
||||
*
|
||||
* This figures out the right URL to use, based on the project's .info file
|
||||
* and the global defaults. Appends optional query arguments when the site is
|
||||
* configured to report usage stats.
|
||||
*
|
||||
* @param $project
|
||||
* The array of project information from update_get_projects().
|
||||
* @param $site_key
|
||||
* The anonymous site key hash (optional).
|
||||
*
|
||||
* @see update_refresh()
|
||||
* @see update_get_projects()
|
||||
*/
|
||||
function _update_build_fetch_url($project, $site_key = '') {
|
||||
$name = $project['name'];
|
||||
$url = _update_get_fetch_url_base($project);
|
||||
$url .= '/'. $name .'/'. DRUPAL_CORE_COMPATIBILITY;
|
||||
// Only append a site_key and the version information if we have a site_key
|
||||
// in the first place, and if this is not a disabled module or theme. We do
|
||||
// not want to record usage statistics for disabled code.
|
||||
if (!empty($site_key) && (strpos($project['project_type'], 'disabled') === FALSE)) {
|
||||
$url .= (strpos($url, '?') === TRUE) ? '&' : '?';
|
||||
$url .= 'site_key=';
|
||||
$url .= rawurlencode($site_key);
|
||||
if (!empty($project['info']['version'])) {
|
||||
$url .= '&version=';
|
||||
$url .= rawurlencode($project['info']['version']);
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base of the URL to fetch available update data for a project.
|
||||
*
|
||||
* @param $project
|
||||
* The array of project information from update_get_projects().
|
||||
* @return
|
||||
* The base of the URL used for fetching available update data. This does
|
||||
* not include the path elements to specify a particular project, version,
|
||||
* site_key, etc.
|
||||
*
|
||||
* @see _update_build_fetch_url()
|
||||
*/
|
||||
function _update_get_fetch_url_base($project) {
|
||||
return isset($project['info']['project status url']) ? $project['info']['project status url'] : variable_get('update_fetch_url', UPDATE_DEFAULT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any notifications that should be done once cron fetches new data.
|
||||
*
|
||||
* This method checks the status of the site using the new data and depending
|
||||
* on the configuration of the site, notifies administrators via email if there
|
||||
* are new releases or missing security updates.
|
||||
*
|
||||
* @see update_requirements()
|
||||
*/
|
||||
function _update_cron_notify() {
|
||||
include_once './includes/install.inc';
|
||||
$status = update_requirements('runtime');
|
||||
$params = array();
|
||||
$notify_all = (variable_get('update_notification_threshold', 'all') == 'all');
|
||||
foreach (array('core', 'contrib') as $report_type) {
|
||||
$type = 'update_'. $report_type;
|
||||
if (isset($status[$type]['severity'])
|
||||
&& ($status[$type]['severity'] == REQUIREMENT_ERROR || ($notify_all && $status[$type]['reason'] == UPDATE_NOT_CURRENT))) {
|
||||
$params[$report_type] = $status[$type]['reason'];
|
||||
}
|
||||
}
|
||||
if (!empty($params)) {
|
||||
$notify_list = variable_get('update_notify_emails', '');
|
||||
if (!empty($notify_list)) {
|
||||
$default_language = language_default();
|
||||
foreach ($notify_list as $target) {
|
||||
if ($target_user = user_load(array('mail' => $target))) {
|
||||
$target_language = user_preferred_language($target_user);
|
||||
}
|
||||
else {
|
||||
$target_language = $default_language;
|
||||
}
|
||||
drupal_mail('update', 'status_notify', $target, $target_language, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* XML Parser object to read Drupal's release history info files.
|
||||
* This uses PHP4's lame XML parsing, but it works.
|
||||
*/
|
||||
class update_xml_parser {
|
||||
var $projects = array();
|
||||
var $current_project;
|
||||
var $current_release;
|
||||
var $current_term;
|
||||
var $current_tag;
|
||||
var $current_object;
|
||||
|
||||
/**
|
||||
* Parse an array of XML data files.
|
||||
*/
|
||||
function parse($data) {
|
||||
foreach ($data as $datum) {
|
||||
$parser = xml_parser_create();
|
||||
xml_set_object($parser, $this);
|
||||
xml_set_element_handler($parser, 'start', 'end');
|
||||
xml_set_character_data_handler($parser, "data");
|
||||
xml_parse($parser, $datum);
|
||||
xml_parser_free($parser);
|
||||
}
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
function start($parser, $name, $attr) {
|
||||
$this->current_tag = $name;
|
||||
switch ($name) {
|
||||
case 'PROJECT':
|
||||
unset($this->current_object);
|
||||
$this->current_project = array();
|
||||
$this->current_object = &$this->current_project;
|
||||
break;
|
||||
case 'RELEASE':
|
||||
unset($this->current_object);
|
||||
$this->current_release = array();
|
||||
$this->current_object = &$this->current_release;
|
||||
break;
|
||||
case 'TERM':
|
||||
unset($this->current_object);
|
||||
$this->current_term = array();
|
||||
$this->current_object = &$this->current_term;
|
||||
break;
|
||||
case 'FILE':
|
||||
unset($this->current_object);
|
||||
$this->current_file = array();
|
||||
$this->current_object = &$this->current_file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function end($parser, $name) {
|
||||
switch ($name) {
|
||||
case 'PROJECT':
|
||||
unset($this->current_object);
|
||||
$this->projects[$this->current_project['short_name']] = $this->current_project;
|
||||
$this->current_project = array();
|
||||
break;
|
||||
case 'RELEASE':
|
||||
unset($this->current_object);
|
||||
$this->current_project['releases'][$this->current_release['version']] = $this->current_release;
|
||||
break;
|
||||
case 'RELEASES':
|
||||
$this->current_object = &$this->current_project;
|
||||
break;
|
||||
case 'TERM':
|
||||
unset($this->current_object);
|
||||
$term_name = $this->current_term['name'];
|
||||
if (!isset($this->current_release['terms'])) {
|
||||
$this->current_release['terms'] = array();
|
||||
}
|
||||
if (!isset($this->current_release['terms'][$term_name])) {
|
||||
$this->current_release['terms'][$term_name] = array();
|
||||
}
|
||||
$this->current_release['terms'][$term_name][] = $this->current_term['value'];
|
||||
break;
|
||||
case 'TERMS':
|
||||
$this->current_object = &$this->current_release;
|
||||
break;
|
||||
case 'FILE':
|
||||
unset($this->current_object);
|
||||
$this->current_release['files'][] = $this->current_file;
|
||||
break;
|
||||
case 'FILES':
|
||||
$this->current_object = &$this->current_release;
|
||||
break;
|
||||
default:
|
||||
$this->current_object[strtolower($this->current_tag)] = trim($this->current_object[strtolower($this->current_tag)]);
|
||||
$this->current_tag = '';
|
||||
}
|
||||
}
|
||||
|
||||
function data($parser, $data) {
|
||||
if ($this->current_tag && !in_array($this->current_tag, array('PROJECT', 'RELEASE', 'RELEASES', 'TERM', 'TERMS', 'FILE', 'FILES'))) {
|
||||
$tag = strtolower($this->current_tag);
|
||||
if (isset($this->current_object[$tag])) {
|
||||
$this->current_object[$tag] .= $data;
|
||||
}
|
||||
else {
|
||||
$this->current_object[$tag] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Update status
|
||||
description = Checks the status of available updates for Drupal and your installed modules and themes.
|
||||
version = VERSION
|
||||
package = Core - optional
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function update_install() {
|
||||
// Create cache table.
|
||||
drupal_install_schema('update');
|
||||
// Remove stale variables from update_status 5.x contrib, if any.
|
||||
_update_remove_update_status_variables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function update_uninstall() {
|
||||
// Remove cache table.
|
||||
drupal_uninstall_schema('update');
|
||||
// Clear any variables that might be in use
|
||||
$variables = array(
|
||||
'update_check_frequency',
|
||||
'update_fetch_url',
|
||||
'update_last_check',
|
||||
'update_notification_threshold',
|
||||
'update_notify_emails',
|
||||
);
|
||||
foreach ($variables as $variable) {
|
||||
variable_del($variable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function update_schema() {
|
||||
$schema['cache_update'] = drupal_get_schema_unprocessed('system', 'cache');
|
||||
$schema['cache_update']['description'] = 'Cache table for the Update module to store information about available releases, fetched from central server.';
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper to clear out stale variables from update_status 5.x contrib.
|
||||
*
|
||||
* @see update_install()
|
||||
* @see update_update_6000()
|
||||
*/
|
||||
function _update_remove_update_status_variables() {
|
||||
variable_del('update_status_settings');
|
||||
variable_del('update_status_notify_emails');
|
||||
variable_del('update_status_check_frequency');
|
||||
variable_del('update_status_notification_threshold');
|
||||
variable_del('update_status_last');
|
||||
variable_del('update_status_fetch_url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out stale variables from update_status.
|
||||
*/
|
||||
function update_update_6000() {
|
||||
_update_remove_update_status_variables();
|
||||
return array();
|
||||
}
|
|
@ -1,626 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* The "Update status" module checks for available updates of Drupal core and
|
||||
* any installed contributed modules and themes. It warns site administrators
|
||||
* if newer releases are available via the system status report
|
||||
* (admin/reports/status), the module and theme pages, and optionally via email.
|
||||
*/
|
||||
|
||||
/**
|
||||
* URL to check for updates, if a given project doesn't define its own.
|
||||
*/
|
||||
define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
|
||||
|
||||
// These are internally used constants for this code, do not modify.
|
||||
|
||||
/**
|
||||
* Project is missing security update(s).
|
||||
*/
|
||||
define('UPDATE_NOT_SECURE', 1);
|
||||
|
||||
/**
|
||||
* Current release has been unpublished and is no longer available.
|
||||
*/
|
||||
define('UPDATE_REVOKED', 2);
|
||||
|
||||
/**
|
||||
* Current release is no longer supported by the project maintainer.
|
||||
*/
|
||||
define('UPDATE_NOT_SUPPORTED', 3);
|
||||
|
||||
/**
|
||||
* Project has a new release available, but it is not a security release.
|
||||
*/
|
||||
define('UPDATE_NOT_CURRENT', 4);
|
||||
|
||||
/**
|
||||
* Project is up to date.
|
||||
*/
|
||||
define('UPDATE_CURRENT', 5);
|
||||
|
||||
/**
|
||||
* Project's status cannot be checked.
|
||||
*/
|
||||
define('UPDATE_NOT_CHECKED', -1);
|
||||
|
||||
/**
|
||||
* No available update data was found for project.
|
||||
*/
|
||||
define('UPDATE_UNKNOWN', -2);
|
||||
|
||||
/**
|
||||
* There was a failure fetching available update data for this project.
|
||||
*/
|
||||
define('UPDATE_NOT_FETCHED', -3);
|
||||
|
||||
/**
|
||||
* Maximum number of attempts to fetch available update data from a given host.
|
||||
*/
|
||||
define('UPDATE_MAX_FETCH_ATTEMPTS', 2);
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function update_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/reports/updates':
|
||||
$output = '<p>'. t('Here you can find information about available updates for your installed modules and themes. Note that each module or theme is part of a "project", which may or may not have the same name, and might include multiple modules or themes within it.') .'</p>';
|
||||
$output .= '<p>'. t('To extend the functionality or to change the look of your site, a number of contributed <a href="@modules">modules</a> and <a href="@themes">themes</a> are available.', array('@modules' => 'http://drupal.org/project/modules', '@themes' => 'http://drupal.org/project/themes')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/build/themes':
|
||||
case 'admin/build/modules':
|
||||
include_once './includes/install.inc';
|
||||
$status = update_requirements('runtime');
|
||||
foreach (array('core', 'contrib') as $report_type) {
|
||||
$type = 'update_'. $report_type;
|
||||
if (isset($status[$type]['severity'])) {
|
||||
if ($status[$type]['severity'] == REQUIREMENT_ERROR) {
|
||||
drupal_set_message($status[$type]['description'], 'error');
|
||||
}
|
||||
elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) {
|
||||
drupal_set_message($status[$type]['description'], 'warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
return '<p>'. t('See the <a href="@available_updates">available updates</a> page for information on installed modules and themes with new versions released.', array('@available_updates' => url('admin/reports/updates'))) .'</p>';
|
||||
|
||||
case 'admin/reports/updates/settings':
|
||||
case 'admin/reports/status':
|
||||
// These two pages don't need additional nagging.
|
||||
break;
|
||||
|
||||
case 'admin/help#update':
|
||||
$output = '<p>'. t("The Update status module periodically checks for new versions of your site's software (including contributed modules and themes), and alerts you to available updates.") .'</p>';
|
||||
$output .= '<p>'. t('The <a href="@update-report">report of available updates</a> will alert you when new releases are available for download. You may configure options for update checking frequency and notifications at the <a href="@update-settings">Update status module settings page</a>.', array('@update-report' => url('admin/reports/updates'), '@update-settings' => url('admin/reports/updates/settings'))) .'</p>';
|
||||
$output .= '<p>'. t('Please note that in order to provide this information, anonymous usage statistics are sent to drupal.org. If desired, you may disable the Update status module from the <a href="@modules">module administration page</a>.', array('@modules' => url('admin/build/modules'))) .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@update">Update status module</a>.', array('@update' => 'http://drupal.org/handbook/modules/update')) .'</p>';
|
||||
return $output;
|
||||
|
||||
default:
|
||||
// Otherwise, if we're on *any* admin page and there's a security
|
||||
// update missing, print an error message about it.
|
||||
if (arg(0) == 'admin' && strpos($path, '#') === FALSE
|
||||
&& user_access('administer site configuration')) {
|
||||
include_once './includes/install.inc';
|
||||
$status = update_requirements('runtime');
|
||||
foreach (array('core', 'contrib') as $report_type) {
|
||||
$type = 'update_'. $report_type;
|
||||
if (isset($status[$type])
|
||||
&& isset($status[$type]['reason'])
|
||||
&& $status[$type]['reason'] === UPDATE_NOT_SECURE) {
|
||||
drupal_set_message($status[$type]['description'], 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function update_menu() {
|
||||
$items = array();
|
||||
|
||||
$items['admin/reports/updates'] = array(
|
||||
'title' => 'Available updates',
|
||||
'description' => 'Get a status report about available updates for your installed modules and themes.',
|
||||
'page callback' => 'update_status',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'update.report.inc',
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/reports/updates/list'] = array(
|
||||
'title' => 'List',
|
||||
'page callback' => 'update_status',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'update.report.inc',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
$items['admin/reports/updates/settings'] = array(
|
||||
'title' => 'Settings',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('update_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'update.settings.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
$items['admin/reports/updates/check'] = array(
|
||||
'title' => 'Manual update check',
|
||||
'page callback' => 'update_manual_status',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'update.fetch.inc',
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the hook_theme() registry.
|
||||
*/
|
||||
function update_theme() {
|
||||
return array(
|
||||
'update_settings' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
'update_report' => array(
|
||||
'arguments' => array('data' => NULL),
|
||||
),
|
||||
'update_version' => array(
|
||||
'arguments' => array('version' => NULL, 'tag' => NULL, 'class' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_requirements().
|
||||
*
|
||||
* @return
|
||||
* An array describing the status of the site regarding available updates.
|
||||
* If there is no update data, only one record will be returned, indicating
|
||||
* that the status of core can't be determined. If data is available, there
|
||||
* will be two records: one for core, and another for all of contrib
|
||||
* (assuming there are any contributed modules or themes enabled on the
|
||||
* site). In addition to the fields expected by hook_requirements ('value',
|
||||
* 'severity', and optionally 'description'), this array will contain a
|
||||
* 'reason' attribute, which is an integer constant to indicate why the
|
||||
* given status is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or
|
||||
* UPDATE_UNKNOWN). This is used for generating the appropriate e-mail
|
||||
* notification messages during update_cron(), and might be useful for other
|
||||
* modules that invoke update_requirements() to find out if the site is up
|
||||
* to date or not.
|
||||
*
|
||||
* @see _update_message_text()
|
||||
* @see _update_cron_notify()
|
||||
*/
|
||||
function update_requirements($phase) {
|
||||
if ($phase == 'runtime') {
|
||||
if ($available = update_get_available(FALSE)) {
|
||||
module_load_include('inc', 'update', 'update.compare');
|
||||
$data = update_calculate_project_data($available);
|
||||
// First, populate the requirements for core:
|
||||
$requirements['update_core'] = _update_requirement_check($data['drupal'], 'core');
|
||||
// We don't want to check drupal a second time.
|
||||
unset($data['drupal']);
|
||||
if (!empty($data)) {
|
||||
// Now, sort our $data array based on each project's status. The
|
||||
// status constants are numbered in the right order of precedence, so
|
||||
// we just need to make sure the projects are sorted in ascending
|
||||
// order of status, and we can look at the first project we find.
|
||||
uasort($data, '_update_project_status_sort');
|
||||
$first_project = reset($data);
|
||||
$requirements['update_contrib'] = _update_requirement_check($first_project, 'contrib');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$requirements['update_core']['title'] = t('Drupal core update status');
|
||||
$requirements['update_core']['value'] = t('No update data available');
|
||||
$requirements['update_core']['severity'] = REQUIREMENT_WARNING;
|
||||
$requirements['update_core']['reason'] = UPDATE_UNKNOWN;
|
||||
$requirements['update_core']['description'] = _update_no_data();
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper method to fill in the requirements array.
|
||||
*
|
||||
* This is shared for both core and contrib to generate the right elements in
|
||||
* the array for hook_requirements().
|
||||
*
|
||||
* @param $project
|
||||
* Array of information about the project we're testing as returned by
|
||||
* update_calculate_project_data().
|
||||
* @param $type
|
||||
* What kind of project is this ('core' or 'contrib').
|
||||
*
|
||||
* @return
|
||||
* An array to be included in the nested $requirements array.
|
||||
*
|
||||
* @see hook_requirements()
|
||||
* @see update_requirements()
|
||||
* @see update_calculate_project_data()
|
||||
*/
|
||||
function _update_requirement_check($project, $type) {
|
||||
$requirement = array();
|
||||
if ($type == 'core') {
|
||||
$requirement['title'] = t('Drupal core update status');
|
||||
}
|
||||
else {
|
||||
$requirement['title'] = t('Module and theme update status');
|
||||
}
|
||||
$status = $project['status'];
|
||||
if ($status != UPDATE_CURRENT) {
|
||||
$requirement['reason'] = $status;
|
||||
$requirement['description'] = _update_message_text($type, $status, TRUE);
|
||||
$requirement['severity'] = REQUIREMENT_ERROR;
|
||||
}
|
||||
switch ($status) {
|
||||
case UPDATE_NOT_SECURE:
|
||||
$requirement_label = t('Not secure!');
|
||||
break;
|
||||
case UPDATE_REVOKED:
|
||||
$requirement_label = t('Revoked!');
|
||||
break;
|
||||
case UPDATE_NOT_SUPPORTED:
|
||||
$requirement_label = t('Unsupported release');
|
||||
break;
|
||||
case UPDATE_NOT_CURRENT:
|
||||
$requirement_label = t('Out of date');
|
||||
$requirement['severity'] = REQUIREMENT_WARNING;
|
||||
break;
|
||||
case UPDATE_UNKNOWN:
|
||||
case UPDATE_NOT_CHECKED:
|
||||
case UPDATE_NOT_FETCHED:
|
||||
$requirement_label = isset($project['reason']) ? $project['reason'] : t('Can not determine status');
|
||||
$requirement['severity'] = REQUIREMENT_WARNING;
|
||||
break;
|
||||
default:
|
||||
$requirement_label = t('Up to date');
|
||||
}
|
||||
if ($status != UPDATE_CURRENT && $type == 'core' && isset($project['recommended'])) {
|
||||
$requirement_label .= ' '. t('(version @version available)', array('@version' => $project['recommended']));
|
||||
}
|
||||
$requirement['value'] = l($requirement_label, 'admin/reports/updates');
|
||||
return $requirement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_cron().
|
||||
*/
|
||||
function update_cron() {
|
||||
$frequency = variable_get('update_check_frequency', 1);
|
||||
$interval = 60 * 60 * 24 * $frequency;
|
||||
// Cron should check for updates if there is no update data cached or if the
|
||||
// configured update interval has elapsed.
|
||||
if (!_update_cache_get('update_available_releases') || ((time() - variable_get('update_last_check', 0)) > $interval)) {
|
||||
update_refresh();
|
||||
_update_cron_notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_form_alter().
|
||||
*
|
||||
* Adds a submit handler to the system modules and themes forms, so that if a
|
||||
* site admin saves either form, we invalidate the cache of available updates.
|
||||
*
|
||||
* @see update_invalidate_cache()
|
||||
*/
|
||||
function update_form_alter(&$form, $form_state, $form_id) {
|
||||
if ($form_id == 'system_modules' || $form_id == 'system_themes_form' ) {
|
||||
$form['#submit'][] = 'update_invalidate_cache';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a warning message when there is no data about available updates.
|
||||
*/
|
||||
function _update_no_data() {
|
||||
$destination = drupal_get_destination();
|
||||
return t('No information is available about potential new releases for currently installed modules and themes. To check for updates, you may need to <a href="@run_cron">run cron</a> or you can <a href="@check_manually">check manually</a>. Please note that checking for available updates can take a long time, so please be patient.', array(
|
||||
'@run_cron' => url('admin/reports/status/run-cron', array('query' => $destination)),
|
||||
'@check_manually' => url('admin/reports/updates/check', array('query' => $destination)),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to try to get the update information from the cache
|
||||
* if possible, and to refresh the cache when necessary.
|
||||
*
|
||||
* In addition to checking the cache lifetime, this function also ensures that
|
||||
* there are no .info files for enabled modules or themes that have a newer
|
||||
* modification timestamp than the last time we checked for available update
|
||||
* data. If any .info file was modified, it almost certainly means a new
|
||||
* version of something was installed. Without fresh available update data,
|
||||
* the logic in update_calculate_project_data() will be wrong and produce
|
||||
* confusing, bogus results.
|
||||
*
|
||||
* @param $refresh
|
||||
* Boolean to indicate if this method should refresh the cache automatically
|
||||
* if there's no data.
|
||||
*
|
||||
* @see update_refresh()
|
||||
* @see update_get_projects()
|
||||
*/
|
||||
function update_get_available($refresh = FALSE) {
|
||||
module_load_include('inc', 'update', 'update.compare');
|
||||
$available = array();
|
||||
|
||||
// First, make sure that none of the .info files have a change time
|
||||
// newer than the last time we checked for available updates.
|
||||
$needs_refresh = FALSE;
|
||||
$last_check = variable_get('update_last_check', 0);
|
||||
$projects = update_get_projects();
|
||||
foreach ($projects as $key => $project) {
|
||||
if ($project['info']['_info_file_ctime'] > $last_check) {
|
||||
$needs_refresh = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$needs_refresh && ($cache = _update_cache_get('update_available_releases')) && $cache->expire > time()) {
|
||||
$available = $cache->data;
|
||||
}
|
||||
elseif ($needs_refresh || $refresh) {
|
||||
// If we need to refresh due to a newer .info file, ignore the argument
|
||||
// and force the refresh (e.g., even for update_requirements()) to prevent
|
||||
// bogus results.
|
||||
$available = update_refresh();
|
||||
}
|
||||
return $available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to load the include file and then refresh the release data.
|
||||
*/
|
||||
function update_refresh() {
|
||||
module_load_include('inc', 'update', 'update.fetch');
|
||||
return _update_refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_mail().
|
||||
*
|
||||
* Constructs the email notification message when the site is out of date.
|
||||
*
|
||||
* @param $key
|
||||
* Unique key to indicate what message to build, always 'status_notify'.
|
||||
* @param $message
|
||||
* Reference to the message array being built.
|
||||
* @param $params
|
||||
* Array of parameters to indicate what kind of text to include in the
|
||||
* message body. This is a keyed array of message type ('core' or 'contrib')
|
||||
* as the keys, and the status reason constant (UPDATE_NOT_SECURE, etc) for
|
||||
* the values.
|
||||
*
|
||||
* @see drupal_mail()
|
||||
* @see _update_cron_notify()
|
||||
* @see _update_message_text()
|
||||
*/
|
||||
function update_mail($key, &$message, $params) {
|
||||
$language = $message['language'];
|
||||
$langcode = $language->language;
|
||||
$message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), $langcode);
|
||||
foreach ($params as $msg_type => $msg_reason) {
|
||||
$message['body'][] = _update_message_text($msg_type, $msg_reason, FALSE, $language);
|
||||
}
|
||||
$message['body'][] = t('See the available updates page for more information:', array(), $langcode) ."\n". url('admin/reports/updates', array('absolute' => TRUE, 'language' => $language));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the appropriate message text when the site is out
|
||||
* of date or missing a security update.
|
||||
*
|
||||
* These error messages are shared by both update_requirements() for the
|
||||
* site-wide status report at admin/reports/status and in the body of the
|
||||
* notification emails generated by update_cron().
|
||||
*
|
||||
* @param $msg_type
|
||||
* String to indicate what kind of message to generate. Can be either
|
||||
* 'core' or 'contrib'.
|
||||
* @param $msg_reason
|
||||
* Integer constant specifying why message is generated.
|
||||
* @param $report_link
|
||||
* Boolean that controls if a link to the updates report should be added.
|
||||
* @param $language
|
||||
* An optional language object to use.
|
||||
* @return
|
||||
* The properly translated error message for the given key.
|
||||
*/
|
||||
function _update_message_text($msg_type, $msg_reason, $report_link = FALSE, $language = NULL) {
|
||||
$langcode = isset($language) ? $language->language : NULL;
|
||||
$text = '';
|
||||
switch ($msg_reason) {
|
||||
case UPDATE_NOT_SECURE:
|
||||
if ($msg_type == 'core') {
|
||||
$text = t('There is a security update available for your version of Drupal. To ensure the security of your server, you should update immediately!', array(), $langcode);
|
||||
}
|
||||
else {
|
||||
$text = t('There are security updates available for one or more of your modules or themes. To ensure the security of your server, you should update immediately!', array(), $langcode);
|
||||
}
|
||||
break;
|
||||
|
||||
case UPDATE_REVOKED:
|
||||
if ($msg_type == 'core') {
|
||||
$text = t('Your version of Drupal has been revoked and is no longer available for download. Upgrading is strongly recommended!', array(), $langcode);
|
||||
}
|
||||
else {
|
||||
$text = t('The installed version of at least one of your modules or themes has been revoked and is no longer available for download. Upgrading or disabling is strongly recommended!', array(), $langcode);
|
||||
}
|
||||
break;
|
||||
|
||||
case UPDATE_NOT_SUPPORTED:
|
||||
if ($msg_type == 'core') {
|
||||
$text = t('Your version of Drupal is no longer supported. Upgrading is strongly recommended!', array(), $langcode);
|
||||
}
|
||||
else {
|
||||
$text = t('The installed version of at least one of your modules or themes is no longer supported. Upgrading or disabling is strongly recommended! Please see the project homepage for more details.', array(), $langcode);
|
||||
}
|
||||
break;
|
||||
|
||||
case UPDATE_NOT_CURRENT:
|
||||
if ($msg_type == 'core') {
|
||||
$text = t('There are updates available for your version of Drupal. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
|
||||
}
|
||||
else {
|
||||
$text = t('There are updates available for one or more of your modules or themes. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode);
|
||||
}
|
||||
break;
|
||||
|
||||
case UPDATE_UNKNOWN:
|
||||
case UPDATE_NOT_CHECKED:
|
||||
case UPDATE_NOT_FETCHED:
|
||||
if ($msg_type == 'core') {
|
||||
$text = t('There was a problem determining the status of available updates for your version of Drupal.', array(), $langcode);
|
||||
}
|
||||
else {
|
||||
$text = t('There was a problem determining the status of available updates for one or more of your modules or themes.', array(), $langcode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($report_link) {
|
||||
$text .= ' '. t('See the <a href="@available_updates">available updates</a> page for more information.', array('@available_updates' => url('admin/reports/updates', array('language' => $language))), $langcode);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private sort function to order projects based on their status.
|
||||
*
|
||||
* @see update_requirements()
|
||||
* @see uasort()
|
||||
*/
|
||||
function _update_project_status_sort($a, $b) {
|
||||
// The status constants are numerically in the right order, so we can
|
||||
// usually subtract the two to compare in the order we want. However,
|
||||
// negative status values should be treated as if they are huge, since we
|
||||
// always want them at the bottom of the list.
|
||||
$a_status = $a['status'] > 0 ? $a['status'] : (-10 * $a['status']);
|
||||
$b_status = $b['status'] > 0 ? $b['status'] : (-10 * $b['status']);
|
||||
return $a_status - $b_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup update_status_cache Private update status cache system
|
||||
* @{
|
||||
*
|
||||
* We specifically do NOT use the core cache API for saving the fetched data
|
||||
* about available updates. It is vitally important that this cache is only
|
||||
* cleared when we're populating it after successfully fetching new available
|
||||
* update data. Usage of the core cache API results in all sorts of potential
|
||||
* problems that would result in attempting to fetch available update data all
|
||||
* the time, including if a site has a "minimum cache lifetime" (which is both
|
||||
* a minimum and a maximum) defined, or if a site uses memcache or another
|
||||
* plug-able cache system that assumes volatile caches.
|
||||
*
|
||||
* Update module still uses the {cache_update} table, but instead of using
|
||||
* cache_set(), cache_get(), and cache_clear_all(), there are private helper
|
||||
* functions that implement these same basic tasks but ensure that the cache
|
||||
* is not prematurely cleared, and that the data is always stored in the
|
||||
* database, even if memcache or another cache backend is in use.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Store data in the private update status cache table.
|
||||
*
|
||||
* Note: this function completely ignores the {cache_update}.headers field
|
||||
* since that is meaningless for the kinds of data we're caching.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID to save the data with.
|
||||
* @param $data
|
||||
* The data to store.
|
||||
* @param $expire
|
||||
* One of the following values:
|
||||
* - CACHE_PERMANENT: Indicates that the item should never be removed except
|
||||
* by explicitly using _update_cache_clear() or update_invalidate_cache().
|
||||
* - A Unix timestamp: Indicates that the item should be kept at least until
|
||||
* the given time, after which it will be invalidated.
|
||||
*/
|
||||
function _update_cache_set($cid, $data, $expire) {
|
||||
$serialized = 0;
|
||||
if (is_object($data) || is_array($data)) {
|
||||
$data = serialize($data);
|
||||
$serialized = 1;
|
||||
}
|
||||
$created = time();
|
||||
db_query("UPDATE {cache_update} SET data = %b, created = %d, expire = %d, serialized = %d WHERE cid = '%s'", $data, $created, $expire, $serialized, $cid);
|
||||
if (!db_affected_rows()) {
|
||||
@db_query("INSERT INTO {cache_update} (cid, data, created, expire, serialized) VALUES ('%s', %b, %d, %d, %d)", $cid, $data, $created, $expire, $serialized);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve data from the private update status cache table.
|
||||
*
|
||||
* @param $cid
|
||||
* The cache ID to retrieve.
|
||||
* @return
|
||||
* The data for the given cache ID, or NULL if the ID was not found.
|
||||
*/
|
||||
function _update_cache_get($cid) {
|
||||
$cache = db_fetch_object(db_query("SELECT data, created, expire, serialized FROM {cache_update} WHERE cid = '%s'", $cid));
|
||||
if (isset($cache->data)) {
|
||||
$cache->data = db_decode_blob($cache->data);
|
||||
if ($cache->serialized) {
|
||||
$cache->data = unserialize($cache->data);
|
||||
}
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates specific cached data relating to update status.
|
||||
*
|
||||
* @param $cid
|
||||
* Optional cache ID of the record to clear from the private update module
|
||||
* cache. If empty, all records will be cleared from the table.
|
||||
*/
|
||||
function _update_cache_clear($cid = NULL) {
|
||||
if (empty($cid)) {
|
||||
db_query("TRUNCATE TABLE {cache_update}");
|
||||
}
|
||||
else {
|
||||
db_query("DELETE FROM {cache_update} WHERE cid = '%s'", $cid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_flush_caches().
|
||||
*
|
||||
* Called from update.php (among others) to flush the caches.
|
||||
* Since we're running update.php, we are likely to install a new version of
|
||||
* something, in which case, we want to check for available update data again.
|
||||
* However, because we have our own caching system, we need to directly clear
|
||||
* the database table ourselves at this point and return nothing, for example,
|
||||
* on sites that use memcache where cache_clear_all() won't know how to purge
|
||||
* this data.
|
||||
*
|
||||
* However, we only want to do this from update.php, since otherwise, we'd
|
||||
* lose all the available update data on every cron run. So, we specifically
|
||||
* check if the site is in MAINTENANCE_MODE == 'update' (which indicates
|
||||
* update.php is running, not update module... alas for overloaded names).
|
||||
*/
|
||||
function update_flush_caches() {
|
||||
if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
|
||||
_update_cache_clear();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all cached data relating to update status.
|
||||
*/
|
||||
function update_invalidate_cache() {
|
||||
_update_cache_clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup update_status_cache".
|
||||
*/
|
|
@ -1,264 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Code required only when rendering the available updates report.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Menu callback. Generate a page about the update status of projects.
|
||||
*/
|
||||
function update_status() {
|
||||
if ($available = update_get_available(TRUE)) {
|
||||
module_load_include('inc', 'update', 'update.compare');
|
||||
$data = update_calculate_project_data($available);
|
||||
return theme('update_report', $data);
|
||||
}
|
||||
else {
|
||||
return theme('update_report', _update_no_data());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme project status report.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_update_report($data) {
|
||||
$last = variable_get('update_last_check', 0);
|
||||
$output = '<div class="update checked">'. ($last ? t('Last checked: @time ago', array('@time' => format_interval(time() - $last))) : t('Last checked: never'));
|
||||
$output .= ' <span class="check-manually">('. l(t('Check manually'), 'admin/reports/updates/check') .')</span>';
|
||||
$output .= "</div>\n";
|
||||
|
||||
if (!is_array($data)) {
|
||||
$output .= '<p>'. $data .'</p>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
$header = array();
|
||||
$rows = array();
|
||||
|
||||
$notification_level = variable_get('update_notification_threshold', 'all');
|
||||
|
||||
foreach ($data as $project) {
|
||||
switch ($project['status']) {
|
||||
case UPDATE_CURRENT:
|
||||
$class = 'ok';
|
||||
$icon = theme('image', 'misc/watchdog-ok.png', t('ok'), t('ok'));
|
||||
break;
|
||||
case UPDATE_UNKNOWN:
|
||||
case UPDATE_NOT_FETCHED:
|
||||
$class = 'unknown';
|
||||
$icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'));
|
||||
break;
|
||||
case UPDATE_NOT_SECURE:
|
||||
case UPDATE_REVOKED:
|
||||
case UPDATE_NOT_SUPPORTED:
|
||||
$class = 'error';
|
||||
$icon = theme('image', 'misc/watchdog-error.png', t('error'), t('error'));
|
||||
break;
|
||||
case UPDATE_NOT_CHECKED:
|
||||
case UPDATE_NOT_CURRENT:
|
||||
default:
|
||||
$class = 'warning';
|
||||
$icon = theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning'));
|
||||
break;
|
||||
}
|
||||
|
||||
$row = '<div class="version-status">';
|
||||
switch ($project['status']) {
|
||||
case UPDATE_NOT_SECURE:
|
||||
$row .= '<span class="security-error">'. t('Security update required!') .'</span>';
|
||||
break;
|
||||
case UPDATE_REVOKED:
|
||||
$row .= '<span class="revoked">'. t('Revoked!') .'</span>';
|
||||
break;
|
||||
case UPDATE_NOT_SUPPORTED:
|
||||
$row .= '<span class="not-supported">'. t('Not supported!') .'</span>';
|
||||
break;
|
||||
case UPDATE_NOT_CURRENT:
|
||||
$row .= '<span class="not-current">'. t('Update available') .'</span>';
|
||||
break;
|
||||
case UPDATE_CURRENT:
|
||||
$row .= '<span class="current">'. t('Up to date') .'</span>';
|
||||
break;
|
||||
default:
|
||||
$row .= check_plain($project['reason']);
|
||||
break;
|
||||
}
|
||||
$row .= '<span class="icon">'. $icon .'</span>';
|
||||
$row .= "</div>\n";
|
||||
|
||||
$row .= '<div class="project">';
|
||||
if (isset($project['title'])) {
|
||||
if (isset($project['link'])) {
|
||||
$row .= l($project['title'], $project['link']);
|
||||
}
|
||||
else {
|
||||
$row .= check_plain($project['title']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$row .= check_plain($project['name']);
|
||||
}
|
||||
$row .= ' '. check_plain($project['existing_version']);
|
||||
if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) {
|
||||
$row .= ' <span class="version-date">('. format_date($project['datestamp'], 'custom', 'Y-M-d') .')</span>';
|
||||
}
|
||||
$row .= "</div>\n";
|
||||
|
||||
$row .= "<div class=\"versions\">\n";
|
||||
|
||||
if (isset($project['recommended'])) {
|
||||
if ($project['status'] != UPDATE_CURRENT || $project['existing_version'] !== $project['recommended']) {
|
||||
|
||||
// First, figure out what to recommend.
|
||||
// If there's only 1 security update and it has the same version we're
|
||||
// recommending, give it the same CSS class as if it was recommended,
|
||||
// but don't print out a separate "Recommended" line for this project.
|
||||
if (!empty($project['security updates']) && count($project['security updates']) == 1 && $project['security updates'][0]['version'] === $project['recommended']) {
|
||||
$security_class = ' version-recommended version-recommended-strong';
|
||||
}
|
||||
else {
|
||||
$security_class = '';
|
||||
$version_class = 'version-recommended';
|
||||
// Apply an extra class if we're displaying both a recommended
|
||||
// version and anything else for an extra visual hint.
|
||||
if ($project['recommended'] !== $project['latest_version']
|
||||
|| !empty($project['also'])
|
||||
|| ($project['install_type'] == 'dev'
|
||||
&& isset($project['dev_version'])
|
||||
&& $project['latest_version'] !== $project['dev_version']
|
||||
&& $project['recommended'] !== $project['dev_version'])
|
||||
|| (isset($project['security updates'][0])
|
||||
&& $project['recommended'] !== $project['security updates'][0])
|
||||
) {
|
||||
$version_class .= ' version-recommended-strong';
|
||||
}
|
||||
$row .= theme('update_version', $project['releases'][$project['recommended']], t('Recommended version:'), $version_class);
|
||||
}
|
||||
|
||||
// Now, print any security updates.
|
||||
if (!empty($project['security updates'])) {
|
||||
foreach ($project['security updates'] as $security_update) {
|
||||
$row .= theme('update_version', $security_update, t('Security update:'), 'version-security'. $security_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($project['recommended'] !== $project['latest_version']) {
|
||||
$row .= theme('update_version', $project['releases'][$project['latest_version']], t('Latest version:'), 'version-latest');
|
||||
}
|
||||
if ($project['install_type'] == 'dev'
|
||||
&& $project['status'] != UPDATE_CURRENT
|
||||
&& isset($project['dev_version'])
|
||||
&& $project['recommended'] !== $project['dev_version']) {
|
||||
$row .= theme('update_version', $project['releases'][$project['dev_version']], t('Development version:'), 'version-latest');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($project['also'])) {
|
||||
foreach ($project['also'] as $also) {
|
||||
$row .= theme('update_version', $project['releases'][$also], t('Also available:'), 'version-also-available');
|
||||
}
|
||||
}
|
||||
|
||||
$row .= "</div>\n"; // versions div.
|
||||
|
||||
$row .= "<div class=\"info\">\n";
|
||||
if (!empty($project['extra'])) {
|
||||
$row .= '<div class="extra">'."\n";
|
||||
foreach ($project['extra'] as $key => $value) {
|
||||
$row .= '<div class="'. $value['class'] .'">';
|
||||
$row .= check_plain($value['label']) .': ';
|
||||
$row .= theme('placeholder', $value['data']);
|
||||
$row .= "</div>\n";
|
||||
}
|
||||
$row .= "</div>\n"; // extra div.
|
||||
}
|
||||
|
||||
$row .= '<div class="includes">';
|
||||
sort($project['includes']);
|
||||
$row .= t('Includes: %includes', array('%includes' => implode(', ', $project['includes'])));
|
||||
$row .= "</div>\n";
|
||||
|
||||
if (!empty($project['base_themes'])) {
|
||||
$row .= '<div class="basethemes">';
|
||||
sort($project['base_themes']);
|
||||
// We use !dependencies and manually call theme('placeholder') here to
|
||||
// avoid breakding the D6 string freeze. This identical string is
|
||||
// already in modules/system/system.admin.inc.
|
||||
$row .= t('Depends on: !dependencies', array('!dependencies' => theme('placeholder', implode(', ', $project['base_themes']))));
|
||||
$row .= "</div>\n";
|
||||
}
|
||||
|
||||
if (!empty($project['sub_themes'])) {
|
||||
$row .= '<div class="subthemes">';
|
||||
sort($project['sub_themes']);
|
||||
// We use !required and manually call theme('placeholder') here to avoid
|
||||
// breakding the D6 string freeze. This identical string is already in
|
||||
// modules/system/system.admin.inc.
|
||||
$row .= t('Required by: !required', array('!required' => theme('placeholder', implode(', ', $project['sub_themes']))));
|
||||
$row .= "</div>\n";
|
||||
}
|
||||
|
||||
$row .= "</div>\n"; // info div.
|
||||
|
||||
if (!isset($rows[$project['project_type']])) {
|
||||
$rows[$project['project_type']] = array();
|
||||
}
|
||||
$row_key = isset($project['title']) ? drupal_strtolower($project['title']) : drupal_strtolower($project['name']);
|
||||
$rows[$project['project_type']][$row_key] = array(
|
||||
'class' => $class,
|
||||
'data' => array($row),
|
||||
);
|
||||
}
|
||||
|
||||
$project_types = array(
|
||||
'core' => t('Drupal core'),
|
||||
'module' => t('Modules'),
|
||||
'theme' => t('Themes'),
|
||||
'disabled-module' => t('Disabled modules'),
|
||||
'disabled-theme' => t('Disabled themes'),
|
||||
);
|
||||
foreach ($project_types as $type_name => $type_label) {
|
||||
if (!empty($rows[$type_name])) {
|
||||
ksort($rows[$type_name]);
|
||||
$output .= "\n<h3>". $type_label ."</h3>\n";
|
||||
$output .= theme('table', $header, $rows[$type_name], array('class' => 'update'));
|
||||
}
|
||||
}
|
||||
drupal_add_css(drupal_get_path('module', 'update') .'/update.css');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the version display of a project.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_update_version($version, $tag, $class) {
|
||||
$output = '';
|
||||
$output .= '<table class="version '. $class .'">';
|
||||
$output .= '<tr>';
|
||||
$output .= '<td class="version-title">'. $tag ."</td>\n";
|
||||
$output .= '<td class="version-details">';
|
||||
$output .= l($version['version'], $version['release_link']);
|
||||
$output .= ' <span class="version-date">('. format_date($version['date'], 'custom', 'Y-M-d') .')</span>';
|
||||
$output .= "</td>\n";
|
||||
$output .= '<td class="version-links">';
|
||||
$links = array();
|
||||
$links['update-download'] = array(
|
||||
'title' => t('Download'),
|
||||
'href' => $version['download_link'],
|
||||
);
|
||||
$links['update-release-notes'] = array(
|
||||
'title' => t('Release notes'),
|
||||
'href' => $version['release_link'],
|
||||
);
|
||||
$output .= theme('links', $links);
|
||||
$output .= '</td>';
|
||||
$output .= '</tr>';
|
||||
$output .= "</table>\n";
|
||||
return $output;
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Code required only for the update status settings form.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Form builder for the update settings tab.
|
||||
*/
|
||||
function update_settings() {
|
||||
$form = array();
|
||||
|
||||
$notify_emails = variable_get('update_notify_emails', array());
|
||||
$form['update_notify_emails'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('E-mail addresses to notify when updates are available'),
|
||||
'#rows' => 4,
|
||||
'#default_value' => implode("\n", $notify_emails),
|
||||
'#description' => t('Whenever your site checks for available updates and finds new releases, it can notify a list of users via e-mail. Put each address on a separate line. If blank, no e-mails will be sent.'),
|
||||
);
|
||||
|
||||
$form['update_check_frequency'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Check for updates'),
|
||||
'#default_value' => variable_get('update_check_frequency', 1),
|
||||
'#options' => array(
|
||||
'1' => t('Daily'),
|
||||
'7' => t('Weekly'),
|
||||
),
|
||||
'#description' => t('Select how frequently you want to automatically check for new releases of your currently installed modules and themes.'),
|
||||
);
|
||||
|
||||
$form['update_notification_threshold'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('E-mail notification threshold'),
|
||||
'#default_value' => variable_get('update_notification_threshold', 'all'),
|
||||
'#options' => array(
|
||||
'all' => t('All newer versions'),
|
||||
'security' => t('Only security updates'),
|
||||
),
|
||||
'#description' => t('You can choose to send e-mail only if a security update is available, or to be notified about all newer versions. If there are updates available of Drupal core or any of your installed modules and themes, your site will always print a message on the <a href="@status_report">status report</a> page, and will also display an error message on administration pages if there is a security update.', array('@status_report' => url('admin/reports/status')))
|
||||
);
|
||||
|
||||
$form = system_settings_form($form);
|
||||
// Custom valiation callback for the email notification setting.
|
||||
$form['#validate'][] = 'update_settings_validate';
|
||||
// We need to call our own submit callback first, not the one from
|
||||
// system_settings_form(), so that we can process and save the emails.
|
||||
unset($form['#submit']);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation callback for the settings form.
|
||||
*
|
||||
* Validates the email addresses and ensures the field is formatted correctly.
|
||||
*/
|
||||
function update_settings_validate($form, &$form_state) {
|
||||
if (!empty($form_state['values']['update_notify_emails'])) {
|
||||
$valid = array();
|
||||
$invalid = array();
|
||||
foreach (explode("\n", trim($form_state['values']['update_notify_emails'])) as $email) {
|
||||
$email = trim($email);
|
||||
if (!empty($email)) {
|
||||
if (valid_email_address($email)) {
|
||||
$valid[] = $email;
|
||||
}
|
||||
else {
|
||||
$invalid[] = $email;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($invalid)) {
|
||||
$form_state['notify_emails'] = $valid;
|
||||
}
|
||||
elseif (count($invalid) == 1) {
|
||||
form_set_error('update_notify_emails', t('%email is not a valid e-mail address.', array('%email' => reset($invalid))));
|
||||
}
|
||||
else {
|
||||
form_set_error('update_notify_emails', t('%emails are not valid e-mail addresses.', array('%emails' => implode(', ', $invalid))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the settings tab.
|
||||
*/
|
||||
function update_settings_submit($form, $form_state) {
|
||||
$op = $form_state['values']['op'];
|
||||
|
||||
if ($op == t('Reset to defaults')) {
|
||||
unset($form_state['notify_emails']);
|
||||
}
|
||||
else {
|
||||
if (empty($form_state['notify_emails'])) {
|
||||
variable_del('update_notify_emails');
|
||||
}
|
||||
else {
|
||||
variable_set('update_notify_emails', $form_state['notify_emails']);
|
||||
}
|
||||
unset($form_state['notify_emails']);
|
||||
unset($form_state['values']['update_notify_emails']);
|
||||
}
|
||||
system_settings_form_submit($form, $form_state);
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Form API callback to validate the upload settings form.
|
||||
*/
|
||||
function upload_admin_settings_validate($form, &$form_state) {
|
||||
if (($form_state['values']['upload_max_resolution'] != '0')) {
|
||||
if (!preg_match('/^[0-9]+x[0-9]+$/', $form_state['values']['upload_max_resolution'])) {
|
||||
form_set_error('upload_max_resolution', t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
|
||||
}
|
||||
}
|
||||
|
||||
$default_uploadsize = $form_state['values']['upload_uploadsize_default'];
|
||||
$default_usersize = $form_state['values']['upload_usersize_default'];
|
||||
|
||||
$exceed_max_msg = t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))) .'<br/>';
|
||||
$more_info = t("Depending on your server environment, these settings may be changed in the system-wide php.ini file, a php.ini file in your Drupal root directory, in your Drupal site's settings.php file, or in the .htaccess file in your Drupal root directory.");
|
||||
|
||||
if (!is_numeric($default_uploadsize) || ($default_uploadsize <= 0)) {
|
||||
form_set_error('upload_uploadsize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
|
||||
}
|
||||
if (!is_numeric($default_usersize) || ($default_usersize <= 0)) {
|
||||
form_set_error('upload_usersize_default', t('The %role file size limit must be a number and greater than zero.', array('%role' => t('default'))));
|
||||
}
|
||||
if ($default_uploadsize * 1024 * 1024 > file_upload_max_size()) {
|
||||
form_set_error('upload_uploadsize_default', $exceed_max_msg . $more_info);
|
||||
$more_info = '';
|
||||
}
|
||||
if ($default_uploadsize > $default_usersize) {
|
||||
form_set_error('upload_uploadsize_default', t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => t('default'))));
|
||||
}
|
||||
|
||||
foreach ($form_state['values']['roles'] as $rid => $role) {
|
||||
$uploadsize = $form_state['values']['upload_uploadsize_'. $rid];
|
||||
$usersize = $form_state['values']['upload_usersize_'. $rid];
|
||||
|
||||
if (!is_numeric($uploadsize) || ($uploadsize <= 0)) {
|
||||
form_set_error('upload_uploadsize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
|
||||
}
|
||||
if (!is_numeric($usersize) || ($usersize <= 0)) {
|
||||
form_set_error('upload_usersize_'. $rid, t('The %role file size limit must be a number and greater than zero.', array('%role' => $role)));
|
||||
}
|
||||
if ($uploadsize * 1024 * 1024 > file_upload_max_size()) {
|
||||
form_set_error('upload_uploadsize_'. $rid, $exceed_max_msg . $more_info);
|
||||
$more_info = '';
|
||||
}
|
||||
if ($uploadsize > $usersize) {
|
||||
form_set_error('upload_uploadsize_'. $rid, t('The %role maximum file size per upload is greater than the total file size allowed per user', array('%role' => $role)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback for the upload settings form.
|
||||
*/
|
||||
function upload_admin_settings() {
|
||||
$upload_extensions_default = variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp');
|
||||
$upload_uploadsize_default = variable_get('upload_uploadsize_default', 1);
|
||||
$upload_usersize_default = variable_get('upload_usersize_default', 1);
|
||||
|
||||
$form['settings_general'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('General settings'),
|
||||
'#collapsible' => TRUE,
|
||||
);
|
||||
$form['settings_general']['upload_max_resolution'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Maximum resolution for uploaded images'),
|
||||
'#default_value' => variable_get('upload_max_resolution', 0),
|
||||
'#size' => 15,
|
||||
'#maxlength' => 10,
|
||||
'#description' => t('The maximum allowed image size (e.g. 640x480). Set to 0 for no restriction. If an <a href="!image-toolkit-link">image toolkit</a> is installed, files exceeding this value will be scaled down to fit.', array('!image-toolkit-link' => url('admin/settings/image-toolkit'))),
|
||||
'#field_suffix' => '<kbd>'. t('WIDTHxHEIGHT') .'</kbd>'
|
||||
);
|
||||
$form['settings_general']['upload_list_default'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('List files by default'),
|
||||
'#default_value' => variable_get('upload_list_default', 1),
|
||||
'#options' => array(0 => t('No'), 1 => t('Yes')),
|
||||
'#description' => t('Display attached files when viewing a post.'),
|
||||
);
|
||||
|
||||
$form['settings_general']['upload_extensions_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default permitted file extensions'),
|
||||
'#default_value' => $upload_extensions_default,
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('Default extensions that users can upload. Separate extensions with a space and do not include the leading dot.'),
|
||||
);
|
||||
$form['settings_general']['upload_uploadsize_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default maximum file size per upload'),
|
||||
'#default_value' => $upload_uploadsize_default,
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The default maximum file size a user can upload. If an image is uploaded and a maximum resolution is set, the size will be checked after the file has been resized.'),
|
||||
'#field_suffix' => t('MB'),
|
||||
);
|
||||
$form['settings_general']['upload_usersize_default'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Default total file size per user'),
|
||||
'#default_value' => $upload_usersize_default,
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The default maximum size of all files a user can have on the site.'),
|
||||
'#field_suffix' => t('MB'),
|
||||
);
|
||||
|
||||
$form['settings_general']['upload_max_size'] = array('#value' => '<p>'. t('Your PHP settings limit the maximum file size per upload to %size.', array('%size' => format_size(file_upload_max_size()))) .'</p>');
|
||||
|
||||
$roles = user_roles(FALSE, 'upload files');
|
||||
$form['roles'] = array('#type' => 'value', '#value' => $roles);
|
||||
|
||||
foreach ($roles as $rid => $role) {
|
||||
$form['settings_role_'. $rid] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Settings for @role', array('@role' => $role)),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['settings_role_'. $rid]['upload_extensions_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Permitted file extensions'),
|
||||
'#default_value' => variable_get('upload_extensions_'. $rid, $upload_extensions_default),
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('Extensions that users in this role can upload. Separate extensions with a space and do not include the leading dot.'),
|
||||
);
|
||||
$form['settings_role_'. $rid]['upload_uploadsize_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Maximum file size per upload'),
|
||||
'#default_value' => variable_get('upload_uploadsize_'. $rid, $upload_uploadsize_default),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The maximum size of a file a user can upload. If an image is uploaded and a maximum resolution is set, the size will be checked after the file has been resized.'),
|
||||
'#field_suffix' => t('MB'),
|
||||
);
|
||||
$form['settings_role_'. $rid]['upload_usersize_'. $rid] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Total file size per user'),
|
||||
'#default_value' => variable_get('upload_usersize_'. $rid, $upload_usersize_default),
|
||||
'#size' => 5,
|
||||
'#maxlength' => 5,
|
||||
'#description' => t('The maximum size of all files a user can have on the site.'),
|
||||
'#field_suffix' => t('MB'),
|
||||
);
|
||||
}
|
||||
|
||||
$form['#validate'] = array('upload_admin_settings_validate');
|
||||
|
||||
return system_settings_form($form);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
name = Upload
|
||||
description = Allows users to upload and attach files to content.
|
||||
package = Core - optional
|
||||
version = VERSION
|
||||
core = 6.x
|
||||
|
||||
; Information added by Drupal.org packaging script on 2016-02-24
|
||||
version = "6.38"
|
||||
project = "drupal"
|
||||
datestamp = "1456343372"
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_install().
|
||||
*/
|
||||
function upload_install() {
|
||||
// Create table. The upload table might have been created in the Drupal 5
|
||||
// to Drupal 6 upgrade, and was migrated from the file_revisions table. So
|
||||
// in this case, there is no need to create the table, it is already there.
|
||||
if (!db_table_exists('upload')) {
|
||||
drupal_install_schema('upload');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_uninstall().
|
||||
*/
|
||||
function upload_uninstall() {
|
||||
// Remove tables.
|
||||
drupal_uninstall_schema('upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_schema().
|
||||
*/
|
||||
function upload_schema() {
|
||||
$schema['upload'] = array(
|
||||
'description' => 'Stores uploaded file information and table associations.',
|
||||
'fields' => array(
|
||||
'fid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Primary Key: The {files}.fid.',
|
||||
),
|
||||
'nid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'The {node}.nid associated with the uploaded file.',
|
||||
),
|
||||
'vid' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'Primary Key: The {node}.vid associated with the uploaded file.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Description of the uploaded file.',
|
||||
),
|
||||
'list' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'Whether the file should be visibly listed on the node: yes(1) or no(0).',
|
||||
),
|
||||
'weight' => array(
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
'description' => 'Weight of this upload in relation to other uploads in this node.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('vid', 'fid'),
|
||||
'indexes' => array(
|
||||
'fid' => array('fid'),
|
||||
'nid' => array('nid'),
|
||||
),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
|
@ -1,642 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* File-handling and attaching files to nodes.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_help().
|
||||
*/
|
||||
function upload_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#upload':
|
||||
$output = '<p>'. t('The upload module allows users to upload files to the site. The ability to upload files is important for members of a community who want to share work. It is also useful to administrators who want to keep uploaded files connected to posts.') .'</p>';
|
||||
$output .= '<p>'. t('Users with the upload files permission can upload attachments to posts. Uploads may be enabled for specific content types on the content types settings page. Each user role can be customized to limit or control the file size of uploads, or the maximum dimension of image files.') .'</p>';
|
||||
$output .= '<p>'. t('For more information, see the online handbook entry for <a href="@upload">Upload module</a>.', array('@upload' => 'http://drupal.org/handbook/modules/upload/')) .'</p>';
|
||||
return $output;
|
||||
case 'admin/settings/uploads':
|
||||
return '<p>'. t('Users with the <a href="@permissions">upload files permission</a> can upload attachments. Users with the <a href="@permissions">view uploaded files permission</a> can view uploaded attachments. You can choose which post types can take attachments on the <a href="@types">content types settings</a> page.', array('@permissions' => url('admin/user/permissions', array('fragment' => 'module-upload')), '@types' => url('admin/content/types'))) .'</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_theme()
|
||||
*/
|
||||
function upload_theme() {
|
||||
return array(
|
||||
'upload_attachments' => array(
|
||||
'arguments' => array('files' => NULL),
|
||||
),
|
||||
'upload_form_current' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
'upload_form_new' => array(
|
||||
'arguments' => array('form' => NULL),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_perm().
|
||||
*/
|
||||
function upload_perm() {
|
||||
return array('upload files', 'view uploaded files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_link().
|
||||
*/
|
||||
function upload_link($type, $node = NULL, $teaser = FALSE) {
|
||||
$links = array();
|
||||
|
||||
// Display a link with the number of attachments
|
||||
if ($teaser && $type == 'node' && isset($node->files) && user_access('view uploaded files')) {
|
||||
$num_files = 0;
|
||||
foreach ($node->files as $file) {
|
||||
if ($file->list) {
|
||||
$num_files++;
|
||||
}
|
||||
}
|
||||
if ($num_files) {
|
||||
$links['upload_attachments'] = array(
|
||||
'title' => format_plural($num_files, '1 attachment', '@count attachments'),
|
||||
'href' => "node/$node->nid",
|
||||
'attributes' => array('title' => t('Read full article to view attachments.')),
|
||||
'fragment' => 'attachments'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_menu().
|
||||
*/
|
||||
function upload_menu() {
|
||||
$items['upload/js'] = array(
|
||||
'page callback' => 'upload_js',
|
||||
'access arguments' => array('upload files'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
$items['admin/settings/uploads'] = array(
|
||||
'title' => 'File uploads',
|
||||
'description' => 'Control how files may be attached to content.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('upload_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_NORMAL_ITEM,
|
||||
'file' => 'upload.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
function upload_menu_alter(&$items) {
|
||||
$items['system/files']['access arguments'] = array('view uploaded files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the limitations on files that a given user may upload. The user
|
||||
* may be in multiple roles so we select the most permissive limitations from
|
||||
* all of their roles.
|
||||
*
|
||||
* @param $user
|
||||
* A Drupal user object.
|
||||
* @return
|
||||
* An associative array with the following keys:
|
||||
* 'extensions'
|
||||
* A white space separated string containing all the file extensions this
|
||||
* user may upload.
|
||||
* 'file_size'
|
||||
* The maximum size of a file upload in bytes.
|
||||
* 'user_size'
|
||||
* The total number of bytes for all for a user's files.
|
||||
* 'resolution'
|
||||
* A string specifying the maximum resolution of images.
|
||||
*/
|
||||
function _upload_file_limits($user) {
|
||||
$file_limit = variable_get('upload_uploadsize_default', 1);
|
||||
$user_limit = variable_get('upload_usersize_default', 1);
|
||||
$all_extensions = explode(' ', variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
|
||||
foreach ($user->roles as $rid => $name) {
|
||||
$extensions = variable_get("upload_extensions_$rid", variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'));
|
||||
$all_extensions = array_merge($all_extensions, explode(' ', $extensions));
|
||||
|
||||
// A zero value indicates no limit, take the least restrictive limit.
|
||||
$file_size = variable_get("upload_uploadsize_$rid", variable_get('upload_uploadsize_default', 1)) * 1024 * 1024;
|
||||
$file_limit = ($file_limit && $file_size) ? max($file_limit, $file_size) : 0;
|
||||
|
||||
$user_size = variable_get("upload_usersize_$rid", variable_get('upload_usersize_default', 1)) * 1024 * 1024;
|
||||
$user_limit = ($user_limit && $user_size) ? max($user_limit, $user_size) : 0;
|
||||
}
|
||||
$all_extensions = implode(' ', array_unique($all_extensions));
|
||||
return array(
|
||||
'extensions' => $all_extensions,
|
||||
'file_size' => $file_limit,
|
||||
'user_size' => $user_limit,
|
||||
'resolution' => variable_get('upload_max_resolution', 0),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_file_download().
|
||||
*/
|
||||
function upload_file_download($filepath) {
|
||||
$filepath = file_create_path($filepath);
|
||||
$result = db_query("SELECT f.*, u.nid FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $filepath);
|
||||
while ($file = db_fetch_object($result)) {
|
||||
if ($filepath !== $file->filepath) {
|
||||
// Since some database servers sometimes use a case-insensitive
|
||||
// comparison by default, double check that the filename is an exact
|
||||
// match.
|
||||
continue;
|
||||
}
|
||||
if (user_access('view uploaded files') && ($node = node_load($file->nid)) && node_access('view', $node)) {
|
||||
return array(
|
||||
'Content-Type: ' . $file->filemime,
|
||||
'Content-Length: ' . $file->filesize,
|
||||
);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save new uploads and store them in the session to be associated to the node
|
||||
* on upload_save.
|
||||
*
|
||||
* @param $node
|
||||
* A node object to associate with uploaded files.
|
||||
*/
|
||||
function upload_node_form_submit(&$form, &$form_state) {
|
||||
global $user;
|
||||
|
||||
$limits = _upload_file_limits($user);
|
||||
$validators = array(
|
||||
'file_validate_extensions' => array($limits['extensions']),
|
||||
'file_validate_image_resolution' => array($limits['resolution']),
|
||||
'file_validate_size' => array($limits['file_size'], $limits['user_size']),
|
||||
);
|
||||
|
||||
// Save new file uploads.
|
||||
if (user_access('upload files') && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
|
||||
$file->list = variable_get('upload_list_default', 1);
|
||||
$file->description = $file->filename;
|
||||
$file->weight = 0;
|
||||
$file->new = TRUE;
|
||||
$form['#node']->files[$file->fid] = $file;
|
||||
$form_state['values']['files'][$file->fid] = (array)$file;
|
||||
}
|
||||
|
||||
if (isset($form_state['values']['files'])) {
|
||||
foreach ($form_state['values']['files'] as $fid => $file) {
|
||||
// If the node was previewed prior to saving, $form['#node']->files[$fid]
|
||||
// is an array instead of an object. Convert file to object for compatibility.
|
||||
$form['#node']->files[$fid] = (object) $form['#node']->files[$fid];
|
||||
$form_state['values']['files'][$fid]['new'] = !empty($form['#node']->files[$fid]->new);
|
||||
}
|
||||
}
|
||||
|
||||
// Order the form according to the set file weight values.
|
||||
if (!empty($form_state['values']['files'])) {
|
||||
$microweight = 0.001;
|
||||
foreach ($form_state['values']['files'] as $fid => $file) {
|
||||
if (is_numeric($fid)) {
|
||||
$form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
|
||||
$microweight += 0.001;
|
||||
}
|
||||
}
|
||||
uasort($form_state['values']['files'], 'element_sort');
|
||||
}
|
||||
}
|
||||
|
||||
function upload_form_alter(&$form, $form_state, $form_id) {
|
||||
if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
|
||||
$form['workflow']['upload'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Attachments'),
|
||||
'#default_value' => variable_get('upload_'. $form['#node_type']->type, 1),
|
||||
'#options' => array(t('Disabled'), t('Enabled')),
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($form['type']) && isset($form['#node'])) {
|
||||
$node = $form['#node'];
|
||||
if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
|
||||
// Attachments fieldset
|
||||
$form['attachments'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#access' => user_access('upload files'),
|
||||
'#title' => t('File attachments'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => empty($node->files),
|
||||
'#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'),
|
||||
'#prefix' => '<div class="attachments">',
|
||||
'#suffix' => '</div>',
|
||||
'#weight' => 30,
|
||||
);
|
||||
|
||||
// Wrapper for fieldset contents (used by ahah.js).
|
||||
$form['attachments']['wrapper'] = array(
|
||||
'#prefix' => '<div id="attach-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
|
||||
// Make sure necessary directories for upload.module exist and are
|
||||
// writable before displaying the attachment form.
|
||||
$path = file_directory_path();
|
||||
$temp = file_directory_temp();
|
||||
// Note: pass by reference
|
||||
if (!file_check_directory($path, FILE_CREATE_DIRECTORY) || !file_check_directory($temp, FILE_CREATE_DIRECTORY)) {
|
||||
$form['attachments']['#description'] = t('File attachments are disabled. The file directories have not been properly configured.');
|
||||
if (user_access('administer site configuration')) {
|
||||
$form['attachments']['#description'] .= ' '. t('Please visit the <a href="@admin-file-system">file system configuration page</a>.', array('@admin-file-system' => url('admin/settings/file-system')));
|
||||
}
|
||||
else {
|
||||
$form['attachments']['#description'] .= ' '. t('Please contact the site administrator.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form['attachments']['wrapper'] += _upload_form($node);
|
||||
$form['#attributes']['enctype'] = 'multipart/form-data';
|
||||
}
|
||||
$form['#submit'][] = 'upload_node_form_submit';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_nodeapi().
|
||||
*/
|
||||
function upload_nodeapi(&$node, $op, $teaser = NULL) {
|
||||
switch ($op) {
|
||||
|
||||
case 'load':
|
||||
$output = '';
|
||||
if (variable_get("upload_$node->type", 1) == 1) {
|
||||
$output['files'] = upload_load($node);
|
||||
return $output;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
if (isset($node->files) && user_access('view uploaded files')) {
|
||||
// Add the attachments list to node body with a heavy
|
||||
// weight to ensure they're below other elements
|
||||
if (count($node->files)) {
|
||||
if (!$teaser && user_access('view uploaded files')) {
|
||||
$node->content['files'] = array(
|
||||
'#value' => theme('upload_attachments', $node->files),
|
||||
'#weight' => 50,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'insert':
|
||||
case 'update':
|
||||
if (user_access('upload files')) {
|
||||
upload_save($node);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
upload_delete($node);
|
||||
break;
|
||||
|
||||
case 'delete revision':
|
||||
upload_delete_revision($node);
|
||||
break;
|
||||
|
||||
case 'search result':
|
||||
return isset($node->files) && is_array($node->files) && user_access('view uploaded files') ? format_plural(count($node->files), '1 attachment', '@count attachments') : NULL;
|
||||
|
||||
case 'rss item':
|
||||
if (is_array($node->files) && user_access('view uploaded files')) {
|
||||
$files = array();
|
||||
foreach ($node->files as $file) {
|
||||
if ($file->list) {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
if (count($files) > 0) {
|
||||
// RSS only allows one enclosure per item
|
||||
$file = array_shift($files);
|
||||
return array(
|
||||
array(
|
||||
'key' => 'enclosure',
|
||||
'attributes' => array(
|
||||
'url' => file_create_url($file->filepath),
|
||||
'length' => $file->filesize,
|
||||
'type' => $file->filemime
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays file attachments in table
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_upload_attachments($files) {
|
||||
$header = array(t('Attachment'), t('Size'));
|
||||
$rows = array();
|
||||
foreach ($files as $file) {
|
||||
$file = (object)$file;
|
||||
if ($file->list && empty($file->remove)) {
|
||||
$href = file_create_url($file->filepath);
|
||||
$text = $file->description ? $file->description : $file->filename;
|
||||
$rows[] = array(l($text, $href), format_size($file->filesize));
|
||||
}
|
||||
}
|
||||
if (count($rows)) {
|
||||
return theme('table', $header, $rows, array('id' => 'attachments'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how much disk space is occupied by a user's uploaded files.
|
||||
*
|
||||
* @param $uid
|
||||
* The integer user id of a user.
|
||||
* @return
|
||||
* The amount of disk space used by the user in bytes.
|
||||
*/
|
||||
function upload_space_used($uid) {
|
||||
return file_space_used($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how much disk space is occupied by uploaded files.
|
||||
*
|
||||
* @return
|
||||
* The amount of disk space used by uploaded files in bytes.
|
||||
*/
|
||||
function upload_total_space_used() {
|
||||
return db_result(db_query('SELECT SUM(f.filesize) FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid'));
|
||||
}
|
||||
|
||||
function upload_save(&$node) {
|
||||
if (empty($node->files) || !is_array($node->files)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($node->files as $fid => $file) {
|
||||
// Convert file to object for compatibility
|
||||
$file = (object)$file;
|
||||
|
||||
// Remove file. Process removals first since no further processing
|
||||
// will be required.
|
||||
if (!empty($file->remove)) {
|
||||
db_query('DELETE FROM {upload} WHERE fid = %d AND vid = %d', $fid, $node->vid);
|
||||
|
||||
// If the file isn't used by any other revisions delete it.
|
||||
$count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $fid));
|
||||
if ($count < 1) {
|
||||
file_delete($file->filepath);
|
||||
db_query('DELETE FROM {files} WHERE fid = %d', $fid);
|
||||
}
|
||||
|
||||
// Remove it from the session in the case of new uploads,
|
||||
// that you want to disassociate before node submission.
|
||||
unset($node->files[$fid]);
|
||||
// Move on, so the removed file won't be added to new revisions.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a new revision, or associate a new file needed.
|
||||
if (!empty($node->old_vid) || $file->new) {
|
||||
db_query("INSERT INTO {upload} (fid, nid, vid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $file->fid, $node->nid, $node->vid, $file->list, $file->description, $file->weight);
|
||||
file_set_status($file, FILE_STATUS_PERMANENT);
|
||||
}
|
||||
// Update existing revision.
|
||||
else {
|
||||
db_query("UPDATE {upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d AND vid = %d", $file->list, $file->description, $file->weight, $file->fid, $node->vid);
|
||||
file_set_status($file, FILE_STATUS_PERMANENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function upload_delete($node) {
|
||||
$files = array();
|
||||
$result = db_query('SELECT DISTINCT f.* FROM {upload} u INNER JOIN {files} f ON u.fid = f.fid WHERE u.nid = %d', $node->nid);
|
||||
while ($file = db_fetch_object($result)) {
|
||||
$files[$file->fid] = $file;
|
||||
}
|
||||
|
||||
foreach ($files as $fid => $file) {
|
||||
// Delete all files associated with the node
|
||||
db_query('DELETE FROM {files} WHERE fid = %d', $fid);
|
||||
file_delete($file->filepath);
|
||||
}
|
||||
|
||||
// Delete all file revision information associated with the node
|
||||
db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid);
|
||||
}
|
||||
|
||||
function upload_delete_revision($node) {
|
||||
if (is_array($node->files)) {
|
||||
foreach ($node->files as $file) {
|
||||
// Check if the file will be used after this revision is deleted
|
||||
$count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid));
|
||||
|
||||
// if the file won't be used, delete it
|
||||
if ($count < 2) {
|
||||
db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
|
||||
file_delete($file->filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete the revision
|
||||
db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid);
|
||||
}
|
||||
|
||||
function _upload_form($node) {
|
||||
global $user;
|
||||
|
||||
$form = array(
|
||||
'#theme' => 'upload_form_new',
|
||||
'#cache' => TRUE,
|
||||
);
|
||||
|
||||
if (!empty($node->files) && is_array($node->files)) {
|
||||
$form['files']['#theme'] = 'upload_form_current';
|
||||
$form['files']['#tree'] = TRUE;
|
||||
foreach ($node->files as $key => $file) {
|
||||
$file = (object)$file;
|
||||
$description = file_create_url($file->filepath);
|
||||
$description = "<small>". check_plain($description) ."</small>";
|
||||
$form['files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
|
||||
$form['files'][$key]['size'] = array('#value' => format_size($file->filesize));
|
||||
$form['files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
|
||||
$form['files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list);
|
||||
$form['files'][$key]['weight'] = array('#type' => 'weight', '#delta' => count($node->files), '#default_value' => $file->weight);
|
||||
$form['files'][$key]['filename'] = array('#type' => 'value', '#value' => $file->filename);
|
||||
$form['files'][$key]['filepath'] = array('#type' => 'value', '#value' => $file->filepath);
|
||||
$form['files'][$key]['filemime'] = array('#type' => 'value', '#value' => $file->filemime);
|
||||
$form['files'][$key]['filesize'] = array('#type' => 'value', '#value' => $file->filesize);
|
||||
$form['files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid);
|
||||
$form['files'][$key]['new'] = array('#type' => 'value', '#value' => FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (user_access('upload files')) {
|
||||
$limits = _upload_file_limits($user);
|
||||
$form['new']['#weight'] = 10;
|
||||
$form['new']['upload'] = array(
|
||||
'#type' => 'file',
|
||||
'#title' => t('Attach new file'),
|
||||
'#size' => 40,
|
||||
'#description' => ($limits['resolution'] ? t('Images are larger than %resolution will be resized. ', array('%resolution' => $limits['resolution'])) : '') . t('The maximum upload size is %filesize. Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions'], '%filesize' => format_size($limits['file_size']))),
|
||||
);
|
||||
$form['new']['attach'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Attach'),
|
||||
'#name' => 'attach',
|
||||
'#ahah' => array(
|
||||
'path' => 'upload/js',
|
||||
'wrapper' => 'attach-wrapper',
|
||||
'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
|
||||
),
|
||||
'#submit' => array('node_form_submit_build_node'),
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the attachments list.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_upload_form_current($form) {
|
||||
$header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
|
||||
drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
|
||||
|
||||
foreach (element_children($form) as $key) {
|
||||
// Add class to group weight fields for drag and drop.
|
||||
$form[$key]['weight']['#attributes']['class'] = 'upload-weight';
|
||||
|
||||
$row = array('');
|
||||
$row[] = drupal_render($form[$key]['remove']);
|
||||
$row[] = drupal_render($form[$key]['list']);
|
||||
$row[] = drupal_render($form[$key]['description']);
|
||||
$row[] = drupal_render($form[$key]['weight']);
|
||||
$row[] = drupal_render($form[$key]['size']);
|
||||
$rows[] = array('data' => $row, 'class' => 'draggable');
|
||||
}
|
||||
$output = theme('table', $header, $rows, array('id' => 'upload-attachments'));
|
||||
$output .= drupal_render($form);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme the attachment form.
|
||||
* Note: required to output prefix/suffix.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_upload_form_new($form) {
|
||||
drupal_add_tabledrag('upload-attachments', 'order', 'sibling', 'upload-weight');
|
||||
$output = drupal_render($form);
|
||||
return $output;
|
||||
}
|
||||
|
||||
function upload_load($node) {
|
||||
$files = array();
|
||||
|
||||
if ($node->vid) {
|
||||
$result = db_query('SELECT * FROM {files} f INNER JOIN {upload} r ON f.fid = r.fid WHERE r.vid = %d ORDER BY r.weight, f.fid', $node->vid);
|
||||
while ($file = db_fetch_object($result)) {
|
||||
$files[$file->fid] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu-callback for JavaScript-based uploads.
|
||||
*/
|
||||
function upload_js() {
|
||||
$cached_form_state = array();
|
||||
$files = array();
|
||||
|
||||
// Load the form from the Form API cache.
|
||||
if (!($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) || !isset($cached_form['#node']) || !isset($cached_form['attachments'])) {
|
||||
form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
|
||||
$output = theme('status_messages');
|
||||
print drupal_to_js(array('status' => TRUE, 'data' => $output));
|
||||
exit();
|
||||
}
|
||||
|
||||
$form_state = array('values' => $_POST);
|
||||
|
||||
// Handle new uploads, and merge tmp files into node-files.
|
||||
upload_node_form_submit($cached_form, $form_state);
|
||||
|
||||
if(!empty($form_state['values']['files'])) {
|
||||
foreach ($form_state['values']['files'] as $fid => $file) {
|
||||
if (isset($cached_form['#node']->files[$fid])) {
|
||||
$files[$fid] = $cached_form['#node']->files[$fid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$node = $cached_form['#node'];
|
||||
|
||||
$node->files = $files;
|
||||
|
||||
$form = _upload_form($node);
|
||||
|
||||
unset($cached_form['attachments']['wrapper']['new']);
|
||||
$cached_form['attachments']['wrapper'] = array_merge($cached_form['attachments']['wrapper'], $form);
|
||||
|
||||
$cached_form['attachments']['#collapsed'] = FALSE;
|
||||
|
||||
form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state);
|
||||
|
||||
foreach ($files as $fid => $file) {
|
||||
if (is_numeric($fid)) {
|
||||
$form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description'];
|
||||
$form['files'][$fid]['list']['#default_value'] = !empty($form_state['values']['files'][$fid]['list']);
|
||||
$form['files'][$fid]['remove']['#default_value'] = !empty($form_state['values']['files'][$fid]['remove']);
|
||||
$form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight'];
|
||||
}
|
||||
}
|
||||
|
||||
// Render the form for output.
|
||||
$form += array(
|
||||
'#post' => $_POST,
|
||||
'#programmed' => FALSE,
|
||||
'#tree' => FALSE,
|
||||
'#parents' => array(),
|
||||
);
|
||||
|
||||
$empty_form_state = array();
|
||||
$data = &$form;
|
||||
$data['__drupal_alter_by_ref'] = array(&$empty_form_state);
|
||||
drupal_alter('form', $data, 'upload_js');
|
||||
|
||||
$form_state = array('submitted' => FALSE);
|
||||
$form = form_builder('upload_js', $form, $form_state);
|
||||
$output = theme('status_messages') . drupal_render($form);
|
||||
|
||||
// We send the updated file attachments form.
|
||||
// Don't call drupal_json(). ahah.js uses an iframe and
|
||||
// the header output by drupal_json() causes problems in some browsers.
|
||||
print drupal_to_js(array('status' => TRUE, 'data' => $output));
|
||||
exit;
|
||||
}
|
Reference in a new issue