diff -up ed_classified_dev/ed_classified.module ed_classified/ed_classified.module --- ed_classified_dev/ed_classified.module 2008-02-23 21:02:58.000000000 -0500 +++ ed_classified/ed_classified.module 2009-01-26 05:44:04.000000000 -0500 @@ -31,6 +31,7 @@ define('EDI_CLASSIFIED_MODULE_NAME', 'ed define('EDI_CLASSIFIED_PATH_NAME', 'ed-classified'); define('EDI_CLASSIFIED_MODULE_VERSION', '$Id: ed_classified.module,v 1.1.4.45 2008/02/24 02:02:58 inactivist Exp $'); define('EDI_CLASSIFIED_VAR_DEF_BODYLEN_LIMIT', 500); +define('EDI_CLASSIFIED_VAR_DEF_EXPIRE', TRUE); define('EDI_CLASSIFIED_VAR_DEF_EXPIRATION_DAYS', 30); define('EDI_CLASSIFIED_VAR_DEF_PURGE_AGE', 15); // how many days after expiration to purge ads? define('EDI_CLASSIFIED_VAR_DEF_AD_EXPIRATION_EMAIL_WARNING_DAYS', 7); @@ -102,15 +103,23 @@ function ed_classified_block($op = 'list * Implementation of hook_cron(). */ function ed_classified_cron() { - $time = time(); - /* Process reminder mails as needed */ - if (_ed_classified_variable_get('send_email_reminders', EDI_CLASSIFIED_VAR_DEF_SEND_EMAIL_REMINDERS)) { - _ed_classified_process_notification_emails($time); - } - _ed_classified_expire_ads($time); + $types = array_keys(_ed_classified_get_types()); + // Variable is array of content types with user-defined expiry option. If true, opt in (default). + $types_expire = _ed_classified_variable_get('types_expire', array()); + foreach ($types as $type) { + // If the variable has not yet been set, default to standard behaviour. + if ((!isset($types_expire["$type"])) || ($types_expire["$type"])) { + $time = time(); + /* Process reminder mails as needed */ + if (_ed_classified_variable_get('send_email_reminders', EDI_CLASSIFIED_VAR_DEF_SEND_EMAIL_REMINDERS)) { + _ed_classified_process_notification_emails($time); + } + _ed_classified_expire_ads($time); - // purge old ads if possible - _ed_classified_purge(); + // purge old ads if possible + _ed_classified_purge(); + } + } } @@ -229,10 +238,23 @@ function ed_classified_menu($may_cache) 'type' => MENU_NORMAL_ITEM, // MENU_SUGGESTED_ITEM, 'callback' => 'ed_classified_page'); $items[] = array( - 'path' => 'node/add/' . EDI_CLASSIFIED_PATH_NAME, - 'title' => _ed_classified_displayname(), - 'access' => user_access('create classified ads'), - ); + 'path' => EDI_CLASSIFIED_PATH_NAME . '/add', + 'title' => t('Submit new Classified Ad'), //_ed_classified_displayname(), + 'access' => user_access('create classified ad'), + 'type' => MENU_ITEM_GROUPING & MENU_EXPANDED, + 'callback' => 'ed_classified_add', + ); + // Create menu items for all ed_classified content types + $sql = "SELECT type, name FROM {node_type} WHERE module = '%s'"; + $result = db_query($sql, EDI_CLASSIFIED_MODULE_NAME); + while ($content_type = db_fetch_object($result)) { + $items[] = array( + 'path' => EDI_CLASSIFIED_PATH_NAME . '/add/'. str_replace('_','-',$content_type->type), + 'title' => 'Create new ' . $content_type->name, + 'type' => MENU_NORMAL_ITEM, + 'access' => user_access('create classified ad'), + ); + } $items[] = array( 'path' => 'admin/content/node/' . EDI_CLASSIFIED_PATH_NAME, 'title' => _ed_classified_displayname(), @@ -244,11 +266,36 @@ function ed_classified_menu($may_cache) 'path' => 'admin/settings/'. EDI_CLASSIFIED_PATH_NAME, 'title' => $name, 'description' => t('Configure @name settings', $parms), - 'callback' => 'drupal_get_form', - 'callback arguments' => array('ed_classified_admin_settings'), + 'callback' => 'ed_classified_admin_page', 'access' => user_access('administer site configuration'), - 'type' => MENU_NORMAL_ITEM, // optional ); + $items[] = array( + 'path' => 'admin/settings/' . EDI_CLASSIFIED_PATH_NAME . '/general', + 'title' => t('General'), + 'description' => t('Allows the user to configure overall default values for @name.', $parms), + 'callback' => 'ed_classified_admin_page', + 'access' => user_access('administer site configuration'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items[] = array( + 'path' => 'admin/settings/' . EDI_CLASSIFIED_PATH_NAME . '/term-defaults', + 'title' => t('Default Term Durations'), + 'description' => t('Allows the user to configure default expiration values for individual terms.'), + 'callback' => 'ed_classified_admin_taxonomy_page', + 'access' => user_access('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + 'weight' => -9, + ); + $items[] = array( + 'path' => 'admin/settings/' . EDI_CLASSIFIED_PATH_NAME . '/content-types', + 'title' => t('Content Type Settings'), + 'description' => t('Allows the user to configure default expiration values for individual terms.'), + 'callback' => 'ed_classified_admin_content_page', + 'access' => user_access('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + 'weight' => -8, + ); $items[] = array('path' => 'admin/' . EDI_CLASSIFIED_PATH_NAME . '/purge', 'title' => t('purge'), 'callback' => '_ed_classified_purge', @@ -366,18 +413,20 @@ function ed_classified_page($type = NULL // get a list of categories and counts $cats = taxonomy_get_tree(_ed_classified_get_vid(), $tid, -1, 1); for ($i=0; $i < count($cats); $i++) { - $cats[$i]->count = taxonomy_term_count_nodes($cats[$i]->tid, EDI_CLASSIFIED_MODULE_NAME); + $cats[$i]->count = taxonomy_term_count_nodes($cats[$i]->tid); $tree = taxonomy_get_tree(_ed_classified_get_vid(), $cats[$i]->tid, -1); $descendant_tids = array_merge(array($cats[$i]->tid), array_map('_taxonomy_get_tid_from_term', $tree)); $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 (%s) AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), implode(',', $descendant_tids), 0, 1)); $cats[$i]->latest = node_load(array('nid' => $last->nid)); } - + $node_types = array_keys(_ed_classified_get_types()); // TODO: order by created date, #views, what? $ads = array(); // echo "tid=$tid
"; var_dump($tid); if ($tid) { - $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); + // Query assembly removed from rewrite to prevent escaping of comma separated list. + $sql = "SELECT n.nid FROM {term_node} t INNER JOIN {node} n ON t.nid=n.nid WHERE n.status=1 AND n.type IN ('" . implode("','",$node_types) . "') AND t.tid=%d ORDER BY n.sticky DESC, n.created DESC"; + $result = pager_query(db_rewrite_sql($sql), _ed_classified_variable_get('ads_per_page', 10), 0, NULL, $tid); while ($node = db_fetch_object($result)) { $ads[] = node_load(array('nid' => $node->nid)); } @@ -481,7 +530,8 @@ function ed_classified_form(&$node,&$par $form['body_filter']['filter'] = filter_form($node->format); /* Set up expiration info fieldset */ - if ($node->expires_on >0) { + $types_expire = _ed_classified_variable_get('types_expire', array()); + if (($node->expires_on >0) && ((!isset($types_expire["$node->type"]) || ($types_expire["$node->type"])))) { $form['expiration_fieldset'] = array('#type'=>'fieldset', '#title'=>t('Ad Expiration'), '#collapsible'=> TRUE, '#collapsed'=>FALSE, '#weight'=>-1); $form['expiration_fieldset']['expires_on_text'] = array('#value' => theme('ed_classified_ending_date', $node->expires_on), '#weight'=>-15); // TODO: fix this - using hidden field to preserve value @@ -529,11 +579,18 @@ function ed_classified_load($node) { * Implementation of hook_node_info(). */ function ed_classified_node_info() { + $info = array(); // beware: these must match nodeapi value; name must be same as $node->type for spam module to add spam reporting links - return array(EDI_CLASSIFIED_MODULE_NAME => +/* $sql = "SELECT type, name, description FROM {node_type} WHERE module = '%s'"; + $result = db_query($sql, EDI_CLASSIFIED_MODULE_NAME); + while ($type = db_fetch_object($result)) { + $info["$type->type"] = array( 'name' => t($type->name), 'module' => EDI_CLASSIFIED_MODULE_NAME, 'description' => t($type->description)); + }*/ + $info[EDI_CLASSIFIED_MODULE_NAME] = array('name' => t('Classified Ads'), // cannot call node_get_types() since it ends up calling this code. 'module' => EDI_CLASSIFIED_MODULE_NAME, - 'description' => t('Contains a title, a body, and an administrator-defined expiration date'))); + 'description' => t('Contains a title, a body, and an administrator-defined expiration date')); + return $info; } @@ -667,3 +724,57 @@ function ed_classified_node_type($op, $i } } } + +/** + * Present a classified ad submission form or a set of links to such forms. + */ +function ed_classified_add($type = NULL) { + global $user; + + $types = _ed_classified_get_types(); + $type = isset($type) ? str_replace('-', '_', $type) : NULL; + // If a node type has been specified, validate its existence. + if (isset($types[$type]) && node_access('create', $type)) { + // Initialize settings: + $node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type); + + drupal_set_title(t('Submit @name', array('@name' => $types[$type]->name))); + $output = drupal_get_form($type .'_node_form', $node); + } + else { + // If no (valid) node type has been provided, display a node type overview. + foreach ($types as $type) { + if (function_exists($type->module .'_form') && node_access('create', $type->type)) { + $type_url_str = str_replace('_', '-', $type->type); + $module_url_str = str_replace('_', '-', $type->module); + $title = t('Add a new @s.', array('@s' => $type->name)); + $out = '
'. l(drupal_ucfirst($type->name), $module_url_str . "/add/$type_url_str", array('title' => $title)) .'
'; + $out .= '
'. filter_xss_admin($type->description) .'
'; + $item[$type->name] = $out; + } + } + + if (isset($item)) { + uksort($item, 'strnatcasecmp'); + $output = t('Choose the appropriate Classifid Ad type from the list:') .'
'. implode('', $item) .'
'; + } + else { + $output = t('No Classified Ad types available.'); + } + } + + return $output; +} + +/* + * Returns an array of node types using ed_classified + */ +function _ed_classified_get_types() { + $types = array(); + $sql = "SELECT * from {node_type} WHERE module = '%s'"; + $result = db_query($sql, EDI_CLASSIFIED_MODULE_NAME); + while ($type = db_fetch_object($result)) { + $types["$type->type"] = $type; + } + return $types; +} Only in ed_classified: ed_classified.patch diff -up ed_classified_dev/ed_classified_settings.inc ed_classified/ed_classified_settings.inc --- ed_classified_dev/ed_classified_settings.inc 2007-08-12 04:00:29.000000000 -0400 +++ ed_classified/ed_classified_settings.inc 2009-01-26 06:30:36.000000000 -0500 @@ -13,9 +13,25 @@ */ /** - * Implementation of hook_settings(). + * Menu callbacks for administation forms */ -function ed_classified_admin_settings() { +function ed_classified_admin_page($op = NULL) { + $output .= drupal_get_form('ed_classified_admin_settings_form'); + return $output; +} +function ed_classified_admin_taxonomy_page($op = NULL) { + $output .= drupal_get_form('ed_classified_admin_taxonomy_settings_form'); + return $output; +} +function ed_classified_admin_content_page($op = NULL) { + $output .= '

' . t('Select Classified Ad Content Types') . '

'; + $output .= drupal_get_form('ed_classified_admin_content_settings_form'); + return $output; +} +/* + * General administration form + */ +function ed_classified_admin_settings_form() { // _ed_classified_check_settings(); $vid = _ed_classified_get_vid(); // ensure that taxonomy is created $form[_ed_classified_cfg_varname('settings_updated')] = array('#type' => 'hidden', '#value' => time());// lifted from image.module - neat trick @@ -76,6 +92,18 @@ $form['file_attachment_enhancements'] = $form['file_attachment_enhancements'][ _ed_classified_cfg_varname('alter_attachment_text_description')] = array('#type' => 'textarea', '#title'=>t('File attachment description text'), '#default_value' => _ed_classified_variable_get('alter_attachment_text_description', t(EDI_CLASSIFIED_VAR_DEF_ALTER_ATTACHMENT_TEXT_DESCRIPTION)), '#description' => t('If you choose to alter the file attachment form behavior, this text will be shown.'), '#required'=>TRUE); + return system_settings_form($form); +} + +/* + * Callback for admin taxonomy settings tab + */ +function ed_classified_admin_taxonomy_settings_form() { + // _ed_classified_check_settings(); + $vid = _ed_classified_get_vid(); // ensure that taxonomy is created + $form[_ed_classified_cfg_varname('settings_updated')] = array('#type' => 'hidden', '#value' => time());// lifted from image.module - neat trick + _ed_classified_settings_get_banner($form); + $form['taxonomy_settings'] = array('#type' => 'fieldset', '#collapsible' => TRUE, @@ -103,3 +131,97 @@ $form['file_attachment_enhancements'] = return system_settings_form($form); } + + +/* + * Callback for admin content type settings tab + * Uses its own Submit. Does not call system_settings_form() + */ +function ed_classified_admin_content_settings_form() { + // _ed_classified_check_settings(); + $vid = _ed_classified_get_vid(); // ensure that taxonomy is created + $form[_ed_classified_cfg_varname('settings_updated')] = array('#type' => 'hidden', '#value' => time());// lifted from image.module - neat trick + _ed_classified_settings_get_banner($form); + // Get a list of all user-defined node types + // Omit anything which must be handled by another contributed module! + $sql="SELECT type, name, description, module FROM {node_type} WHERE module IN ('node','" . EDI_CLASSIFIED_MODULE_NAME . "')"; + $result = db_query($sql); + $form['content_settings'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#title' => t('User-defined Content Settings'), + '#description' => t('Site designers have the option of associating their own content types with this module. For Classified ads to work, AT LEAST ONE content type must be associated with the module. See README.txt for more instructions. The checkbox to use ' . EDI_CLASSIFIED_MODULE_NAME . ' to expire ads should only be unchecked if you have enabled another module, like LM PayPal or Ubercart, to handle publication and user notification. Otherwise, leave it checked (the default) and ' . EDI_CLASSIFIED_MODULE_NAME . ' will handle the tasks.'), + ); + $types_expire = _ed_classified_variable_get('types_expire', array()); + while ($types = db_fetch_object($result)) { + + $form['content_settings']["$types->type"] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => ($types->module != EDI_CLASSIFIED_MODULE_NAME), + '#title' => t($types->name), + '#description' => t($types->description), + ); + $form['content_settings']["$types->type"]['use'] = array( + '#type' => 'checkbox', + '#title' => t('Use ' . EDI_CLASSIFIED_MODULE_NAME . ' to display in Classified Ads list'), + '#default_value' => ($types->module == EDI_CLASSIFIED_MODULE_NAME), + '#prefix' => '', + ); + $form['content_settings']["$types->type"]['expire'] = array( + '#type' => 'checkbox', + '#title' => t('Use ' . EDI_CLASSIFIED_MODULE_NAME . ' to expire Classified Ads'), + '#default_value' => isset( $types_expire["$types->type"]) ? $types_expire["$types->type"] : EDI_CLASSIFIED_VAR_DEF_EXPIRE, + '#prefix' => '
', + '#suffix' => '', + '#suffix' => '
', + ); + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + +/* + * Make sure at least one content type is declared. + */ +function ed_classified_admin_content_settings_form_validate($form_id, $form_values){ + $used = FALSE; + // Find the first content type used. + foreach ($form_values['content_settings'] as $type => $value) { + $used = $value['use']; + if ($used) { + break; + } + } + if (! $used) { + form_set_error('', t('At least one content type MUST be set. No changes have been made in your configuration!')); + } +} + +/* + * Update content type settings after admin form submitted + */ +function ed_classified_admin_content_settings_form_submit($form_id, $form_values){ + $sql = "UPDATE {node_type} SET module = '%s' WHERE type = '%s'"; + foreach ($form_values['content_settings'] as $type => $value) { + db_query($sql, (($value['use']) ? EDI_CLASSIFIED_MODULE_NAME : 'node'), $type); + // Set path aliases for menu + path_set_alias( 'node/add/' . str_replace('_','-',$type), EDI_CLASSIFIED_PATH_NAME . '/add/' . str_replace('_','-',$type)); + $types_expire = _ed_classified_variable_get('types_expire', array()); + if ($value['use']) { + $types_expire["$type"] = $value['expire']; + } else { + unset( $types_expire["$type"] ); + } + _ed_classified_variable_set('types_expire', $types_expire); + } + drupal_set_message('Configuration has been updated.'); +} + + diff -up ed_classified_dev/ed_classified_themefuncs.inc ed_classified/ed_classified_themefuncs.inc --- ed_classified_dev/ed_classified_themefuncs.inc 2007-08-12 04:00:29.000000000 -0400 +++ ed_classified/ed_classified_themefuncs.inc 2009-01-26 06:02:17.000000000 -0500 @@ -51,7 +51,8 @@ function theme_ed_classified_ads_block($ function theme_ed_classified_taxonomy($cats, $ads) { $content = ''; if(user_access('create classified ads')) { - $content .= '\n"; + /* $content .= '\n";*/ + $content .= '\n"; } if (count($cats)) { @@ -145,8 +146,11 @@ function theme_ed_classified_category_li $content .= '
' . t('Created on ') . format_date($ad->created)."
\n"; } $expires = ed_classified_get_ad_expiration($ad->nid); - if ($expires > 0) { + $types_expire = _ed_classified_variable_get('types_expire', array()); + if (($expires > 0) && (!isset($types_expire["$ad->type"]) || ($types_expire["$ad->type"]))) { $content .= '
' . theme('ed_classified_ending_date', $expires) . "
\n"; + } else { + $content .= "

\n"; } } $content .= "\n"; @@ -179,11 +183,17 @@ function theme_ed_classified_ads_stats() } function theme_ed_classified_teaser($node) { - return '
'. check_markup($node->teaser, $node->format, FALSE) . '

' . theme('ed_classified_ending_date', $node->expires_on); + $types_expire = _ed_classified_variable_get('types_expire', array()); + return '
'. check_markup($node->teaser, $node->format, FALSE) . '

' . + ((!isset($types_expire["$node->type"]) || ($types_expire["$node->type"])) + ? theme('ed_classified_ending_date', $node->expires_on) : ''); } function theme_ed_classified_body($node) { - return '
' . check_markup($node->body, $node->format, FALSE) . '

' . theme('ed_classified_ending_date', $node->expires_on); + $types_expire = _ed_classified_variable_get('types_expire', array()); + return '
' . check_markup($node->body, $node->format, FALSE) . '

' . + ((!isset($types_expire["$node->type"]) || ($types_expire["$node->type"])) + ? theme('ed_classified_ending_date', $node->expires_on) : ''); } /** diff -up ed_classified_dev/README.txt ed_classified/README.txt --- ed_classified_dev/README.txt 2007-02-20 00:47:26.000000000 -0500 +++ ed_classified/README.txt 2009-01-26 02:18:15.000000000 -0500 @@ -60,3 +60,35 @@ enabled access logging in admin/logs/set 'Enable access log' settings.) Authorized users may now add classified ads. + +User-defined Content Types Patch +-------------------------------- + +User-defined content types may be associated with the ed_classified module, permitting the +site designer to create different formats of ad for users to post. When associated, these +content types will be displayed as classified ads according to the taxonomy, will have +their body text limited, and will be expired using the ed_classified expiry system. + +New content types should be created as defaulting to unpublished if a payment or moderation +system is in place to publish them. + +If the content types are associated with the Classified Ads vocabulary, the user has full +control over the classification of the ads when created. If the site designer wishes to +create different types for each classification (eg. For Sale or Personals), use of the +Taxonomy Defaults module (http://drupal.org/project/taxonomy_defaults) is recommended. This +module allows the designer to associate a default term with the content type without +exposing the full taxonomy to the user. + +To associate a content type with ed_classified, go to admin > settings > ed-classified and +select the Content Type Settings tab. You will see a list of available content types, +and those already using ed_classified will be shown expanded. To associate addiional +types, expand them and select the check box in each type you wish to add. When you save +the configuration, those content types will now use the ed_classified module for display. +(Any content type which requires another module, even if user-defined, is excluded from +the choices available.) + +New menu items are made availabl to the users for ad creation. When the user clicks on +the Create New Add link, they are taken to a page similar to the administration Create +Content page, which lists the Classified Ad types available, with links to the forms +to add each type +--------------------------------