Issue #470476 by jenlampton: Provide a theme function for output HTML
[project/paging.git] / paging.module
1 <?php
2 // Original module written by Marco Scutari.
3 // Rewritten and considerably shortened and made more Drupal-friendly by Earl Miles.
4 // Yet again rewritten, extended and currently maintained by Gurpartap Singh.
5
6 /**
7 * Implementation of hook_help().
8 */
9 function paging_help($path, $arg = 'none') {
10 switch ($path) {
11 case 'admin/help#paging':
12 return '<p>' . t('Break long pages into smaller ones by means of a page break tag (e.g. %separator):</p>
13 <pre>First page here.
14 %separator
15 Second page here.
16 %separator
17 More pages here.</pre>', array('%separator' => '<!--pagebreak-->')) . '<p>' . t('Automatic page breaking based on character or word count is also supported.') . '</p>';
18 break;
19 }
20 }
21
22 /**
23 * Implementation of hook_theme().
24 */
25 function paging_theme() {
26 return array(
27 // Secondary Pager Navigation with page names in drop down list.
28 'paging_drop_down' => array('tags' => NULL, 'limit' => NULL, 'element' => NULL, 'parameters' => NULL, 'quantity' => NULL),
29 // Helper theme function to generates the select list for drop down pager.
30 'paging_drop_down_option' => array('url_chunk' => NULL, 'page_name' => NULL, 'page_no' => NULL, 'selected' => NULL),
31 'pager_wrapper' => array('output' => NULL),
32 );
33 }
34
35 /**
36 * Implementation of hook_menu().
37 */
38 function paging_menu() {
39 $items = array();
40
41 $items['admin/settings/paging'] = array(
42 'title' => t('Paging'),
43 'description' => t('Enable or disable paging, configure separator string, toggle automatic paging and more for each content types.'),
44 'page callback' => 'drupal_get_form',
45 'page arguments' => array('paging_settings'),
46 'access arguments' => array('administer site configuration'),
47 );
48
49 return $items;
50 }
51
52 /**
53 * Menu callback; display module settings form.
54 */
55 function paging_settings() {
56 $form = array();
57
58 // Set the id of the top-level form tag
59 $form['#id'] = 'paging';
60
61 $paging_filter = FALSE;
62 // Retrieve all input filters.
63 foreach (filter_formats() as $format) {
64 // Further retrieve all input formats.
65 foreach (filter_list_format($format->format) as $filter) {
66 // Check if any of the input formats have paging filter enabled.
67 if ($filter->module == 'paging') {
68 $paging_filter = TRUE;
69 break;
70 }
71 }
72 }
73 if (!$paging_filter) {
74 // Warn if paging filter is not yet enabled for any input format.
75 drupal_set_message(t('Paging filter has not yet been enabled for any input formats. !link!', array('!link' => l(t('Take action'), 'admin/settings/filters'))), 'warning paging-warning');
76 }
77
78 // Traverse available node types.
79 foreach (node_get_types('names') as $type => $name) {
80 // Container fieldset.
81 $form[$type]['paging_config'] = array(
82 '#type' => 'fieldset',
83 '#title' => $name,
84 '#collapsible' => TRUE,
85 '#collapsed' => FALSE,
86 '#attributes' => array('class' => 'paging-fieldset', 'id' => 'paging-type-' . $type),
87 );
88
89 // Left column fieldset.
90 $form[$type]['paging_config']['paging_left'] = array(
91 '#type' => 'fieldset',
92 '#collapsible' => FALSE,
93 '#collapsed' => FALSE,
94 '#attributes' => array('class' => 'paging-left'),
95 );
96 // Paging toggle checkbox.
97 $form[$type]['paging_config']['paging_left']['paging_enabled_' . $type] = array(
98 '#type' => 'checkbox',
99 '#title' => '',
100 '#default_value' => variable_get('paging_enabled_' . $type, 0),
101 '#attributes' => array('class' => 'paging-enabled'),
102 );
103 // Paging separator string.
104 $form[$type]['paging_config']['paging_left']['paging_separator_' . $type] = array(
105 '#type' => 'textfield',
106 '#title' => t('Page separator string'),
107 '#size' => 20,
108 '#maxlength' => 255,
109 '#required' => TRUE,
110 '#default_value' => variable_get('paging_separator_' . $type, '<!--pagebreak-->'),
111 '#description' => t('Use an HTML tag that will render reasonably when paging is not enabled, such as %pagebreak or %hr.', array('%pagebreak' => '<!--pagebreak-->', '%hr' => '<hr />')),
112 );
113 // "Paging separator" insertion widget. Can be a normal or image form button.
114 // Accompanied by paging.js.
115 $form[$type]['paging_config']['paging_left']['paging_separator_widget_' . $type] = array(
116 '#type' => 'radios',
117 '#title' => t('Paging separator widget'),
118 '#options' => array(t('Disabled'), t('Image'), t('Button'),
119 ),
120 '#required' => TRUE,
121 '#description' => t('Choose the style of separator string widget. This widget attaches itself to the body textarea and when clicked, inserts separator at cursor position.'),
122 '#default_value' => variable_get('paging_separator_widget_' . $type, 0),
123 '#attributes' => array('class' => 'paging-method'),
124 );
125 // Change "Read more" path when first page is greater than or equal to the teaser.
126 $form[$type]['paging_config']['paging_left']['paging_read_more_enabled_' . $type] = array(
127 '#type' => 'checkbox',
128 '#title' => t('Link "Read more" to second page'),
129 '#description' => t('When enabled, the "Read more" link under teasers will link to the second page of the content, iff the teaser is larger than the first page or if they are the same.'),
130 '#default_value' => variable_get('paging_read_more_enabled_' . $type, 0),
131 );
132 // Style/theme of page navigation links. "Custom" option enables a textfield to enter function name.
133 // Accompanied by paging.js.
134 $form[$type]['paging_config']['paging_left']['paging_pager_widget_' . $type] = array(
135 '#type' => 'radios',
136 '#title' => t('Pager style'),
137 '#options' => array(
138 'pager' => t('Default <small>- <code>!pager</code></small>', array(
139 '!pager' => l('theme_pager()', 'http://api.drupal.org/api/function/theme_pager/6', array('attributes' => array('target' => '_blank'))),
140 )),
141 'paging_drop_down' => t('Drop down list <small>- <code>theme_paging_drop_down()</code></small>'),
142 'custom' => t('Custom'),
143 ),
144 '#required' => TRUE,
145 '#description' => t('Choose style of page navigation. See also: !link.', array('!link' => l('Overriding theme output', 'http://drupal.org/node/173880#function-override'))),
146 '#default_value' => variable_get('paging_pager_widget_' . $type, 'pager'),
147 '#attributes' => array('class' => 'paging-pager'),
148 );
149 // Textfield to accept custom pager theme function name.
150 $form[$type]['paging_config']['paging_left']['paging_pager_widget_custom_' . $type] = array(
151 '#type' => 'textfield',
152 '#title' => t('Custom pager theme function'),
153 '#size' => 20,
154 '#maxlength' => 255,
155 '#required' => TRUE,
156 '#default_value' => variable_get('paging_pager_widget_custom_' . $type, 'pager'),
157 '#description' => t('Enter the part after the prefix <em>theme_</em> of your custom theme function.'),
158 '#attributes' => array('class' => 'paging-pager-custom-' . $type),
159 '#field_prefix' => 'theme_',
160 );
161
162 // Right column fieldset.
163 $form[$type]['paging_config']['paging_right'] = array(
164 '#type' => 'fieldset',
165 '#collapsible' => FALSE,
166 '#collapsed' => FALSE,
167 '#attributes' => array('class' => 'paging-right'),
168 );
169 // Positions to place the page navigation links at.
170 $form[$type]['paging_config']['paging_right']['paging_pager_widget_position_' . $type] = array(
171 '#type' => 'radios',
172 '#title' => t('Pager position'),
173 '#options' => array(
174 'below' => t('Below content'),
175 'above' => t('Above content'),
176 'both' => t('Below and above content'),
177 'manual' => t('None (No output)'),
178 ),
179 '#required' => TRUE,
180 '#description' => t('Choose the position of page navigation. If set to %none, <code>@paging</code> can be used to place it at a customizable location.', array('%none' => t('None'), '@paging' => '$node->paging'))
181 . (module_exists('content') ? ' ' . t('Position of enabled pager(s) can further be customized for <a href="@url">content types</a> under <em>Manage fields</em> tab.', array('@url' => url('admin/content/types'))) : ''),
182 '#default_value' => variable_get('paging_pager_widget_position_' . $type, 'below'),
183 '#attributes' => array('class' => 'paging-pager'),
184 );
185 // Toggle dynamic loading of pages using AJAX.
186 $form[$type]['paging_config']['paging_right']['paging_ajax_enabled_' . $type] = array(
187 '#type' => 'checkbox',
188 '#title' => t('Dynamically load pages using AJAX'),
189 '#description' => t('Clicking on a pager links will load the page content dynamically. Supports both <em>Default</em> and <em>Drop down list</em> pager styles.'),
190 '#default_value' => variable_get('paging_ajax_enabled_' . $type, 0),
191 '#attributes' => array('class' => 'paging-ajax'),
192 );
193 // Toggle UI that helps with assigning names to pages. Disabled with automatic paging.
194 $form[$type]['paging_config']['paging_right']['paging_names_enabled_' . $type] = array(
195 '#type' => 'checkbox',
196 '#title' => t('Display Page names interface'),
197 '#description' => t('Add an interface to manage page names, which otherwise is a text format within content body, like <em>&lt;!--pagenames:First page||Page no. 2||Page 3--&gt;</em>. Disabled when an automatic paging method is selected.'),
198 '#default_value' => variable_get('paging_names_enabled_' . $type, 0),
199 '#attributes' => array('class' => 'paging-names'),
200 );
201 // Set the browser's title to current page's name.
202 $form[$type]['paging_config']['paging_right']['paging_name_title_' . $type] = array(
203 '#type' => 'checkbox',
204 '#title' => t('Change page title to name of current page'),
205 '#description' => t("Change the node's and browser window's title into name of the current page."),
206 '#default_value' => variable_get('paging_name_title_' . $type, 0),
207 );
208 // Optional automatic paging method. Each option opens the corresponding character/word length select list.
209 // Accompanied by paging.admin.js.
210 $form[$type]['paging_config']['paging_right']['paging_automatic_method_' . $type] = array(
211 '#type' => 'radios',
212 '#title' => t('Automatic paging method'),
213 '#options' => array(t('Disabled'), t('Limit by characters <small>(recommended)</small>'), t('Limit by words'),
214 ),
215 '#required' => TRUE,
216 '#description' => t('Choose the method for automatic paging. Automatic paging is ignored where paging separator string is used.'),
217 '#default_value' => variable_get('paging_automatic_method_' . $type, 0),
218 '#attributes' => array('class' => 'paging-method'),
219 );
220 // Automatic paging method. Select list to choose the number of characters per page.
221 $form[$type]['paging_config']['paging_right']['paging_automatic_chars_' . $type] = array(
222 '#type' => 'select',
223 '#title' => t('Length of each page'),
224 '#options' => array(
225 500 => t('500 characters'), 750 => t('750 characters'),
226 1000 => t('1000 characters'), 1500 => t('1500 characters'),
227 2000 => t('2000 characters'), 2500 => t('2500 characters'),
228 3000 => t('3000 characters'), 3500 => t('3500 characters'),
229 4000 => t('4000 characters'), 4500 => t('4500 characters'),
230 5000 => t('5000 characters'), 5500 => t('5500 characters'),
231 6000 => t('6000 characters'), 6500 => t('6500 characters'),
232 7000 => t('7000 characters'), 7500 => t('7500 characters'),
233 ),
234 '#required' => TRUE,
235 '#description' => '<br />' . t('Select the number of characters to display per page.'),
236 '#default_value' => variable_get('paging_automatic_chars_' . $type, 4000),
237 '#prefix' => '<div class="container-inline paging-chars paging-chars-' . $type . '">',
238 '#suffix' => '</div>',
239 );
240 // Automatic paging method. Select list to choose the number of words per page.
241 $form[$type]['paging_config']['paging_right']['paging_automatic_words_' . $type] = array(
242 '#type' => 'select',
243 '#title' => t('Length of each page'),
244 '#options' => array(
245 50 => t('100 words'), 150 => t('150 words'),
246 200 => t('200 words'), 250 => t('250 words'),
247 300 => t('300 words'), 350 => t('350 words'),
248 400 => t('400 words'), 450 => t('450 words'),
249 500 => t('500 words'), 550 => t('550 words'),
250 600 => t('600 words'), 650 => t('650 words'),
251 700 => t('700 words'), 750 => t('750 words'),
252 ),
253 '#required' => TRUE,
254 '#description' => '<br />' . t('Select the number of words to display per page.'),
255 '#default_value' => variable_get('paging_automatic_words_' . $type, 400),
256 '#prefix' => '<div class="container-inline paging-words paging-words-' . $type . '">',
257 '#suffix' => '</div>',
258 );
259 }
260
261 // Add HTML to setup tabbed interface.
262 $form['paging_footer'] = array(
263 '#value' => '<div id="paging-vertical-tabs"><ul class="ui-tabs-nav"></ul></div>',
264 );
265
266 // Vertical tabs for node types. Degrades in absence of JavaScript.
267 $module_path = drupal_get_path('module', 'paging');
268 drupal_add_js($module_path . '/admin/jquery.cookie.min.js', 'module');
269 drupal_add_js($module_path . '/admin/ui.tabs.min.js', 'module');
270 drupal_add_js($module_path . '/admin/paging.admin.js', 'module');
271 drupal_add_css($module_path . '/admin/paging.admin.css', 'module');
272
273 return system_settings_form($form);
274 }
275
276 /**
277 * Implementation of hook_form_alter().
278 */
279 function paging_form_alter(&$form, $form_state, $form_id) {
280 // Check if paging is enabled for the node type.
281 if (isset($form['type']) && isset($form['#node']) && variable_get('paging_enabled_' . $form['#node']->type, 0)) {
282 // Load the required variables.
283 $separator = variable_get('paging_separator_' . $form['#node']->type, '<!--pagesbreak-->');
284 $widget = variable_get('paging_separator_widget_' . $form['#node']->type, 0);
285 $page_names = variable_get('paging_names_enabled_' . $form['#node']->type, 0);
286 $automatic_paging = variable_get('paging_automatic_method_' . $form['#node']->type, 0);
287 $module_path = drupal_get_path('module', 'paging');
288
289 // Expose configuration variables.
290 drupal_add_js(array(
291 'paging' => array(
292 'separator' => $separator,
293 'widget' => $widget,
294 'page_names' => $page_names && !$automatic_paging, // Disable page names UI when automatic paging is enabled.
295 'module_path' => $module_path),
296 ), 'setting');
297
298 // Add JS for button and names handling.
299 drupal_add_js($module_path . '/paging.js', 'module');
300
301 // Add CSS to adjust button location.
302 if ($widget) {
303 drupal_add_css($module_path . '/paging.css', 'module');
304 }
305 }
306 }
307
308 /**
309 * Implementation of hook_block().
310 */
311 function paging_block($op = 'list', $delta = 0, $edit = array()) {
312 // List the block in administration.
313 if ($op == 'list') {
314 $blocks[0] = array(
315 'info' => t('Page Navigation (Paging)'),
316 'weight' => 0,
317 'status' => 0, // Disabled by default.
318 );
319 return $blocks;
320 }
321 // View block on node view pages.
322 else if ($op == 'view' && $delta == 0 && $GLOBALS['_paging_display_block']) {
323 $block = array(
324 'subject' => t('Page navigation'),
325 'content' => paging_render_names(), // Load a rendered list of page links.
326 );
327 return $block;
328 }
329 }
330
331 /**
332 * Returns a rendered list of page links.
333 *
334 * @param $nid
335 * Node ID to render page links for.
336 */
337 function paging_render_names($nid = NULL) {
338 global $pager_page_array;
339 // Load node ID form URL, if none was supplied.
340 $nid = $nid ? $nid : arg(1);
341 // Fetch a structured array containing page names.
342 $names = paging_fetch_names($nid);
343 // Load the node object to counting total number of expected pages.
344 $node = node_load($nid);
345 // Invoke 'load' operation in hook_nodeapi() implementation to calculate the actual number of pages in the node body.
346 paging_nodeapi($node, 'load');
347 // Comparing and mapping the number of pages in $names and $node->page_count.
348 $fake = array_fill(0, (($node->page_count - 1) < 1 ? 1 : ($node->page_count - 1)) + 1, '');
349 $length = count($fake) > count($names) ? count($fake) : count($names);
350 for ($i=0; $i<$length; ++$i) {
351 $merged[$i] = $names[$i];
352 }
353 // Fill the empty names with node title and page number.
354 $names = _paging_populate_empty_names($merged, $node->title);
355 $rendered_links = array();
356 // Element value to distinguish between multiple pagers on one page.
357 $element = 1;
358 // Convert the names into links.
359 foreach ($names as $key => $name) {
360 $page_new = pager_load_array($key, $element, $pager_page_array);
361 $rendered_links[] = theme('pager_link', $name, $page_new, $element);
362 }
363 return theme('item_list', $rendered_links);
364 }
365
366 /**
367 * Helper function to populate empty page names.
368 */
369 function _paging_populate_empty_names($names, $title) {
370 foreach ($names as $key => $name) {
371 trim($names[$key]);
372 if (empty($names[$key])) {
373 $names[$key] = t('!title - Page !number', array('!title' => check_plain($title), '!number' => ($key + 1)));;
374 }
375 }
376 return $names;
377 }
378
379 /**
380 * Return an array of page names for a node.
381 *
382 * @param $node_body
383 * Either the nid of the node or the node object itself.
384 *
385 * @return
386 * An array of page names found in the node body.
387 */
388 function paging_fetch_names($node_body) {
389 if (is_numeric($node_body)) {
390 $node = node_load($node_body);
391 // Support for CCK.
392 if (isset($node->field_body[0]['view'])) {
393 $node_body = $node->field_body[0]['view'];
394 }
395 // Support for CCK.
396 elseif (isset($node->field_body[0]['value'])) {
397 $node_body = $node->field_body[0]['value'];
398 }
399 else {
400 $node_body = $node->body;
401 }
402 }
403 preg_match("/<!--pagenames:(.*?)-->/", $node_body, $matches);
404 return explode('||', $matches[1]);
405 }
406
407 /**
408 * Implementation of hook_nodeapi().
409 */
410 function paging_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
411 // Act only when paging is enabled for node's type and when node is being rendered for normal views.
412 if (variable_get('paging_enabled_' . $node->type, 0) && ($node->build_mode == NODE_BUILD_NORMAL)) {
413 switch ($op) {
414 case 'load':
415 case 'view':
416 case 'alter':
417 // Support for CCK.
418 if (isset($node->field_body[0]['view'])) {
419 _paging_nodeapi($node,
420 $node->field_body[0]['view'],
421 $node->field_teaser[0]['view'],
422 $op, $teaser, $page);
423 }
424 // Support for CCK.
425 elseif (isset($node->field_body[0]['value'])) {
426 _paging_nodeapi($node,
427 $node->field_body[0]['value'],
428 $node->field_teaser[0]['value'],
429 $op, $teaser, $page);
430 }
431 else {
432 _paging_nodeapi($node, $node->body, $node->teaser, $op, $teaser, $page);
433 }
434 break;
435 case 'prepare':
436 // Pass the paging separator string for input format filter tips.
437 $GLOBALS['_paging_sep'] = variable_get('paging_separator_' . $node->type, '<!--pagebreak-->');
438 break;
439 }
440 }
441 elseif ( in_array($op, array('load','view','alter')) && strpos($teaser ? $node->teaser : $node->body, '<!--paging_filter-->') !== FALSE) {
442 // Remove internal tag: <!--paging_filter-->, added in the input filter
443 $node->content['body']['#value'] = str_replace('<!--paging_filter-->', '', $node->content['body']['#value']);
444 }
445 }
446
447 /**
448 * Helper function for paging_nodeapi().
449 */
450 function _paging_nodeapi(&$node, &$node_body, &$node_teaser, $op, $teaser, $page) {
451 switch ($op) {
452 case 'load':
453 $paging_separator = variable_get('paging_separator_' . $node->type, '<!--pagebreak-->');
454 // Check if manual page separators were used.
455 if (strpos($teaser ? $node_teaser : $node_body, $paging_separator) !== FALSE) {
456 $node->pages = explode($paging_separator, $node_body);
457 $node->page_count = count($node->pages);
458 }
459 else {
460 $body_parts = $node_body;
461 // Automatic paging based on character count.
462 if (variable_get('paging_automatic_method_' . $node->type, 0) == 1 && ($max_chars = variable_get('paging_automatic_chars_' . $node->type, 4000)) != 0) {
463 $total_chars = strlen($node_body);
464 // Check if pagination is possible.
465 if ($total_chars > $max_chars) {
466 $body = $node_body;
467 $breaks = (int)($total_chars / $max_chars);
468 $bodypart = array();
469 for ($i = 0; $i <= $breaks; $i++) {
470 $bodypart[$i] = _paging_body_shift($body, $max_chars);
471 $bodycount = strlen($bodypart[$i]);
472 $body = substr($body, $bodycount);
473 }
474 $body_parts = implode($paging_separator, $bodypart);
475 }
476 }
477 // Automatic paging based on word count.
478 elseif (variable_get('paging_automatic_method_' . $node->type, 0) == 2 && ($max_words = variable_get('paging_automatic_words_' . $node->type, 400)) != 0) {
479 $words = explode(' ', $node_body);
480 $total_words = count($words);
481 // Check if pagination is possible.
482 if ($total_words > $max_words) {
483 $breaks = (int)($total_words / $max_words);
484 for ($i = 1; $i < $breaks; $i++) {
485 $index = $i * $max_words;
486 $words[$index] .= $paging_separator;
487 }
488 }
489 $body_parts = implode(' ', $words);
490 }
491 $node->pages = explode($paging_separator, $body_parts);
492 $node->page_count = count($node->pages);
493 }
494 break;
495 case 'view':
496 // Fetch a structured array containing page names.
497 $node->page_names = paging_fetch_names($node->body);
498 // Check if node is being viewed as a teaser (and not as a preview).
499 if ($teaser && strpos($node_teaser, '<!--paging_filter-->') !== FALSE) {
500 // Check to see if the teaser is longer than our first page.
501 if ($node->page_count > 1 && strlen($node->teaser) > strlen($node->pages[0])) {
502 $node->pagemore = true;
503 }
504 }
505 if (strpos($teaser ? $node_teaser : $node_body, '<!--paging_filter-->') !== FALSE) {
506 // Element value to distinguish between multiple pagers on one page.
507 $element = 1;
508 $page = isset($_GET['page']) ? $_GET['page'] : '';
509 $page_elements = explode(',', $page);
510 $node->page_current = $page_elements[1];
511
512 // Only do paging
513 // a) if not in teaser mode;
514 // b) if there is more than one page;
515 // c) if a printable version is not being requested; or
516 // d) if a non-paged version is not being explicitly requested
517 // e.g. http://www.example.com/node/1?page=full or node/1/full.
518 if (!$teaser && $node->page_count > 1 && arg(2) != 'print' && arg(2) != 'full' && $page != 'full') {
519 global $pager_page_array, $pager_total;
520 $pager_page_array = explode(',', $page);
521 $pager_total[$element] = $node->page_count;
522 $page = isset($pager_page_array[$element]) ? $pager_page_array[$element] : 0;
523
524 // Put the current page contents into the node body.
525 $node->content['body']['#value'] = check_markup($node->pages[$page], $node->format, FALSE);
526
527 // Mapping the pages in $node->page_names and $node->page_count to set number of pages as the array length.
528 $fake = array_fill(0, ($node->page_count - 1) + 1, '');
529 $length = count($fake) > count($node->page_names) ? count($fake) : count($node->page_names);
530 for ($i=0; $i<$length; ++$i) {
531 $merged[$i] = $node->page_names[$i];
532 }
533 // Fill the empty names with node title and page number.
534 $node->page_names = _paging_populate_empty_names($merged, $node->title);
535
536 // For use in AJAX.
537 $pager_id = 'paging-pager-' . $node->nid;
538
539 $return_json = FALSE;
540 // Capture pager JSON request
541 if (isset($_REQUEST['paging_json_request']) && $_REQUEST['paging_json_request'] == $pager_id) {
542 // Unset before calling a pager theming function to prevent unecessarily cluttered link URLs.
543 unset($_REQUEST['paging_json_request']);
544 $return_json = TRUE;
545 }
546
547 // Load the page navigation links into $node->paging. Also accessible in node theming.
548 $node->paging = paging_pager_style($node, $element);
549
550 // Find the position to display the page navigation links at.
551 $position = variable_get('paging_pager_widget_position_' . $node->type, 'below');
552
553 if ($position == 'above' || $position == 'both') {
554 $node->content['paging_above']['#value'] = $node->paging;
555 // Get possible manual weight for paging field from CCK setting.
556 if (function_exists('content_extra_field_weight')) {
557 $node->content['paging_above']['#weight'] = content_extra_field_weight($node->type, 'paging_above');
558 }
559 else {
560 $node->content['paging_above']['#weight'] = $node->content['body']['#weight'] - 1;
561 }
562 }
563 if ($position == 'below' || $position == 'both') {
564 $node->content['paging']['#value'] = $node->paging;
565 // Get possible manual weight for paging field from CCK setting.
566 if (function_exists('content_extra_field_weight')) {
567 $node->content['paging']['#weight'] = content_extra_field_weight($node->type, 'paging');
568 }
569 else {
570 $node->content['paging']['#weight'] = $node->content['body']['#weight'] + 1;
571 }
572 }
573
574 $module_path = drupal_get_path('module', 'paging');
575 drupal_add_css($module_path . '/paging.css', 'module');
576 if (variable_get('paging_ajax_enabled_' . $node->type, 0)) {
577 _paging_content_wrap($node);
578 if ($return_json) {
579 $content = array(
580 'paging_above' => $node->content['paging_above'],
581 'body' => $node->content['body'],
582 'paging' => $node->content['paging'],
583 );
584 $response = array(
585 'content' => drupal_render($content),
586 );
587 // Exit with replacement data.
588 exit(drupal_json($response));
589 }
590 // Add scripts for AJAX driven page loading.
591 drupal_add_js($module_path . '/paging.js', 'module');
592 }
593
594 // Set a global value for block visibility.
595 $GLOBALS['_paging_display_block'] = TRUE;
596
597 if (variable_get('paging_name_title_' . $node->type, 0) && !empty($page)) {
598 // Set the browser title to page's name.
599 drupal_set_title($node->page_names[$page]);
600 }
601 }
602 // Remove internal <!--paging_filter--> tag from final output.
603 $node->content['body']['#value'] = str_replace('<!--paging_filter-->', '', $node->content['body']['#value']);
604 }
605 break;
606 }
607 }
608
609 /**
610 * Wrap the pager(s) and node body in a div for compatibility with AJAX replacement method.
611 */
612 function _paging_content_wrap(&$node) {
613 foreach (array('paging_above', 'body', 'paging') as $value) {
614 if (array_key_exists($value, $node->content)) {
615 $x[$value] = $node->content[$value];
616 }
617 }
618 // Sort by #weight.
619 uasort($x, 'element_sort');
620
621 // Opening tag in first item.
622 $start = reset(array_keys($x));
623 $node->content[$start]['#value'] = '<div id="paging-pager-' . $node->nid . '" class="paging-pager-contents">' . $node->content[$start]['#value'];
624
625 // Closing tag in last item.
626 $end = end(array_keys($x));
627 $node->content[$end]['#value'] .= '</div>';
628 }
629
630 /**
631 * Check and return pager navigation in specified format theme.
632 */
633 function paging_pager_style($node = NULL, $element = NULL) {
634 $theme_widget = variable_get('paging_pager_widget_' . $node->type, 'pager');
635
636 if ($theme_widget == 'custom') {
637 // Load the custom user function.
638 $theme_widget = variable_get('paging_pager_widget_custom_' . $node->type, 'pager');
639 }
640
641 if ($theme_widget == 'paging_drop_down') {
642 // Include CSS for 'Drop down pager' styling.
643 drupal_add_css(drupal_get_path('module', 'paging') . '/paging.css', 'module');
644 }
645
646 // Decode the comma entity.
647 $output = str_replace('%2C', ',', theme($theme_widget, NULL, 1, $element, array(), 9, $node->page_names));
648
649 return theme('pager_wrapper', $output);
650 }
651
652 /**
653 * Helper function for automatic page separation by character limit.
654 */
655 function _paging_body_shift($body, $size) {
656 // If we have a short body, the entire body is the teaser.
657 if (drupal_strlen($body) <= $size) {
658 return $body;
659 }
660
661 // If the delimiter has not been specified, try to split at paragraph or
662 // sentence boundaries.
663
664 // The teaser may not be longer than maximum length specified. Initial slice.
665 $teaser = truncate_utf8($body, $size);
666
667 // Store the actual length of the UTF8 string -- which might not be the same
668 // as $size.
669 $max_rpos = strlen($teaser);
670
671 // How much to cut off the end of the teaser so that it doesn't end in the
672 // middle of a paragraph, sentence, or word.
673 // Initialize it to maximum in order to find the minimum.
674 $min_rpos = $max_rpos;
675
676 // Store the reverse of the teaser. We use strpos on the reversed needle and
677 // haystack for speed and convenience.
678 $reversed = strrev($teaser);
679
680 // Build an array of arrays of break points grouped by preference.
681 $break_points = array();
682
683 // A paragraph near the end of sliced teaser is most preferable.
684 $break_points[] = array('</p>' => 0);
685
686 // If no complete paragraph then treat line breaks as paragraphs.
687 $line_breaks = array('<br />' => 6, '<br>' => 4);
688 // Newline only indicates a line break if line break converter
689 // filter is present.
690 if (isset($filters['filter/1'])) {
691 $line_breaks["\n"] = 1;
692 }
693 $break_points[] = $line_breaks;
694
695 // If the first paragraph is too long, split at the end of a sentence.
696 $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
697
698 // Iterate over the groups of break points until a break point is found.
699 foreach ($break_points as $points) {
700 // Look for each break point, starting at the end of the teaser.
701 foreach ($points as $point => $offset) {
702 // The teaser is already reversed, but the break point isn't.
703 $rpos = strpos($reversed, strrev($point));
704 if ($rpos !== FALSE) {
705 $min_rpos = min($rpos + $offset, $min_rpos);
706 }
707 }
708
709 // If a break point was found in this group, slice and return the teaser.
710 if ($min_rpos !== $max_rpos) {
711 // Don't slice with length 0. Length must be <0 to slice from RHS.
712 return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
713 }
714 }
715
716 // If a break point was not found, still return a teaser.
717 return $teaser;
718 }
719
720 /**
721 * Implementation of hook_content_extra_fields().
722 *
723 * CCK hook to allow sorting of the pager field.
724 */
725 function paging_content_extra_fields($type_name = NULL) {
726 $position = variable_get('paging_pager_widget_position_' . $type_name, 'below');
727 if (!empty($type_name) && variable_get('paging_enabled_' . $type_name, 0) && $position != 'manual') {
728 if ($position == 'above' || $position == 'both') {
729 $fields['paging_above'] = array(
730 'label' => t('Paging (Above)'),
731 'description' => t('Pager navigation display.'),
732 'weight' => -1,
733 );
734 }
735 if ($position == 'below' || $position == 'both') {
736 $fields['paging'] = array(
737 'label' => t('Paging (Below)'),
738 'description' => t('Pager navigation display.'),
739 'weight' => 1,
740 );
741 }
742 return $fields;
743 }
744 }
745
746 /**
747 * Implementation of hook_link().
748 */
749 function paging_link($type, $node = NULL, $teaser = FALSE) {
750 $links = array();
751 // Specify a new "Read more" which links to second page of the node if teaser is less than or
752 // equal to the first page contents and if the setting is enabled.
753 if ($type == 'node' && $teaser && $node->teaser && !empty($node->pagemore) && variable_get('paging_read_more_enabled_' . $node->type, 0)) {
754 $links['paging_read_more'] = array(
755 'title' => t('Read more'),
756 'href' => drupal_get_path_alias("node/$node->nid"),
757 'attributes' => array('title' => t('Read the rest of this posting.'), 'class' => 'read-more-paging'),
758 'query' => 'page=0,1'
759 );
760 }
761 return $links;
762 }
763
764 /**
765 * Implementation of hook_link_alter().
766 */
767 function paging_link_alter(&$links, $node) {
768 // Remove the "Read more" link set by node.module.
769 if (!empty($node->teaser) && !empty($node->pagemore) && variable_get('paging_read_more_enabled_' . $node->type, 0)) {
770 unset($links['node_read_more']);
771 }
772 }
773
774 /**
775 * Implementation of hook_filter().
776 */
777 function paging_filter($in_op, $in_delta = 0, $in_format = -1, $in_text = '') {
778 switch ($in_op) {
779 case 'list':
780 return array(t('Paging'));
781 break;
782 case 'description':
783 return t('Allows content to be broken up into pages, using a separator tag (e.g. %separator), configurable on <a href="!url">paging settings page</a>.', array('%separator' => '<!--pagebreak-->', '!url' => url('admin/settings/paging')));
784 break;
785 case 'process':
786 // The filter gets called before the nodeapi 'view' so,
787 // add a comment to the body to inform the nodeapi hook to apply the filter.
788 return '<!--paging_filter-->' . $in_text;
789 break;
790 default:
791 return $in_text;
792 break;
793 }
794 }
795
796 /**
797 * Implementation of hook_filter_tips().
798 */
799 function paging_filter_tips($delta, $format, $long = FALSE) {
800 if ($long) {
801 // Display paging help text in filter tips.
802 return '<h1>' . t('Paging Help') . '</h1>' . paging_help('admin/help#paging', array());
803 }
804 else {
805 return t('Use %separator to create page breaks.', array('%separator' => $GLOBALS['_paging_sep']));
806 }
807 }
808
809 /**
810 * Format a query pager.
811 *
812 * Menu callbacks that display paged query results should call theme('pager') to
813 * retrieve a pager control so that users can view other results.
814 * Format a list of nearby pages with additional query results.
815 *
816 * @param $tags
817 * An array of labels for the controls in the pager.
818 * @param $limit
819 * The number of query results to display per page.
820 * @param $element
821 * An optional integer to distinguish between multiple pagers on one page.
822 * @param $parameters
823 * An associative array of query string parameters to append to the pager links.
824 * @param $quantity
825 * The number of pages in the list.
826 * @return
827 * An HTML string that generates the query pager.
828 *
829 * @ingroup themeable
830 */
831 function theme_paging_drop_down($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9, $page_names = array()) {
832 global $pager_page_array, $pager_total;
833
834 $output = '';
835
836 // Calculate various markers within this pager piece:
837 // Middle is used to "center" pages around the current page.
838 $pager_middle = ceil($quantity / 2);
839 // current is the page we are currently paged to
840 $pager_current = $pager_page_array[$element] + 1;
841 // max is the maximum page number
842 $pager_max = $pager_total[$element];
843 // End of marker calculations.
844
845 $prev_name = truncate_utf8(t($page_names[$pager_current - 2]), 50, TRUE, TRUE);
846 $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('Previous page: !prev', array('!prev' => $prev_name))), $limit, $element, 1);
847
848 $next_name = truncate_utf8(t($page_names[$pager_current]), 50, TRUE, TRUE);
849 $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('Next page: !next', array('!next' => $next_name))), $limit, $element, 1);
850
851 if ($pager_total[$element] > 1) {
852
853 $output .= '<table id="paging-title-navigation" class="paging-drop-down">
854 <tbody>
855 <tr>
856 <td class="previous-page" style="width: 50%; text-align: left;">';
857
858 $output .= '<span>' . $li_previous . '</span>';
859 $options = '';
860
861 // Now generate the actual pager piece.
862 for ($i = 1; $i <= $pager_max; $i++) {
863 $page = $_GET['page'] ? $_GET['page'] : '0,' . ($i-1);
864 if (strpos($page, ',') == 1) {
865 $page = $page[0] . ',' . ($i-1);
866 }
867 else if (!empty($_GET['page']) && strpos($page, ',') === FALSE) {
868 $page = $_GET['page'] . ',' . ($i-1);
869 }
870 $url = $i == 1 ? url($_GET['q']) : url($_GET['q'], array('query' => array('page' => $page)));
871 // Decode the comma entity.
872 $url = str_replace('%2C', ',', $url);
873 $options .= theme('paging_drop_down_option', $url, $page_names[$i-1], $i, ($i == ($pager_current)));
874 }
875
876 $output .= '</td><td class="paging-drop-down" style="text-align: center;">';
877 $output .= '<select name="paging_drop_down_page" onload="Drupal.theme(\'paging_drop_down\');">' . $options . '</select>';
878 $output .= '</td>
879 <td class="next-page" style="width: 50%; text-align: right;">';
880 $output .= '<span>' . $li_next . '</span>';
881 $output .= '</td>
882 </tr>
883 </tbody>
884 </table>';
885
886 drupal_add_js("Drupal.theme.prototype.paging_drop_down = function() {
887 $('.paging-drop-down select').bind('change', function() {
888 location.href = $(this).val();
889 });
890 };
891
892 $(document).ready(function() {
893 Drupal.theme('paging_drop_down');
894 });", 'inline');
895
896 return $output;
897 }
898 }
899
900 /**
901 * Format an "option" HTML element.
902 *
903 * @param $url_chunk
904 * Part of the URL with page query.
905 * @param $page_name
906 * Name of the page.
907 * @param $page_no
908 * Page number.
909 * @param $selected
910 * An optional integer to tell when the option is also the current page.
911 *
912 * @return
913 * An HTML string that generates the select list for drop down pager.
914 *
915 * @ingroup themeable
916 */
917 function theme_paging_drop_down_option($url_chunk = NULL, $page_name = NULL, $page_no = NULL, $selected = NULL) {
918 return '<option' . ($selected ? ' selected=""' : '') . ' value="' . $url_chunk . '">' . $page_name . '</option>';
919 }
920
921 /**
922 * Implementation of hook_wysiwyg_include_directory().
923 */
924 function paging_wysiwyg_include_directory($type) {
925 switch ($type) {
926 case 'plugins':
927 return 'wysiwyg';
928 }
929 }
930
931 /**
932 * Format the wrapper around pager links.
933 *
934 * @param $output
935 * The themed pager links
936 *
937 * @return
938 * An HTML string.
939 */
940 function theme_pager_wrapper($output){
941 return '<div class="links">' . $output . '</div>';
942 }