/[drupal]/contributions/modules/contemplate/contemplate.module
ViewVC logotype

Contents of /contributions/modules/contemplate/contemplate.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.10 - (show annotations) (download) (as text)
Mon Sep 29 15:28:45 2008 UTC (13 months, 4 weeks ago) by jrglasgow
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +5 -5 lines
File MIME type: text/x-php
added some extra documentation
1 <?php
2 // $Id: contemplate.module,v 1.8 2006/12/12 18:14:26 jjeff Exp $
3 // by Jeff Robbins - Lullabot - www.lullabot.com
4
5 define('CONTEMPLATE_TEASER_ENABLED', 0x0001);
6 define('CONTEMPLATE_BODY_ENABLED', 0x0002);
7 define('CONTEMPLATE_RSS_ENABLED', 0x0004);
8
9 /**
10 * @file
11 * Create templates to customize teaser and body content.
12 *
13 * @todo
14 * - allow deletion of templates
15 */
16
17 /**
18 * Implementation of hook_help().
19 */
20 function contemplate_help($section) {
21 switch ($section) {
22 case 'admin/content/templates/'. arg(3):
23 case 'admin/content/types/'. arg(3) .'/template':
24 return t('<p>Please note that by creating a template for this content type, you are taking full control of its output and you will need to manually add all of the fields that you would like to see in the output. Click <em>reset</em> to remove template control for this content type.</p>'. theme('more_help_link', url('admin/help/contemplate')));
25 case 'admin/help#contemplate':
26 return t('<p>The Content Templates (a.k.a. contemplate) module allows modification of the teaser and body fields using administrator defined templates. These templates use PHP code and all of the node object variables are available for use in the template. An example node object is displayed and it is as simple as clicking on its properties to add them to the current template.</p>
27
28 <p>This module was written to solve a need with the Content Construction Kit (CCK), where it had a tendency toward outputting content in a not-very-pretty way. And as such, it dovetails nicely with CCK, adding a "template" tab to CCK content-type editing pages and pre-populating the templates with CCK\'s default layout. This makes it easy to rearrange fields, output different fields for teaser and body, remove the field title headers, output fields wrapped for use with tabs.module (part of JSTools), or anything you need.</p>
29
30 <p>But Content Template can actually be used on any content type and allows modification of the teaser and body properties before they go out in an RSS feed or are handed off to the theme.</p>
31
32 <h3>Creating templates</h3>
33
34 <p>Enter PHP code similar to <a href="http://drupal.org/node/11816">PHPTemplate</a>. The main difference is that you only have access to the $node object. However, PHPTemplate templates only affect output to the page. Contemplate additionally affects display in RSS feeds and search results.</p>');
35 }
36 }
37
38
39 /**
40 * Implementation of hook_menu().
41 */
42 function contemplate_menu($may_cache) {
43 $items = array();
44
45 if ($may_cache) {
46 $items[] = array(
47 'path' => 'admin/content/templates',
48 'title' => t('Content templates'),
49 'description' => t('Create templates to customize output of teaser and body content.'),
50 'access' => user_access('administer templates'),
51 'callback' => 'contemplate_edit_type'
52 );
53 }
54 else {
55
56 if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types' && arg(3)) {
57 $access = user_access('administer templates');
58
59 $items[] = array(
60 'path' => 'admin/content/types/'. arg(3) .'/template',
61 'title' => t('Template'),
62 'callback' => 'contemplate_edit_type',
63 'access' => $access,
64 'callback arguments' => array(arg(3)),
65 'type' => MENU_LOCAL_TASK,
66 'weight' => 7,
67 );
68 }
69 }
70
71 return $items;
72 }
73
74 /**
75 * Implementation of hook_perm()
76 *
77 */
78 function contemplate_perm(){
79 return array('administer templates');
80 }
81
82
83 /**
84 * Implementation of hook_nodeapi().
85 */
86 function contemplate_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
87 switch ($op) {
88
89 case 'rss item':
90 if ($template = contemplate_get_template($node->type)){
91 if (CONTEMPLATE_RSS_ENABLED & $template->flags && trim($template->rss)) { // only if there's content in teaser field
92 $rss = contemplate_eval($template->rss, $node);
93 // set both teaser and body because we don't know how they've set Drupal
94 $node->teaser = $rss;
95 $node->body = $rss;
96 if ($template->enclosure) {
97 if ($file = contemplate_eval_enclosure($template->enclosure, $node)) {
98 return array(
99 array(
100 'key' => 'enclosure',
101 'attributes' => array(
102 'url' => file_create_url($file->filepath),
103 'length' => $file->filesize,
104 'type' => $file->filemime
105 )
106 )
107 );
108 }
109 }
110 }
111 }
112 break;
113
114 case 'alter':
115 if ($template = contemplate_get_template($node->type)){
116 if($teaser){
117 if (CONTEMPLATE_TEASER_ENABLED & $template->flags && trim($template->teaser)) { // only if there's content in teaser field
118 $node->teaser = contemplate_eval($template->teaser, $node);
119 }
120 }
121 elseif (CONTEMPLATE_BODY_ENABLED & $template->flags && trim($template->body)) { // only if there's content in the body field
122 $node->body = contemplate_eval($template->body, $node);
123 }
124 }
125 break;
126
127 }
128 }
129
130 /**
131 * Admin page... list out the node types
132 *
133 */
134 function contemplate_admin(){
135 $types = node_get_types();
136 $templates = contemplate_get_templates();
137 foreach($types as $type){
138 $rows[] = array(
139 $type->name,
140 l($templates[$type->type] ? t('edit template') : t('create template'), 'admin/content/templates/'. $type->type),
141 );
142 }
143 $header = array(
144 t('content type'),
145 ''
146 );
147 $output .= theme("table", $header, $rows);
148 $output .= contemplate_version();
149 return $output;
150 }
151
152 /**
153 * Menu callback
154 * Edit the template for a specific node-type
155 *
156 * @param string $type
157 */
158 function contemplate_edit_type_form($type = NULL){
159
160 $example = contemplate_examples($type);
161
162 $template = contemplate_get_template($type);
163
164 if($default = contemplate_cck_get_fields($type)){
165 $default_teaser = $default_body = $default;
166 }
167 else {
168 $default_teaser = "<?php print \$teaser ?>\n";
169 $default_body = "<?php print \$body ?>\n";
170 }
171
172 $form['teaser'] = array(
173 '#type' => 'fieldset',
174 '#title' => t('teaser'),
175 '#collapsible' => TRUE,
176 '#collapsed' => CONTEMPLATE_TEASER_ENABLED & $template->flags ? FALSE : TRUE,
177 );
178
179 $form['teaser']['teaser-enable'] = array(
180 '#type' => 'checkbox',
181 '#title' => '<strong>'. t('Affect teaser output') .'</strong>',
182 '#default_value' => CONTEMPLATE_TEASER_ENABLED & $template->flags ? TRUE : FALSE,
183 '#attributes' => array('toggletarget' => '#edit-teaser'),
184 );
185
186 $form['teaser']['teaser'] = array(
187 '#type' => 'textarea',
188 '#title' => t('Teaser Template'),
189 '#default_value' => $template->teaser ? $template->teaser : $default_teaser,
190 '#rows' => 15,
191 '#description' => t('Leave this field blank to leave teaser unaffected.'),
192 '#prefix' => '<div class="contemplate-input">',
193 '#suffix' => '</div>',
194
195 );
196
197 $intro = t("
198 <p>An example node has been loaded and it's properties appear below. Click on the the property names to add them to your template.</p>
199 ");
200
201 $form['teaser']['teaser_example'] = array(
202 '#type' => 'markup',
203 '#value' => '<div class="contemplate-tips form-item"><label>Teaser Variables:</label><div id="edit-teaser-keys" class="contemplate-scroller resizable">'. $intro . $example['teaser'] .'</div></div>'
204 );
205
206 $form['body'] = array(
207 '#type' => 'fieldset',
208 '#title' => t('body'),
209 '#collapsible' => TRUE,
210 '#collapsed' => CONTEMPLATE_BODY_ENABLED & $template->flags ? FALSE : TRUE,
211 );
212
213 $form['body']['body-enable'] = array(
214 '#type' => 'checkbox',
215 '#title' => '<strong>'. t('Affect body output') .'</strong>',
216 '#default_value' => CONTEMPLATE_BODY_ENABLED & $template->flags ? TRUE : FALSE,
217 '#attributes' => array('toggletarget' => '#edit-body'),
218 );
219
220 $form['body']['body'] = array(
221 '#type' => 'textarea',
222 '#title' => t('Body Template'),
223 '#default_value' => $template->body ? $template->body : $default_body,
224 '#rows' => 15,
225 '#description' => t('Leave this field blank to leave body unaffected.'),
226 '#prefix' => '<div class="contemplate-input">',
227 '#suffix' => '</div>',
228
229 );
230
231 $intro = t("
232 <p>An example node has been loaded and it's properties appear below. Click on the the property names to add them to your template.</p>
233 ");
234
235 $form['body']['body_example'] = array(
236 '#type' => 'markup',
237 '#value' => '<div class="contemplate-tips form-item"><label>Body Variables:</label><div id="edit-body-keys" class="contemplate-scroller resizable">'. $intro . $example['body'] .'</div></div>'
238 );
239
240 /* START RSS STUFF */
241
242 $form['rss'] = array(
243 '#type' => 'fieldset',
244 '#title' => t('RSS'),
245 '#collapsible' => TRUE,
246 '#collapsed' => CONTEMPLATE_RSS_ENABLED & $template->flags ? FALSE : TRUE,
247 );
248
249 $form['rss']['rss-enable'] = array(
250 '#type' => 'checkbox',
251 '#title' => '<strong>'. t('Affect RSS output') .'</strong>',
252 '#default_value' => CONTEMPLATE_RSS_ENABLED & $template->flags ? TRUE : FALSE,
253 '#attributes' => array('toggletarget' => '#edit-rss'),
254 '#description' => t('Note that if you do not enable this, Drupal will use either the teaser or body as specified in your <a href="@url">RSS publishing settings</a>.', array('@url' => url('admin/content/rss-publishing'))),
255 );
256
257 $form['rss']['rss'] = array(
258 '#type' => 'textarea',
259 '#title' => t('RSS Template'),
260 '#default_value' => $template->rss ? $template->rss : $default_body,
261 '#rows' => 15,
262 '#description' => t('Leave this field blank to leave RSS unaffected.'),
263 '#prefix' => '<div class="contemplate-input">',
264 '#suffix' => '</div>',
265 );
266
267 $intro = t("
268 <p>An example node has been loaded and it's properties appear below. Click on the the property names to add them to your template.</p>
269 ");
270
271 $form['rss']['rss_example'] = array(
272 '#type' => 'markup',
273 '#value' => '<div class="contemplate-tips form-item"><label>Node Variables:</label><div id="edit-rss-keys" class="contemplate-scroller resizable">'. $intro . $example['rss'] .'</div></div>'
274 );
275
276 $form['rss'][] = array(
277 '#type' => 'markup',
278 '#value' => '<div style="clear:both"></div>',
279 );
280
281 //$fids = contemplate_get_fids();
282
283 global $contemplate_fids;
284
285 if (is_array($contemplate_fids)) {
286 $contemplate_fids = drupal_map_assoc(array_unique($contemplate_fids));
287 $contemplate_fids = array(0 => t('&lt;none&gt; (other modules may add)')) + $contemplate_fids;
288 $form['rss']['enclosure'] = array(
289 '#type' => 'radios',
290 '#title' => t('RSS enclosures'),
291 '#options' => $contemplate_fids,
292 '#default_value' => $template->enclosure,
293 );
294 }
295
296 /* END RSS STUFF */
297
298 $form['type'] = array(
299 '#type' => 'hidden',
300 '#value' => $type,
301 );
302
303 $form['reset'] = array(
304 '#type' => 'submit',
305 '#value' => t('Reset'),
306 '#attributes' => array('onclick' => 'return(confirm("'. t("Are you sure you want to reset this form?\\nAny customizations will be lost.") .'"));'),
307 );
308
309 $form['submit'] = array(
310 '#type' => 'submit',
311 '#value' => t('Submit'),
312 );
313
314 return $form;
315 }
316
317 function contemplate_edit_type($type = NULL) {
318
319 $types = node_get_types();
320
321 if(!$types[$type]){ // if the argument isn't a valid node type, output admin page
322 return contemplate_admin();
323 }
324 drupal_set_title(t('Template for %type', array("%type" => $types[$type]->name)));
325 if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'templates' && arg(3)){
326 $breadcrumbs = drupal_get_breadcrumb();
327 $breadcrumbs[] = l(t('Templates'), 'admin/content/templates');
328 drupal_set_breadcrumb($breadcrumbs);
329 }
330
331 $output = drupal_get_form('contemplate_edit_type_form', $type);
332 return $output;
333 }
334
335 /**
336 * Get a single template
337 *
338 */
339 function contemplate_get_template($type){
340 //only load each template once per page hit
341 static $types = array();
342
343 if(!isset($types[$type])){
344 $types[$type] = db_fetch_object(db_query('SELECT * FROM {contemplate} WHERE type = "%s"', $type));
345 // set the flag incase there is no template so we don't get an undefined index
346 if (empty($types[$type])) {
347 $types[$type] = array('flags' => 0);
348 }
349 }
350 return $types[$type];
351 }
352
353 /**
354 * Get all of the current templates
355 *
356 * @return array of all templates indexed by content typw
357 */
358 function contemplate_get_templates(){
359 $result = db_query('SELECT * FROM {contemplate}');
360 while($r = db_fetch_object($result)){
361 $templates[$r->type]['teaser'] = $r->teaser;
362 $templates[$r->type]['body'] = $r->body;
363 $templates[$r->type]['rss'] = $r->rss;
364 $templates[$r->type]['enclosure'] = $r->enclosure;
365 }
366 return $templates;
367 }
368
369 function contemplate_edit_type_form_submit($form_id, $form_values){
370 $op = isset($_POST['op']) ? $_POST['op'] : '';
371 if ($op == t('Reset')) {
372 contemplate_delete($form_values['type']);
373 drupal_set_message(t('%type template has been reset.', array("%type" => $form_values['type'])));
374 }
375 else {
376 contemplate_save($form_values);
377 drupal_set_message(t('%type template saved.', array('%type' => $form_values['type'])));
378 if (arg(0) == 'admin' && arg(1) == 'node' && arg(2) == 'template'){
379 drupal_goto('admin/content/templates');
380 }
381 }
382 }
383
384 function contemplate_save($edit){
385 contemplate_delete($edit['type']);
386 $flags |= $edit['teaser-enable'] ? CONTEMPLATE_TEASER_ENABLED : 0;
387 $flags |= $edit['body-enable'] ? CONTEMPLATE_BODY_ENABLED : 0;
388 $flags |= $edit['rss-enable'] ? CONTEMPLATE_RSS_ENABLED : 0;
389 return db_query('INSERT INTO {contemplate} (type, teaser, body, rss, enclosure, flags) VALUES ("%s", "%s", "%s", "%s", "%s", %d)', $edit['type'], $edit['teaser'], $edit['body'], $edit['rss'], $edit['enclosure'], $flags);
390
391 }
392
393 function contemplate_delete($type){
394 return db_query('DELETE FROM {contemplate} WHERE type = "%s"', $type);
395 }
396
397
398 /**
399 * Load an example node and display its parts
400 * - used only on template edit page
401 *
402 * @param $type
403 * node type
404 * @return array
405 */
406 function contemplate_node_views($type){
407 // get the nid of the latest node of this type
408 $nid = db_result(db_query('SELECT nid FROM {node} WHERE type = "%s" ORDER BY created DESC', $type));
409 if($nid){
410 $node = node_load($nid);
411 $bodynode = contemplate_node_view($node, FALSE, TRUE);
412 unset($bodynode->teaser);
413
414 // for debugging...
415 // I'm outputting the length of the string representation of the $bodynode object so I can compare it
416 // drupal_set_message('checksum/length of $bodynode: '. strlen(print_r($bodynode, TRUE))); // outputs the correct value
417
418 $teasernode = contemplate_node_view($node, TRUE, FALSE);
419 unset($teasernode->body);
420
421 // drupal_set_message('checksum/length of $teasernode: '. strlen(print_r($teasernode, TRUE)));
422
423 // here's the bug... In PHP5 this next line
424 // outputs the same value as $teasernode... NOT $bodynode... very odd
425 // In PHP4 it's fine
426 // drupal_set_message('checksum/length of $bodynode: '. strlen(print_r($bodynode, TRUE)));
427
428 return array('body'=>$bodynode, 'teaser'=>$teasernode);
429 }
430 else {
431 return FALSE;
432 }
433 }
434
435 /**
436 * Load an example node and display its parts
437 * - used only on template edit page
438 *
439 * @param $type
440 * node type
441 * @return
442 * an array containing the 'body' and 'teaser' versions of the
443 */
444 function contemplate_examples($type){
445 $path = drupal_get_path('module', 'contemplate');
446 drupal_add_js($path .'/contemplate.js');
447 drupal_add_js($path .'/divresizer.js');
448 drupal_add_css($path .'/contemplate.css');
449
450 if($views = contemplate_node_views($type)){
451 $boutput = contemplate_array_variables((array)$views['body'], 'body');
452 $toutput = contemplate_array_variables((array)$views['teaser'], 'teaser');
453 $routput = contemplate_array_variables((array)$views['teaser'], 'rss');
454 }
455 else {
456 $error = t('No %type content items exist to use as an example. Please create a %type item and then come back here to see an output of its parts.', array("%type"=> $type));
457 $toutput = $boutput = $routput = $error;
458 }
459 return array('body' => $boutput, 'teaser' => $toutput, 'rss' => $routput);
460 }
461
462
463 /**
464 * Recursive function goes through node object
465 * returns html representation of the node
466 * strings are clickable and insert into teaser/body fields
467 *
468 * @param $array
469 * array to recurse through
470 * @param $target
471 * target field for javascript insert
472 * @param $parents
473 * used by recursion
474 * @param $object
475 * used by recursion
476 * @return string - html dictionary of node variables
477 */
478 function contemplate_array_variables($array, $target, $parents = FALSE, $object = FALSE){
479 global $contemplate_fids;
480
481 if(is_object($array)){
482 $array = (array)$array;
483 }
484 if(is_array($array)){
485 $output .= "<dl>\n";
486 foreach($array as $field => $value){
487 if($parents){
488 if($object){
489 $field = $parents .'->'.$field;
490 }
491 else {
492 if(is_int($field)){
493 $field = $parents .'['. $field .']';
494 }
495 else {
496 if ($field == 'fid') { // make a note of the fields named "fid"
497 $contemplate_fids[] = "\$node->". $parents .'[\''. $field .'\']';
498 }
499 $field = $parents .'[\''. $field .'\']';
500 }
501 }
502 }
503
504 $type = "";
505 if(!is_string($value)){
506 $type = " (". gettype($value) .")";
507 }
508
509 if(!is_array($value) && !is_object($value)){
510 $output .= "<dt><a href=\"#\" onclick=\"insertAtCursor(document.getElementById('edit-$target'), '<?php print \$node->". addslashes($field) ." ?>');return false;\" title=\"insert this variable into $target\">\$node->$field</a>$type</dt>\n";
511 }
512 else {
513 $output .= "<dt>\$node->$field$type</dt>\n";
514 }
515
516 $output .= "<dd>\n";
517 if(is_array($value)){
518 $output .= contemplate_array_variables($value, $target, $field);
519 }
520 elseif(is_object($value)){
521 $output .= contemplate_array_variables((array)$value, $target, $field, TRUE);
522 }
523 else {
524 $value = is_bool($value) ? ($value ? 'TRUE' : 'FALSE') : $value;
525 $output .= htmlspecialchars(print_r($value, TRUE)) ."\n";
526 }
527 $output .= "</dd>\n";
528 }
529 $output .= "</dl>\n";
530 }
531 return $output;
532 }
533
534 /**
535 * Run example node through view hooks to present the node object parts
536 *
537 * This is an exact copy of node_view() changed just to return the node object rather than the themed node view
538 *
539 * - used only on the template editing pages
540 */
541 function contemplate_node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE){
542 $node = (object)$node;
543
544 $node = node_build_content($node, $teaser, $page);
545
546 if ($links) {
547 $node->links = module_invoke_all('link', 'node', $node, !$page);
548
549 foreach (module_implements('link_alter') AS $module) {
550 $function = $module .'_link_alter';
551 $function($node, $node->links);
552 }
553 }
554
555 // Set the proper node part, then unset unused $node part so that a bad
556 // theme can not open a security hole.
557 $content = drupal_render($node->content);
558
559 /*
560 if ($teaser) {
561 $node->teaser = $content;
562 unset($node->body);
563 }
564 else {
565 $node->body = $content;
566 unset($node->teaser);
567 }*/
568
569 // Allow modules to modify the fully-built node.
570 node_invoke_nodeapi($node, 'alter', $teaser, $page);
571
572 // drupal_set_message('checksum/length returned: '. strlen(print_r($node, TRUE)));
573
574 return $node;
575 }
576
577
578 /**
579 * Copy of drupal_eval(), but extracts the node object so that variables are available to the template
580 *
581 * @param $tmplt
582 * the template code
583 * @param $obj
584 * an object to extract into the local variables
585 * @return
586 * executed template code
587 */
588 function contemplate_eval($tmplt, $obj){
589 extract((array)$obj);
590 $node = $obj;
591 ob_start();
592 print eval('?>'. $tmplt);
593 $output = ob_get_contents();
594 ob_end_clean();
595 return $output;
596 }
597
598 function contemplate_eval_enclosure($field, $node) {
599 $tmplt = "<?php print ". $field ." ?>";
600 $fid = contemplate_eval($tmplt, $node);
601 if (is_numeric($fid)) {
602 $file = db_fetch_object(db_query('SELECT * FROM {files} WHERE fid = %d', $fid));
603 return $file;
604 }
605 return FALSE;
606 }
607
608
609 function contemplate_cck_get_fields($type_name){
610 if(module_exists('content')){
611 $return = array();
612
613 // for compatibility with both CVS and 4.7 versions of CCK
614 // remove conditionals once "content_types()" is committed to 4.7
615
616 // start remove
617 if(function_exists('content_types')){
618 $type = content_types($type_name); // <-- keep this part
619 }
620 else {
621 $types = _content_types();
622 $type = (array)$types[$type_name];
623 }
624 // end remove
625
626 if($type){
627 // if this is a CCK field
628 foreach($type['fields'] as $field_name => $field){
629 $return[] = theme('contemplate_field', $field);
630 }
631 $return = implode("\n", $return);
632 }
633 else {
634 $return = FALSE;
635 }
636 }
637 else {
638 $return = FALSE;
639 }
640 return $return;
641 }
642
643 /**
644 * Rewrite of theme_field to output default CCK output into the template.
645 *
646 * @param unknown_type $node
647 * @param unknown_type $field
648 * @param unknown_type $items
649 * @param unknown_type $teaser
650 * @param unknown_type $page
651 * @return unknown
652 */
653 function theme_contemplate_field(&$field) {
654 $output = '';
655
656 $output .= '<div class="field field-type-'. strtr($field['type'], '_', '-') .' field-'. strtr($field['field_name'], '_', '-') .'">'."\n";
657
658 $output .= ' <h3 class="field-label">'. $field['widget']['label'] .'</h3>'."\n";
659
660 $output .= ' <div class="field-items">'."\n";
661 $output .= " <?php foreach ((array)\$". $field['field_name'] ." as \$item) { ?>"."\n";
662 $output .= ' <div class="field-item">'. "<?php print \$item['view'] ?>" .'</div>'."\n";
663 $output .= " <?php } ?>"."\n";
664 $output .= ' </div>'."\n";
665
666 $output .= '</div>'."\n";
667
668 return $output;
669 }
670
671 function contemplate_version(){
672 return str_replace(array('$RCSf'.'ile:', ',v', '$Re'.'vision: ', '$Da'.'te: ', '$'), '', '<p style="font-size:x-small">$RCSfile: contemplate.module,v $ version: <b>$Revision: 1.8 $</b>, $Date: 2006/12/12 18:14:26 $</p>');
673 }

  ViewVC Help
Powered by ViewVC 1.1.2