<?php // -*-php-*-
/*
* Original module by Alaa Abd El Fatah.
* Mohammed Sameer: My Modifications Copyright (c) 2004 Mohammed Sameer,
* under the GNU GPL v2 or later.
* Mohammed Sameer: 2004 10 30
* * Ported to drupal 4.5 API.
*
* 04/04/2005 Ported to drupal 4.6 API by Amr Mostafa
*/
setlocale(LC_ALL, "ar_EG.UTF-8");
/**
* Implementation of hook_help()
*/
function bidi_help($section) {
$output = "";
switch ($section) {
case 'admin/modules#description':
$output = t("Automaticaly sets line direction");
break;
}
return $output;
}
/**
* Implementation of hook_filter_tips()
*/
function bidi_filter_tips($delta, $format, $long = false) {
if ($long) {
return t("You may write mixed Arabic and English freely, line direction will be computed automaticaly");
}
else {
return t("You may write mixed Arabic and English freely, line direction will be computed automaticaly");
}
}
/**
* Implementation of hook_filter()
*/
function bidi_filter($op, $delta = 0, $format = -1, $text = "") {
switch ($op) {
case 'list':
return array(0 => t('BiDi'));
case 'description':
return t("Bidi filter");
case "process":
return _bidi_filter_process($text);
default:
return $text;
}
}
/**
* Determine text language (arabic or english).
* This function needs to be split into a single include file so it can be used on theme and other places.
*
* @param $text
* The text needs to be.
* @return
* right2left or left2right depending on the detected language.
*/
function _get_dir($text) {
$text = strtolower($text);
$len = strlen($text);
$arabic = 0;
$english = 0;
// constants
$a = ord('a');
$z = ord('z');
// arabic UTF-8 letters have one of these values in the 1st byte
$ar1 = 0xd8;
$ar2 = 0xd9;
// this calculates the dominant language in the text, most bidi implementations just use the first letter (which may be more efficient)
for ($i = 0; $i < $len; ++$i) {
$bin = ord($text[$i]);
if ($bin == $ar1 || $bin == $ar2) {
++$arabic;
++$i;
}
else if ($bin >= $a && $bin <= $z) {
++$english;
}
}
// should this return a bool instead?
if ($english || $arabic) {
if ($english > $arabic)
return ' dir="LTR" ';
else
return ' dir="RTL" ';
}
else {
return "";
}
}
/**
* Sets HTML text line directions (rtl or ltr) as needed.
*
* @param $text
* The HTML text needs to be processed.
*
* @return
* The processed result, with line directions are set using css
* classes "righ2left" and "left2right".
*/
function _bidi_filter_process($text) {
$output = "";
$len = strlen($text);
$i = 0;
while ($i < $len) {
// find next tag
$e = strpos(substr($text, $i), "<");
if ($e === FALSE) {
break;
}
$output .= substr($text, $i, $e);
$i += $e;
// find end of next tag
$e = strcspn(substr($text, $i), "/> ");
$tag = substr($text, $i+1, $e-1);
$output .= substr($text, $i, $e);
$i += $e;
switch ($tag) {
// single tag entity
case "img":
case "br":
case "hr":
continue;
break;
//nested cases
case "ul":
case "ol":
case "dl":
case "div":
$e = 0;
// stack counter
$j = 1;
// if $j = 0 then stack is empty
while ($j > 0) {
$e += strpos(substr($text, $i+$e) , $tag);
// yeah I know I'm assuming certain sizes for tags, sue me
// new nested block
if ($text[$i+$e-1] == '<') {
++$j;
}
// end of block
else if ($text[$i+$e-1] == '/') {
--$j;
}
// skip current tag
$e += 2;
}
$content = substr($text, $i+1, $e);
$output .= _get_dir(strip_tags($content));
$e += strlen($tag);
$output.= substr($text, $i, $e);
$i += $e;
break;
// block elements these get out attention
case "p":
case "blockquote":
case "pre":
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
// nested lists are proken better think of a fix
case "li":
$close_tag = "</".$tag.">";
// breaks on extra whitespaces, maybe I should use regexps instead
$e = strpos(substr($text, $i), $close_tag);
$content = substr($text, $i+1, $e-1);
// calculate directions
$output.= _get_dir(strip_tags($content));
$e += strlen($close_tag);
$output.= substr($text, $i, $e);
$i += $e;
break;
// non block tags we are not handeling
default:
$close_tag = "</".$tag.">";
$e = strpos(substr($text, $i), $close_tag) + strlen($close_tag);
$output.= substr($text, $i, $e);
$i += $e;
}
}
// any missing content
$output .= substr($text, $i);
return $output;
}
?>
This is a patch I made for drupal's captcha module.
Since the captcha can only be read using graphical browsers, I thought of this idea to enable users with textish browsers to read the captcha code too.
It's not secure though. Since the captcha code gets printed in a HTML tag (that's how textish browsers can read it), a script can read the code and bypass your captcha protection. I don't recommend applying the patch if you are going to use captcha in anything other than fighting spam comments :).
captcha.module.patch
--- captcha.module.orig 2005-01-19 17:00:08.000000000 +0200
+++ captcha.module 2005-03-05 00:12:03.921875000 +0200
@@ -83,9 +83,10 @@
if (_captcha_istrue("captcha_user_register") && !$newuser->uid && !$user->uid)
switch ($type) {
case t("register"):
- // Add two items to the resigtration form.
+ $string = _captcha_code();
- $output .= form_item("", '<img src="/'.url('captcha/image/'.time()).'" alt="Captcha Image: you will need to recognize the text in it."/>');
+ // Add two items to the resigtration form.
+ $output .= form_item("", '<img src="/'.url('captcha/image/'.time()).'" alt="Captcha: '.$string.'"/>');
$output .= form_textfield(t('Word'), 'captchaword', NULL, 15, 15, 'Please type in the letters/numbers that are shown in the image above.', NULL, TRUE);
return array(array('title' => t('Verify Registration'), 'data'=>$output));
@@ -137,7 +138,8 @@
case 'form':
if (sess_read('captcha_comment_correct')!='ok') {
- $output .= form_item("", '<img src="/'.url('captcha/image/'.time()).'" alt="Captcha Image: you will need to recognize the text in it."/>');
+ $string = _captcha_code();
+ $output .= form_item("", '<img src="/'.url('captcha/image/'.time()).'" alt="Captcha: '.$string.'"/>');
$output .= form_textfield(t('Word'), 'captchaword', NULL, 15, 15, 'Please type in the letters/numbers that are shown in the image above.', NULL, TRUE);
return form_group(t('Verify comment authorship'), $output);
} else return NULL;
@@ -206,7 +208,7 @@
header('Content-type: image/png');
- $string = _captcha_code();
+ $string = sess_read('captcha');
//set up image, the first number is the width and the second is the height
$im = imagecreatetruecolor(120, 20);
<?php
// $Id: diff.module,v 1.24 2008/03/05 21:03:17 weitzman Exp $
/**
* Number of items on one page of the revision list.
*/
define('REVISION_LIST_SIZE', 50);
/**
* Implementation of hook_help().
*/
function diff_help($section) {
switch ($section) {
case 'admin/help#diff':
$output = '<p>'. t('The diff module overwrites the normal revisions view. The revisions table is enhanced with a possibility to view the difference between two node revisions. Users with the %view_revisions permission will also be able to view the changes between any two selected revisions. You may disable this for individual content types on the content type configuration page. This module also provides a nifty %preview_changes button while editing a post.', array('%preview_changes' => t('Preview changes'), '%view_revisions' => t('view revisions'))).'</p>';
return $output;
}
}
/**
* Implementation of hook_requirements().
* Checks if the diff modules is loaded after the node module in the hook ordering.
*/
function diff_requirements($phase) {
// Don't check when installing
if ($phase == 'install') {
return;
}
$modules = array_keys(module_list());
if (array_search('diff', $modules) <= array_search('node', $modules)) {
diff_autoadjust();
}
}
/**
* Implementation of hook_menu()
* The menu path 'node/$nid/revisions' is overriden with 'diff_diffs'.
*/
function diff_menu($may_cache) {
$items = array();
if (!$may_cache) {
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
if ($node->nid) {
$revisions_access = (user_access('view revisions') || user_access('administer nodes')) &&
node_access('view', $node) &&
db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', arg(1))) > 1;
$items[] = array(
'path' => 'node/'. arg(1) .'/revisions', 'title' => t('Revisions'),
'callback' => 'diff_diffs',
'access' => $revisions_access,
'weight' => 4,
'type' => MENU_LOCAL_TASK,
);
}
}
}
return $items;
}
/**
* Adjust the module weights for diff to load after node module.
*/
function diff_autoadjust() {
$modules = array_keys(module_list());
if (array_search('diff', $modules) <= array_search('node', $modules)) {
module_load_install('diff');
diff_set_weight();
}
}
/**
* Menu callback for diff related activities.
*/
function diff_diffs() {
if (is_numeric(arg(1)) && arg(2) == 'revisions') {
$op = arg(3) ? arg(3) : 'overview';
switch ($op) {
case 'overview':
$node = node_load(arg(1));
if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
return diff_diffs_overview($node);
}
drupal_access_denied();
return;
case 'view':
if (is_numeric(arg(4)) && is_numeric(arg(5))) {
$node = node_load(arg(1));
if ($node->nid) {
if ((user_access('view revisions') || user_access('administer nodes')) && node_access('view', $node)) {
drupal_set_title(t('Diff for %title', array('%title' => $node->title)));
return diff_diffs_show($node, arg(4), arg(5));
}
drupal_access_denied();
return;
}
}
break;
default:
// A view, revert or delete operation from the orignial node module,
// so call the original node module to handle this.
return node_revisions();
break;
}
}
drupal_not_found();
}
/**
* Generate an overview table of older revisions of a node and provide
* an input form to select two revisions for a comparison.
*/
function diff_diffs_overview(&$node) {
$output = '';
drupal_set_title(t('Revisions for %title', array('%title' => $node->title)));
$output .= drupal_get_form('diff_node_revisions', $node);
return $output;
}
/**
* Input form to select two revisions.
*
* @param $node
* Node whose revisions are displayed for selection.
*/
function diff_node_revisions(&$node) {
global $form_values;
$form = array();
$form['nid'] = array(
'#type' => 'hidden',
'#value' => $node->nid,
);
$revision_list = node_revision_list($node);
if (count($revision_list) > REVISION_LIST_SIZE) {
// If the list of revisions is longer than the number shown on one page split the array.
$page = isset($_GET['page']) ? $_GET['page'] : '0';
$revision_chunks = array_chunk(node_revision_list($node), REVISION_LIST_SIZE);
$revisions = $revision_chunks[$page];
// Set up global pager variables as would 'pager_query' do.
// These variables are then used in the theme('pager') call later.
global $pager_page_array, $pager_total, $pager_total_items;
$pager_total_items[0] = count($revision_list);
$pager_total[0] = ceil(count($revision_list) / REVISION_LIST_SIZE);
$pager_page_array[0] = max(0, min($page, ((int)$pager_total[0]) - 1));
} else {
$revisions = $revision_list;
}
$revert_permission = FALSE;
if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
$revert_permission = TRUE;
}
$delete_permission = FALSE;
if (user_access('administer nodes')) {
$delete_permission = TRUE;
}
foreach ($revisions as $revision) {
$operations = array();
$revision_ids[$revision->vid] = '';
if ($revision->current_vid > 0) {
$form['info'][$revision->vid] = array(
'#value' => t('!date by !username', array(
'!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid"),
'!username' => theme('username', $revision)))
. (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : ''),
);
}
else {
$form['info'][$revision->vid] = array(
'#value' => t('!date by !username', array(
'!date' => l(format_date($revision->timestamp, 'small'), "node/$node->nid/revisions/$revision->vid/view"),
'!username' => theme('username', $revision)))
. (($revision->log != '') ? '<p class="revision-log">'. filter_xss($revision->log) .'</p>' : '')
);
if ($revert_permission) {
$operations[] = array('#value' => l(t('revert'), "node/$node->nid/revisions/$revision->vid/revert"));
}
if ($delete_permission) {
$operations[] = array('#value' => l(t('delete'), "node/$node->nid/revisions/$revision->vid/delete"));
}
// Set a dummy, even if the user has no permission for the other
// operations, so that we can check if the operations array is
// empty to know if the row denotes the current revision.
$operations[] = array();
}
$form['operations'][$revision->vid] = $operations;
}
$new_vid = key($revision_ids);
next($revision_ids);
$old_vid = key($revision_ids);
$form['diff']['old'] = array(
'#type' => 'radios',
'#options' => $revision_ids,
'#default_value' => $old_vid
);
$form['diff']['new'] = array(
'#type' => 'radios',
'#options' => $revision_ids,
'#default_value' => $new_vid
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Show diff'));
if (count($revision_list) > REVISION_LIST_SIZE) {
$form['#suffix'] = theme('pager', NULL, REVISION_LIST_SIZE, 0);
}
return $form;
}
/**
* Theme function to display the revisions formular with means to select
* two revisions.
*/
function theme_diff_node_revisions($form) {
// Overview table:
$header = array(
t('Revision'),
array('data' => drupal_render($form['submit']), 'colspan' => 2),
array('data' => t('Operations'), 'colspan' => 2)
);
if (isset($form['info']) && is_array($form['info'])) {
foreach (element_children($form['info']) as $key) {
$row = array();
if (isset($form['operations'][$key][0])) {
// Note: even if the commands for revert and delete are not permitted,
// the array is not empty since we set a dummy in this case.
$row[] = drupal_render($form['info'][$key]);
$row[] = drupal_render($form['diff']['old'][$key]);
$row[] = drupal_render($form['diff']['new'][$key]);
$row[] = drupal_render($form['operations'][$key][0]);
$row[] = drupal_render($form['operations'][$key][1]);
$rows[] = $row;
}
else {
// its the current revision (no commands to revert or delete)
$row[] = array('data' => drupal_render($form['info'][$key]), 'class' => 'revision-current');
$row[] = array('data' => drupal_render($form['diff']['old'][$key]), 'class' => 'revision-current');
$row[] = array('data' => drupal_render($form['diff']['new'][$key]), 'class' => 'revision-current');
$row[] = array('data' => theme('placeholder', t('current revision')), 'class' => 'revision-current', 'colspan' => '2');
$rows[] = array(
'data' => $row,
'class' => 'error',
);
}
}
}
$output .= theme('table', $header, $rows);
$output .= drupal_render($form);
return $output;
}
/**
* Submit code for input form to select two revisions.
*/
function diff_node_revisions_submit($form_id, $form_values) {
// the ids are ordered so the old revision is always on the left
$old_vid = min($form_values['old'], $form_values['new']);
$new_vid = max($form_values['old'], $form_values['new']);
return 'node/'.$form_values['nid'].'/revisions/view/'.$old_vid.'/'.$new_vid;
}
/**
* Validation for input form to select two revisions.
*/
function diff_node_revisions_validate($form_id, $form_values) {
$old_vid = $form_values['old'];
$new_vid = $form_values['new'];
if ($old_vid==$new_vid || !$old_vid || !$new_vid) {
form_set_error('diff', t('Select different revisions to compare.'));
}
}
/**
* Create output string for a comparison of 'node' between
* versions 'old_vid' and 'new_vid'.
*
* @param $node
* Node on which to perform comparison
* @param $old_vid
* Version ID of the old revision.
* @param $new_vid
* Version ID of the new revision.
*/
function diff_diffs_show(&$node, $old_vid, $new_vid) {
$lame_revisions = node_revision_list($node);
foreach ($lame_revisions as $revision) {
$node_revisions[$revision->vid] = $revision;
}
$old_node = node_load($node->nid, $old_vid);
$new_node = node_load($node->nid, $new_vid);
// Generate table header (date, username, logmessage).
$old_header = t('!date by !username', array(
'!date' => l(format_date($old_node->revision_timestamp), "node/$node->nid/revisions/$old_node->vid/view"),
'!username' => theme('username', $node_revisions[$old_vid]),
));
$new_header = t('!date by !username', array(
'!date' => l(format_date($new_node->revision_timestamp), "node/$node->nid/revisions/$new_node->vid/view"),
'!username' => theme('username', $node_revisions[$new_vid]),
));
$old_log = $old_node->log != '' ? '<p class="revision-log">'. filter_xss($old_node->log) .'</p>' : '';
$new_log = $new_node->log != '' ? '<p class="revision-log">'. filter_xss($new_node->log) .'</p>' : '';
// Generate previous diff/next diff links.
$next_vid = _diff_get_next_vid($node_revisions, $new_vid);
if ($next_vid) {
$next_link = l(t('next diff >'), 'node/'. $node->nid .'/revisions/view/'. $new_vid .'/'. $next_vid);
}
else {
$next_link = '';
}
$prev_vid = _diff_get_previous_vid($node_revisions, $old_vid);
if ($prev_vid) {
$prev_link = l(t('< previous diff'), 'node/'. $node->nid .'/revisions/view/'. $prev_vid .'/'. $old_vid);
}
else {
$prev_link = '';
}
$cols = array(
array(
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
array(
'class' => 'diff-marker',
),
array(
'class' => 'diff-content',
),
),
);
$header = array(
array(
'data' => $old_header,
'colspan' => 2
),
array(
'data' => $new_header,
'colspan' => 2
)
);
$rows = array();
if ($old_log || $new_log) {
$rows[] = array(
array(
'data' => $old_log,
'colspan' => 2
),
array(
'data' => $new_log,
'colspan' => 2
)
);
}
$rows[] = array(
array(
'data' => $prev_link,
'class' => 'diff-prevlink',
'colspan' => 2
),
array(
'data' => $next_link,
'class' => 'diff-nextlink',
'colspan' => 2
)
);
$rows = array_merge($rows, _diff_body_rows($old_node, $new_node));
$output = theme('diff_table', $header, $rows, array('class' => 'diff'), NULL, $cols);
if ($node->vid == $new_vid) {
$output .= '<div class="diff-section-title">'. t('Current revision:') .'</div>';
}
else {
$output .= '<div class="diff-section-title">'. t('Revision of !new_date:', array('!new_date' => format_date($new_node->revision_timestamp))) .'</div>';
}
// Don't include node links (final argument) when viewing the diff.
$output .= node_view($new_node, FALSE, FALSE, FALSE);
return $output;
}
/**
* Creates an array of rows which represent a diff between $old_node and $new_node.
* The rows can be used via theme('diff_table') to be displayed.
*
* @param $old_node
* Node for comparison which will be displayed on the left side.
* @param $new_node
* Node for comparison which will be displayed on the right side.
*/
function _diff_body_rows(&$old_node, &$new_node) {
drupal_add_css(drupal_get_path('module', 'diff') .'/diff.css', 'module', 'all', FALSE);
include_once('DiffEngine.php');
include_once('node.inc');
if (module_exists('taxonomy')) {
include_once('taxonomy.inc');
}
if (module_exists('upload')) {
include_once('upload.inc');
}
if (module_exists('content')) {
include_once('cck.inc');
}
$rows = array();
$any_visible_change = false;
$node_diffs = module_invoke_all('diff', $old_node, $new_node);
// We start off assuming all form elements are in the correct order.
$node_diffs['#sorted'] = TRUE;
// Recurse through all child elements.
$count = 0;
foreach(element_children($node_diffs) as $key) {
// Assign a decimal placeholder weight to preserve original array order.
if (!isset($node_diffs[$key]['#weight'])) {
$node_diffs[$key]['#weight'] = $count/1000;
}
else {
// If one of the child elements has a weight then we will need to sort
// later.
unset($node_diffs['#sorted']);
}
$count++;
}
// One of the children has a #weight.
if (!isset($node_diffs['#sorted'])) {
uasort($node_diffs, "_element_sort");
}
foreach($node_diffs as $node_diff) {
$diff = new Diff($node_diff['#old'], $node_diff['#new']);
$formatter = new DrupalDiffFormatter();
if (isset($node_diff['#format'])) {
$formatter->show_header = $node_diff['#format']['show_header'];
}
$diff_rows = $formatter->format($diff);
if (count($diff_rows)) {
$rows[] = array(
array(
'data' => t('Changes to %name', array('%name' => $node_diff['#name'])),
'class' => 'diff-section-title',
'colspan' => 4
)
);
$rows = array_merge($rows, $diff_rows);
$any_visible_change = true;
}
}
if (!$any_visible_change) {
$rows[] = array(
array(
'data' => t(