/** * Convert an annotation to an Atom entry * Logically this is part of the Annotation class, but different applications implement * that differently (OJS in particular), so it's here, but called through Annotation->toAtom(). */ function annotationToAtom(&$annotation, $tagHost, $servicePath, $strippedRoot = '') { $NS_XHTML = 'http://www.w3.org/1999/xhtml'; $sUserId = htmlspecialchars($annotation->getUserId()); $sUserName = htmlspecialchars($annotation->getUserName()); $sNote = htmlspecialchars($annotation->getNote()); $sQuote = htmlspecialchars($annotation->getQuote()); $sUrl = htmlspecialchars($annotation->getUrl()); $sLink = htmlspecialchars($annotation->getLink()); $sQuoteTitle = htmlspecialchars($annotation->getQuoteTitle()); $sQuoteAuthorId = htmlspecialchars($annotation->getQuoteAuthorId()); $sQuoteAuthorName = htmlspecialchars($annotation->getQuoteAuthorName()); $sAccess = htmlspecialchars($annotation->getAccess()); $sAction = htmlspecialchars($annotation->getAction()); // title for display to reader if ('edit' == $annotation->getAction()) { $title = "Edit to \"{$sQuoteTitle}\""; } elseif ($sNote) { $title = "Annotation of \"{$sQuoteTitle}\""; } else { $title = "Highlight of \"{$sQuoteTitle}\""; } // title and summary for display to reader if ($sNote && $sQuote) { $summary = $sNote . ": \"" . $sQuote . "\""; } elseif ($sNote) { $summary = $sNote; } else { $summary = $sQuote; } $s = " <entry>\n"; // Emit range in two formats: sequence for sorting, xpath for authority and speed $sequenceRange = $annotation->getSequenceRange(); if ($sequenceRange) { $s .= " <ptr:range format='sequence'>" . htmlspecialchars($sequenceRange->toString()) . "</ptr:range>\n"; } // Make 100% certain that the XPath expression contains no unsafe calls (e.g. to document()) $xpathRange = $annotation->getXPathRange(); if ($xpathRange && XPathPoint::isXPathSafe($xpathRange->start->getPathStr()) && XPathPoint::isXPathSafe($xpathRange->end->getPathStr())) { $s .= " <ptr:range format='xpath'>" . htmlspecialchars($xpathRange->toString()) . "</ptr:range>\n"; } $s .= " <ptr:access>{$sAccess}</ptr:access>\n" . " <ptr:action>{$sAction}</ptr:action>\n" . " <title>{$title}</title>\n"; // Use double quotes for some attributes because it's easier than passing ENT_QUOTES to // each call to htmlspecialchars $s .= " <link rel='self' type='application/xml' href=\"" . htmlspecialchars($servicePath . '/' . $annotation->getAnnotationId()) . "\"/>\n" . " <link rel='alternate' type='text/html' title=\"{$sQuoteTitle}\" href=\"{$sUrl}\"/>\n"; if ($annotation->getLink()) { $s .= " <link rel='related' type='text/html' title=\"{$sNote}\" href=\"{$sLink}\"/>\n"; } // TODO: Is this international-safe? I could use htmlsecialchars on it, but that might not match the // restrictions on IRIs. #GEOF# $s .= " <id>tag:{$tagHost}," . date('Y-m-d', $annotation->getCreated()) . ':annotation/' . $annotation->getAnnotationId() . "</id>\n" . " <updated>" . MarginaliaHelper::timeToIso($annotation->getModified()) . "</updated>\n" . " <ptr:created>" . MarginaliaHelper::timeToIso($annotation->getCreated()) . "</ptr:created>\n"; // Selected text as summary //echo " <summary>$summary</summary>\n"; // Author of the annotation $s .= " <author>\n" . " <name>{$sUserName}</name>\n" . " <ptr:userid>{$sUserId}</ptr:userid>\n" . " </author>\n"; // Contributor is the sources of the selected text $s .= " <contributor>\n" . " <name>{$sQuoteAuthorName}</name>\n" . " <ptr:userid>{$sQuoteAuthorId}</ptr:userid>\n" . " </contributor>\n"; // Content area /* This ends up making the client display ... for the note, which is confusing and wrong. if ( $sLink ) { if ( $sNote ) $sNote = "<a href=\"$sLink\">$sNote</a>"; else $sNote = "<a href=\"$sLink\">...</a>"; } */ $sQuote = "<q>{$sQuote}</q>"; if ('edit' == $annotation->getAction()) { if ($sNote) { $sNote = "<ins>{$sNote}</ins>"; } if ($sQuote) { $sQuote = "<del>{$sQuote}</del>"; } } $link = ''; if ($annotation->getLink()) { $link = htmlspecialchars($annotation->getLink()); if ($annotation->getLinkTitle()) { $link = "<cite><a href=\"{$link}\">" . htmlspecialchars($annotation->getLinkTitle()) . "</a></cite>"; } else { $link = "<a href=\"{$link}\">See Also</a>"; } } $s .= " <content type='xhtml'>\n" . " <div xmlns='{$NS_XHTML}' class='annotation'>\n" . "<p class='quote'>{$sQuote} ― <span class='quoteAuthor' title='{$sQuoteAuthorId}'>{$sQuoteAuthorName}</span> in " . "<cite><a href=\"{$sUrl}\">{$sQuoteTitle}</a></cite></p>\n" . "<p class='note'>{$sNote}</p>\n" . $link . " </div>\n" . " </content>\n" . " </entry>\n"; return $s; }
function show() { global $CFG; $search = $this->search; $this->errorpage = array_key_exists('error', $_GET) ? $_GET['error'] : null; $query = "SELECT count(*) FROM {$CFG->prefix}marginalia_event_log"; $total_events = count_records_sql($query); $query = "SELECT e.id AS id, e.userid AS userid, e.service AS service, e.action AS action" . ", e.description AS description, e.object_type AS object_type" . ", e.object_id AS object_id, e.modified AS modified" . ", e.course as course" . ", concat(u.firstname, ' ', u.lastname) AS fullname" . ", a.sheet_type AS sheet_type" . ", a.quote AS quote" . ", a.note AS note" . ", a.object_type AS an_object_type" . ", a.object_id AS an_object_id" . ", concat(qu.firstname, ' ', qu.lastname) AS qu_fullname" . ", p.id AS p_id" . ", p.subject AS p_name" . ", p.discussion AS p_discussion" . ", p.created AS p_created" . ", d.name AS d_name" . ", d.id AS d_id" . ", c.shortname AS c_name" . " FROM {$CFG->prefix}marginalia_event_log e" . " LEFT OUTER JOIN {$CFG->prefix}course c ON c.id=e.course" . " LEFT OUTER JOIN {$CFG->prefix}marginalia_annotation_log a" . " ON (e.object_type=" . AN_OTYPE_ANNOTATION . " AND a.eventid=e.id)" . " LEFT OUTER JOIN {$CFG->prefix}user u ON u.id=e.userid" . " LEFT OUTER JOIN {$CFG->prefix}forum_posts p" . " ON (a.object_type=" . AN_OTYPE_POST . " AND p.id=a.object_id)" . " LEFT OUTER JOIN {$CFG->prefix}forum_discussions d" . " ON (a.object_type=" . AN_OTYPE_POST . " AND d.id=p.discussion)" . " LEFT OUTER JOIN {$CFG->prefix}USER qu " . " ON (e.object_type=" . AN_OTYPE_ANNOTATION . " AND qu.id=a.quote_author_id)" . " WHERE 1=1"; if ($search->course) { $query .= " AND e.course=" . (int) $search->course . "\n"; } if ($search->user) { $query .= " AND e.userid=" . (int) $search->user . "\n"; } if ($search->discussion) { $query .= " AND d.id=" . (int) $search->discussion . "\n"; } if ($search->object_type) { $query .= " AND e.object_type=" . (int) $search->object_type; if ($search->object_id) { $query .= " AND e.object_id=" . (int) $search->object_id; } $query .= "\n"; } $query .= " ORDER BY e.modified DESC"; // echo "Query: $query\n"; $events = get_records_sql($query, $search->first - 1, $this->maxrecords); echo "<p>" . $search->desc() . "</p>\n"; echo "<table id='events'>\n"; echo " <thead><tr>\n"; echo " <th>Time</th><th>Course</th><th>User</th><th>Service</th><th>Action</th><th>Description</th><th>Object</th>\n"; if ($this->extracolumns) { echo "<th>Sheet</th><th>Quote</th><th>Note</th><th>Post</th><th>Discussion</th><th>Post Author</th><th>Posted</th>\n"; } echo " </tr></thead><tbody>\n"; if ($events && count($events)) { foreach ($events as $event) { echo " <tr>\n"; echo " <td>" . s(MarginaliaHelper::timeToIso($event->modified)) . "</td>\n"; echo " <td>" . s($event->c_name) . $this->zoom_link(array('course' => $event->course)) . "</td>\n"; echo " <td>" . s($event->fullname) . $this->zoom_link(array('user' => $event->userid)) . "</td>\n"; echo " <td>" . s($event->service) . "</td>\n"; echo " <td>" . s($event->action) . "</td>\n"; echo " <td>" . s($event->description) . "</td>\n"; if (AN_OTYPE_ANNOTATION == $event->object_type) { echo "<td>annotation #" . (int) $event->object_id . $this->zoom_link(array('object_type' => $event->object_type, 'object_id' => $event->object_id)) . "</td>\n"; if ($this->extracolumns) { if (AN_OTYPE_ANNOTATION == $event->object_type) { echo "<td>" . s(annotation_globals::sheet_str($event->sheet_type)) . "</td>\n"; echo "<td>" . s($event->quote) . "</td>\n"; echo "<td>" . s($event->note) . "</td>\n"; if (AN_OTYPE_POST == $event->an_object_type) { $url = $CFG->wwwroot . '/mod/forum/permalink.php?p=' . $event->p_id; echo "<td><a href='" . s($url) . "'>" . s($event->p_id) . "</a></td>\n"; echo "<td>" . s($event->d_name) . $this->zoom_link(array('d' => $event->d_id)) . "</td>\n"; echo "<td>" . s($event->qu_fullname) . "</a></td>\n"; echo "<td>" . s(MarginaliaHelper::timeToIso($event->p_created)) . "</td>\n"; } else { echo "<td colspan='4'></td>\n"; } } else { echo "<td colspan='8'></td>\n"; } } } else { if (AN_OTYPE_POST == $event->object_type) { echo "<td>post #" . (int) $event->object_id . $this->zoom_link(array('object_type' => $event->object_type, 'object_id' => $event->object_id)) . "</td>\n"; echo "<td colspan='8'></td>\n"; } else { echo "<td colspan='9'></td>\n"; } } echo " </tr>\n"; } echo "</tbody></table>\n"; $tsearch = $search->derive(); $tsearch->first = '{first}'; marginalia_summary_lib::show_result_pages($search->first, $total_events, $this->maxrecords, $tsearch->url()); } }