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

Contents of /contributions/modules/photoblog/photoblog.module

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


Revision 1.5 - (show annotations) (download) (as text)
Sat Dec 30 07:38:46 2006 UTC (2 years, 10 months ago) by davidlesieur
Branch: MAIN
CVS Tags: HEAD
Changes since 1.4: +32 -12 lines
File MIME type: text/x-php
Added option to start from the first post instead of the latest post. Nice for slideshows.
1 <?php
2 // $Id: photoblog.module,v 1.2 2006/12/12 01:44:19 davidlesieur Exp $
3
4 /**
5 * @file
6 *
7 * This module implements photoblog features.
8 *
9 * The main specifications/constraints that guide the design of this module are:
10 *
11 * - Have one node per page.
12 *
13 * - Nodes are in chronological order, with simple previous/next navigation.
14 *
15 * - The previous/next navigation links should be consistent, permanent links
16 * (unlike the standard Drupal pager, which uses relative page numbers).
17 *
18 * - Allow for posting comments in each post's page, and without losing the
19 * navigation context (which precludes both the Drupal pager and Views,
20 * unfortunately).
21 *
22 * - Allow the most recent photoblog post to be used as the site's front page.
23 *
24 * To comply with these specifications, photoblog posts must be displayed as
25 * node pages (node/nid paths). Therefore, the navigation context can only be
26 * inferred from the current node.
27 *
28 * So the resulting compromises are:
29 *
30 * - It is possible to navigate through posts from all users, or from one user
31 * at a time, but only one of these two modes is available site-wide.
32 *
33 * - The previous/next navigation follows a term from the photoblog vocabulary.
34 * The navigation path must be unambiguous, which means that any given node
35 * should use only one term from that vocabulary. Fortunately, terms from other
36 * vocabularies can be used without restraint since they are ignored by the
37 * photoblog navigation logic.
38 *
39 * Suggestions to improve this design are always welcome. ;-)
40 */
41
42 /**
43 * Implementation of hook_perm().
44 */
45 function photoblog_perm() {
46 return array('administer photoblogs');
47 }
48
49 /**
50 * Implementation of hook_menu().
51 */
52 function photoblog_menu($may_cache) {
53 $items = array();
54 if ($may_cache) {
55 $items[] = array(
56 'path' => 'admin/content/photoblog',
57 'title' => t('Photoblogs'),
58 'callback' => 'photoblog_admin',
59 'access' => user_access('administer photoblogs'),
60 'description' => t('Create and manage photoblogs.'),
61 );
62 $items[] = array(
63 'path' => 'admin/content/photoblog/list',
64 'title' => t('List'),
65 'access' => user_access('administer photoblogs'),
66 'type' => MENU_DEFAULT_LOCAL_TASK,
67 'weight' => -10,
68 );
69 $items[] = array(
70 'path' => 'admin/content/photoblog/add',
71 'title' => t('Add photoblog'),
72 'callback' => 'photoblog_admin_add',
73 'access' => user_access('administer photoblogs'),
74 'type' => MENU_LOCAL_TASK,
75 );
76 $items[] = array(
77 'path' => 'photoblog/latest',
78 'callback' => 'photoblog_goto',
79 'callback arguments' => array('latest'),
80 'access' => user_access('access content'),
81 'type' => MENU_CALLBACK,
82 );
83 $items[] = array(
84 'path' => 'photoblog/first',
85 'callback' => 'photoblog_goto',
86 'callback arguments' => array('first'),
87 'access' => user_access('access content'),
88 'type' => MENU_CALLBACK,
89 );
90 $items[] = array(
91 'path' => 'admin/settings/photoblog',
92 'title' => ('Photoblog'),
93 'callback' => 'drupal_get_form',
94 'callback arguments' => array('photoblog_admin_settings'),
95 'access' => user_access('administer site configuration'),
96 'description' => t('Configure photoblog settings.'),
97 'type' => MENU_NORMAL_ITEM,
98 );
99 }
100 elseif (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'photoblog' && arg(3) == 'edit' && is_numeric(arg(4))) {
101 $term = taxonomy_get_term((int)arg(4));
102 if ($term) {
103 $items[] = array(
104 'path' => 'admin/content/photoblog/edit',
105 'title' => t('Edit photoblog'),
106 'callback' => 'photoblog_admin_add',
107 'callback arguments' => array((array)$term),
108 'access' => user_access('administer photoblogs'),
109 'type' => MENU_CALLBACK,
110 );
111 }
112 }
113
114 return $items;
115 }
116
117 /**
118 * Implementation of hook_nodeapi().
119 */
120 function photoblog_nodeapi(&$node, $op, $teaser) {
121 if ($node->type == 'image') {
122 switch ($op) {
123 case 'load':
124 $data['photoblog_next_nid'] = photoblog_get_next($node);
125 $data['photoblog_prev_nid'] = photoblog_get_prev($node);
126 return $data;
127
128 case 'view':
129 // Output navigation links in node content, if appropriate
130 $links_output = _photoblog_get_navigation_links_output();
131 if (isset($links_output['content'])) {
132 $node->content['photoblog-navigation-links'] = array(
133 '#value' => theme('links', photoblog_get_navigation_links($node, 'content'), array('class' => 'links photoblog-navigation-links')),
134 '#weight' => 0,
135 );
136 }
137 break;
138
139 case 'insert':
140 case 'update':
141 // Set front page, if appropriate
142 if (variable_get('photoblog_set_frontpage', FALSE) && $node->status && photoblog_is_photoblog_post($node->nid)) {
143 $args = explode('/', variable_get('site_frontpage', ''));
144 if (($args[0] != 'node' && !is_numeric($args[1])) || $node->nid > $args[1]) {
145 variable_set('site_frontpage', "node/$node->nid");
146 }
147 }
148 break;
149 }
150 }
151 }
152
153 /**
154 * Implementation of hook_link(). Outputs the navigation links as node links.
155 */
156 function photoblog_link($type, $node = NULL, $teaser = FALSE) {
157 $links = array();
158 $links_output = _photoblog_get_navigation_links_output();
159 if ($type == 'node' && $node && !$teaser && isset($links_output['links'])) {
160 $links = photoblog_get_navigation_links($node, 'links');
161 }
162 return $links;
163 }
164
165 /**
166 * Implementation of hook_block(). Outputs the navigation links in a block.
167 */
168 function photoblog_block($op = 'list', $delta = 0, $edit = array()) {
169 if ($op == 'list') {
170 $links_output = _photoblog_get_navigation_links_output();
171 if (isset($links_output['block'])) {
172 $blocks[0]['info'] = t('Photoblog navigation');
173 }
174 $blocks[1]['info'] = t('Photoblogs list');
175 return $blocks;
176 }
177 elseif ($op == 'view') {
178 switch ($delta) {
179 case 0:
180 // Find what node is currently displayed
181 if (arg(0) == 'node' && is_numeric(arg(1)) && ($node = node_load(arg(1)))) {
182 // Display the navigation links for the currently displayed node
183 $block['content'] = theme('links', photoblog_get_navigation_links($node, 'block'));
184 }
185 return $block;
186
187 case 1:
188 $block['subject'] = t('Photoblogs');
189 $block['content'] = photoblog_get_start_links();
190 return $block;
191 }
192 }
193 }
194
195 /**
196 * Callback to edit photoblog terms.
197 */
198 function photoblog_admin() {
199 $header = array(t('Name'), t('Operations'));
200
201 $tree = taxonomy_get_tree(_photoblog_get_vid());
202 if ($tree) {
203 foreach ($tree as $term) {
204 $rows[] = array(str_repeat(' -- ', $term->depth) .' '. _photoblog_format_start_link($term->name, '*', $term->tid), l(t('edit photoblog'), "admin/content/photoblog/edit/$term->tid"));
205 }
206 return theme('table', $header, $rows);
207 }
208 else {
209 return t('No photoblogs available');
210 }
211 }
212
213 function photoblog_admin_add($edit = array()) {
214 if ($_POST['op'] == t('Delete') || $_POST['confirm']) {
215 return drupal_get_form('photoblog_confirm_delete', $edit['tid']);
216 }
217
218 return drupal_get_form('photoblog_admin_form', $edit);
219 }
220
221 function photoblog_admin_form($edit = array()) {
222 if (empty($edit)) {
223 $edit['vid'] = _photoblog_get_vid();
224 }
225
226 $form['name'] = array(
227 '#type' => 'textfield',
228 '#title' => t('Photoblog name'),
229 '#default_value' => $edit['name'],
230 '#size' => 60,
231 '#maxlength' => 64,
232 '#description' => t('The name is used to identify the photoblog.'),
233 '#required' => TRUE,
234 );
235 $form['description'] = array(
236 '#type' => 'textarea',
237 '#title' => t('Description'),
238 '#default_value' => $edit['description'],
239 '#cols' => 60,
240 '#rows' => 5,
241 '#description' => ('The description can be used to provide more information about the photoblog.'),
242 );
243 $form['weight'] = array(
244 '#type' => 'weight',
245 '#title' => t('Weight'),
246 '#default_value' => $edit['weight'],
247 '#delta' => 10,
248 '#description' => t('When listing photoblogs, those with with light (small) weights get listed before photoblogs with heavier (larger) weights. Photoblogs with equal weights are sorted alphabetically.'),
249 );
250 $form['start'] = array(
251 '#type' => 'radios',
252 '#title' => t('Start'),
253 '#description' => t("Select what post to start from whenever a link to this photoblog gets generated (e.g. in the photoblog list block). Photoblogs usually start with the latest post, but linking to the first post might be more appropriate if you need more a slideshow than a photoblog."),
254 '#options' => array(
255 'latest' => t('Start from the latest post'),
256 'first' => t('Start from the first post'),
257 ),
258 '#default_value' => variable_get('photoblog_start_'. $edit['tid'], 'latest'),
259 );
260 $form['vid'] = array(
261 '#type' => 'hidden',
262 '#value' => _photoblog_get_vid(),
263 );
264 $form['submit' ] = array(
265 '#type' => 'submit',
266 '#value' => t('Submit'),
267 );
268 if ($edit['tid']) {
269 $form['delete'] = array(
270 '#type' => 'submit',
271 '#value' => t('Delete'),
272 );
273 $form['tid'] = array(
274 '#type' => 'hidden',
275 '#value' => $edit['tid'],
276 );
277 }
278 $form['#base'] = 'photoblog_admin';
279
280 return $form;
281 }
282
283 function photoblog_admin_submit($form_id, $form_values) {
284 $status = taxonomy_save_term($form_values);
285 variable_set('photoblog_start_'. $form_values['tid'], $form_values['start']);
286 switch ($status) {
287 case SAVED_NEW:
288 drupal_set_message(t('Created new photoblog %term.', array('%term' => $form_values['name'])));
289 break;
290 case SAVED_UPDATED:
291 drupal_set_message(t('The photoblog %term has been updated.', array('%term' => $form_values['name'])));
292 break;
293 case SAVED_DELETED:
294 drupal_set_message(t('The photoblog %term has been deleted.', array('%term' => $form_values['name'])));
295 break;
296 }
297 return 'admin/content/photoblog';
298 }
299
300 function photoblog_confirm_delete($tid) {
301 $term = taxonomy_get_term($tid);
302
303 $form['tid'] = array(
304 '#type' => 'value',
305 '#value' => $tid,
306 );
307 $form['name'] = array(
308 '#type' => 'value',
309 '#value' => $term->name,
310 );
311
312 return confirm_form($form, t('Are you sure you want to delete the photoblog %name?', array('%name' => $term->name)), 'admin/content/photoblog', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
313 }
314
315 function photoblog_confirm_delete_submit($form_id, $form_values) {
316 variable_del('photoblog_start_'. $form_values['tid']);
317 taxonomy_del_term($form_values['tid']);
318 drupal_set_message(t('The photoblog %term has been deleted.', array('%term' => $form_values['name'])));
319
320 return 'admin/content/photoblog';
321 }
322
323 /**
324 * Menu callback to redirect to a photoblog post.
325 *
326 * Expects arg(2) for uid, arg(3) for tid, '*' is allowed as a wildcard in both cases.
327 *
328 * @param $dest
329 * Either 'first' to move to the first post of the photoblog, or 'latest' to
330 * move to its latest post.
331 */
332 function photoblog_goto($dest) {
333 if (!arg(2) || arg(2) == '*') {
334 $uid = 0;
335 }
336 elseif (is_numeric(arg(2))) {
337 $uid = arg(2);
338 }
339 else {
340 drupal_not_found();
341 return;
342 }
343
344 if (!arg(3) || arg(3) == '*') {
345 $tid = 0;
346 }
347 elseif (is_numeric(arg(3))) {
348 $tid = arg(3);
349 }
350 else {
351 drupal_not_found();
352 return;
353 }
354
355 $func = "photoblog_get_$dest";
356 if ($nid = $func($uid, $tid)) {
357 drupal_goto("node/$nid");
358 }
359 else {
360 if ($uid) {
361 $user = user_load(array('uid' => $uid));
362 }
363 if ($tid) {
364 $term = taxonomy_get_term($tid);
365 }
366 if ($user && $term) {
367 drupal_set_message(t('There is no photoblog post in %photoblog by %user.', array('%photoblog' => $term->name, '%user' => $user->name)));
368 drupal_goto();
369 }
370 elseif ($user && !$tid) {
371 drupal_set_message(t('There is no photoblog post by %user.', array('%user' => $user->name)));
372 drupal_goto();
373 }
374 elseif (!$uid && $term) {
375 drupal_set_message(t('There is no photoblog post in %photoblog.', array('%photoblog' => $term->name)));
376 drupal_goto();
377 }
378 elseif (!$uid && !$tid) {
379 drupal_set_message(t('There is no photoblog post.'));
380 drupal_goto();
381 }
382 else {
383 drupal_not_found();
384 }
385 }
386 }
387
388 /**
389 * Admin settings callback.
390 */
391 function photoblog_admin_settings() {
392 $vocabs = array();
393 if ($vocabularies = taxonomy_get_vocabularies('image')) {
394 foreach ($vocabularies as $vid => $vocabulary) {
395 $vocabs[$vid] = $vocabulary->name;
396 }
397 }
398 $form['photoblog_set_frontpage'] = array(
399 '#type' => 'checkbox',
400 '#title' => t('Automatically set the front page to the latest post.'),
401 '#default_value' => variable_get('photoblog_set_frontpage', FALSE),
402 '#description' => t('Enable this if you wish to update your site\'s front page each time a new photoblog post is made. This will assign the site\'s front page to the new post. This will apply to any new published content belonging to a photoblog, from any author.'),
403 );
404 $form['navigation'] = array(
405 '#type' => 'fieldset',
406 '#title' => t('Navigation'),
407 '#description' => t('Navigation in photoblogs is done through <em>previous</em> and <em>next</em> links.'),
408 );
409 $form['navigation']['photoblog_navigation_mode'] = array(
410 '#type' => 'select',
411 '#title' => t('Navigation mode'),
412 '#options' => array('global' => t('Global'), 'user' => t('Per-user')),
413 '#multiple' => FALSE,
414 '#default_value' => _photoblog_get_navigation_mode(),
415 '#description' => t('What should the destinations of navigation links be? Previous and next image from any author (<em>global</em>), or from the same author as the current image being viewed? In any case, the previous and next images will belong to the same photoblog category as the current image being viewed.'),
416 );
417 $form['navigation']['photoblog_navigation_links_output'] = array(
418 '#type' => 'select',
419 '#title' => t('Output navigation links into'),
420 '#options' => array('block' => t('Block'), 'content' => t('Content'), 'links' => t('Links')),
421 '#multiple' => TRUE,
422 '#default_value' => _photoblog_get_navigation_links_output(),
423 '#description' => t('Where to output the photoblog navigation links? Choose the option(s) that best meet your theming needs. You may select more than one option. If you choose <em>block</em>, you will have to !enable.', array('!enable' => l(t('enable the photoblog navigation block'), 'admin/build/block'))),
424 );
425 return system_settings_form($form);
426 }
427
428 /**
429 * Callback that lists available photoblogs.
430 */
431 function photoblog_get_start_links() {
432 if (_photoblog_get_navigation_mode() == 'user') {
433 // Find which users have photoblog posts
434 $result = db_query(db_rewrite_sql("SELECT DISTINCT(u.uid), u.name FROM {users} u INNER JOIN {node} n ON u.uid = n.uid INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {term_data} td ON td.tid = tn.tid WHERE n.type = 'image' AND n.status = 1 AND td.vid = %d ORDER BY u.name", 'u'), _photoblog_get_vid());
435 while ($user = db_fetch_object($result)) {
436 $items = _photoblog_get_start_links($user);
437 if (count($items)) {
438 $content .= theme('item_list', $items, t('@user\'s photoblogs', array('@user' => $user->name)));
439 }
440 }
441 }
442 else {
443 $items = _photoblog_get_start_links();
444 if (count($items)) {
445 $content .= theme('item_list', $items);
446 }
447 }
448 return $content;
449 }
450
451 /**
452 * Return an array with navigation links.
453 *
454 * @node Current node.
455 *
456 * @context Context where the navigation links are to be displayed, either
457 * 'content', 'links', or 'block'.
458 */
459 function photoblog_get_navigation_links($node, $context) {
460 $links = array();
461 if ($node->photoblog_prev_nid) {
462 $links['photoblog-prev'] = theme('photoblog_prev', $node->photoblog_prev_nid, $context);
463 }
464 if ($node->photoblog_next_nid) {
465 $links['photoblog-next'] = theme('photoblog_next', $node->photoblog_next_nid, $context);
466 }
467 return $links;
468 }
469
470 /**
471 * Return true when the specified node is considered as a photoblog post.
472 */
473 function photoblog_is_photoblog_post($nid) {
474 static $cache = array();
475 if (!isset($cache[$nid])) {
476 $cache[$nid] = db_result(db_query(db_rewrite_sql("SELECT COUNT(*) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {term_data} td ON tn.tid = td.tid WHERE n.status = 1 AND n.type = 'image' AND td.vid = %d AND tn.nid = %d"), _photoblog_get_vid(), $nid));
477 }
478 return $cache[$nid];
479 }
480
481 /**
482 * Return the nid of the latest image for the specified user and term.
483 */
484 function photoblog_get_latest($uid, $tid) {
485 return _photoblog_get_post($uid, $tid, 'DESC');
486 }
487
488 /**
489 * Return the nid of the first image for the specified user and term.
490 */
491 function photoblog_get_first($uid, $tid) {
492 return _photoblog_get_post($uid, $tid, 'ASC');
493 }
494
495 /**
496 * Return the nid of the most recent image older than the given node.
497 */
498 function photoblog_get_prev($node) {
499 return _photoblog_get_prev_or_next($node, '<', 'DESC');
500 }
501
502 /**
503 * Return the nid of the next image after the given node.
504 */
505 function photoblog_get_next($node) {
506 return _photoblog_get_prev_or_next($node, '>', 'ASC');
507 }
508
509 /**
510 * Formats a link to a photoblog.
511 */
512 function _photoblog_format_start_link($name, $uid, $tid) {
513 $dest = variable_get('photoblog_start_'. $tid, 'latest');
514 return l($name, "photoblog/$dest/$uid/$tid");
515 }
516
517 /**
518 * Return the an image by the specified user and with the specified term. The
519 * first or latest image's nid is returned, depending on the given sort option.
520 */
521 function _photoblog_get_post($uid, $tid, $sort = 'DESC') {
522 $uid_cond = $uid ? "AND n.uid = $uid" : '';
523 $tid_cond = $tid ? "AND tn.tid = $tid" : '';
524 return db_result(db_query(db_rewrite_sql("SELECT n.nid FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {term_data} td ON td.tid = tn.tid WHERE n.type = 'image' AND n.status = 1 AND td.vid = %d $tid_cond $uid_cond ORDER BY n.created $sort LIMIT 1"), _photoblog_get_vid()));
525 }
526
527 /**
528 * Return the nid of the previous or next image relative to the given node.
529 *
530 * The previous and next images are images that share a taxonomy term (from the
531 * vocabulary selected in the module's settings) and have the same author with the
532 * given node.
533 */
534 function _photoblog_get_prev_or_next($node, $op, $order) {
535 $uid_cond = _photoblog_get_navigation_mode() == 'user' ? "AND n.uid = $node->uid" : '';
536 $nid = db_result(db_query(db_rewrite_sql("SELECT n.nid FROM {node} n INNER JOIN {term_node} tn ON tn.nid = n.nid INNER JOIN {term_data} td ON td.vid = %d INNER JOIN {term_node} tn0 ON tn0.tid = tn.tid WHERE n.type = 'image' AND n.status = 1 AND n.created $op %d AND tn0.nid = %d $uid_cond ORDER BY n.created $order LIMIT 1"), _photoblog_get_vid(), $node->created, $node->nid));
537 return $nid;
538 }
539
540 /**
541 * Returns (and possibly creates) a new vocabulary for photoblogs.
542 */
543 function _photoblog_get_vid() {
544 $vid = variable_get('photoblog_vocabulary', 0);
545 if (!$vid) {
546 // Check to see if a photoblog vocabulary exists
547 $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module='photoblog'"));
548 if (!$vid) {
549 $vocabulary = array(
550 'name' => t('Photoblogs'),
551 'multiple' => 0,
552 'required' => 0,
553 'hierarchy' => 0,
554 'relations' => 0,
555 'module' => 'photoblog',
556 'nodes' => array('image' => 1),
557 );
558 taxonomy_save_vocabulary($vocabulary);
559 $vid = $vocabulary['vid'];
560 }
561 variable_set('photoblog_vocabulary', $vid);
562 }
563
564 return $vid;
565 }
566
567 /**
568 * Returns an array containing the output target for navigation links. Possible values are 'links', 'block', or 'content'.
569 */
570 function _photoblog_get_navigation_links_output() {
571 return variable_get('photoblog_navigation_links_output', array('links'));
572 }
573
574 /**
575 * Returns the navigation mode, either 'global' or 'user'.
576 */
577 function _photoblog_get_navigation_mode() {
578 return variable_get('photoblog_navigation_mode', 'global');
579 }
580
581 /**
582 * Returns an array with links to the photoblogs for the given user that have at least one post.
583 */
584 function _photoblog_get_start_links($user = NULL) {
585 $uid_cond = isset($user) ? "AND n.uid = $user->uid" : '';
586 $uid_arg = isset($user) ? $user->uid : '*';
587 $items = array();
588
589 // Build links to each photoblog
590 $tree = taxonomy_get_tree(_photoblog_get_vid());
591 foreach ($tree as $term) {
592 $count = db_result(db_query(db_rewrite_sql("SELECT COUNT(*) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE n.type = 'image' AND n.status = 1 AND tn.tid = %d $uid_cond"), $term->tid));
593 if ($count > 0) {
594 $items[] = _photoblog_format_start_link($term->name, $uid_arg, $term->tid);
595 }
596 }
597
598 return $items;
599 }
600
601 /**
602 * Theme the title of the navigation link for the previous image.
603 * Returns an array in hook_links() fashion.
604 */
605 function theme_photoblog_prev($nid, $context) {
606 return array(
607 'title' => t('Previous'),
608 'href' => "node/$nid",
609 );
610 }
611
612 /**
613 * Theme the title of the navigation link for the next image.
614 * Returns an array in hook_links() fashion.
615 */
616 function theme_photoblog_next($nid, $context) {
617 return array(
618 'title' => t('Next'),
619 'href' => "node/$nid",
620 );
621 }

  ViewVC Help
Powered by ViewVC 1.1.2