Added tests for 'link_separate' formatter.
[project/link.git] / link.module
1 <?php
2
3 /**
4 * @file
5 * Defines simple link field types.
6 */
7
8 /**
9 * Implements hook_help().
10 */
11 function link_help($path, $arg) {
12 switch ($path) {
13 case 'admin/help#link':
14 $output = '';
15 $output .= '<h3>' . t('About') . '</h3>';
16 $output .= '<p>' . t('The Link module defines a simple link field type for the Field module. Links are external URLs, can have an optional title for each link, and they can be formatted when displayed. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>';
17 return $output;
18 }
19 }
20
21 /**
22 * Implements hook_field_info().
23 */
24 function link_field_info() {
25 $types['link'] = array(
26 'label' => t('Link'),
27 'description' => t('Stores a URL string, optional varchar title, and optional blob of attributes to assemble a link.'),
28 'instance_settings' => array(
29 'title' => DRUPAL_OPTIONAL,
30 ),
31 'default_widget' => 'link_default',
32 'default_formatter' => 'link',
33 );
34 return $types;
35 }
36
37 /**
38 * Implements hook_field_instance_settings_form().
39 */
40 function link_field_instance_settings_form($field, $instance) {
41 $form['title'] = array(
42 '#type' => 'radios',
43 '#title' => t('Allow link title'),
44 '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : DRUPAL_OPTIONAL,
45 '#options' => array(
46 DRUPAL_DISABLED => t('Disabled'),
47 DRUPAL_OPTIONAL => t('Optional'),
48 DRUPAL_REQUIRED => t('Required'),
49 ),
50 );
51 return $form;
52 }
53
54 /**
55 * Implements hook_field_load().
56 */
57 function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
58 foreach ($entities as $id => $entity) {
59 foreach ($items[$id] as $delta => &$item) {
60 // Unserialize the attributes into an array. The value stored in the
61 // field data should either be NULL or a non-empty serialized array.
62 if (empty($item['attributes'])) {
63 $item['attributes'] = array();
64 }
65 else {
66 $item['attributes'] = unserialize($item['attributes']);
67 }
68 }
69 }
70 }
71
72 /**
73 * Implements hook_field_is_empty().
74 */
75 function link_field_is_empty($item, $field) {
76 return !isset($item['url']) || $item['url'] === '';
77 }
78
79 /**
80 * Implements hook_field_presave().
81 */
82 function link_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
83 foreach ($items as $delta => &$item) {
84 // Trim any spaces around the URL and title.
85 $item['url'] = trim($item['url']);
86 $item['title'] = trim($item['title']);
87
88 // Serialize the attributes array.
89 $item['attributes'] = !empty($item['attributes']) ? serialize($item['attributes']) : NULL;
90 }
91 }
92
93 /**
94 * Implements hook_field_widget_info().
95 */
96 function link_field_widget_info() {
97 $widgets['link_default'] = array(
98 'label' => 'Link',
99 'field types' => array('link'),
100 );
101 return $widgets;
102 }
103
104 /**
105 * Implements hook_field_widget_form().
106 */
107 function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
108 $settings = $instance['settings'];
109
110 $element['url'] = array(
111 '#type' => 'url',
112 '#title' => t('URL'),
113 '#default_value' => isset($items[$delta]['url']) ? $items[$delta]['url'] : NULL,
114 '#maxlength' => 2048,
115 '#required' => $element['#required'],
116 );
117 $element['title'] = array(
118 '#type' => 'textfield',
119 '#title' => t('Title'),
120 '#default_value' => isset($items[$delta]['title']) ? $items[$delta]['title'] : NULL,
121 '#maxlength' => 255,
122 '#access' => $settings['title'] != DRUPAL_DISABLED,
123 );
124 // Post-process the title field to make it conditionally required if URL is
125 // non-empty. Omit the validation on the field edit form, since the field
126 // settings cannot be saved otherwise.
127 $is_field_edit_form = ($element['#entity'] === NULL);
128 if (!$is_field_edit_form && $settings['title'] == DRUPAL_REQUIRED) {
129 $element['#element_validate'][] = 'link_field_widget_validate_title';
130 }
131
132 // Exposing the attributes array in the widget is left for alternate and more
133 // advanced field widgets.
134 $element['attributes'] = array(
135 '#type' => 'value',
136 '#tree' => TRUE,
137 '#value' => !empty($items[$delta]['attributes']) ? $items[$delta]['attributes'] : array(),
138 '#attributes' => array('class' => array('link-field-widget-attributes')),
139 );
140
141 return $element;
142 }
143
144 /**
145 * Form element validation handler for link_field_widget_form().
146 *
147 * Conditionally requires the link title if a URL value was filled in.
148 */
149 function link_field_widget_validate_title(&$element, &$form_state, $form) {
150 if ($element['url']['#value'] !== '' && $element['title']['#value'] === '') {
151 $element['title']['#required'] = TRUE;
152 form_error($element['title'], t('!name field is required.', array('!name' => $element['title']['#title'])));
153 }
154 }
155
156 /**
157 * Implements hook_field_prepare_view().
158 */
159 function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
160 foreach ($entities as $id => $entity) {
161 foreach ($items[$id] as $delta => &$item) {
162 // Split out the link into the parts required for url(): path and options.
163 $parsed = drupal_parse_url($item['url']);
164 $item['path'] = $parsed['path'];
165 $item['options'] = array(
166 'query' => $parsed['query'],
167 'fragment' => $parsed['fragment'],
168 'attributes' => &$item['attributes'],
169 );
170 }
171 }
172 }
173
174 /**
175 * Implements hook_field_formatter_info().
176 */
177 function link_field_formatter_info() {
178 $formatters['link'] = array(
179 'label' => t('Link'),
180 'field types' => array('link'),
181 'settings' => array(
182 'trim_length' => 80,
183 'rel' => NULL,
184 'target' => NULL,
185 'url_only' => FALSE,
186 'url_plain' => FALSE,
187 ),
188 );
189 // @todo Merge into 'link' formatter once there is a #type like 'item' that
190 // can render a compound label and content outside of a form context.
191 $formatters['link_separate'] = array(
192 'label' => t('Separate title and URL'),
193 'field types' => array('link'),
194 'settings' => array(
195 'trim_length' => 80,
196 'rel' => NULL,
197 'target' => NULL,
198 ),
199 );
200 return $formatters;
201 }
202
203 /**
204 * Implements hook_field_formatter_settings_form().
205 */
206 function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
207 $display = $instance['display'][$view_mode];
208 $settings = $display['settings'];
209
210 $element['trim_length'] = array(
211 '#type' => 'number',
212 '#title' => t('Trim link text length'),
213 '#field_suffix' => t('characters'),
214 '#default_value' => $settings['trim_length'],
215 '#min' => 1,
216 '#description' => t('Leave blank to allow unlimited link text lengths.'),
217 );
218 $element['url_only'] = array(
219 '#type' => 'checkbox',
220 '#title' => t('URL only'),
221 '#default_value' => $settings['url_only'],
222 '#access' => $display['type'] == 'link',
223 );
224 $element['url_plain'] = array(
225 '#type' => 'checkbox',
226 '#title' => t('Show URL as plain text'),
227 '#default_value' => $settings['url_plain'],
228 '#access' => $display['type'] == 'link',
229 '#states' => array(
230 'visible' => array(
231 ':input[name*="url_only"]' => array('checked' => TRUE),
232 ),
233 ),
234 );
235 $element['rel'] = array(
236 '#type' => 'checkbox',
237 '#title' => t('Add rel="nofollow" to links'),
238 '#return_value' => 'nofollow',
239 '#default_value' => $settings['nofollow'],
240 );
241 $element['target'] = array(
242 '#type' => 'checkbox',
243 '#title' => t('Open link in new window'),
244 '#return_value' => '_blank',
245 '#default_value' => $settings['target'],
246 );
247
248 return $element;
249 }
250
251 /**
252 * Implements hook_field_formatter_settings_form().
253 */
254 function link_field_formatter_settings_summary($field, $instance, $view_mode) {
255 $display = $instance['display'][$view_mode];
256 $settings = $display['settings'];
257
258 $summary = array();
259
260 if (!empty($settings['trim_length'])) {
261 $summary[] = t('Link text trimmed to @limit characters', array('@limit' => $settings['trim_length']));
262 }
263 else {
264 $summary[] = t('Link text not trimmed');
265 }
266 if (!empty($settings['url_only'])) {
267 if (!empty($settings['url_plain'])) {
268 $summary[] = t('Show URL only as plain-text');
269 }
270 else {
271 $summary[] = t('Show URL only');
272 }
273 }
274 if (!empty($settings['rel'])) {
275 $summary[] = t('Add rel="@rel"', array('@rel' => $settings['rel']));
276 }
277 if (!empty($settings['target'])) {
278 $summary[] = t('Open link in new window');
279 }
280
281 return implode('<br />', $summary);
282 }
283
284 /**
285 * Implements hook_field_formatter_prepare_view().
286 */
287 function link_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
288 foreach ($entities as $id => $entity) {
289 $settings = $displays[$id]['settings'];
290
291 foreach ($items[$id] as $delta => &$item) {
292 // Add optional 'rel' attribute to link options.
293 if (!empty($settings['rel'])) {
294 $item['options']['attributes']['rel'] = $settings['rel'];
295 }
296 // Add optional 'target' attribute to link options.
297 if (!empty($settings['target'])) {
298 $item['options']['attributes']['target'] = $settings['target'];
299 }
300 }
301 }
302 }
303
304 /**
305 * Implements hook_field_formatter_view().
306 */
307 function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
308 $element = array();
309 $settings = $display['settings'];
310
311 foreach ($items as $delta => $item) {
312 // By default use the full URL as the link title.
313 $link_title = $item['url'];
314
315 // If the title field value is available, use it for the link title.
316 if (empty($settings['url_only']) && !empty($item['title'])) {
317 // Unsanitizied token replacement here because $options['html'] is FALSE
318 // by default in theme_link().
319 $link_title = token_replace($item['title'], array($entity_type => $entity), array('sanitize' => FALSE, 'clear' => TRUE));
320 }
321
322 // Trim the link title to the desired length.
323 if (!empty($settings['trim_length'])) {
324 $link_title = truncate_utf8($link_title, $settings['trim_length'], FALSE, TRUE);
325 }
326
327 if ($display['type'] == 'link') {
328 if (!empty($settings['url_only']) && !empty($settings['url_plain'])) {
329 $element[$delta] = array(
330 '#type' => 'markup',
331 '#markup' => check_plain($link_title),
332 );
333 }
334 else {
335 $element[$delta] = array(
336 '#type' => 'link',
337 '#title' => $link_title,
338 '#href' => $item['path'],
339 '#options' => $item['options'],
340 );
341 }
342 }
343 elseif ($display['type'] == 'link_separate') {
344 // The link_separate formatter has two titles; the link title (as in the
345 // field values) and the URL itself. If there is no title value,
346 // $link_title defaults to the URL, so it needs to be unset.
347 // The URL title may need to be trimmed as well.
348 if (empty($item['title'])) {
349 $link_title = NULL;
350 }
351 $url_title = $item['url'];
352 if (!empty($settings['trim_length'])) {
353 $url_title = truncate_utf8($item['url'], $settings['trim_length'], FALSE, TRUE);
354 }
355 $element[$delta] = array(
356 '#theme' => 'link_formatter_link_separate',
357 '#title' => $link_title,
358 '#url_title' => $url_title,
359 '#href' => $item['path'],
360 '#options' => $item['options'],
361 );
362 }
363 }
364 return $element;
365 }
366
367 /**
368 * Implements hook_theme().
369 */
370 function link_theme() {
371 return array(
372 'link_formatter_link_separate' => array(
373 'variables' => array('title' => NULL, 'url_title' => NULL, 'href' => NULL, 'options' => array()),
374 ),
375 );
376 }
377
378 /**
379 * Formats a link as separate title and URL elements.
380 */
381 function theme_link_formatter_link_separate($vars) {
382 $output = '';
383 $output .= '<div class="link-item">';
384 if (!empty($vars['title'])) {
385 $output .= '<div class="link-title">' . check_plain($vars['title']) . '</div>';
386 }
387 $output .= '<div class="link-url">' . l($vars['url_title'], $vars['href'], $vars['options']) . '</div>';
388 $output .= '</div>';
389 return $output;
390 }