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

Contents of /contributions/modules/ed_classified/ed_classified.module

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


Revision 1.4 - (show annotations) (download) (as text)
Sun Sep 13 03:06:58 2009 UTC (2 months, 2 weeks ago) by milesgillham
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +85 -32 lines
File MIME type: text/x-php
stable checkin
1 <?php
2 // $Id: ed_classified.module,v 1.3 2009/09/11 04:01:36 milesgillham Exp $
3
4 /**
5 * @file
6 * Simple text-based classified ads module.
7 * Michael Curry, Exodus Development, Inc.
8 * exodusdev@gmail.com
9 * for more information, please visit http://exodusdev.com/drupal/modules/classified.module
10 * Copyright (c) 2006, 2007 Exodus Development, Inc. All Rights Reserved.
11 * Licensed under the terms of the GNU Public License (GPL) version 2. Please see LICENSE.txt for
12 * license terms. Possession and use of this code signifies acceptance of license
13 * terms.
14 */
15
16 /**
17 * If you want to remove taxonomy links from the node, and you are using the phptemplate theme engine,
18 * Simply create a node-ed_classified.tpl.php and remove (or modify) the contents of the $terms
19 * theme variable. $taxonomy array is also available.
20 * See: http://drupal.org/node/46012
21 */
22
23 /**
24 * TODO
25 * db_rewrite_sql must change for Drupal 7 (http://drupal.org/node/224333#db_rewrite_sql)
26 */
27 define('EDI_CLASSIFIED_MODULE_NAME', 'ed_classified');
28
29 /*
30 * Drupal 7 provides REQUEST_TIME as time of start of request. This
31 * is more efficient than using time() every time. Adopted here.
32 */
33 if (!defined(REQUEST_TIME)) {
34 define('REQUEST_TIME', time());
35 }
36
37 // During bootstrap process the VERSION variable is not available so this
38 // way we have our own hint.
39 $DRUPAL_VERSION=variable_get('ed_classified_drupal_version', 'UNKNOWN');
40 if ('UNKNOWN' == $DRUPAL_VERSION) {
41 if (defined('VERSION')) {
42 variable_set('ed_classified_drupal_version', $DRUPAL_VERSION = VERSION);
43 define('DRUPAL_VERSION', VERSION);
44 } else {
45 // This shouldn't happen ever more than once so shouldn't be too serious.
46 watchdog(EDI_CLASSIFIED_MODULE_NAME, 'Unable to determine Drupal version in bootstrap');
47 }
48 } else {
49 define('DRUPAL_VERSION', $DRUPAL_VERSION);
50 }
51
52 /**
53 * Set EDI_CLASSIFIED_PATH_NAME to override the URL path component for classified ads.
54 * For example, if you know that you won't collide with other module node types, you could use 'classified' or 'classified-ads' or whatever you want
55 *
56 * @See: http://drupal.org/node/122260 - Drupal 5.x node.module seems to dislike underscores in node types.. sigh. Do not use underscores here or you will
57 * see strange side effects.
58 * Also, Drupal 5 and 6 seem to require that you use a string matching the module name. More work to be done here. Looks like you can create a URL alias
59 * from the value of EDI_CLASSIFIED_PATH_NAME => ed-classified (or ed_classified) and then the system will work. So, perhaps we can auto-generate a path alias
60 * from the currrent value of EDI_CLASSIFIED_PATH_NAME -> ed-classified.
61 *
62 * Also note: you must clear the menu caches in order to see this change take effect post-install.
63 */
64 define('EDI_CLASSIFIED_PATH_NAME', 'ed-classified');
65
66
67 define('EDI_CLASSIFIED_MODULE_VERSION', '$Id: ed_classified.module,v 1.3 2009/09/11 04:01:36 milesgillham Exp $');
68 define('EDI_CLASSIFIED_VAR_DEF_BODYLEN_LIMIT', 500);
69 define('EDI_CLASSIFIED_VAR_DEF_EXPIRATION_DAYS', 30);
70 define('EDI_CLASSIFIED_VAR_DEF_PURGE_AGE', 15); // how many days after expiration to purge ads?
71 define('EDI_CLASSIFIED_VAR_DEF_AD_EXPIRATION_EMAIL_WARNING_DAYS', 7);
72 define('EDI_CLASSIFIED_VAR_DEF_SHOW_CONTACT_LINK_ON_POSTS', TRUE);
73 define('EDI_CLASSIFIED_VAR_DEF_SEND_EMAIL_REMINDERS', FALSE);
74 define('EDI_CLASSIFIED_VAR_DEF_SHOW_BODY_IN_AD_LIST', TRUE);
75 define('EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT', TRUE); // If upload_image module is present, offer enhanced functionality
76 define('EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT_DESCRIPTION', 'Add any photos or other images to your ad here. Please be sure to check the \'list\' checkbox in order to ensure that the photo is visible in your ad. Note that changes made to the attachments are not permanent until you save this classified ad by clicking the [Submit] button.');
77 define('EDI_CLASSIFIED_VAR_DEF_EMAIL_BODY', 'One or more of your classified ads on !sitename (!siteurl) are expiring soon. Please sign in and visit !user_ads_url to check your ads.');
78 define('EDI_CLASSIFIED_VAR_DEF_EMAIL_SUBJ', '!sitename reminder: classified ads expiring soon!');
79 define('EDI_CLASSIFIED_VAR_DEF_EXP_EMAIL_BODY', 'A classified ad on !sitename (!siteurl) has expired. Please sign in and visit !user_ads_url to check your ads.');
80 define('EDI_CLASSIFIED_VAR_DEF_EXP_EMAIL_SUBJ', '!sitename notification: classified ad expired!');
81
82 define('EDI_CLASSIFIED_BANNER_URL', 'http://exodusdev.com/sites/default/files/ed-classified-banner-small.png');
83 define('EDI_CLASSIFIED_INFO_URL', 'http://exodusdev.com/drupal/modules/ed_classified.module');
84
85 // since drupal_get_path() appears not to be available at module load time, we need to fake it.
86 // there may be a better way to find the module path. Investigate.
87 define('EDI_CLASSIFIED_MODULE_PATH', dirname(drupal_get_filename('module', 'ed_classified')));
88
89 // ditto for version
90
91 require_once(EDI_CLASSIFIED_MODULE_PATH .'/ed_classified_themefuncs.inc');
92 require_once(EDI_CLASSIFIED_MODULE_PATH .'/ed_classified_settings.inc');
93
94 /*
95 xodule_load_include('inc', 'ed_classified', 'ed_classified_utils');
96 xodule_load_include('inc', 'ed_classified', 'ed_classified_themefuncs');
97 xodule_load_include('inc', 'ed_classified', 'ed_classified_views');
98
99 xodule_load_include('inc', 'ed_classified', 'ed_classified_notifications'); // only when we need to send a notification
100 xodule_load_include('inc', 'ed_classified', 'ed_classified_delete'); // only when we need to delete
101 xodule_load_include('inc', 'ed_classified', 'ed_classified_settings'); // only in admin settings.
102 */
103
104 /**
105 * Replacement for Drupal 5
106 */
107 if (!function_exists('module_load_include')) {
108 function module_load_include($suffix, $module, $file) {
109 if (empty($suffix)) {
110 $suffix = 'inc';
111 }
112 require_once(EDI_CLASSIFIED_MODULE_PATH. '/' . "$file.$suffix");
113 }
114 }
115
116 /**
117 * Implementation of hook_block().
118 *
119 */
120 function ed_classified_block($op = 'list', $delta = 0, $edit = array()) {
121 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
122
123 $parms = _ed_classified_displayname_parms();
124 if ($op == 'list') {
125 $blocks[0]['info'] = t('@name - Popular (requires access log enabled)', $parms);
126 $blocks[1]['info'] = t('@name - Latest', $parms);
127 $blocks[2]['info'] = t('@name - Stats', $parms);
128 return $blocks;
129 }
130 else if ($op == 'configure') {
131 // OPTIONAL: Enter form elements to add to block configuration screen, if required.
132 }
133 else if ($op == 'save') {
134 // OPTIONAL: Add code to trigger when block configuration is saved, if required.
135 }
136 else if ($op == 'view') {
137 if (user_access('access content')) {
138 switch ($delta) {
139 case 0:
140 $block['subject'] = t('Popular @name', $parms);
141 $block['content'] = ed_classified_get_popular_ads_list();
142 break;
143 case 1:
144 $block['subject'] = t('Latest @name', $parms);
145 $block['content'] = ed_classified_get_latest_ads_list();
146 break;
147
148 case 2:
149 $block['subject'] = t('@name Statistics', $parms);
150 $block['content'] = ed_classified_get_ad_stats();
151 break;
152 }
153 }
154 // suppress empty blocks
155 if (empty($block['content'])) {
156 $block = array(); // zap empty block
157 }
158 return $block;
159 }
160 }
161
162
163
164 /**
165 * Implementation of hook_cron().
166 */
167 function ed_classified_cron() {
168 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
169 module_load_include('inc', 'ed_classified', 'ed_classified_delete'); // only when we need to delete
170 module_load_include('inc', 'ed_classified', 'ed_classified_notifications'); // only when we need to send a notification
171 $time = REQUEST_TIME;
172 /* Process reminder mails as needed */
173 if (_ed_classified_variable_get('send_email_reminders', EDI_CLASSIFIED_VAR_DEF_SEND_EMAIL_REMINDERS)) {
174 _ed_classified_process_notification_emails($time);
175 }
176 _ed_classified_expire_ads($time);
177
178 // purge old ads if possible
179 _ed_classified_purge();
180 }
181
182
183 /**
184 * Implementation of hook_form_alter().
185 * For image ads, we suggest using attachments and the upload_image module. If the upload_image module is enabled,
186 * and the classified ad node type has an 'attachments' form element, this function will
187 * add explanatory text to help users understand how to add an image to their ads.
188 * Also, this will un-collapse the attachments block, so that it is visible at all times.
189 * If you don't want this feature, disable it in admin/settings
190 */
191
192 switch (reset(explode('.', DRUPAL_VERSION))) {
193 case 5:
194 // Drupal 5 hook
195 function ed_classified_form_alter($form_id, &$form) {
196 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
197
198 if ($form['type']['#value'] == EDI_CLASSIFIED_MODULE_NAME) {
199 if ($form_id == 'ed_classified_node_form' && $form['attachments'] && _ed_classified_variable_get('alter_attachment_text', EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT) ) {
200 // Don't allow the attachments block to be collapsed.
201 $form['attachments']['#collapsed']=FALSE;
202 $form['attachments']['#collapsible']=FALSE;
203 // Enhance the help for classified ads.
204 // NOTE: this is appropriate for the upload_image module enhancements only!
205 $form['attachments']['#title']=t('Photo Attachments');
206 $form['attachments']['#description']= _ed_classified_variable_get('alter_attachment_text_description', t(EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT_DESCRIPTION));
207 }
208 }
209 }
210 break;
211 case 6:
212 case 7:
213 function ed_classified_form_alter(&$form, $form_state, $form_id) {
214 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
215
216 if ($form['type']['#value'] == EDI_CLASSIFIED_MODULE_NAME) {
217 if ($form_id == 'ed_classified_node_form' && $form['attachments'] && _ed_classified_variable_get('alter_attachment_text', EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT) ) {
218 // Don't allow the attachments block to be collapsed.
219 $form['attachments']['#collapsed']=FALSE;
220 $form['attachments']['#collapsible']=FALSE;
221 // Enhance the help for classified ads.
222 // NOTE: this is appropriate for the upload_image module enhancements only!
223 $form['attachments']['#title']=t('Photo Attachments');
224 $form['attachments']['#description']= _ed_classified_variable_get('alter_attachment_text_description', t(EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT_DESCRIPTION));
225 }
226 }
227 }
228 break;
229 default:
230 drupal_set_message(t('Classified Ad Module - Unknown Drupal version'));
231 break;
232 }
233
234
235 /**
236 * Implementation of hook_help().
237 */
238 switch (reset(explode('.', DRUPAL_VERSION))) {
239 case 5:
240 function ed_classified_help($section) {
241 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
242
243 $parms = _ed_classified_displayname_parms();
244 switch ($section) {
245 case 'admin/help#ed_classified':
246 return t('You can manage current @name using this page.', $parms);
247 case 'admin/modules#description':
248 return t('@name module', $parms);
249 case 'node/add#ed_classified':
250 return t('Create a @name.', $parms);
251 }
252 }
253 break;
254 case 6:
255 case 7:
256 function ed_classified_help($path, $arg) {
257 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
258
259 $parms = _ed_classified_displayname_parms();
260 switch ($path) {
261 case 'admin/help#ed_classified':
262 return t('You can manage current @name using this page.', $parms);
263 case 'admin/modules#description':
264 return t('@name module', $parms);
265 case 'node/add#ed_classified':
266 return t('Create a @name.', $parms);
267 }
268 }
269 break;
270 default:
271 drupal_set_message(t('Classified Ad Module - Unknown Drupal version'));
272 break;
273 }
274
275 /**
276 * Implementation of hook_link().
277 */
278 function ed_classified_link($type, $node = NULL, $teaser = FALSE) {
279 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
280
281 $links = array();
282 global $user;
283
284 if (_ed_classified_node_is_classified($node)) {
285 if (user_access('access user profiles') && _ed_classified_variable_get('show_contact_form_link_on_posts', TRUE) && _ed_classified_module_exists('contact')) {
286 $ad_author = user_load(array('uid' => $node->uid));
287 if ($ad_author && $user->uid != $ad_author->uid && $ad_author->uid != 0) {
288 $links['ed_classified_contact'] =
289 array('title' => t('View the advertiser\'s (@advertiser) profile.', array('@advertiser' => $ad_author->name)),
290 'href' => 'user/'. $ad_author->uid,
291 'html' => TRUE);
292 }
293 }
294
295 // Show contact link
296
297 if (0 != $user->uid && module_exists('contact')) { // only if logged in and there's a sitewide contact form
298 $links['ed_classified_suggest_new_category'] =
299 array('title' => t('Suggest a new category'),
300 'href' => 'contact',
301 'attributes' => array('title' => t('Click here to suggest a new classified ad category')));
302 }
303 }
304 return $links;
305 }
306
307 /**
308 * Implementation of hook_link_alter (&$links, $node)
309 * Find and destroy old taxonomy links for the classified ads node. This
310 * will create links to the custom classified ads taxonomy view.
311 * This would be easier if the hook callback knew what module was calling it,
312 * and the type of links that were just added. Since we don't we have to
313 * do some checking to find what we are looking for.
314 */
315 function ed_classified_link_alter(&$node, &$links) {
316 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
317
318 // array with keys like [taxonomy_term_n] => title, href, etc.
319 if (_ed_classified_node_is_classified($node)) {
320 $tax = $node->taxonomy;
321 foreach ($tax as $t) {
322 // kill any links like taxonomy_term_n
323 // OR, replace them with links to ed_classified/tid/n?
324 $key = 'taxonomy_term_'. $t->tid;
325 if (_ed_tid_is_classified_term($t->tid) && isset($links[$key])) {
326 $links[$key]['href'] = _ed_classified_make_category_path($t->tid);
327 $links[$key]['attributes'] = array('title' => t('View other ads like this one in the \'!cat\' category.', array('!cat' => $t->name)));
328 }
329 }
330 }
331 }
332
333 /**
334 * Implementation of hook_boot()
335 */
336
337 /*
338 function ed_classified_boot() {
339 // Code that is executed on each page request, even for cached pages.
340 }
341 */
342
343 /**
344 * Implementation of hook_init()
345 *
346 */
347 function ed_classified_init() {
348 // Code that is executed on page requests for non-cached pages only.
349 // inject our css per http://api.drupal.org/api/HEAD/function/hook_init and http://api.drupal.org/api/HEAD/function/hook_menu
350 drupal_add_css(EDI_CLASSIFIED_MODULE_PATH .'/ed_classified.css');
351
352 // During bootstrap process the VERSION variable is not available so this
353 // way we have our own hint. Update our variable if the Drupal VERSION changes.
354 $DRUPAL_VERSION = variable_get('ed_classified_drupal_version', 'UNKNOWN');
355 if ((defined('VERSION')) && (('UNKNOWN' == $DRUPAL_VERSION) || (VERSION != $DRUPAL_VERSION))) {
356 variable_set('ed_classified_drupal_version', VERSION);
357 }
358 }
359
360 /**
361 * Implementation of hook_menu().
362 */
363 function ed_classified_menu() {
364 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
365
366 global $user;
367 $items = array();
368 $parms = _ed_classified_displayname_parms();
369 $name = _ed_classified_displayname();
370
371 $items[EDI_CLASSIFIED_PATH_NAME] = array(
372 'title' => $name,
373 'access arguments' => array('access content'),
374 'type' => MENU_NORMAL_ITEM, // MENU_SUGGESTED_ITEM,
375 'page callback' => 'ed_classified_page');
376
377 $items['admin/content/node/'. EDI_CLASSIFIED_PATH_NAME] = array(
378 'title' => _ed_classified_displayname(),
379 'access arguments' => array('administer classified ads'),
380 'type' => MENU_LOCAL_TASK,
381 'page callback' => 'ed_classified_admin_overview'
382 );
383
384 $items['admin/content/'. EDI_CLASSIFIED_PATH_NAME] = array(
385 'title' => _ed_classified_displayname(),
386 'access arguments' => array('administer classified ads'),
387 'type' => MENU_NORMAL_ITEM,
388 'page callback' => 'ed_classified_admin_overview',
389 'description' => 'List and manage '. $name .' nodes.'
390 );
391 $items['admin/settings/'. EDI_CLASSIFIED_PATH_NAME] = array(
392 'title' => $name,
393 'title arguments' => $parms,
394 'description' => "Configure $name settings",
395 'page callback' => 'drupal_get_form',
396 'page arguments' => array('ed_classified_admin_settings'),
397 'access arguments' => array('administer site configuration'),
398 'type' => MENU_NORMAL_ITEM, // optional
399 );
400 $items['admin/'. EDI_CLASSIFIED_PATH_NAME .'/purge'] = array(
401 'title' => 'purge',
402 'page callback' => '_ed_classified_user_purge',
403 'access arguments' => array(user_access('administer classified ads')),
404 'type' => MENU_CALLBACK
405 );
406
407 /* per-user options - use menu loader wildcards */
408 if (
409 (user_access('create classified ads')) ||
410 (user_access('edit own classified ads')) ||
411 (user_access('reset classified ad expiration'))
412 ) {
413 $items['user/%user/'. EDI_CLASSIFIED_PATH_NAME] = array(
414 'title' => 'My @name list',
415 'title arguments' => $parms,
416 'page callback' => 'ed_classified_by_user',
417 'page arguments' => array(1),
418 'access arguments' => array('access user profiles'),
419 'type' => MENU_LOCAL_TASK,
420 );
421 }
422
423 return $items;
424 }
425
426 function ed_classified_admin_expired() {
427 return ed_classified_admin_overview(0, TRUE);
428 }
429
430 /**
431 * Get a list of classified ads for a given user
432 */
433 function ed_classified_by_user($user) {
434 if (!$user) {
435 // TODO this may be a bit abrupt, should we do softer error checking?
436 drupal_not_found();
437 }
438 else {
439 return ed_classified_admin_overview($user->uid);
440 }
441 }
442
443 /**
444 * Present admin options.
445 */
446
447 function ed_classified_admin_overview($uid=0, $expired=FALSE) {
448 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
449
450 global $user;
451 $can_edit = user_access('administer classified ads') || ($uid == $user->uid && user_access('edit own classified ads'));
452 $showstats = module_exists('statistics') && variable_get('statistics_count_content_views', 0);
453 $header = array(
454 array('data' => t('Title'), 'field' => 'title'),
455 /* array('data' => t('Creator'), 'field' => ' */
456 array('data' => t('Created'), 'field' => 'created'),
457 array('data' => t('Published?'), 'field' => 'status'),
458 array('data' => t('Expires'), 'field' => 'expires_on'));
459 if ($showstats) {
460 $header[] = array('data' => t('Total'), 'field' => 'totalcount');
461 $header[] = array('data' => t('Recent'), 'field' => 'daycount');
462 }
463 if ($can_edit) {
464 $header[] = array('data' => t('Edit'));
465 }
466 $user_select ='';
467 if ($uid != 0) {
468 $user_select = " AND n.uid = $uid";
469 }
470
471
472 if ($expired) {
473 $unpublished = ' AND n.status = 0 AND ec.expires_on < '. REQUEST_TIME;
474 }
475
476 // if statistics enabled, etc.
477 if ($showstats) {
478 // outer join allows us to see nodes that have no entries in the node_counter table
479 // The select columns have been modified to work for postgres
480 $sql = "SELECT n.*, ec.expires_on, ec.expiration_notify_last_sent, nc.totalcount, nc.daycount FROM {node} n, {edi_classified_nodes} ec LEFT OUTER JOIN {node_counter} AS nc ON nc.nid = ec.nid WHERE n.vid = ec.vid $unpublished $user_select ". tablesort_sql($header);
481 }
482 else {
483 $sql = "SELECT n.*, ec.expires_on, ec.expiration_notify_last_sent FROM {node} n, {edi_classified_nodes} ec WHERE n.vid = ec.vid $unpublished $user_select ". tablesort_sql($header);
484 }
485
486 $result = pager_query($sql, 50);
487 $time = REQUEST_TIME;
488 while ($ad = db_fetch_object($result)) {
489 $expired = ed_classified_ad_expired($ad, $time);
490 $expire_interval = format_interval($ad->expires_on-$time, 2);
491 $fields = array(
492 array('data' => l($ad->title, drupal_get_path_alias("node/$ad->nid"), array('attributes' => array('title' => check_markup($ad->teaser))))),
493 array('data' => format_date($ad->created, 'custom', 'n/j/y'), 'nowrap' => 'nowrap'),
494 array('data' => $ad->status ? t('yes') : t('no')),
495 array('data' => $expired ? t('expired') : format_date($ad->expires, 'custom', 'n/j/y', $ad->expires_on) . t(' (!expire_interval)', array('!expire_interval' => $expire_interval)), 'nowrap' => 'nowrap',
496 'class' => $expired ? 'classified-expired-flag' : 'classified-unexpired-flag'));
497 if ($showstats) {
498 // because some nodes may never have been counted, we put 0 by default
499 $fields[] = array('data' => ($ad->totalcount == NULL ? 0 : $ad->totalcount));
500 $fields[] = array('data' => ($ad->daycount == NULL ? 0 : $ad->daycount));
501 }
502 if ($can_edit) {
503 $fields[] = array('data' => ' ['. _ed_classified_make_edit_link($ad, 'edit', array('title' => 'Edit this ad.')) .']');
504 }
505
506 $rows[] = $fields;
507 }
508
509 // TODO: use similar approach as the /admin/node page - checkboxes, delete button, confirmation
510 if ($uid != 0 && user_access('create classified ads')) {
511 $output = '<div class="classified-profile-link-add">'.
512 l(t('Create a new ad'), drupal_get_path_alias("node/add/". EDI_CLASSIFIED_PATH_NAME), array('attributes' => array('title' => t("Click here to create a new Classified Ad.")))) ."</div>\n";
513 }
514
515 $output .= theme('table', $header, $rows) . theme('pager', NULL, 50, 0);
516 if ($uid != 0 && user_access('administer classified ads')) {
517 $days = _ed_classified_variable_get('ad_expired_purge_age', EDI_CLASSIFIED_VAR_DEF_PURGE_AGE);
518 $output .= l(
519 t('Purge old expired ads'),
520 drupal_get_path_alias('admin/'. EDI_CLASSIFIED_PATH_NAME .'/purge'),
521 array('attributes' => array('title' => t("Delete ads that are !days days old, marked expired and unpublished.", array('!days' => $days))))
522 );
523 }
524
525 return $output;
526 }
527
528
529 /**
530 * Display a page of classified ads, as appropriate.
531 * Lifted from image_gallery module. Shameless.
532 */
533
534 function ed_classified_page($type = NULL, $tid = 0) {
535 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
536
537 // get a list of categories and counts
538 $cats = taxonomy_get_tree(_ed_classified_get_vid(), $tid, -1, 1);
539 for ($i=0; $i < count($cats); $i++) {
540 $cats[$i]->count = taxonomy_term_count_nodes($cats[$i]->tid, EDI_CLASSIFIED_MODULE_NAME);
541 $tree = taxonomy_get_tree(_ed_classified_get_vid(), $cats[$i]->tid, -1);
542 $descendant_tids = array_merge(array($cats[$i]->tid), array_map('_taxonomy_get_tid_from_term', $tree));
543 $last = db_fetch_object(db_query_range(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. implode(',', $descendant_tids) .') AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), 0, 1));
544 if (isset($last->nid)) {
545 $cats[$i]->latest = node_load(array('nid' => $last->nid));
546 }
547 }
548
549 // TODO: order by created date, #views, what?
550 $ads = array();
551 if ($tid) {
552 # $result = pager_query(db_rewrite_sql("SELECT n.nid FROM {term_node} t INNER JOIN {node} n ON t.nid=n.nid WHERE n.status=1 AND n.type='ed_classified' AND t.tid=%d ORDER BY n.sticky DESC, n.created DESC"), _ed_classified_variable_get('ads_per_page', 10), 0, NULL, $tid);
553 // The following line from phdhiren works, just not sure how vid is appropriate, but it works (well, vid is overloaded as version and as vocabulary in different contexts)
554 $result = pager_query(db_rewrite_sql("SELECT n.nid FROM {term_node} t INNER JOIN {node} n ON t.vid=n.vid WHERE n.status=1 AND n.type='ed_classified' AND t.tid=%d ORDER BY n.sticky DESC, n.created DESC"), _ed_classified_variable_get('ads_per_page', 10), 0, NULL, $tid);
555 while ($node = db_fetch_object($result)) {
556 $ads[] = node_load(array('nid' => $node->nid));
557 }
558 $classified_cat = taxonomy_get_term($tid);
559 $parents = taxonomy_get_parents($tid);
560 foreach ($parents as $parent) {
561 $breadcrumb[] = l($parent->name, drupal_get_path_alias(_ed_classified_make_category_path($parent->tid)));
562 }
563 $breadcrumb[] = l(_ed_classified_displayname(), drupal_get_path_alias(EDI_CLASSIFIED_PATH_NAME));
564 $breadcrumb = array_reverse($breadcrumb);
565 drupal_set_title($classified_cat->name);
566 }
567
568 // $breadcrumb[] = l('blah', $_GET['q']);
569 drupal_set_breadcrumb($breadcrumb); // menu_set_location($breadcrumb);
570
571 $content = theme('ed_classified_taxonomy', $cats, $ads);
572
573 return $content;
574 }
575
576 switch (reset(explode('.', DRUPAL_VERSION))) {
577 case 5:
578 case 6:
579 /**
580 * Implementation of hook_perm().
581 */
582 function ed_classified_perm() {
583 return array('create classified ads', 'edit own classified ads', 'reset classified ad expiration', 'administer classified ads');
584 }
585 break;
586 case 7:
587 /**
588 * Implementation of hook_permisssion().
589 */
590 function ed_classified_permission() {
591 return array(
592 // TODO, do we need a "bypass node access" perm as well?
593 'administer classified ads' => array(
594 'title' => t('Administer classified ads'),
595 'description' => t('Administer classified ads')
596 ),
597 'create classified ads' => array(
598 'title' => t('Create classified ads'),
599 'description' => t('Create classified ads')
600 ),
601 'edit own classified ads' => array(
602 'title' => t('Edit own classified ads'),
603 'description' => t('Edit own classified ads')
604 ),
605 'reset classified ad expiration' => array(
606 'title' => t('Reset classified ad expiration'),
607 'description' => t('Reset classified ad expiration')
608 )
609 );
610 }
611 break;
612 }
613
614
615
616 switch (reset(explode('.', DRUPAL_VERSION))) {
617 case 5:
618 case 6:
619 /**
620 * Implementation of hook_access().
621 * Removed in Drupal 7 in favour of hook_node_access()
622 * TODO - should we create hook_node_access() ?
623 */
624 function ed_classified_access($op, $node, $account) {
625 //echo "hook_access: $op, $node->nid, $account->uid, $node->uid<br/>";
626 if ($op == 'create') {
627 return user_access('create classified ads', $account);
628 }
629
630 if ($op == 'update' || $op == 'delete') {
631 if (user_access('edit own classified ads', $account) && ($account->uid == $node->uid)) {
632 return TRUE;
633 }
634 }
635 }
636 break;
637 case 7:
638 break;
639 }
640
641
642
643 /**
644 * Implementation of hook_delete().
645 */
646 function ed_classified_delete(&$node) {
647 db_query('DELETE FROM {edi_classified_nodes} WHERE nid = %d', $node->nid);
648 }
649
650
651
652 /**
653 * Implementation of hook_form().
654 */
655 function ed_classified_form(&$node) {
656 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
657
658 $type = node_get_types('type', $node);
659 $form['title'] = array(
660 '#type' => 'textfield',
661 '#title' => check_plain($type->title_label),
662 '#required' => TRUE,
663 '#default_value' => $node->title);
664 $max_body_length = _ed_classified_variable_get('ad_standard_body_length', EDI_CLASSIFIED_VAR_DEF_BODYLEN_LIMIT);
665 $current_body_length = strlen(trim($node->body));
666
667 $form['body_filter']['body'] = array(
668 '#type' => 'textarea',
669 '#title' => check_plain($type->body_label),
670 '#default_value' => $node->body,
671 '#required' => TRUE,
672 '#description' => t('The main body text of your ad. Please note that ads are limited to !limit characters or less.', array('!limit' => $max_body_length)),
673 '#rows' => 10,
674 // Add a length counter so people know they have gone over.
675 // http://lists.evolt.org/archive/Week-of-Mon-20040315/156773.html
676 // TODO: This should be a part of the jstools module...
677 // TODO: need a function and handle onblur, focus, etc. so clipboard paste works.
678 // Use behaviors js rather than this clunky code?
679 // onkeypress doesn't detect all keys in IE. Use onkeydown/onkeyup
680
681 // TODO: use "className" for IE6
682 // http://www.webmasterworld.com/forum91/5280.htm
683 '#attributes' => array('onkeyup' => "document . getElementById('_edi_classified_body_length_remaining') . innerHTML=this.value.length;document.getElementById('_edi_classified_body_length_remaining') . setAttribute('class', this.value.length <= $max_body_length ? 'classified-bodylength-ok' : 'classified-bodylength-exceeded');"),
684 );
685 $form['body_filter']['body_length_remaining']
686 = array('#type' => 'markup',
687 '#value' => t('Body characters used: <span id="_edi_classified_body_length_remaining">%initial_value</span> of %max_value',
688 array('%initial_value' => $current_body_length, '%max_value' => $max_body_length)),
689 '#prefix' => '<span id="classified-bodylength-msg">',
690 '#suffix' => '</span>',
691 );
692 // END counter code
693 $form['body_filter']['filter'] = filter_form($node->format);
694
695 /* Set up expiration info fieldset */
696 if ($node->expires_on >0) { // if not creating ad for the first time
697 $form['expiration_fieldset'] = array('#type' => 'fieldset', '#title' => t('Ad Expiration'), '#collapsible' => TRUE, '#collapsed' => FALSE );
698 $form['expiration_fieldset']['expires_on_text'] = array('#value' => theme('ed_classified_ending_date', $node->expires_on));
699 // TODO: fix this - using hidden field to preserve value
700 $form['expiration_fieldset']['expires_on'] = array('#type' => 'hidden', '#value' => $node->expires_on);
701 // if has proper perms, allow editing of expiration date as appropriate
702 if (user_access('reset classified ad expiration')) {
703 $form['expiration_fieldset']['reset_expiration'] =
704 array('#type' => 'checkbox', '#default_value' => '0',
705 '#title' => t('Reset ad expiration (extend expiration date)'),
706 '#description' => t('If this is checked, the ad\'s expiration date will be reset upon saving changes. Duration may depend on assigned categories.'));
707 }
708 }
709 // TODO: Enter additional form elements
710 // TODO: Expiration, contact info, category (from the classified taxonomy dedicated to classified)
711 // if user has appropriate access, display and allow change of state, expiration date.
712 if (user_access('administer classified ads')) {
713 // show state here.
714 }
715 // append our form submit handler
716 $form['#submit'][]= '_ed_classified_form_submit';
717 return $form;
718 }
719
720
721 /**
722 * Implementation of hook_insert().
723 */
724 function ed_classified_insert($node) {
725 db_query("INSERT INTO {edi_classified_nodes} (vid, nid, expires_on) VALUES (%d, %d, %d)", $node->vid, $node->nid, $node->expires_on);
726 }
727
728
729 /**
730 * Implementation of hook_load().
731 */
732 function ed_classified_load($node) {
733 // TODO: Obtain and return additional fields added to the node type, for example:
734 // $additions = db_fetch_object(db_query('SELECT color, quantity FROM {node_example} WHERE vid = %d', $node->vid));
735 $additions = db_fetch_object(db_query('SELECT * FROM {edi_classified_nodes} WHERE vid = %d', $node->vid));
736 return $additions;
737 }
738
739
740 /**
741 * Implementation of hook_node_info().
742 */
743 function ed_classified_node_info() {
744 // beware: these must match nodeapi value; name must be same as $node->type for spam module to add spam reporting links
745 return array(EDI_CLASSIFIED_MODULE_NAME =>
746 array('name' => t('Classified Ad'), // cannot call node_get_types() since it ends up calling this code.
747 'module' => EDI_CLASSIFIED_MODULE_NAME,
748 'description' => t('Contains a title, a body, and an administrator-defined expiration date'),
749 'has_title' => TRUE,
750 'title_label' => t('Ad Title'),
751 'has_body' => TRUE,
752 'body_label' => t('Ad Text')));
753 }
754
755
756 /**
757 * Implementation of form submission handler
758 */
759 function _ed_classified_form_submit($form, &$form_state) {
760 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
761
762 $node = $form['#node'];
763 $terms = $form['#post']['taxonomy'];
764
765 $expiration_changed = FALSE;
766 $expiration_old = $node->expires_on;
767 $expiration = REQUEST_TIME + _ed_classified_days_to_seconds(_ed_classified_get_longest_duration($terms));
768
769 $user_reset = $form_state['values']['reset_expiration'];
770
771 if ($user_reset && !user_access('reset classified ad expiration')) {
772 drupal_set_message(t('You do not have permission to reset ad expiration!'));
773 $user_reset = FALSE;
774 }
775
776 // calc expiration date as appropriate
777 // If new ad, or user wants to reset expiration, or taxonomy was changed... recalc expiration?
778 if (empty($node->expires_on) || $user_reset) {
779 // it's a new ad, or the user chose to reset the expiration
780 $form_state['values']['expires_on'] = $expiration; // _ed_classified_get_default_ad_duration_in_seconds();
781 // _edi_wd(sprintf('Ad expiration was %d (%s), now %d (%s)', $old_expires, _edi_safe_date_fmt($old_expires), $node->expires_on, _edi_safe_date_fmt($node->expires_on)));
782 // we deal with republishing further down
783 $expiration_changed = TRUE;
784 }
785 else {
786 // This is not a new ad, and the user didn't choose to (or is not allowed to) reset the ad expiration
787 // so, let's check the tentative new expiration, and if it's shorter than the current expiration - we need to use the new (shorter) one
788 // Rationale: User may have changed the taxonomy terms for this ad (would be nice to be able to detect this accurately) and
789 // we don't want someone to end up creating a long-living ad that they wouldn't be able to create by normal means
790 if ($expiration < $node->expires_on) {
791 $form_state['values']['expires_on'] = $expiration;
792 // we deal with republishing further down
793 $expiration_changed = TRUE;
794 }
795 }
796 // log and notify if needed
797 if ($expiration_changed) {
798 // Sanity check - did expiration really change?
799 if ($expiration_old != $expiration) {
800 if (0 == $form_state['values']['status']) {
801 $form_state['values']['status'] = 1;
802 $publish_status = ' (setting to published)';
803 } else {
804 $publish_status = ' (already published)';
805 }
806 $msg = t('Ad expiration extended from %expires_old to %expires_new %publish_status.',
807 array('%nid' => $node->nid, '%expires_old' => _edi_safe_date_fmt($expiration_old),
808 '%expires_new' => _edi_safe_date_fmt($expiration),
809 '%publish_status' => $publish_status
810 )
811 );
812 _edi_wd($msg, WATCHDOG_NOTICE, l('view', drupal_get_path_alias('node/'. $node->nid)));
813 drupal_set_message($msg);
814 } else {
815 drupal_set_message(t('No change to ad expiration date.'));
816 }
817 }
818 // print '<pre>'.print_r($form_state,1).'</pre>';
819 }
820
821
822 /**
823 * Implementation of hook_update().
824 */
825 function ed_classified_update($node) {
826 if ($node->revision) {
827 ed_classified_insert($node);
828 }
829 else {
830 db_query("UPDATE {edi_classified_nodes} SET expires_on='%d' WHERE vid = %d", $node->expires_on, $node->vid);
831 }
832 }
833
834
835 /**
836 * Implementation of hook_validate().
837 */
838 function ed_classified_validate(&$form, $form_state) {
839 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
840
841 // TODO: Enter form validation code here
842 // - Min/max expiration dates
843 // - settings: min/max expiration dates
844 //
845 // TODO: allow override on some ads, if paid for longer ads
846 $maxlen = _ed_classified_variable_get('ad_standard_body_length', EDI_CLASSIFIED_VAR_DEF_BODYLEN_LIMIT);
847 if (strlen(trim($form->body)) > $maxlen) {
848 form_set_error('body', t('Ad text length is limited to !length characters. Please shorten your entry to !length characters or less.', array('!length' => $maxlen)));
849 }
850 }
851
852
853 /**
854 * Implementation of hook_view().
855 */
856 function ed_classified_view(&$node, $teaser = FALSE, $page = FALSE) {
857 module_load_include('inc', 'ed_classified', 'ed_classified_utils');
858
859 if ($page) {
860 // modify the breadcrumbs and navigation
861 $vid = _ed_classified_get_vid();
862 $terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
863 $term = array_pop($terms);
864 if ($term) {
865 $vocab = taxonomy_vocabulary_load(_ed_classified_get_vid());
866 // Breadcrumb navigation
867 $breadcrumb = array();
868 $breadcrumb[] = l(t('Home'), NULL);
869 $breadcrumb[] = l($vocab->name, drupal_get_path_alias(EDI_CLASSIFIED_PATH_NAME));
870 if ($parents = taxonomy_get_parents_all($term->tid)) {
871 $parents = array_reverse($parents);
872 foreach ($parents as $p) {
873 $breadcrumb[] = l($p->name, drupal_get_path_alias(_ed_classified_make_category_path($p->tid)));
874 }
875 }
876 // TODO: do we want to include this item in the breadcrumb?
877 // $breadcrumb[] = l($node->title, $node->nid); // array('path' => 'node/'. $node->nid);
878 drupal_set_breadcrumb($breadcrumb);
879 }
880 }
881
882 $node = node_prepare($node, $teaser);
883 if ($page) {
884 $node->content['body']['#value'] = theme('ed_classified_body', $node);
885 }
886 else if ($teaser) {
887 $node->content['body']['#value'] = theme('ed_classified_teaser', $node);
888 }
889 return $node;
890 }
891
892 /**
893 * Implementation of hook_node_type()
894 */
895
896 function ed_classified_node_type($op, $info) {
897
898 if (!empty($info->old_type) && $info->old_type != $info->type) {
899 $update_count = node_type_update_nodes($info->old_type, $info->type);
900 if ($update_count) {
901 $substr_pre = 'Changed the content type of ';
902 $substr_post = strtr(' from %old-type to %type.', array(
903 '%old-type' => theme('placeholder', $info->old_type),
904 '%type' => theme('placeholder', $info->type)));
905 drupal_set_message(format_plural($update_count, $substr_pre
906 .'@count post'. $substr_post, $substr_pre .'@count posts'.
907 $substr_post));
908 }
909 }
910 }
911
912 /**
913 * Implementation of hook_mail()
914 */
915
916 function ed_classified_mail($key, &$message, $params) {
917 $language = $message['language'];
918 $variables = array_merge(user_mail_tokens($params['account'], $language), $params['context']);
919 switch ($key) {
920 case 'expired':
921 $message['subject'] = t(EDI_CLASSIFIED_VAR_DEF_EXP_EMAIL_SUBJ, $variables, $language->language);
922 $message['body'][] = t(EDI_CLASSIFIED_VAR_DEF_EXP_EMAIL_BODY, $variables, $language->language);
923 break;
924 case 'expiring':
925 $message['subject'] = t(EDI_CLASSIFIED_VAR_DEF_EMAIL_SUBJ, $variables, $language->language);
926 $message['body'][] = t(EDI_CLASSIFIED_VAR_DEF_EMAIL_BODY, $variables, $language->language);
927 break;
928 default:
929 $message['subject'] = t('Classified Ad notification from !site', $variables, $language->language);
930 break;
931 }
932 }
933
934 /*
935 * Implements hook_views_api()
936 */
937
938 function ed_classified_views_api() {
939 module_load_include('inc', 'ed_classified', 'ed_classified_views');
940 return array('api' => 2);
941 }

  ViewVC Help
Powered by ViewVC 1.1.2