$path, 'title' => 'BUEditor', 'access' => user_access('administer site configuration'), 'callback' => 'bueditor_admin', 'description' => t('Customize your text editor.'), ); } else if (arg(2)=='bueditor' && arg(1)=='settings' && arg(0)=='admin') { $editors = bueditor_editors('all'); foreach ($editors as $eid => $editor) { $items[] = array('path' => $path.'/'.$eid, 'title' => $editor->name, 'callback' => 'bueditor_get_form_editor', ); $items[] = array('path' => $path.'/'.$eid.'/delete', 'title' => t('Delete'), 'callback' => 'bueditor_confirm_delete', ); } $items[] = array('path' => $path.'/new', 'title' => t('Add new editor'), 'callback' => 'bueditor_get_form_editor', ); } return $items; } /** * Admin main page. */ function bueditor_admin() { $editors = bueditor_editors('all'); $header = array(t('Editor name'), array('data' => t('Operations'), 'colspan' => 2)); $rows = array(); foreach ($editors as $eid=>$editor) { $rows[] = array($editor->name, l(t('Edit'), 'admin/settings/bueditor/'.$eid), l(t('Delete'), 'admin/settings/bueditor/'. $eid .'/delete')); } $rows[] = array(array('data' => l(t('Add new editor'), 'admin/settings/bueditor/new'), 'colspan' => 3)); $output = '

'. t('Available editors'). '

'; $output .= theme('table', $header, $rows); $output .= drupal_get_form('bueditor_form_admin'); return $output; } /** * Admin form. */ function bueditor_form_admin() { $form = array('#tree' => TRUE,); $options = array(0 => t('none')); foreach (bueditor_editors('all') as $eid=>$editor) { $options[$eid] = $editor->name; } if ($GLOBALS['user']->uid == 1) { $form['user1editor'] = array('#type' => 'select', '#options' => $options, '#default_value' => variable_get('bueditor_user1', 1),); } foreach (bueditor_sorted_roles() as $rid=>$role) { $form['roles'][$rid]['name'] = array('#type' => 'markup', '#value' => $role['name'],); $form['roles'][$rid]['weight'] = array('#type' => 'weight', '#default_value' => $role['weight'],); $form['roles'][$rid]['editor'] = array('#type' => 'select', '#options' => $options, '#default_value' => $role['editor'],); } $form['submit'] = array('#type' => 'submit', '#value' => t('Save'),); return $form; } /** * Admin form submit. */ function bueditor_form_admin_submit($form_id, $edit) { if (is_array($edit['roles'])) { uasort($edit['roles'], 'bueditor_rolesort'); variable_set('bueditor_roles', $edit['roles']); drupal_set_message(t('Changes have been saved.')); } if (isset($edit['user1editor'])) { variable_set('bueditor_user1', $edit['user1editor']); } } /** * Admin form themed. */ function theme_bueditor_form_admin($form) { $header = array(t('User role'), t('Weight'), t('Assigned editor')); $rows = isset($form['user1editor']) ? array(array(t('user #1'), '---', drupal_render($form['user1editor']))) : array(); foreach (bueditor_sorted_roles() as $rid=>$role) { $rows[] = array(drupal_render($form['roles'][$rid]['name']), drupal_render($form['roles'][$rid]['weight']), drupal_render($form['roles'][$rid]['editor'])); } $output = '

'. t('User access settings'). '

'; $output .= theme('table', $header, $rows); $output .= '
'. t('Assign editors to user roles. For users who have multiple roles, weight property will determine the assigned editor. Lighter roles that are placed upper will take the precedence. So, an administrator role should be placed over other roles by having a smaller weight, ie. -10.') .'
'; $output .= drupal_render($form); return $output; } /** * Template form for buttons */ function bueditor_form_button($button=NULL) { $button = (object) $button; $form = array(); $form['title'] = array('#type' => 'textfield', '#default_value' => $button->title, '#size' => 14, ); $form['content'] = array('#type' => 'textarea', '#default_value' => $button->content, '#rows' => min(10, max(2, substr_count($button->content, "\n")+1)), ); $form['icon'] = array('#type' => 'select', '#options' => array(''=>t('Caption')) + bueditor_icons(), '#default_value' => $button->icon, ); $form['caption'] = array('#type' => 'textfield', '#default_value' => bueditor_isimage($button->icon) ? '' : $button->icon, '#size' => 6, ); $form['accesskey'] = array('#type' => 'textfield', '#default_value' => $button->accesskey, '#size' => 2, '#maxlength' => 1, ); $form['weight'] = array('#type' => 'weight', '#default_value' => $button->weight, ); return $form; } /** * Editor form processed. */ function bueditor_get_form_editor() { $path = drupal_get_path('module', 'bueditor'); drupal_add_css($path.'/admin/admin.css'); drupal_add_js($path.'/admin/admin.js'); drupal_add_js('var editorPath = "'. base_path(). $path .'/";', 'inline'); $help = '
'. t('To add a new button, you can either specify it at the bottom of the button list or import a CSV file which contains previosly exported buttons. For more information about buttons and editor API please read !readme.', array('!readme' => 'README.txt')) .'
'; if (bueditor_settle(bueditor_editors(arg(3)))) { $demo = '

'. t('Demo'). '

'; } return $help.drupal_get_form('bueditor_form_editor').$demo; } /** * Editor form. */ function bueditor_form_editor() { $editors = bueditor_editors('all'); $new = ($eid = arg(3)) == 'new'; $editor = $editors[$eid]; $form = array('#tree' => TRUE, '#attributes' => array('enctype' => 'multipart/form-data'),); $form['editor']['name'] = array('#type' => 'textfield', '#title' => t('Editor name'), '#default_value' => $editor->name, '#required' => TRUE, ); $form['editor']['pages'] = array('#type' => 'textarea', '#title' => t('Show the editor on specific pages'), '#default_value' => $new ? "node/add/*\nnode/*/edit\ncomment/reply/*" : $editor->pages, '#description' => t('Enter one page per line as Drupal paths. The * character is a wildcard.'), ); $form['editor']['excludes'] = array('#type' => 'textarea', '#title' => t('Hide the editor for specific textareas'), '#default_value' => $new ? 'edit-log' : $editor->excludes, '#description' => t('Enter one textarea ID per line. The * character is a wildcard.'), ); $form['editor']['eid'] = array('#type' => 'hidden', '#value' => $eid); //buttons foreach (bueditor_buttons($eid) as $bid=>$button) { $form['button'][$bid] = bueditor_form_button($button); $form['checks'][$bid] = array('#type' => 'checkbox'); } //new buttons are previewed. not saved yet. if ($file = $_SESSION['buecsv']) { $import = bueditor_import_buttons($file); for ($i=0; $button=$import[$i]; $i++) { $form['button']['new'.$i] = bueditor_form_button($button); } drupal_set_message($i>0 ? t('New buttons are ready to be saved.') : t('There is no button to be imported.', 'error')); unset($_SESSION['buecsv']); } //there is always a new button form. $form['button']['new'] = bueditor_form_button(); //action for selected buttons $form['selaction'] = array('#type' => 'select', '#options' => array('' => t('... selecteds'), 'delete' => t('Delete'), 'export' => t('Export')), ); $form['go'] = array('#type' => 'submit', '#value' => t('Go'), '#id' =>'edit-go'); $form['submit'] = array('#type' => 'submit', '#value' => t('Save'),); $form['buecsv'] = array('#type' => 'file', '#title' => t('CSV file containing the buttons'),); $form['import'] = array('#type' => 'submit', '#value' => t('Import'),); return $form; } /** * Editor form themed. */ function theme_bueditor_form_editor($form) { $eid = $form['editor']['eid']['#value']; $header = array(array('data' => t('Title'), 'class' => 'title'), array('data' => t('Content'), 'class' => 'content'), array('data' => t('Icon'), 'class' => 'icon'), array('data' => t('Key'), 'class' => 'key'), array('data' => t('Weight'), 'class' => 'weight'), array('data' => ' ', 'class' => 'check')); $rows = array(); foreach ($form['button'] as $bid=>$button) { $new = substr($bid, 0, 3)=='new'; if (is_array($button['title'])) { $cells = array(); $cells[] = drupal_render($form['button'][$bid]['title']); $cells[] = drupal_render($form['button'][$bid]['content']); $cells[] = drupal_render($form['button'][$bid]['icon']). drupal_render($form['button'][$bid]['caption']); $cells[] = drupal_render($form['button'][$bid]['accesskey']); $cells[] = drupal_render($form['button'][$bid]['weight']); $cells[] = $new ? ''.t('new').'' : drupal_render($form['checks'][$bid]); $rows[] = $new ? array('data' => $cells, 'class' => 'new-button', 'title' => t('Add new button')) : $cells; } } $table = theme('table', $header, $rows, array('class' => 'button-table', 'id' => 'button-table')); $vis = theme('fieldset', array('#title' => t('Visibility settings'), '#children' => drupal_render($form['editor']['pages']). drupal_render($form['editor']['excludes']), '#collapsible' => TRUE, '#collapsed' => $eid != 'new',)); $selaction = '
'. drupal_render($form['selaction']) . drupal_render($form['go']) .'
'; $output = drupal_render($form['editor']['name']) . ($eid == 'new' ? $vis : ''); $output .= '

'. t('Buttons') .'

'.$table.$selaction; $output .= drupal_render($form['submit']) . ($eid == 'new' ? '' : $vis); $output .= theme('fieldset', array('#title' => t('Import Buttons'), '#children' => drupal_render($form['buecsv']). drupal_render($form['import']), '#collapsible' => TRUE, '#collapsed' => TRUE,)); $output .= drupal_render($form); return $output; } /** * Editor form submitted. */ function bueditor_form_editor_submit($form_id, $edit) { $eid = $edit['editor']['eid']; switch ($edit['op']) { case t('Save'): $eid = bueditor_editor_add($edit['editor']); foreach ($_POST['button'] as $bid=>$button) { $button['bid'] = $bid; $button['eid'] = $eid; bueditor_button_add($button); } drupal_set_message(t('Changes have been saved.')); break; case t('Go'): if (is_array($edit['checks'])) { $bids = array(); foreach ($edit['checks'] as $bid=>$val) { if ($val) { $bids[] = $bid; } } if (count($bids)) { if ($edit['selaction'] == 'delete') { foreach ($bids as $bid) { bueditor_button_delete($bid); } drupal_set_message(t('Selected buttons have been deleted.')); } else if ($edit['selaction'] == 'export') { bueditor_export_buttons($bids); } } } break; case t('Import'): if ($file=file_check_upload('buecsv')) { $_SESSION['buecsv'] = $file->filepath; } break; } return 'admin/settings/bueditor/'.$eid; } /** * Insert/update a button. */ function bueditor_button_add($button) { $sets = array(); $vals = array(); $bid = $button['bid']; $button['content'] = str_replace("\r\n", "\n", $button['content']); $button['icon'] = $button['icon'] ? $button['icon'] : ($button['caption'] ? $button['caption'] : substr($button['title'], 0, 2)); unset($button['caption'], $button['bid']); foreach ($button as $key=>$val) { $sets[] = $key."='%s'"; $vals[] = $val; } if (is_numeric($bid)) {//button with an ID. update $vals[] = $bid; db_query("UPDATE {bueditor_buttons} SET ". implode(', ', $sets) ." WHERE bid = %d", $vals); } else if ($button['title']) {//button with no real ID. insert db_query("INSERT INTO {bueditor_buttons} (". implode(', ', array_keys($button)) .") VALUES(". str_repeat("'%s', ", count($vals)-1) ."'%s')", $vals); } } /** * Delete a button. */ function bueditor_button_delete($bid) { db_query("DELETE FROM {bueditor_buttons} WHERE bid = %d", $bid); } /** * Insert/update an editor. */ function bueditor_editor_add($editor) { $sets = array(); $vals = array(); $eid = $editor['eid']; unset($editor['eid']); foreach ($editor as $key=>$val) { $sets[] = $key."='%s'"; $vals[] = $val; } if (is_numeric($eid)) {//bueditor with an ID. update $vals[] = $eid; db_query("UPDATE {bueditor_editors} SET ". implode(', ', $sets) ." WHERE eid = %d", $vals); } else if ($editor['name']) {//bueditor with no real ID. insert $eid = db_next_id('{bueditor_editors}_eid'); $vals[] = $eid; db_query("INSERT INTO {bueditor_editors} (". implode(', ', array_keys($editor)) .", eid) VALUES(". str_repeat("'%s', ", count($vals)-1) ."%d)", $vals); drupal_set_message(t('New editor has been added.')); } return $eid; } /** * Delete an editor. */ function bueditor_editor_delete($eid) { db_query("DELETE FROM {bueditor_buttons} WHERE eid = %d", $eid); db_query("DELETE FROM {bueditor_editors} WHERE eid = %d", $eid); } /** * Confirm editor delete */ function bueditor_confirm_delete() { $editor = bueditor_editors(arg(3)); return drupal_get_form('bueditor_confirm_delete_form', $editor->name); } function bueditor_confirm_delete_form($name) { return confirm_form($form, t('Are you sure you want to delete the editor !name?', array('!name' => theme('placeholder', $name))), 'admin/settings/bueditor', t('All buttons and settings for this editor will be removed.').' '.t('This action cannot be undone.'), t('Delete'), t('Cancel')); } function bueditor_confirm_delete_form_submit($form_id, $edit) { $editor = bueditor_editors(arg(3)); bueditor_editor_delete($editor->eid); drupal_set_message(t('!name editor has been deleted.', array('!name' => theme('placeholder', $editor->name)))); drupal_goto('admin/settings/bueditor'); } /** * Editor(s). */ function bueditor_editors($eid, $qall=TRUE) { static $editors; if (!$eid) return; if (!$qall) return isset($editors) ? $editors[$eid] : db_fetch_object(db_query("SELECT * FROM {bueditor_editors} WHERE eid = %d", $eid)); if (!isset($editors)) { $editors = array(); $result = db_query("SELECT * FROM {bueditor_editors}"); while ($editor=db_fetch_object($result)) { $editors[$editor->eid] = $editor; } } return $eid == 'all' ? $editors : $editors[$eid]; } /** * All buttons of an editor. */ function bueditor_buttons($eid) { $buttons = array(); $result = db_query("SELECT * FROM {bueditor_buttons} WHERE eid = %d ORDER BY weight, title", $eid); while ($button = db_fetch_object($result)) { $buttons[$button->bid] = $button; } return $buttons; } /** * Processed buttons. Evaluate php code for php buttons. */ function bueditor_buttons_processed($eid) { $buttons = bueditor_buttons($eid); $processed = array(); foreach ($buttons as $bid=>$button) { if (substr($button->content, 0, 4) == 'php:') { if ($output = drupal_eval('content, 4) .' ?>')) { $button->content = $output; } else {//php returned false or nothing. dont include this button. continue; } } $button->title = substr($button->title, 0, 2) == 't:' ? t(trim(substr($button->title, 2))) : $button->title; $processed[] = array($button->title, $button->content, $button->icon, $button->accesskey); } return $processed; } /** * Icons in "icons" folder. */ function bueditor_icons() { $icons = array(); $dir = drupal_get_path('module', 'bueditor').'/icons'; if ($handle = @opendir($dir)) { while (($file = readdir($handle)) !== FALSE) { if (bueditor_isimage($file)) { $icons[$file] = $file; } } closedir($handle); } return $icons; } /** * Export an array of buttons as CSV. */ function bueditor_export_buttons($bids=array()) { if ($n=count($bids)) { $result = db_query("SELECT * FROM {bueditor_buttons} WHERE bid IN (". str_repeat("%d, ", $n-1) ."%d) ORDER BY weight, title", $bids); while ($button=db_fetch_array($result)) { unset($button['bid'], $button['eid']); if (!isset($output)) { $output = '"'. implode('", "', array_keys($button)) .'"'."\n"; } $output .= '"'. implode('", "', array_map('addslashes', array_values($button))) .'"'."\n"; } } if (isset($output)) { header('Content-type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename=bueditor_buttons.csv'); print $output; exit(); } drupal_set_message(t('There is no button to export.'), 'error'); } /** * Import buttons from a CSV file. */ function bueditor_import_buttons($file) { $buttons = array(); if (is_file($file) && $fp=fopen($file, 'r')) { $fields = fgetcsv($fp, 100000); while($values = fgetcsv($fp, 100000)) { $button = array(); for ($i=0; $field=$fields[$i]; $i++) { $button[$field] = stripslashes($values[$i]); } $buttons[] = $button; } } return $buttons; } /** * Implementation of hook_elements(). */ function bueditor_elements() { return array('textarea' => array('#process' => array('bueditor_textarea' => array()))); } /** * Integrate the editor into textareas. */ function bueditor_textarea($element) { static $editor, $pageok, $settled; if (!isset($pageok)) { $editor = bueditor_user_editor($GLOBALS['user']); $pageok = $editor->pages ? bueditor_check_match($editor->pages, $_GET['q']) : FALSE; } if ($pageok && $settled !== FALSE) { if (!bueditor_check_match($editor->excludes, $element['#id'])) { $element['#attributes']['class'] .= ' editor-textarea'; $settled = isset($settled) ? $settled : bueditor_settle($editor); } } return $element; } /** * Include necessary js and css files for editor settlement. */ function bueditor_settle($editor) { if ($editor->eid && count($buttons = bueditor_buttons_processed($editor->eid))) { $dir = drupal_get_path('module', 'bueditor'); drupal_add_css($dir .'/bueditor.css'); drupal_add_js($dir .'/bueditor.js'); bueditor_js_buttons($dir, $buttons); bueditor_js_library($dir.'/library');//load common js files. if (is_dir($edir = $dir.'/library/'.$editor->name)) { bueditor_js_library($edir);//load editor specific js files. } return TRUE; } return FALSE; } /** * Include buttons in a js file to support browser caching. if fails include them inline. */ function bueditor_js_buttons($dir, $buttons) { $str = 'editor.path="'. base_path() . $dir .'/";editor.buttons='. drupal_to_js($buttons); if (variable_get('file_downloads', '') != FILE_DOWNLOADS_PRIVATE) { $file = file_directory_path() .'/bueditor/'. md5($str) .'.js'; if (file_exists($file) || (arg(0) != 'admin' && bueditor_save_data($str, $file))) { drupal_add_js($file); return $file; } } drupal_add_js($str, 'inline'); } /** * Save data to a file. return true on success. */ function bueditor_save_data($data, $file) { return file_check_directory($dir = dirname($file), FILE_CREATE_DIRECTORY) && ($fp = @fopen($file, 'w')) && @fwrite($fp, $data) && @fclose($fp) && @chmod($file, 0664); } /** * Include external javascript files from "library" directory */ function bueditor_js_library($dir) { if ($handle = @opendir($dir)) { while (($file = readdir($handle)) !== FALSE) { if (substr($file, -3) == '.js') { drupal_add_js($dir .'/'. $file); } } closedir($handle); } } /** * Return the editor assigned to the user. */ function bueditor_user_editor($user) { return bueditor_editors(bueditor_user_eid($user), FALSE); } /** * Return the editor id assigned to the user. */ function bueditor_user_eid($user) { if ($user->uid==1) { return variable_get('bueditor_user1', 1); } $stored = variable_get('bueditor_roles', array()); if (!$user->uid) { return $stored[DRUPAL_ANONYMOUS_RID]['editor']; } foreach ($stored as $rid=>$role) { if (isset($user->roles[$rid])) { return $role['editor']; } } } /** * Sort roles according to their weights. */ function bueditor_sorted_roles() { static $sorted; if (!isset($sorted)) { $sorted = array(); $roles = user_roles(); $stored = variable_get('bueditor_roles', array()); $stored[DRUPAL_ANONYMOUS_RID]['weight'] = 10; foreach ($roles as $rid=>$rname) { $sorted[$rid] = array('name' => $rname, 'weight' => (int)$stored[$rid]['weight'], 'editor' => (int)$stored[$rid]['editor']); } uasort($sorted, 'bueditor_rolesort'); } return $sorted; } /** * Check matching lines of the needle in haystack.(page and textarea id) */ function bueditor_check_match($needle, $haystack) { $needle = '/^'. preg_replace("/\r\n?|\n/", '|', str_replace(array('*', '-', '/'), array('.*', '\\-', '\\/'), trim($needle))) .'$/'; return preg_match($needle, $haystack); } /** * user sorting function for roles. */ function bueditor_rolesort($r1, $r2) { return $r1['weight']-$r2['weight']; } /** * Implementation of hook_cron. Clean button script files for every 15 days */ function bueditor_cron() { if (variable_get('file_downloads', '') != FILE_DOWNLOADS_PRIVATE) { $now = time(); $period = 15*24*60*60; $last = variable_get('bueditor_cron_last', 0); if (($now - $last) > $period && is_dir($dir = file_directory_path().'/bueditor') && $handle = @opendir($dir)) { while (($file = readdir($handle)) !== FALSE) { if (substr($file, -3) == '.js') { @unlink($dir.'/'.$file); } } closedir($handle); variable_set('bueditor_cron_last', $now); } } } /** * check if the icon is image.(just a string check, not a file check) */ function bueditor_isimage($text) { return preg_match('/\.(png|gif|jpg|jpeg)$/i', $text); }