Added missing </span> closing tag.
[project/quote_plus.git] / quote_plus_fqb / quote_plus_fqb.module
1 <?php
2
3 /**
4 * @file
5 * "Full quote buster" feature extension for Quote plus module.
6 *
7 * Checks if given quotes need to be considered full quotes
8 * depending on defined criteria and, if necessary, shortens them
9 * and adds a link to retrieve the entire quote length.
10 */
11
12 /**
13 * Default configuration values.
14 */
15 define('QUOTE_PLUS_FQB_THRESHOLD', 750);
16 define('QUOTE_PLUS_FQB_MAXLENGTH', 500);
17
18 define('QUOTE_PLUS_FQB_TOGGLE_LINK_NONE', 0);
19 define('QUOTE_PLUS_FQB_TOGGLE_LINK_ADD', 1);
20 define('QUOTE_PLUS_FQB_TOGGLE_LINK_PLACEHOLDER', 2);
21
22 /**
23 * Implements hook_comment().
24 */
25 function quote_plus_fqb_comment(&$a1, $op) {
26 // Initialize js flags.
27 static $js_added = FALSE;
28 $add_js = FALSE;
29 // Prepare static request path and query details cache.
30 static $is_node_view, $is_preview, $fq;
31 if (!isset($fq)) {
32 $fq = is_array($_GET['fq']) ? array_flip($_GET['fq']) : array();
33 }
34 if ($op == 'view') {
35 // We will only work on node views.
36 if (!isset($is_node_view)) {
37 $is_node_view = preg_match('#^node/[0-9]+(/view)?$#', $_GET['q']) != 0;
38 $is_preview = !$is_node_view && preg_match('#^comment/(quote|reply)/[0-9]+(/[0-9]+)?$#', $_GET['q']) != 0;
39 }
40 if ($is_node_view || $is_preview) {
41 while (preg_match('/<!--fqb-start=([0-9]+)\.([0-9]+)-->(.*?)<!--fqb-end-->/s', $a1->comment, $found)) {
42 if (isset($fq[$found[1]])) {
43 // Replace short quotes, if requested.
44 // 1. Retrieve pre-filtered long quote.
45 if ($is_node_view) {
46 $longquote = _quote_plus_fqb_load($found[1], $found[2], $a1->cid, TRUE, TRUE, FALSE, QUOTE_PLUS_FQB_TOGGLE_LINK_ADD);
47 // 2. Add toggle link.
48 $longquote .= ' ' . _quote_plus_fqb_toggle_link($found[1], $a1->cid, FALSE);
49 $add_js = TRUE;
50 $a1->comment = str_replace($found[0], $longquote, $a1->comment);
51 }
52 }
53 else {
54 // Otherwise, remove comment tags.
55 $a1->comment = str_replace($found[0], $found[3], $a1->comment);
56 }
57 }
58 if ($is_node_view) {
59 // Replace any <!--fql-toggle--> link placeholder with a toggle link.
60 while (preg_match('/<!--fql-toggle=([0-9]+)-->/', $a1->comment, $found)) {
61 $a1->comment = str_replace(
62 $found[0],
63 ' ' . _quote_plus_fqb_toggle_link($found[1], $a1->cid, TRUE),
64 $a1->comment
65 );
66 $add_js = TRUE;
67 }
68 }
69 }
70 }
71 if ($add_js && !$js_added) {
72 drupal_add_js(drupal_get_path('module', 'quote_plus_fqb') . '/quote_plus_fqb.js');
73 drupal_add_js(array('quotePlusFqb' => array('callbackPath' => 'js/quote_plus_fqb/ajax')), 'setting');
74 $js_added = TRUE;
75 }
76 }
77
78 /**
79 * Implements hook_cron().
80 */
81 function quote_plus_fqb_cron() {
82 // Remove shortened quotes after one week.
83 // That should be long enough to cover looooong browser sessions.
84 // (Otherwise the Ajax retrieval of a quote could fail just in case
85 // a user falls asleep while reading a comment page...
86
87 db_query("DELETE FROM {quote_plus_fqb} where created < %d", time() - 7 * 24 * 60 * 60);
88 }
89
90 /**
91 * Implements hook_disable().
92 */
93 function quote_plus_fqb_disable() {
94 // If fqb submodule is being disabled, there may still be
95 // shortened quotes in the filter format cache. They would
96 // still be displayed without a chance to switch to full quotes.
97 cache_clear_all('*', 'cache_filter', TRUE);
98 }
99
100 /**
101 * Implements hook_enable().
102 */
103 function quote_plus_fqb_enable() {
104 // If fqb submodule was not disabled, there may still be
105 // unshortened quotes in the filter format cache.
106 // fqb module would be useless for up to one day.
107 cache_clear_all('*', 'cache_filter', TRUE);
108 }
109
110 /**
111 * Implements hook_js().
112 */
113 function quote_plus_fqb_js() {
114 return array(
115 'ajax' => array(
116 'callback' => '_quote_plus_fqb_ajax',
117 'includes' => array('unicode'),
118 ),
119 );
120 }
121
122 /**
123 * Implements hook_menu().
124 */
125 function quote_plus_fqb_menu() {
126 return array(
127 'js/quote_plus_fqb/ajax' => array(
128 'title' => 'JS fullquote toggle',
129 'page callback' => '_quote_plus_fqb_ajax',
130 'type' => MENU_CALLBACK,
131 ),
132 );
133 }
134
135 /**
136 * Retrieve quotes from the DB and serve them via JSON output.
137 *
138 * Checks user access, evaluates POST data and conditionally returns
139 * a shortened or a full length quote.
140 */
141 function _quote_plus_fqb_ajax() {
142 if (user_access('access comments')) {
143 // Evaluate POST params.
144 if (is_numeric($qid = $_POST['qid']) && is_numeric($full = $_POST['full']) && is_numeric($cid = $_POST['cid']) && is_numeric($sid = $_POST['sid'])) {
145 // Retrieve the short or full quote.
146 $quote = _quote_plus_fqb_load($qid, $sid, $cid, $full == 1, TRUE, FALSE, QUOTE_PLUS_FQB_TOGGLE_LINK_ADD);
147 drupal_json(array('content' => $quote));
148 }
149 }
150 }
151
152 /**
153 * Process one text block out of one quote level.
154 *
155 * @param $block
156 * Text block without sub quotes. Not yet filtered,
157 * may contain HTML etc.
158 * @param $format
159 * Current filter format (settings related).
160 *
161 * @return string
162 * The processed block content.
163 */
164 function _quote_plus_fqb_process($block, $format) {
165 // Remove the filter indicator we have set to force refiltering,
166 // in case e.g. the HTML filter did not do that yet.
167 // @see quote_plus_fqb_comment()
168 $block = preg_replace('/<!--fqb-->$/', '', $block);
169
170 // Configuration data.
171 static $threshold = array();
172 static $maxlength = array();
173 if (!isset($threshold[$format])) {
174 $threshold[$format] = variable_get(
175 "quote_plus_fqb_threshold_$format",
176 QUOTE_PLUS_FQB_THRESHOLD
177 );
178 $maxlength[$format] = variable_get(
179 "quote_plus_fqb_maxlength_$format",
180 QUOTE_PLUS_FQB_MAXLENGTH
181 );
182 }
183
184 // Shortened quotes and length determination will base on plain text.
185 $plain_text = decode_entities(strip_tags($block));
186 $total_length = drupal_strlen($plain_text);
187
188 // Find out if the block is a candidate and, if so, process it.
189 if ($total_length > $threshold[$format]) {
190
191 $data = array(
192 'sid' => rand(100000, 999999),
193 'full' => check_markup($block, $format),
194 'short' => truncate_utf8($plain_text, $maxlength[$format], TRUE, TRUE),
195 'length' => $total_length,
196 'created' => time(),
197 );
198 drupal_write_record('quote_plus_fqb', $data);
199
200 // Only shorten if DB operation was successful.
201 if ($data['qid']) {
202 $block = _quote_plus_fqb_load($data['qid'], $data['sid'], NULL, FALSE, TRUE, TRUE, QUOTE_PLUS_FQB_TOGGLE_LINK_PLACEHOLDER);
203 }
204 }
205 return $block;
206 }
207
208 /**
209 * Load a formatted quote from the database.
210 *
211 * @param $qid
212 * Internal quote ID.
213 * @param $sid
214 * The additional "secure" ID.
215 * @param $cid
216 * ID of the comment the quote belongs to.
217 * @param $full
218 * If TRUE, load the full quote. Otherwise the shortened version.
219 * @param $container
220 * If TRUE, return the quote embedded in a shortquote/fullquote container
221 * consisting of a div with properly named classes and ID and comment
222 * tags indicating a quote sections start, end and a placeholder for a
223 * toggle link.
224 * @param $delimiters
225 * Whether to enclose the container in <!--fqb-start--><!--fqb-end-->
226 * delimiters (needed for conditional replacement of the pre-filtered
227 * cache data) in hook_comment.
228 * @param $toggle_link
229 * Whether to add a toggle link or a placeholder or none of these.
230 * See QUOTE_PLUS_FQB_TOGGLE_LINK_ constant definitions.
231 *
232 * @return string|bool
233 * The quote related to $qid or FALSE, if loading failed.
234 */
235 function _quote_plus_fqb_load($qid, $sid, $cid, $full = TRUE, $container = FALSE, $delimiters = FALSE, $toggle_link = QUOTE_PLUS_FQB_TOGGLE_LINK_NONE) {
236 if (is_numeric($qid) && is_numeric($sid)) {
237 if ($res = db_query('SELECT * FROM {quote_plus_fqb} WHERE qid = %d AND sid = %d', $qid, $sid)) {
238 $data = db_fetch_array($res);
239 if ($container) {
240 $ret = '<div class="quote-plus-fqb quote-plus-fqb-' . ($full ? 'full' : 'short')
241 . 'quote" id="quote-plus-fqb-' . $data['qid']. '"'
242 ;
243 if (!$full) {
244 $ret .= ' title="' . t(
245 'This quote block has been shortened from a total length of appr. !length characters for better readability.',
246 array('!length' => $data['length'])
247 ) . '"';
248 }
249 $ret .= '><span class="quote-plus-fqb-sid" id="quote-plus-fqb-sid-' . $data['sid'] . '"></span>' . $data[$full ? 'full' : 'short'];
250 if ($toggle_link == QUOTE_PLUS_FQB_TOGGLE_LINK_ADD) {
251 $ret .= ' ' . _quote_plus_fqb_toggle_link($data['qid'], $cid, !$full);
252 }
253 elseif ($toggle_link == QUOTE_PLUS_FQB_TOGGLE_LINK_PLACEHOLDER) {
254 $ret .= '<!--fql-toggle=' . $data['qid'] . '--></div>';
255 }
256 if ($delimiters) {
257 $ret = '<!--fqb-start=' . $data['qid'] . '.' . $data['sid'] . '-->' . $ret . '<!--fqb-end-->';
258 }
259 }
260 else {
261 $ret = $data[$full ? 'full' : 'short'];
262 }
263 return $ret;
264 }
265 }
266 return FALSE;
267 }
268
269 /**
270 * Generate a toggle link.
271 *
272 * @param $qid
273 * Internal quote ID (for link CSS id).
274 * @param $cid
275 * Comment ID (for direct redirection in non-JS calls).
276 * @param $full
277 * If true, link to a full quote. Otherwise to a short quote.
278 *
279 * @return string
280 * An HTML link snippet.
281 *
282 * @todo
283 * Add theming support?
284 */
285 function _quote_plus_fqb_toggle_link($qid, $cid, $full) {
286 static $query, $fq;
287 if (!isset($query)) {
288 $query = is_array($_GET) ? $_GET : array();
289 unset($query['fq']);
290 unset($query['q']);
291 $fq = is_array($_GET['fq']) ? $_GET['fq'] : array();
292 }
293 if ($full) {
294 return l(
295 '(' . t('Show full quote') . ')',
296 'comment/' . $cid,
297 array(
298 'attributes' => array(
299 'class' => 'quote-plus-fqb-toggle',
300 'id' => 'quote-plus-fqb-toggle-' . $qid,
301 ),
302 'query' => array_merge($query, array('fq' => array_merge((array)$_GET['fq'], array($qid)))),
303 )
304 );
305 }
306 else {
307 return l(
308 '(' . t('Hide full quote') . ')',
309 'comment/' . $cid,
310 array(
311 'attributes' => array(
312 'class' => 'quote-plus-fqb-toggle',
313 'id' => 'quote-plus-fqb-toggle-' . $qid,
314 ),
315 'query' => array_merge($query, array('fq' => array_diff((array)$_GET['fq'], array($qid))))
316 )
317 );
318 }
319 }
320
321 /**
322 * Validation callback for fqb specific filter settings.
323 *
324 * @see quote_plus.module
325 * @see quote_plus_form_alter()
326 */
327 function _quote_plus_fqb_settings_validate($form, &$form_state) {
328 if (!is_numeric($form_state['values']['quote_plus_fqb_threshold'])) {
329 form_set_error('quote_plus_fqb_threshold', t('The long quote threshold must be a positive integer.'));
330 }
331 if (
332 !is_numeric($form_state['values']['quote_plus_fqb_maxlength'])
333 ||
334 $form_state['values']['quote_plus_fqb_maxlength'] >= $form_state['values']['quote_plus_fqb_threshold']
335 ) {
336 form_set_error('quote_plus_fqb_maxlength', t('The maximum short quote length must be a positive integer and smaller than the threshold.'));
337 }
338 }