<?php
/**
* Read a book's contents (or a note's contents)
*
* Copyright (C) 2005 Wayne Davison <hide@address.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
require 'lib/main.inc';
read_page();
function read_page()
{
global $build_tag_offsets, $tag_offsets;
global $icon_names, $disp_mods, $map_defaults, $span_num;
$book_num = $_GET['b'];
$book_sect = $_GET['s'];
$book_target = $_GET['t'] + 0;
if (!preg_match('/^\d+;-?\d+;\d+$/',
join(';', array($book_num, $book_sect, $book_target)))) {
redir_main();
return;
}
open_db();
list($uID, $scrname) = get_auth_cookie();
add_sidebar_divider();
add_sidebar_image('btn-library', '.');
$do = "SELECT Title, Copyright, SectionCount, UserID, Access FROM Books WHERE BookID = $book_num LIMIT 1";
$result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
list($title, $copyright, $sect_cnt, $book_user, $access) = mysql_fetch_row($result);
if ($book_user != $uID && $uID != 1 && !preg_match("/(^|,)($uID|0)(,|$)/", $access)) {
close_db();
start_content('Error');
if ($book_sect < 0)
echo "Sorry -- this note is attached to a book that you don't have permission to read.";
else
echo "Sorry -- you don't have permission to read this book.";
end_content();
return;
}
if ($uID)
add_sidebar_image('btn-bookmarks', 'bkmk.php');
add_sidebar_divider();
add_sidebar_image('btn-sect_list', "toc.php?b=$book_num");
if ($book_sect > 0) {
$i = $book_sect - 1;
$up_action = "?b=$book_num&s=$i";
add_sidebar_image('btn-prev_sect', $up_action);
} else
add_sidebar_image('dbtn-prev_sect');
if ($book_sect >= 0 && $book_sect < $sect_cnt - 1) {
$i = $book_sect + 1;
$dn_action = "?b=$book_num&s=$i";
add_sidebar_image('btn-next_sect', $dn_action);
} else
add_sidebar_image('dbtn-next_sect');
//if (!$note_num && $book_user == $uID || $uID == 1)
//echo button('btn-editbook', "books.php?b=$book_num", 'width=34 height=20');
sidebar_pg_up_dn($up_action, $dn_action);
add_sidebar_divider();
add_sidebar_image('btn-dissect', "dissect.php?b=$book_num&s=$book_sect");
add_sidebar_image('btn-margin', "margin.php?b=$book_num&s=$book_sect");
add_sidebar_image('btn-reports', "report.php?b=$book_num&s=$book_sect");
sidebar_signin($uID, $scrname);
start_content($title);
$find = array(
'/<>/',
'/^----/',
"/(?<!\f)'''(.*?'*)(?<!\f)'''/",
"/(?<!\f)''(.*?'*)(?<!\f)''/",
'/^====== (.*) ======$/',
'/^===== (.*) =====$/',
'/^==== (.*) ====$/',
'/^=== (.*) ===$/',
'/^== (.*) ==$/',
'/^= (.*) =$/',
'/^[*#:;]*[*#](?![*#:;]) */', // avoiding \s on purpose!
'/^[*#:;]*:(?![*#:;]) */',
'/^[*#:;]*;(?![*#:;]) *(.*?): */',
'/^[*#:;]*;(?![*#:;]) */',
'/^\{\|(.*)/',
'/^\|\}/',
'/^\|\+(.*)/',
'/^\|-(.*)/',
'/^!([^\n|]*)\| */',
'/^! */',
'/^\|([^\n|]*)\| */',
'/^\| */',
'/(?<!\f)\[\[Image:([^]<> ]+) +([^]<>]*)(?<!\f)\]\]/',
'/(?<!\f)\[\[Image:([^]<> ]+)(?<!\f)\]\]/',
'/(?<!\f)\[\[(\d+),(\d+[^] ]*) +([^]]+)(?<!\f)\]\]/',
'/(?<!\f)\[\[(\d+[^] ]*) +([^]]+)(?<!\f)\]\]/',
'/(?<!\f)\[\[([^] ]+) +([^]]+)(?<!\f)\]\]/',
'/(?<!\f)\[\[(?!\?|[a-z]+:)([^]<> ]+)(?<!\f)\]\]/',
'/(?<!\f)\[\[([^]<> ]+)(?<!\f)\]\]/',
'/^(<t[dhr]\b.*?[>])\n/',
'/\f/', // \f is internal representation of a quoting backslash.
'/\s+$/',
'/^\s+/',
'/ +/');
$repl = array(
"\xA0", // 0xA0 =
'<hr>',
'<b>$1</b>',
'<i>$1</i>',
'<h6>$1</h6>',
'<h5>$1</h5>',
'<h4>$1</h4>',
'<h3>$1</h3>',
'<h2>$1</h2>',
'<h1>$1</h1>',
'<li>',
'<dd>',
'<dt>$1<dd>',
'<dt>',
'<table $1>',
'</table>',
'<caption>$1</caption>',
'<tr $1>',
'<th $1>',
'<th>',
'<td $1>',
'<td>',
'<img src="$1" $2 />',
'<img src="$1" border=0 />',
'<a href="?b=$1&s=$2">$3</a>',
"<a href='?b=$book_num&s=$1'>$2</a>",
'<a href="$1">$2</a>',
'<a href="http://$1">$1</a>',
'<a href="$1">$1</a>',
'$1',
'',
'',
'',
' ');
$groups = array();
$do = "SELECT e.GroupID
FROM EnabledGroups AS e
LEFT JOIN Groups AS g USING(GroupID)
WHERE e.UserID = $uID AND g.CanView REGEXP '(^|,)($uID|0)(,|$)'";
$result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
while (($row = mysql_fetch_row($result)) !== FALSE)
$groups[$row[0]] = $row[0];
$group_regex = '(^|,)(' . join('|', $groups) . ')(,|$)';
if ($book_target) {
$do = "SELECT AnchorStart
FROM BookAnnotations
WHERE ID = $book_target AND BookID = $book_num AND ParentID = $book_sect";
$result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
list($target_offset) = mysql_fetch_row($result);
if ($target_offset == '')
$book_target = 0;
}
echo <<<EOT
<script>
document.onmousedown = check_selection;
document.onmouseup = handle_selection;
document.checkScrollPos = 1;
document.annoTypes = new Array();
obj = document.annoTypes[0] = new Object(); obj.HelpText = "Don't create an annotation here";
EOT;
$do = "SELECT TypeID, UserID, Name, LeftClick, HelpText, EntryType
FROM AnnotationTypes
ORDER BY UserID, TypeID";
$result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
while (($obj = mysql_fetch_object($result)) !== FALSE) {
$help = preg_replace('/"/', '"e;', $obj->HelpText);
$disabled = $obj->EntryType == 'disabled' ? 1 : 0;
echo <<<EOT
obj = document.annoTypes[{$obj->TypeID}] = new Object(); obj.LeftClick = '{$obj->LeftClick}'; obj.Disabled = $disabled; obj.HelpText = "$help";
EOT;
if ($obj->EntryType == 'normal') {
$selmenu[$obj->UserID] .= <<<EOT
<li><a href="javascript:create_anno({$obj->TypeID})" id=atype{$obj->TypeID}>Create {$obj->Name}</a></li>
EOT;
}
}
echo <<<EOT
document.bookTarget = $book_target;
document.UserID = $uID;
</script>
<div id="pbSelMenuDiv" class="menulist" onmouseover="mousein_menu()" onmouseout="mouseout_menu()"><ul>
EOT;
echo $selmenu[1];
if ($uID != 1 && $selmenu[$uID])
echo '<hr>', $selmenu[$uID];
echo <<<EOT
<li><a href="javascript:hide_menu()" id=atype0>Cancel</a></li>
</ul></div>
<form name=selform method=post action="annotate.php?do=input&b=$book_num&s=$book_sect">
<input type=hidden name=nest>
<input type=hidden name=pos>
<input type=hidden name=sel>
<input type=hidden name=tID>
<input type=hidden name=annoID value=0>
</form>
EOT;
if ($book_sect >= 0) {
$do = "SELECT Text, Offsets IS NULL AS NoOffsets
FROM BookSections
WHERE BookID = $book_num AND SectionID = $book_sect
LIMIT 1";
} else {
$note_num = -$book_sect;
$do = "SELECT _51 AS Text, NoteOffsets IS NULL AS NoOffsets, ParentID, UserID
FROM BookAnnotations
WHERE ID = $note_num AND BookID = $book_num
LIMIT 1";
}
$result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
if (($obj = mysql_fetch_object($result)) === FALSE) {
if ($book_sect == 0)
$text = "''No content yet.''";
else if ($note_num)
$text = "'''''This note was deleted!'''''";
else
$text = "'''''Error fetching book content!'''''";
} else {
$text = $obj->Text;
$build_tag_offsets = $obj->NoOffsets;
$note_parent = $obj->ParentID;
$note_user = $obj->UserID;
}
$anno_attrs = get_attr_data(0);
$disp_mods = $need_attrs = array();
$do_vars = join(',', $icon_names);
$marginalia = $_COOKIE['marginalia'];
if (preg_match('/^([yn])((?: \d+,\d+,\d+,\d+)*)$/', $marginalia, $out)) {
if ($out[1] == 'y') {
// Set all colors and the font variety to their default.
$disp_mods[] = array(0,0,1,2,0,1);
$disp_mods[] = array(0,0,1,4,0,1);
$disp_mods[] = array(0,0,1,5,0,1);
$disp_mods[] = array(0,0,1,6,0,1);
}
$min_max = array();
foreach (explode(' ', trim($out[2])) as $set) {
list($tID,$aID,$method,$display) = explode(',', $set);
if ($tID < 0 || $aID < 0 || $display < 1 || $display > 99)
continue;
if (($aID > 0 && !$anno_attrs[$aID]) || !$anno_attrs[$display])
continue;
if ($method == 2 && $aID > 0) {
$obj = $min_max[$aID];
if (!$obj) {
$do = "SELECT MIN(`_$aID`) as lo, MAX(`_$aID`) as hi
FROM BookAnnotations
WHERE BookID = $book_num";
if (($result = mysql_query($do)) === FALSE
|| ($obj = mysql_fetch_object($result)) === FALSE)
continue;
$min_max[$aID] = $obj;
}
$min = $obj->lo;
$max = $obj->hi;
} else {
$min = 0;
$max = 1;
}
$disp_mods[] = array($tID,$aID,$method,$display,$min,$max);
$need_attrs[$aID] = 1;
}
// ff00cc ff0066 ff0000 ff6600 ffcc00 ccff00 66ff00 00ff00 00ff66 00ffcc ??
$map_defaults['IconTextColor'] = array('blue', 'green', '#CDAD00', 'orange', 'red');
$map_defaults['IconBorderColor'] = $map_defaults['IconTextColor'];
$map_defaults['IconBackgroundColor'] = array('red', 'orange', 'green', 'blue', 'black');
$map_defaults['IconFontStyle'] = array('.i.', 'b..', 'bi.', '.ic', 'bic');
}
$need_attrs[50] = 1; // We need the URL.
foreach ($anno_attrs as $aID => $aobj) {
if (substr($aID, 0, 1) == '#')
continue;
if ($need_attrs[$aID])
$do_vars .= ", _$aID";
}
#echo "#$do_vars#";
$do = "SELECT ID, b.TypeID, b.UserID, AnchorStart, AnchorLen, $do_vars,
_51 != '' AS HasNote, l.Name AS AnnoType
FROM BookAnnotations AS b LEFT JOIN AnnotationTypes AS l USING(TypeID)
WHERE BookID = $book_num AND ParentID = $book_sect AND _99 REGEXP '$group_regex'
ORDER BY AnchorStart";
$link_result = mysql_query($do) or die('SELECT failed: ' . mysql_error() . " (cmd: $do)");
if (($lnk = mysql_fetch_assoc($link_result)) !== FALSE)
$anno_start = $lnk['AnchorStart'];
else
$anno_start = 9999999;
// Make table parsing easier by transforming multi-cell-per-line syntax
// into single-cell-per-line syntax *WITHOUT* changing the string length!
preg_match_all('/^[|!].*$/m', $text, $table_lines, PREG_OFFSET_CAPTURE);
foreach ($table_lines[0] as $match) {
list($line,$line_offset) = $match;
preg_match_all($line[0] == '!' ? '/!!/' : '/\|\|/', $line, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $match)
$text[$line_offset + $match[1]] = "\n";
}
$cur_list = '';
$span_num = 0;
$offset = 0;
$prev_pos = -1;
$need_p = 1;
$tag_str = '';
$anno_data = '';
$anno_cnt = 0;
foreach (preg_split('/\r?\n/', $text) as $line) {
if ($line == '') {
$need_p = 1;
$offset++;
continue;
}
$line_len = strlen($line) + 1;
$extra_tags = array();
if (preg_match('/^([*#:;]*;(?![*#:;]).*?):/', $line, $out))
$extra_tags[] = array('DD', strlen($out[1]));
$extra = 0;
while (1) {
if ($book_target && $target_offset <= $anno_start
&& ($pos = $target_offset - $offset) < $line_len) {
if ($pos < 0)
$pos = 0;
$add = '<a name=pblnk></a>';
$line = substr_replace($line, $add, $pos + $extra, 0);
$extra += strlen($add);
$book_target = 0;
}
if (($pos = $anno_start - $offset) >= $line_len)
break;
if ($pos < 0)
$pos = 0;
if ($pos != $prev_pos) {
$span_num++;
$add = "<span id=s$span_num>" . $line[$pos + $extra] . '</span>';
$line = substr_replace($line, $add, $pos + $extra, 1);
$extra += strlen($add) - 1;
$prev_pos = $pos;
}
$anno_data .= get_anno_data($lnk);
$anno_cnt++;
if (($lnk = mysql_fetch_assoc($link_result)) !== FALSE)
$anno_start = $lnk['AnchorStart'];
else
$anno_start = 9999999;
}
$prev_pos = -1;
if (preg_match('/^[*#:;]+/', $line, $out))
$new_list = preg_replace('/;/', ':', $out[0]);
else {
$new_list = '';
if ($cur_list != '')
$need_p = 1;
}
#echo ":$cur_list:$new_list:<br />";
if ($new_list != $cur_list) {
$list = $new_list;
$cur_len = strlen($cur_list);
$len = strlen($list);
while ($len < $cur_len) {
$list .= ' ';
$len++;
}
while ($len > $cur_len) {
$cur_list .= ' ';
$cur_len++;
}
$add = array();
while ($list != $cur_list) {
#echo "<pre>!$cur_list!\n!$list!\n</pre>";
$x = substr($cur_list, -1, 1);
$cur_list = substr($cur_list, 0, -1);
switch ($x) {
case '#':
$tag_str = pop_tag($tag_str, 'OL');
echo "</ol>\n";
break;
case '*':
$tag_str = pop_tag($tag_str, 'UL');
echo "</ul>\n";
break;
case ':':
$tag_str = pop_tag($tag_str, 'DL');
echo "</dl>\n";
break;
}
$x = substr($list, -1, 1);
$list = substr($list, 0, -1);
switch ($x) {
case '#':
array_unshift($add, 'ol');
$need_p = 0;
break;
case '*':
array_unshift($add, 'ul');
$need_p = 0;
break;
case ':':
array_unshift($add, 'dl');
$need_p = 0;
break;
}
#echo "<pre>!$cur_list!\n!$list!!\n</pre>";
}
foreach ($add as $tag) {
echo '<', $tag, ">\n";
$tag_str = push_tag($tag_str, strtoupper($tag), $offset);
}
$cur_list = $new_list;
}
if ($need_p) {
if (!preg_match(':^(=|</?blockquote>):', $line)) {
echo '<p>';
$tag_str = push_tag($tag_str, 'P', $offset);
}
$need_p = 0;
}
$line = preg_replace($find, $repl, $line) . "\n";
if (preg_match(':<(h[1-6]|table|tr|td|th|li|dd|dt|div|blockquote|center)[ >]:i', $line, $out)) {
$tag = strtoupper($out[1]);
preg_match(':/([A-Z]+[1-6]?)(#\d+)?/([A-Z]+[1-6]?)(#\d+)?$:', $tag_str, $out);
if (($top = $out[3]) == 'P')
$top = $out[1];
switch ($tag) {
case 'TD':
case 'TH':
if ($top == 'TABLE') {
echo '<tr>';
$tag_str = push_tag($tag_str, 'TR', $offset);
} elseif ($top == 'TD')
echo '</td>';
elseif ($top == 'TH')
echo '</th>';
break;
case 'TR':
if ($top == 'TD')
echo '</td></tr>';
elseif ($top == 'TH')
echo '</th></tr>';
break;
}
$tag_str = push_tag($tag_str, $tag, $offset);
foreach ($extra_tags as $tag_array)
$tag_str = push_tag($tag_str, $tag_array[0], $offset + $tag_array[1]);
}
if (preg_match(':</(h[1-6]|table|tr|td|th|li|dd|dt|div|blockquote|center)[ >]:i', $line, $out)) {
$tag = strtoupper($out[1]);
if ($tag == 'TABLE')
echo '</td></tr>';
echo $line;
$tag_str = pop_tag($tag_str, $tag);
} else
echo $line;
$offset += $line_len;
}
if ($cur_list != '') {
for ($j = strlen($cur_list); $j--; ) {
switch ($cur_list[$j]) {
case '#':
echo "</ol>\n";
break;
case '*':
echo "</ul>\n";
break;
case ':':
echo "</dl>\n";
break;
}
}
}
if ($anno_start < 9000000) { // 9999999 doesn't work here?!?
echo '<p>';
if ($book_target)
echo '<a name=pblnk></a>';
$span_num++;
echo "<span id=s$span_num><i>[Orphaned Annotations]</i></span>\n";
do {
$lnk['AnchorLen'] = 22;
$anno_data .= get_anno_data($lnk);
$anno_cnt++;
} while (($lnk = mysql_fetch_assoc($link_result)) !== FALSE);
}
if ($note_num) {
echo "<p><a href='?b=$book_num&s=$note_parent&t=$note_num#pblnk'>[Go up a level to this note's margin icon]</a>\n";
if ($note_user == $uID) {
echo ' ',
button('btn-editnote',
"annotate.php?do=editnote&b=$book_num&s=$note_parent&a=$note_num",
'width=34 height=20');
}
} elseif ($book_sect == 0)
echo '<p><font size=1>', $copyright, '</font></p>';
if ($book_sect >= 0 && $book_sect < $sect_cnt - 1) {
echo '<br clear=all><p align=center>',
button('btn-next_sect', $dn_action, 'width=128 height=20'),
'</p>';
}
if ($anno_data != '') {
echo <<<EOT
<script>
document.bookNum = $book_num;
document.bookRef = '&b=$book_num';
document.sectRef = '&s=$book_sect';
document.annoData = new Array(0$anno_data);
document.annoCount = $anno_cnt;
//document.oncontextmenu = handle_selection;
</script>
EOT;
}
if ($build_tag_offsets) {
if ($book_sect >= 0) {
$do = "UPDATE BookSections
SET Offsets = '$tag_offsets'
WHERE BookID = $book_num AND SectionID = $book_sect";
} else {
$do = "UPDATE BookAnnotations
SET NoteOffsets = '$tag_offsets'
WHERE ID = $note_num AND BookID = $book_num";
}
mysql_query($do) or die('UPDATE failed: ' . mysql_error() . " (cmd: $do)");
#echo "<hr><pre>\n", $tag_offsets, "\n</pre><hr>";
}
close_db();
end_content();
}
function get_anno_data($lnk)
{
global $icon_names, $disp_mods, $map_defaults, $span_num;
global $COLOR_LIST, $FONT_LIST;
foreach ($disp_mods as $array) {
list($tID,$aID,$method,$display,$min,$max) = $array;
if ($tID > 0 && $tID != $lnk['TypeID'])
continue;
if ($aID == 0)
$val = preg_replace('/\$_AnnoType/', $lnk['AnnoType'], $anno_attrs[$display]->DefaultValue);
else {
$val = $lnk['_'.$aID];
if ($val == '')
continue;
}
$var = $icon_names[$display];
$val = preg_replace('/\s+/', ' ', $val);
if ($method == 2) { // scale/map values
$diff = $max - $min;
if ($diff > 0)
$val = ($val - $min) / $diff;
else
$val = 1;
if ($map_defaults[$var])
$val = $map_defaults[$var][$val * 4 + 0.5];
else
$val = ((int)($val * 10000) / 100) . '%';
} elseif ($method >= 20) {
$cnt = 20;
foreach ($FONT_LIST as $val => $font) {
if ($cnt++ == $method)
break;
}
} elseif ($method >= 10) {
$cnt = 10;
foreach ($COLOR_LIST as $val => $color) {
if ($cnt++ == $method)
break;
}
}
$lnk[$icon_names[$display]] = $val;
}
$anno_text = preg_replace('/\|/', '|', $lnk['IconText']);
$anno_title = preg_replace('/\|/', '|', $lnk['IconMouseoverText']);
$fg_color = preg_replace('/[^#0-9a-z]/i', '', $lnk['IconTextColor']);
$bg_color = preg_replace('/[^#0-9a-z]/i', '', $lnk['IconBackgroundColor']);
$border_color = preg_replace('/[^#0-9a-z]/i', '', $lnk['IconBorderColor']);
$font = preg_replace('/[^.ibc]/', '', $lnk['IconFontStyle']);
$anno_url = preg_replace('/\|/', '|', $lnk['_50']); // URL
if ($anno_url == '' && $lnk['HasNote'])
$anno_url = '&s=-' . $lnk['ID'];
return ",\n\"" . join('|', array_map('addslashes',
array($span_num, $lnk['ID'], $lnk['TypeID'], $lnk['UserID'], $lnk['AnchorLen'],
$anno_text, $anno_title, $fg_color, $bg_color, $border_color, $font, $anno_url))) . '"';
}
function push_tag($tag_str, $tag, $offset)
{
global $depth_count;
global $build_tag_offsets, $tag_offsets;
if ($tag != 'TABLE')
$tag_str = preg_replace(':/P(#\d+)?$:', '', $tag_str);
if ($tag == 'TR')
$tag_str = preg_replace(':/(TD|TH)(#\d+)?$:', '', $tag_str);
elseif ($tag == 'DD')
$tag_str = preg_replace(':/DT(#\d+)?$:', '', $tag_str);
elseif ($tag == 'DT')
$tag_str = preg_replace(':/DD(#\d+)?$:', '', $tag_str);
if (preg_match('/^(LI|DT|DD|TD|TH|TR)$/', $tag))
$tag_str = preg_replace(':/'.$tag.'(#\d+)?$:', '', $tag_str);
$tag_str .= '/' . $tag;
$cnt = ++$depth_count[$tag_str];
if ($cnt > 1)
$tag_str .= '#' . $cnt;
if ($build_tag_offsets)
$tag_offsets .= $tag_str . ':' . $offset . "\n";
return $tag_str;
}
function pop_tag($tag_str, $tag)
{
while (preg_match(':/([A-Z]+[1-6]?)(#\d+)?$:', $tag_str, $out)) {
$pop = $out[1];
if ($pop != $tag && $pop != 'P') {
switch ($tag) {
case 'TABLE':
if (!preg_match('/^(TD|TH|TR)$/', $pop))
return $tag_str; // Impossible...
break;
case 'OL':
case 'UL':
if ($pop != 'LI')
return $tag_str; // Impossible...
break;
case 'DL':
if ($pop != 'DD' && $pop != 'DT')
return $tag_str; // Impossible...
break;
}
}
$tag_str = preg_replace(':/([A-Z]+[1-6]?)(#\d+)?$:', '', $tag_str);
if ($pop == $tag)
break;
}
return $tag_str;
}
?>