diff --git a/picture.admin.inc b/picture.admin.inc index d9a1d74..e5c5e0c 100644 --- a/picture.admin.inc +++ b/picture.admin.inc @@ -216,3 +216,132 @@ function picture_admin_settings($form, &$form_state) { return system_settings_form($form); } + +/** + * Choose which picture groups are available in the CKEditor image dialog. + */ +function picture_ckeditor_settings() { + $form = array(); + $picture_groups = picture_mapping_load(); + $ckeditor_groups = array(); + + // check if picture group mappings have been configured before proceeding. + if ($picture_groups) { + // create a settings form. + $form['description'] = array( + '#type' => 'item', + '#title' => t('Choose which picture groups will be available in the + CKEditor image dialog.'), + ); + + //retrieve pre-existing settings. + $ckeditor_groups = variable_get('picture_ckeditor_groups', array()); + + // loop through each picture group and place a checkbox and weight. + foreach ($picture_groups as $picture_group) { + $machine_name = $picture_group->machine_name; + $form[$machine_name] = array( + '#type' => 'fieldset', + '#title' => t('"@name" picture group', array('@name' => $machine_name)), + ); + $form[$machine_name][$machine_name . '_enabled'] = array( + '#type' => 'checkbox', + '#default_value' => isset($ckeditor_groups[$machine_name]) ? + $ckeditor_groups[$machine_name]['enabled'] : 0, + '#title' => t('Include "@name" picture group in the CKEditor image + dialog', array('@name' => $machine_name)), + ); + $form[$machine_name][$machine_name . '_weight'] = array( + '#type' => 'select', + '#title' => t('Weight'), + '#options' => array( + '1' => t('1'), + '2' => t('2'), + '3' => t('3'), + '4' => t('4'), + '5' => t('5'), + '6' => t('6'), + '7' => t('7'), + '8' => t('8'), + '9' => t('9'), + '10' => t('10'), + ), + '#default_value' => isset($ckeditor_groups[$machine_name]) ? + $ckeditor_groups[$machine_name]['weight'] : 1, + '#description' => t('Control the sort order of picture groups in the + CKEditor "size" drop-down. Higher weights sink to the bottom of the list.'), + ); + $form[$machine_name][$machine_name . '_fallback'] = array( + '#type' => 'select', + '#title' => t('Fallback image style'), + '#options' => drupal_map_assoc(array_keys(image_styles())), + '#default_value' => isset($ckeditor_groups[$machine_name]) ? + $ckeditor_groups[$machine_name]['fallback'] : NULL, + ); + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + } + return $form; +} + +/** + * Validate handler for the picture_ckeditor_settings form. + * It checks that a fallback image style is selected for every + * picture group that has been enabled for the CKEditor image dialog. + */ +function picture_ckeditor_settings_validate($form, &$form_state) { + $picture_groups = picture_mapping_load(); + $ckeditor_groups = array(); + foreach ($picture_groups as $picture_group) { + $machine_name = $picture_group->machine_name; + if ($form_state['values'][$machine_name . '_enabled'] == 1) { + if (empty($form_state['values'][$machine_name . '_fallback'])) { + form_set_error($machine_name . '_fallback', + t('Please choose a fallback image style for this picture group')); + } + } + } +} + +/** + * Submit handler for the picture_ckeditor_settings form. Place chosen picture + * groups into the variables table, and generate a custom plugin file for + * CKEditor. + */ +function picture_ckeditor_settings_submit($form, &$form_state) { + $picture_groups = picture_mapping_load(); + $ckeditor_groups = array(); + + // Loop each picture group and record the settings. + foreach ($picture_groups as $picture_group) { + $machine_name = $picture_group->machine_name; + $ckeditor_groups[$machine_name]['enabled'] = + $form_state['values'][$machine_name . '_enabled']; + $ckeditor_groups[$machine_name]['weight'] = + $form_state['values'][$machine_name . '_weight']; + $ckeditor_groups[$machine_name]['fallback'] = + $form_state['values'][$machine_name . '_fallback']; + } + + uasort($ckeditor_groups, 'picture_compare_weights'); + variable_set('picture_ckeditor_groups', $ckeditor_groups); + drupal_set_message(t('Your settings have been saved')); + + // Generate the custom CKEditor plugin file + +} + +/** + * Helper function to sort picture groups for the CKEditor image dialog + */ + +function picture_compare_weights($a, $b) { + if ($a['weight'] == $b['weight']) { + return 0; + } + return ($a['weight'] < $b['weight']) ? -1 : 1; +} diff --git a/picture.info b/picture.info index 59cc9da..75d4bbe 100644 --- a/picture.info +++ b/picture.info @@ -4,3 +4,4 @@ core = 7.x dependencies[] = breakpoints configure = admin/config/media/picture package = Picture +stylesheets[all][] = picture_wysiwyg.css diff --git a/picture.module b/picture.module index 523b24c..79b2312 100644 --- a/picture.module +++ b/picture.module @@ -90,6 +90,17 @@ function picture_menu() { } } + $items['admin/config/media/picture/ckeditor'] = array( + 'title' => 'CKEditor', + 'type' => MENU_LOCAL_TASK, + 'description' => 'Choose picture groups to present in the CKEditor image dialog', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('picture_ckeditor_settings'), + 'access arguments' => array('administer picture'), + 'file' => 'picture.admin.inc', + 'weight' => 0, + ); + return $items; } @@ -687,6 +698,12 @@ function theme_picture($variables) { $attributes['data-' . $key] = $variables[$key]; } } + // Add attributes that are already prefixed by 'data-' + foreach (array('data-picture-group', 'data-picture-align') as $key) { + if (isset($variables[$key])) { + $attributes[$key] = $variables[$key]; + } + } $output[] = ''; // Add source tags to the output. @@ -935,3 +952,152 @@ function picture_file_formatter_picture_settings($form, &$form_state, $settings) return $element; } + +/** + * Implements hook_filter_info() + */ +function picture_filter_info() { + $filters = array(); + $filters['picture'] = array( + 'title' => t('Make images responsive with the picture module'), + 'description' => t('Replace img tags with markup that contains media width + breakpoints. The appropriate image file size will be chosen.'), + 'process callback' => '_picture_filter_process', + 'tips callback' => '_picture_filter_tips', + //@TODO remove following cache line when development is finished. + 'cache' => FALSE, + ); + + return $filters; +} + +/** + * Process callback for inline image filter + */ +function _picture_filter_process($text, $filter) { + + // create DOM object. A consequence of using DOM is that HTML will be + // corrected, which normally is a selectable filter. In other words, using + // this filter is the same as using this filter and the Drupal + // "Correct faulty and chopped off HTML" together. + $dom_document = filter_dom_load($text); + $xpath = new DOMXpath($dom_document); + $images = $xpath->query('//img[@data-picture-group]'); + foreach ($images as $image) { + // create the render array expected by theme_picture_formatter + $image_render_array = _picture_filter_prepare_image($image); + + // get the responsive markup for this image + $new_markup = theme('picture_formatter', $image_render_array); + + //replace the original img tag with the responsive markup + $new_dom = filter_dom_load($new_markup); + $new_node = $new_dom->getElementsByTagName('body'); + $import = $dom_document->importNode($new_node->item(0), TRUE); + $image->parentNode->replaceChild($import, $image); + } + // turn the DOMDocument back into HTML + $text = filter_dom_serialize($dom_document); + return $text; +} + +/** + * Prepare a Render Array for theme_picture_formatter(...) + * similar to picture_field_formatter_picture_view(...) + * with modifications for inline images + * + * @param $image + * A DOMNode corresponding to the img tag + */ +function _picture_filter_prepare_image($image) { + $image_render_array = array(); + $breakpoint_styles = array(); + $fallback_image_style = ''; + $group_name = $image->getAttribute('data-picture-group'); + $mappings = picture_mapping_load($group_name); + + if ($mappings) { + foreach ($mappings->mapping as $breakpoint_name => $multipliers) { + if (!empty($multipliers)) { + foreach ($multipliers as $multiplier => $image_style) { + if (!empty($image_style)) { + if (empty($fallback_image_style)) { + $fallback_image_style = $image_style; + } + if (!isset($breakpoint_styles[$breakpoint_name])) { + $breakpoint_styles[$breakpoint_name] = array(); + } + $breakpoint_styles[$breakpoint_name][$multiplier] = $image_style; + } + } + } + } + } + + // figure out the schema for the file since theme_picture expects + // a file uri, not the root relative path + // @TODO Please review this: + // I'm not sure how to get a 'uri' with a 'schema' (public or private...) + // knowing just the drupal root relative file path. + // This currently will work only with public files. + $file_root_relative = $image->getAttribute('src'); + $public_path = variable_get('file_public_path', conf_path() . '/files'); + $uri = 'public://' . str_replace($public_path, '', $file_root_relative); + $uri = file_stream_wrapper_uri_normalize($uri); + $image_info = image_get_info($uri); + $picture_groups = variable_get('picture_ckeditor_groups', array()); + $image_render_array = array( + '#theme' => 'picture_formatter', + '#attached' => array('library' => array( + array('picture', 'matchmedia'), + array('picture', 'picturefill'), + array('picture', 'picture.ajax'), + )), + '#item' => array( + 'style_name' => $picture_groups[$image->getAttribute('data-picture-group')] + ['fallback'], + 'uri' => $uri, + 'width' => $image_info['width'], + 'height' => $image_info['height'], + 'data-picture-group' => $image->getAttribute('data-picture-group'), + 'data-picture-align' => $image->getAttribute('data-picture-align'), + 'alt' => $image->getAttribute('alt'), + 'title' => $image->getAttribute('title'), + 'filemime' => $image_info['mime_type'], + ), + '#image_style' => $picture_groups[$image->getAttribute('data-picture-group')] + ['fallback'], + '#breakpoints' => $breakpoint_styles, + '#path' => '', + ); + + return $image_render_array; + +} +/** + * Implements picture filter tips callback. + */ +function _picture_filter_tips($filter, $format, $long = FALSE) { + $tips = 'Images will be responsive, with a file size appropriate for the + browser width. This functions only for local public files. Note that this + filter includes the effects of Drupal\'s "Correct faulty and chopped off + HTML"'; + + return $tips; +} + +/** + * Implements hook_page_alter() + * + * Add the image processing javascript to every page. This allows these scripts + * to get included in aggregation, which is probably good since there will be + * pictures needing this javascript on most pages. The library does not get + * added twice, even if it's attached to multiple fields that are also being + * displayed with responsive images. Maybe this should check that the + * page is not an admin theme page? + */ +function picture_page_alter(&$page) { + drupal_add_library('picture', 'matchmedia', TRUE); + drupal_add_library('picture', 'picturefill', TRUE); + drupal_add_library('picture', 'picture.ajax', TRUE); +} diff --git a/picture_wysiwyg.css b/picture_wysiwyg.css new file mode 100644 index 0000000..604f52b --- /dev/null +++ b/picture_wysiwyg.css @@ -0,0 +1,6 @@ +span[data-picture-align="left"] { + float: left; +} +span[data-picture-align="right"] { + float: right; +}