diff --git a/node_class.info b/node_class.info index 1bb1b8b..3f301fd 100644 --- a/node_class.info +++ b/node_class.info @@ -1,4 +1,4 @@ name = Node Class description = Allows assigning CSS classes to nodes. core = 7.x - +configure = admin/config/content/nodeclass diff --git a/node_class.install b/node_class.install index 75f17e3..1f35662 100644 --- a/node_class.install +++ b/node_class.install @@ -1,10 +1,10 @@ array( 'type' => 'varchar', 'length' => '32', - 'not null' => TRUE + 'not null' => TRUE, ), - 'css_class' => array( 'type' => 'varchar', 'length' => '255', 'not null' => TRUE, 'default' => '', 'description' => 'String containing the classes for the block.', - ) + ), ), 'primary key' => array('nid'), ); @@ -31,8 +30,19 @@ function node_class_schema() { return $schema; } - +/** + * Implements hook_install(). + */ function node_class_install() { - drupal_set_message('Node Class was installed. Check README.txt before using the module'); + drupal_set_message(st('Node Class was installed. Check README.txt before using the module'), 'status'); } +/** + * Implements hook_uninstall(). + */ +function node_class_uninstall() { + variable_del('node_class_location'); + variable_del('node_class_widget'); + variable_del('node_class_source'); + variable_del('node_class_predefined_values'); +} \ No newline at end of file diff --git a/node_class.module b/node_class.module index 21b79f7..2dd2bcc 100644 --- a/node_class.module +++ b/node_class.module @@ -1,51 +1,110 @@ 'Node Class', + 'description' => 'Configure where the css classes should be displayed. Also allows settings for dropdown values.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('node_class_admin_form'), + 'access arguments' => array('administer nodeclass settings'), + 'type' => MENU_NORMAL_ITEM, + ); + $items['nodeclass/autocomplete'] = array( + 'title' => 'Node Class Autocomplete', + 'page callback' => '_node_class_get_values_autocomplete', + 'access arguments' => array('administer nodes'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implements hook_permission(). + */ +function node_class_permission() { + return array( + 'administer nodeclass settings' => array( + 'title' => t('Administer Node Class settings'), + ), + ); +} + +/** * Extends the node's classes with any user defined classes. */ function node_class_preprocess_node(&$vars) { - $node = $vars['node']; - $classes = node_class($node); + $location = variable_get('node_class_location', 'node'); + if ($location == 'node' || $location == 'both') { + $node = $vars['node']; + $classes = node_class($node); + $vars['classes_array'] = array_merge($vars['classes_array'], explode(' ', $classes)); + } +} - $vars['classes_array'] = array_merge($vars['classes_array'], explode(' ', $classes)); +/** + * Extends the body's classes with any user defined classes. + */ +function node_class_preprocess_html(&$vars) { + $location = variable_get('node_class_location', 'node'); + if ($location == 'body' || $location == 'both') { + $node = menu_get_object(); + $classes = node_class($node); + $vars['classes_array'] = array_merge($vars['classes_array'], + explode(' ', $classes)); + } } -/* +/** * Accessor for css_class information */ function node_class($node) { $attributes = node_class_attributes($node); - return $attributes; } -/* +/** * Gets CSS class information for a given node. */ function node_class_attributes($node) { - // dev note: - // Get css attribute information ONLY if node already existed. - // if we're here the $form[#node] object will have already been - // created but it won't have an nid because it hasn't been saved - if (!isset($node->nid)) { - return ""; + if (isset($node->nid)) { + $query = db_select('node_class', 'n'); + $query->fields('n', array('css_class')); + $query->condition('n.nid', $node->nid, '='); + $result = $query->execute()->fetchField(); } - - return db_query('SELECT css_class FROM {node_class} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField(); + return isset($result) ? $result : ''; } -/* +/** * Implementation of hook_form_alter(). */ function node_class_form_alter(&$form, &$form_state, $form_id) { - // Check form type && if user_access is ok - if (isset($form['#node']) && ($form_id == $form['#node']->type .'_node_form') && user_access('administer nodes')) { + // Check form type && if user_access is ok + if (isset($form['#node']) && ($form_id == $form['#node']->type . '_node_form') && + user_access('administer nodes') + ) { + // Add form elements in vertical tab to node settings. $form['node_class'] = array( '#type' => 'fieldset', '#title' => t('Node Class settings'), + '#description' => t('Assign CSS classes to the node.'), '#collapsible' => TRUE, - '#weight' => -1, + '#collapsed' => TRUE, + '#access' => user_access('administer nodes'), + '#weight' => 80, '#tree' => TRUE, + '#group' => 'additional_settings', + '#attributes' => array('class' => array('node-class-form')), ); // Get css attribute information @@ -66,43 +125,60 @@ function node_class_form_alter(&$form, &$form_state, $form_id) { '#description' => t('Separate classes with a space.'), ); - // Add submit handler for node_class module - $form['#submit'][] = 'node_class_form_submit'; - } + // Check the widget defined in the settings + $widget = variable_get('node_class_widget', 'textfield'); + if ($widget == 'textfield') { + $form['node_class']['css_class']['#type'] = 'textfield'; + $form['node_class']['css_class']['#description'] = t('Separate classes with a space.'); + } + elseif ($widget == 'dropdown') { + $form['node_class']['css_class']['#type'] = 'select'; + $form['node_class']['css_class']['#options'] = _node_class_get_values_dropdown($attributes); + } + elseif ($widget == 'autocomplete') { + $form['node_class']['css_class']['#type'] = 'textfield'; + $form['node_class']['css_class']['#autocomplete_path'] = 'nodeclass/autocomplete'; + } + } } /** - * Form submission handler for node form. + * Implementation of hook_node_insert(). */ - function node_class_form_submit($form, &$form_state) { - if ($form_state['values']['form_id'] == $form['#node']->type .'_node_form') { - if (isset($form_state['values']['node_class']['css_class']) && user_access('administer nodes')) { - $nid = $form_state['values']['nid']; - $class = check_plain($form_state['values']['node_class']['css_class']); - - // update existing nodes that already have nid AND a class - if (isset($form_state['values']['node_class']['existing_css']) && !is_null($nid)) { - db_update('node_class')->fields(array('css_class' => $class))->condition('nid', $nid)->execute(); - } - - // update existing nodes that are getting classes for the first time - // quickest fix without a refactoring. - if (!isset($form_state['values']['node_class']['existing_css']) && !is_null($nid) - && !is_null($form_state['values']['node_class']['css_class'])) { - // technically this could replace the above, but merges are expensive. - db_merge('node_class') - ->key(array('nid' => $nid)) - ->fields(array('css_class' => $class)) - ->execute(); - } - } - } - } +function node_class_node_insert($node) { + if (isset($node->node_class['css_class'])) { + $nid = $node->nid; + $class = check_plain($node->node_class['css_class']); + node_class_upsert($nid, $class); + } +} +/** + * Implementation of hook_node_update(). + */ +function node_class_node_update($node) { + if (isset($node->node_class['css_class'])) { + $nid = $node->nid; + $class = check_plain($node->node_class['css_class']); + node_class_upsert($nid, $class); + } +} +/** + * Implementatios node_class_upsert(). + */ +function node_class_upsert($nid, $class) { + $count = db_select('node_class', 'n')->fields('n', array('nid'))->condition('n.nid', $nid, '=')->execute()->rowCount(); + if (!$count) { + $id = db_insert('node_class')->fields(array('nid' => $nid, 'css_class' => $class))->execute(); + } + else { + db_update('node_class')->fields(array('css_class' => $class))->condition('nid', $nid)->execute(); + } +} /** - * Implements hook_node_delete(). + * Implementation of hook_node_delete(). */ function node_class_node_delete($node) { // Delete class information for the node @@ -110,10 +186,117 @@ function node_class_node_delete($node) { } /** - * Implements hook_node_insert(). + * Creates the settings form. */ -function node_class_node_insert($node) { - if(isset($node->node_class['css_class']) && $node->node_class['css_class'] != '') { - $id = db_insert('node_class')->fields(array('nid' => $node->nid, 'css_class' => $node->node_class['css_class']))->execute(); +function node_class_admin_form() { + $form = array(); + $form['node_class_location'] = array( + '#type' => 'radios', + '#title' => t('Node Class Location'), + '#default_value' => variable_get('node_class_location', 'node'), + '#options' => array('node' => t('Node (node.tpl.php)'), 'body' => t('Body (html.tpl.php)'), 'both' => t('Both')), + '#description' => t("Pick if the class will be displayed in node.tpl.php, html.tpl.php or both."), + ); + $form['node_class_widget'] = array( + '#type' => 'select', + '#title' => t('Widget'), + '#default_value' => variable_get('node_class_widget', 'textfield'), + '#options' => array('textfield' => t('Textfield'), 'dropdown' => t('Dropdown'), 'autocomplete' => t('Autocomplete')), + '#description' => t("By default the widget is textfield. Use dropdown when you only use one value. Use autocomplete when you want autocomplete on multiple values."), + ); + $form['node_class_source'] = array( + '#type' => 'select', + '#title' => t('Source of the dropdown/autocomplete'), + '#default_value' => variable_get('node_class_source', 'database'), + '#options' => array('database' => t('Database'), 'predefined' => t('Predefined set of values')), + '#description' => t("By choosing database, it'll search every distinct set of values. Predefined set of values allows the administrator to arbitrary define the classes available."), + ); + $form['node_class_predefined_values'] = array( + '#type' => 'textarea', + '#title' => t('Predefined values'), + '#default_value' => variable_get('node_class_predefined_values', ''), + '#description' => t("Enter a list of classes separated by a space, comma, or line-break."), + ); + $form['#attached']['js'][] = drupal_get_path('module', 'node_class') . '/node_class.js'; + + return system_settings_form($form); +} + +/** + * Function to get all the values from the database/predefined and return them + * combined with the default value. + */ +function _node_class_get_values_dropdown($default) { + $result = array(); + $result_keys = array(); + + // Check the source type. + $source = variable_get('node_class_source', 'database'); + if ($source == 'database') { + $result = db_select('node_class', 'c') + ->fields('c', array('css_class')) + ->distinct() + ->execute() + ->fetchCol(); + } + elseif ($source == 'predefined') { + // Gets the list of predefined values + $predefined = variable_get('node_class_predefined_values', ''); + if (!empty($predefined)) { + $result = preg_split( "/[\s,]+/", $predefined); + } + } + if (!empty($default) && !in_array($default, $result)) { + array_unshift($result, $default); } + // Let's define a way to not specify any class. + $result_keys = $result; + array_unshift($result_keys, ''); + array_unshift($result, t('- No Class -')); + return array_combine($result_keys, $result); } + +/** + * Function to get the values from the database/predefined filtered by a keyword + */ +function _node_class_get_values_autocomplete($keywords) { + $result = array(); + $matches = array(); + $keywords_exploded = preg_split( "/[\s,]+/", $keywords); + $last_keyword = trim(array_pop($keywords_exploded)); + + // Check the source type. + $source = variable_get('node_class_source', 'database'); + if ($source == 'database') { + $result = db_select('node_class', 'c') + ->fields('c', array('css_class')) + ->condition('css_class', '%' . $last_keyword . '%', 'LIKE') + ->distinct() + ->range(0, 20) + ->execute() + ->fetchCol(); + } + elseif ($source == 'predefined') { + // Gets the list of predefined values + $predefined = variable_get('node_class_predefined_values', ''); + if (!empty($predefined)) { + $data = preg_split( "/[\s,]+/", $predefined); + $result = array_filter($data, function ($item) use ($last_keyword) { + if (stripos($item, $last_keyword) !== FALSE) { + return TRUE; + } + return FALSE; + }); + $result = array_slice($result, 0, 20); + } + } + + // Prefix the results with previous data to allow autocomplete to run for + // every word + $prefix = count($keywords_exploded) ? implode(' ', $keywords_exploded) . ' ' : ''; + foreach ($result as $class) { + $matches[$prefix . $class] = check_plain($class); + } + + return drupal_json_output($matches); +} \ No newline at end of file