link.module 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527
  1. <?php
  2. /**
  3. * @file
  4. * Defines simple link field types.
  5. */
  6. define('LINK_EXTERNAL', 'external');
  7. define('LINK_INTERNAL', 'internal');
  8. define('LINK_FRONT', 'front');
  9. define('LINK_EMAIL', 'email');
  10. define('LINK_NEWS', 'news');
  11. define('LINK_TARGET_DEFAULT', 'default');
  12. define('LINK_TARGET_NEW_WINDOW', '_blank');
  13. define('LINK_TARGET_TOP', '_top');
  14. define('LINK_TARGET_USER', 'user');
  15. /**
  16. * Maximum URLs length - needs to match value in link.install.
  17. */
  18. define('LINK_URL_MAX_LENGTH', 2048);
  19. /**
  20. * Implements hook_field_info().
  21. */
  22. function link_field_info() {
  23. return array(
  24. 'link_field' => array(
  25. 'label' => t('Link'),
  26. 'description' => t('Store a title, href, and attributes in the database to assemble a link.'),
  27. 'settings' => array(
  28. 'attributes' => _link_default_attributes(),
  29. 'url' => 0,
  30. 'title' => 'optional',
  31. 'title_value' => '',
  32. 'title_maxlength' => 128,
  33. 'enable_tokens' => 1,
  34. 'display' => array(
  35. 'url_cutoff' => 80,
  36. ),
  37. ),
  38. 'instance_settings' => array(
  39. 'attributes' => _link_default_attributes(),
  40. 'url' => 0,
  41. 'title' => 'optional',
  42. 'title_value' => '',
  43. 'title_label_use_field_label' => FALSE,
  44. 'title_maxlength' => 128,
  45. 'enable_tokens' => 1,
  46. 'display' => array(
  47. 'url_cutoff' => 80,
  48. ),
  49. 'validate_url' => 1,
  50. 'absolute_url' => 1,
  51. ),
  52. 'default_widget' => 'link_field',
  53. 'default_formatter' => 'link_default',
  54. // Support hook_entity_property_info() from contrib "Entity API".
  55. 'property_type' => 'field_item_link',
  56. 'property_callbacks' => array('link_field_property_info_callback'),
  57. ),
  58. );
  59. }
  60. /**
  61. * Implements hook_field_instance_settings_form().
  62. */
  63. function link_field_instance_settings_form($field, $instance) {
  64. $form = array(
  65. '#element_validate' => array('link_field_settings_form_validate'),
  66. );
  67. $form['absolute_url'] = array(
  68. '#type' => 'checkbox',
  69. '#title' => t('Absolute URL'),
  70. '#default_value' => isset($instance['settings']['absolute_url']) && ($instance['settings']['absolute_url'] !== '') ? $instance['settings']['absolute_url'] : TRUE,
  71. '#description' => t('If checked, the URL will always render as an absolute URL.'),
  72. );
  73. $form['validate_url'] = array(
  74. '#type' => 'checkbox',
  75. '#title' => t('Validate URL'),
  76. '#default_value' => isset($instance['settings']['validate_url']) && ($instance['settings']['validate_url'] !== '') ? $instance['settings']['validate_url'] : TRUE,
  77. '#description' => t('If checked, the URL field will be verified as a valid URL during validation.'),
  78. );
  79. $form['url'] = array(
  80. '#type' => 'checkbox',
  81. '#title' => t('Optional URL'),
  82. '#default_value' => isset($instance['settings']['url']) ? $instance['settings']['url'] : '',
  83. '#return_value' => 'optional',
  84. '#description' => t('If checked, the URL field is optional and submitting a title alone will be acceptable. If the URL is omitted, the title will be displayed as plain text.'),
  85. );
  86. $title_options = array(
  87. 'optional' => t('Optional Title'),
  88. 'required' => t('Required Title'),
  89. 'value' => t('Static Title'),
  90. 'none' => t('No Title'),
  91. );
  92. $form['title'] = array(
  93. '#type' => 'radios',
  94. '#title' => t('Link Title'),
  95. '#default_value' => isset($instance['settings']['title']) ? $instance['settings']['title'] : 'optional',
  96. '#options' => $title_options,
  97. '#description' => t('If the link title is optional or required, a field will be displayed to the end user. If the link title is static, the link will always use the same title. If <a href="http://drupal.org/project/token">token module</a> is installed, the static title value may use any other entity field as its value. Static and token-based titles may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'),
  98. );
  99. $form['title_value'] = array(
  100. '#type' => 'textfield',
  101. '#title' => t('Static title'),
  102. '#default_value' => isset($instance['settings']['title_value']) ? $instance['settings']['title_value'] : '',
  103. '#description' => t('This title will always be used if &ldquo;Static Title&rdquo; is selected above.'),
  104. );
  105. $form['title_label_use_field_label'] = array(
  106. '#type' => 'checkbox',
  107. '#title' => t('Use field label as the label for the title field'),
  108. '#default_value' => isset($instance['settings']['title_label_use_field_label']) ? $instance['settings']['title_label_use_field_label'] : FALSE,
  109. '#description' => t('If this is checked the field label will be hidden.'),
  110. );
  111. $form['title_maxlength'] = array(
  112. '#type' => 'textfield',
  113. '#title' => t('Max length of title field'),
  114. '#default_value' => isset($instance['settings']['title_maxlength']) ? $instance['settings']['title_maxlength'] : '128',
  115. '#description' => t('Set a maximum length on the title field (applies only if Link Title is optional or required). The maximum limit is 255 characters.'),
  116. '#maxlength' => 3,
  117. '#size' => 3,
  118. );
  119. if (module_exists('token')) {
  120. // Add token module replacements fields.
  121. $form['enable_tokens'] = array(
  122. '#type' => 'checkbox',
  123. '#title' => t('Allow user-entered tokens'),
  124. '#default_value' => isset($instance['settings']['enable_tokens']) ? $instance['settings']['enable_tokens'] : 1,
  125. '#description' => t('Checking will allow users to enter tokens in URLs and Titles on the entity edit form. This does not affect the field settings on this page.'),
  126. );
  127. $entity_info = entity_get_info($instance['entity_type']);
  128. $form['tokens_help'] = array(
  129. '#theme' => 'token_tree',
  130. '#token_types' => array($entity_info['token type']),
  131. '#global_types' => TRUE,
  132. '#click_insert' => TRUE,
  133. '#dialog' => TRUE,
  134. );
  135. }
  136. $form['display'] = array(
  137. '#tree' => TRUE,
  138. );
  139. $form['display']['url_cutoff'] = array(
  140. '#type' => 'textfield',
  141. '#title' => t('URL Display Cutoff'),
  142. '#default_value' => isset($instance['settings']['display']['url_cutoff']) ? $instance['settings']['display']['url_cutoff'] : '80',
  143. '#description' => t('If the user does not include a title for this link, the URL will be used as the title. When should the link title be trimmed and finished with an elipsis (&hellip;)? Leave blank for no limit.'),
  144. '#maxlength' => 3,
  145. '#size' => 3,
  146. );
  147. $target_options = array(
  148. LINK_TARGET_DEFAULT => t('Default (no target attribute)'),
  149. LINK_TARGET_TOP => t('Open link in window root'),
  150. LINK_TARGET_NEW_WINDOW => t('Open link in new window'),
  151. LINK_TARGET_USER => t('Allow the user to choose'),
  152. );
  153. $form['attributes'] = array(
  154. '#tree' => TRUE,
  155. );
  156. $form['attributes']['target'] = array(
  157. '#type' => 'radios',
  158. '#title' => t('Link Target'),
  159. '#default_value' => empty($instance['settings']['attributes']['target']) ? LINK_TARGET_DEFAULT : $instance['settings']['attributes']['target'],
  160. '#options' => $target_options,
  161. );
  162. $form['attributes']['rel'] = array(
  163. '#type' => 'textfield',
  164. '#title' => t('Rel Attribute'),
  165. '#description' => t('When output, this link will have this rel attribute. The most common usage is <a href="http://en.wikipedia.org/wiki/Nofollow">rel=&quot;nofollow&quot;</a> which prevents some search engines from spidering entered links.'),
  166. '#default_value' => empty($instance['settings']['attributes']['rel']) ? '' : $instance['settings']['attributes']['rel'],
  167. '#field_prefix' => 'rel = "',
  168. '#field_suffix' => '"',
  169. '#size' => 20,
  170. );
  171. $rel_remove_options = array(
  172. 'default' => t('Keep rel as set up above (untouched/default)'),
  173. 'rel_remove_external' => t('Remove rel if given link is external'),
  174. 'rel_remove_internal' => t('Remove rel if given link is internal'),
  175. );
  176. $form['rel_remove'] = array(
  177. '#type' => 'radios',
  178. '#title' => t('Remove rel attribute automatically'),
  179. '#default_value' => !isset($instance['settings']['rel_remove']) ? 'default' : $instance['settings']['rel_remove'],
  180. '#description' => t('Turn on/off if rel attribute should be removed automatically, if user given link is internal/external'),
  181. '#options' => $rel_remove_options,
  182. );
  183. $form['attributes']['configurable_class'] = array(
  184. '#title' => t("Allow the user to enter a custom link class per link"),
  185. '#type' => 'checkbox',
  186. '#default_value' => empty($instance['settings']['attributes']['configurable_class']) ? '' : $instance['settings']['attributes']['configurable_class'],
  187. );
  188. $form['attributes']['class'] = array(
  189. '#type' => 'textfield',
  190. '#title' => t('Additional CSS Class'),
  191. '#description' => t('When output, this link will have this class attribute. Multiple classes should be separated by spaces. Only alphanumeric characters and hyphens are allowed'),
  192. '#default_value' => empty($instance['settings']['attributes']['class']) ? '' : $instance['settings']['attributes']['class'],
  193. );
  194. $form['attributes']['configurable_title'] = array(
  195. '#title' => t("Allow the user to enter a link 'title' attribute"),
  196. '#type' => 'checkbox',
  197. '#default_value' => empty($instance['settings']['attributes']['configurable_title']) ? '' : $instance['settings']['attributes']['configurable_title'],
  198. );
  199. $form['attributes']['title'] = array(
  200. '#title' => t("Default link 'title' Attribute"),
  201. '#type' => 'textfield',
  202. '#description' => t('When output, links will use this "title" attribute if the user does not provide one and when different from the link text. Read <a href="http://www.w3.org/TR/WCAG10-HTML-TECHS/#links">WCAG 1.0 Guidelines</a> for links comformances. Tokens values will be evaluated.'),
  203. '#default_value' => empty($instance['settings']['attributes']['title']) ? '' : $instance['settings']['attributes']['title'],
  204. '#field_prefix' => 'title = "',
  205. '#field_suffix' => '"',
  206. '#size' => 20,
  207. );
  208. return $form;
  209. }
  210. /**
  211. * #element_validate handler for link_field_instance_settings_form().
  212. */
  213. function link_field_settings_form_validate($element, &$form_state, $complete_form) {
  214. if ($form_state['values']['instance']['settings']['title'] === 'value' && empty($form_state['values']['instance']['settings']['title_value'])) {
  215. form_set_error('title_value', t('A default title must be provided if the title is a static value.'));
  216. }
  217. if (!empty($form_state['values']['instance']['settings']['display']['url_cutoff']) && !is_numeric($form_state['values']['instance']['settings']['display']['url_cutoff'])) {
  218. form_set_error('display', t('URL Display Cutoff value must be numeric.'));
  219. }
  220. if (empty($form_state['values']['instance']['settings']['title_maxlength'])) {
  221. form_set_value($element['title_maxlength'], '128', $form_state);
  222. }
  223. elseif (!is_numeric($form_state['values']['instance']['settings']['title_maxlength'])) {
  224. form_set_error('title_maxlength', t('The max length of the link title must be numeric.'));
  225. }
  226. elseif ($form_state['values']['instance']['settings']['title_maxlength'] > 255) {
  227. form_set_error('title_maxlength', t('The max length of the link title cannot be greater than 255 characters.'));
  228. }
  229. }
  230. /**
  231. * Implements hook_field_is_empty().
  232. */
  233. function link_field_is_empty($item, $field) {
  234. return empty($item['title']) && empty($item['url']);
  235. }
  236. /**
  237. * Implements hook_field_load().
  238. */
  239. function link_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  240. foreach ($entities as $id => $entity) {
  241. foreach ($items[$id] as $delta => $item) {
  242. $items[$id][$delta]['attributes'] = _link_load($field, $item, $instances[$id]);
  243. }
  244. }
  245. }
  246. /**
  247. * Implements hook_field_validate().
  248. */
  249. function link_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  250. $optional_field_found = FALSE;
  251. if ($instance['settings']['validate_url'] !== 0 || is_null($instance['settings']['validate_url']) || !isset($instance['settings']['validate_url'])) {
  252. foreach ($items as $delta => $value) {
  253. _link_validate($items[$delta], $delta, $field, $entity, $instance, $langcode, $optional_field_found, $errors);
  254. }
  255. }
  256. if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] && !$optional_field_found) {
  257. $errors[$field['field_name']][$langcode][0][] = array(
  258. 'error' => 'link_required',
  259. 'message' => t('At least one title or URL must be entered.'),
  260. 'error_element' => array('url' => FALSE, 'title' => TRUE),
  261. );
  262. }
  263. }
  264. /**
  265. * Implements hook_field_insert().
  266. */
  267. function link_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
  268. foreach ($items as $delta => $value) {
  269. _link_process($items[$delta], $delta, $field, $entity, $instance);
  270. }
  271. }
  272. /**
  273. * Implements hook_field_update().
  274. */
  275. function link_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
  276. foreach ($items as $delta => $value) {
  277. _link_process($items[$delta], $delta, $field, $entity, $instance);
  278. }
  279. }
  280. /**
  281. * Implements hook_field_prepare_view().
  282. */
  283. function link_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
  284. foreach ($items as $entity_id => $entity_items) {
  285. foreach ($entity_items as $delta => $value) {
  286. _link_sanitize($items[$entity_id][$delta], $delta, $field, $instances[$entity_id], $entities[$entity_id]);
  287. }
  288. }
  289. }
  290. /**
  291. * Implements hook_field_widget_info().
  292. */
  293. function link_field_widget_info() {
  294. return array(
  295. 'link_field' => array(
  296. 'label' => 'Link',
  297. 'field types' => array('link_field'),
  298. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  299. ),
  300. );
  301. }
  302. /**
  303. * Implements hook_field_widget_form().
  304. */
  305. function link_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  306. $element += array(
  307. '#type' => $instance['widget']['type'],
  308. '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
  309. );
  310. return $element;
  311. }
  312. /**
  313. * Implements hook_field_widget_error().
  314. */
  315. function link_field_widget_error($element, $error, $form, &$form_state) {
  316. if ($error['error_element']['title']) {
  317. form_error($element['title'], $error['message']);
  318. }
  319. elseif ($error['error_element']['url']) {
  320. form_error($element['url'], $error['message']);
  321. }
  322. }
  323. /**
  324. * Unpacks the item attributes for use.
  325. */
  326. function _link_load($field, $item, $instance) {
  327. if (isset($item['attributes'])) {
  328. if (!is_array($item['attributes'])) {
  329. $item['attributes'] = unserialize($item['attributes']);
  330. }
  331. return $item['attributes'];
  332. }
  333. elseif (isset($instance['settings']['attributes'])) {
  334. return $instance['settings']['attributes'];
  335. }
  336. else {
  337. return $field['settings']['attributes'];
  338. }
  339. }
  340. /**
  341. * Prepares the item attributes and url for storage.
  342. *
  343. * @param $item
  344. * Link field values.
  345. *
  346. * @param $delta
  347. * The sequence number for current values.
  348. *
  349. * @param $field
  350. * The field structure array.
  351. *
  352. * @param $entity
  353. * Entity object.
  354. *
  355. * @param $instance
  356. * The instance structure for $field on $entity's bundle.
  357. *
  358. */
  359. function _link_process(&$item, $delta, $field, $entity, $instance) {
  360. // Trim whitespace from URL.
  361. if (!empty($item['url'])) {
  362. $item['url'] = trim($item['url']);
  363. }
  364. // If no attributes are set then make sure $item['attributes'] is an empty
  365. // array, so $field['attributes'] can override it.
  366. if (empty($item['attributes'])) {
  367. $item['attributes'] = array();
  368. }
  369. // Serialize the attributes array.
  370. if (!is_string($item['attributes'])) {
  371. $item['attributes'] = serialize($item['attributes']);
  372. }
  373. // Don't save an invalid default value (e.g. 'http://').
  374. if ((isset($field['widget']['default_value'][$delta]['url']) && $item['url'] == $field['widget']['default_value'][$delta]['url']) && is_object($entity)) {
  375. $langcode = !empty($entity) ? field_language($instance['entity_type'], $entity, $instance['field_name']) : LANGUAGE_NONE;
  376. if (!link_validate_url($item['url'], $langcode)) {
  377. unset($item['url']);
  378. }
  379. }
  380. }
  381. /**
  382. * Validates that the link field has been entered properly.
  383. */
  384. function _link_validate(&$item, $delta, $field, $entity, $instance, $langcode, &$optional_field_found, &$errors) {
  385. if ($item['url'] && !(isset($instance['default_value'][$delta]['url']) && $item['url'] === $instance['default_value'][$delta]['url'] && !$instance['required'])) {
  386. // Validate the link.
  387. if (!link_validate_url(trim($item['url']), $langcode)) {
  388. $errors[$field['field_name']][$langcode][$delta][] = array(
  389. 'error' => 'link_required',
  390. 'message' => t('The value %value provided for %field is not a valid URL.', array(
  391. '%value' => trim($item['url']),
  392. '%field' => $instance['label'],
  393. )),
  394. 'error_element' => array('url' => TRUE, 'title' => FALSE),
  395. );
  396. }
  397. // Require a title for the link if necessary.
  398. if ($instance['settings']['title'] == 'required' && strlen(trim($item['title'])) == 0) {
  399. $errors[$field['field_name']][$langcode][$delta][] = array(
  400. 'error' => 'link_required',
  401. 'message' => t('Titles are required for all links.'),
  402. 'error_element' => array('url' => FALSE, 'title' => TRUE),
  403. );
  404. }
  405. }
  406. // Require a link if we have a title.
  407. if ($instance['settings']['url'] !== 'optional' && strlen(isset($item['title']) ? $item['title'] : NULL) > 0 && strlen(trim($item['url'])) == 0) {
  408. $errors[$field['field_name']][$langcode][$delta][] = array(
  409. 'error' => 'link_required',
  410. 'message' => t('You cannot enter a title without a link url.'),
  411. 'error_element' => array('url' => TRUE, 'title' => FALSE),
  412. );
  413. }
  414. // In a totally bizzaro case, where URLs and titles are optional but the field is required, ensure there is at least one link.
  415. if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional'
  416. && (strlen(trim($item['url'])) !== 0 || strlen(trim($item['title'])) !== 0)) {
  417. $optional_field_found = TRUE;
  418. }
  419. // Require entire field.
  420. if ($instance['settings']['url'] === 'optional' && $instance['settings']['title'] === 'optional' && $instance['required'] == 1 && !$optional_field_found && isset($instance['id'])) {
  421. $errors[$field['field_name']][$langcode][$delta][] = array(
  422. 'error' => 'link_required',
  423. 'message' => t('At least one title or URL must be entered.'),
  424. 'error_element' => array('url' => FALSE, 'title' => TRUE),
  425. );
  426. }
  427. }
  428. /**
  429. * Clean up user-entered values for a link field according to field settings.
  430. *
  431. * @param array $item
  432. * A single link item, usually containing url, title, and attributes.
  433. * @param int $delta
  434. * The delta value if this field is one of multiple fields.
  435. * @param array $field
  436. * The CCK field definition.
  437. * @param object $entity
  438. * The entity containing this link.
  439. */
  440. function _link_sanitize(&$item, $delta, &$field, $instance, &$entity) {
  441. // Don't try to process empty links.
  442. if (empty($item['url']) && empty($item['title'])) {
  443. return;
  444. }
  445. if (empty($item['html'])) {
  446. $item['html'] = FALSE;
  447. }
  448. // Replace URL tokens.
  449. $entity_type = $instance['entity_type'];
  450. $entity_info = entity_get_info($entity_type);
  451. $property_id = $entity_info['entity keys']['id'];
  452. $entity_token_type = isset($entity_info['token type']) ? $entity_info['token type'] : (
  453. $entity_type == 'taxonomy_term' || $entity_type == 'taxonomy_vocabulary' ? str_replace('taxonomy_', '', $entity_type) : $entity_type
  454. );
  455. if (isset($instance['settings']['enable_tokens']) && $instance['settings']['enable_tokens']) {
  456. $text_tokens = token_scan($item['url']);
  457. if (!empty($text_tokens)) {
  458. // Load the entity if necessary for entities in views.
  459. if (isset($entity->{$property_id})) {
  460. $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
  461. $entity_loaded = array_pop($entity_loaded);
  462. }
  463. else {
  464. $entity_loaded = $entity;
  465. }
  466. $item['url'] = token_replace($item['url'], array($entity_token_type => $entity_loaded));
  467. }
  468. }
  469. $type = link_url_type($item['url']);
  470. // If the type of the URL cannot be determined and URL validation is disabled,
  471. // then assume LINK_EXTERNAL for later processing.
  472. if ($type == FALSE && $instance['settings']['validate_url'] === 0) {
  473. $type = LINK_EXTERNAL;
  474. }
  475. $url = link_cleanup_url($item['url']);
  476. $url_parts = _link_parse_url($url);
  477. if (!empty($url_parts['url'])) {
  478. $item['url'] = url($url_parts['url'],
  479. array('query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
  480. 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
  481. 'absolute' => !empty($instance['settings']['absolute_url']),
  482. 'html' => TRUE,
  483. )
  484. );
  485. }
  486. // Create a shortened URL for display.
  487. if ($type == LINK_EMAIL) {
  488. $display_url = str_replace('mailto:', '', $url);
  489. }
  490. else {
  491. $display_url = url($url_parts['url'],
  492. array(
  493. 'query' => isset($url_parts['query']) ? $url_parts['query'] : NULL,
  494. 'fragment' => isset($url_parts['fragment']) ? $url_parts['fragment'] : NULL,
  495. 'absolute' => !empty($instance['settings']['absolute_url']),
  496. )
  497. );
  498. }
  499. if ($instance['settings']['display']['url_cutoff'] && strlen($display_url) > $instance['settings']['display']['url_cutoff']) {
  500. $display_url = substr($display_url, 0, $instance['settings']['display']['url_cutoff']) . "...";
  501. }
  502. $item['display_url'] = $display_url;
  503. // Use the title defined at the instance level.
  504. if ($instance['settings']['title'] == 'value' && strlen(trim($instance['settings']['title_value']))) {
  505. $title = $instance['settings']['title_value'];
  506. if (function_exists('i18n_string_translate')) {
  507. $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
  508. $title = i18n_string_translate($i18n_string_name, $title);
  509. }
  510. }
  511. // Use the title defined by the user at the widget level.
  512. elseif (isset($item['title'])) {
  513. $title = $item['title'];
  514. }
  515. else {
  516. $title = '';
  517. }
  518. // Replace title tokens.
  519. if ($title && ($instance['settings']['title'] == 'value' || $instance['settings']['enable_tokens'])) {
  520. $text_tokens = token_scan($title);
  521. if (!empty($text_tokens)) {
  522. // Load the entity if necessary for entities in views.
  523. if (isset($entity->{$property_id})) {
  524. $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
  525. $entity_loaded = array_pop($entity_loaded);
  526. }
  527. else {
  528. $entity_loaded = $entity;
  529. }
  530. $title = token_replace($title, array($entity_token_type => $entity_loaded));
  531. }
  532. $title = filter_xss($title, array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
  533. $item['html'] = TRUE;
  534. }
  535. $item['title'] = empty($title) ? $item['display_url'] : $title;
  536. if (!isset($item['attributes'])) {
  537. $item['attributes'] = array();
  538. }
  539. // Unserialize attributtes array if it has not been unserialized yet.
  540. if (!is_array($item['attributes'])) {
  541. $item['attributes'] = (array) unserialize($item['attributes']);
  542. }
  543. // Add default attributes.
  544. if (!is_array($instance['settings']['attributes'])) {
  545. $instance['settings']['attributes'] = _link_default_attributes();
  546. }
  547. else {
  548. $instance['settings']['attributes'] += _link_default_attributes();
  549. }
  550. // Merge item attributes with attributes defined at the field level.
  551. $item['attributes'] += $instance['settings']['attributes'];
  552. // If user is not allowed to choose target attribute, use default defined at
  553. // field level.
  554. if ($instance['settings']['attributes']['target'] != LINK_TARGET_USER) {
  555. $item['attributes']['target'] = $instance['settings']['attributes']['target'];
  556. }
  557. elseif ($item['attributes']['target'] == LINK_TARGET_USER) {
  558. $item['attributes']['target'] = LINK_TARGET_DEFAULT;
  559. }
  560. // Remove the target attribute if the default (no target) is selected.
  561. if (empty($item['attributes']) || (isset($item['attributes']['target']) && $item['attributes']['target'] == LINK_TARGET_DEFAULT)) {
  562. unset($item['attributes']['target']);
  563. }
  564. // Remove rel attribute for internal or external links if selected.
  565. if (isset($item['attributes']['rel']) && isset($instance['settings']['rel_remove']) && $instance['settings']['rel_remove'] != 'default') {
  566. if (($instance['settings']['rel_remove'] != 'rel_remove_internal' && $type != LINK_INTERNAL) ||
  567. ($instance['settings']['rel_remove'] != 'rel_remove_external' && $type != LINK_EXTERNAL)) {
  568. unset($item['attributes']['rel']);
  569. }
  570. }
  571. // Handle "title" link attribute.
  572. if (!empty($item['attributes']['title']) && module_exists('token')) {
  573. $text_tokens = token_scan($item['attributes']['title']);
  574. if (!empty($text_tokens)) {
  575. // Load the entity (necessary for entities in views).
  576. if (isset($entity->{$property_id})) {
  577. $entity_loaded = entity_load($entity_type, array($entity->{$property_id}));
  578. $entity_loaded = array_pop($entity_loaded);
  579. }
  580. else {
  581. $entity_loaded = $entity;
  582. }
  583. $item['attributes']['title'] = token_replace($item['attributes']['title'], array($entity_token_type => $entity_loaded));
  584. }
  585. $item['attributes']['title'] = filter_xss($item['attributes']['title'], array('b', 'br', 'code', 'em', 'i', 'img', 'span', 'strong', 'sub', 'sup', 'tt', 'u'));
  586. }
  587. // Handle attribute classes.
  588. if (!empty($item['attributes']['class'])) {
  589. $classes = explode(' ', $item['attributes']['class']);
  590. foreach ($classes as &$class) {
  591. $class = drupal_clean_css_identifier($class);
  592. }
  593. $item['attributes']['class'] = implode(' ', $classes);
  594. }
  595. unset($item['attributes']['configurable_class']);
  596. // Remove title attribute if it's equal to link text.
  597. if (isset($item['attributes']['title']) && $item['attributes']['title'] == $item['title']) {
  598. unset($item['attributes']['title']);
  599. }
  600. unset($item['attributes']['configurable_title']);
  601. // Remove empty attributes.
  602. $item['attributes'] = array_filter($item['attributes']);
  603. }
  604. /**
  605. * Because parse_url doesn't work with relative urls.
  606. *
  607. * @param string $url
  608. * URL to parse.
  609. *
  610. * @return array
  611. * Array of url pieces - only 'url', 'query', and 'fragment'.
  612. */
  613. function _link_parse_url($url) {
  614. $url_parts = array();
  615. // Separate out the anchor, if any.
  616. if (strpos($url, '#') !== FALSE) {
  617. $url_parts['fragment'] = substr($url, strpos($url, '#') + 1);
  618. $url = substr($url, 0, strpos($url, '#'));
  619. }
  620. // Separate out the query string, if any.
  621. if (strpos($url, '?') !== FALSE) {
  622. $query = substr($url, strpos($url, '?') + 1);
  623. $url_parts['query'] = _link_parse_str($query);
  624. $url = substr($url, 0, strpos($url, '?'));
  625. }
  626. $url_parts['url'] = $url;
  627. return $url_parts;
  628. }
  629. /**
  630. * Replaces the PHP parse_str() function.
  631. *
  632. * Because parse_str replaces the following characters in query parameters name
  633. * in order to maintain compatibility with deprecated register_globals directive:
  634. *
  635. * - chr(32) ( ) (space)
  636. * - chr(46) (.) (dot)
  637. * - chr(91) ([) (open square bracket)
  638. * - chr(128) - chr(159) (various)
  639. *
  640. * @param string $query
  641. * Query to parse.
  642. *
  643. * @return array
  644. * Array of query parameters.
  645. *
  646. * @see http://php.net/manual/en/language.variables.external.php#81080
  647. */
  648. function _link_parse_str($query) {
  649. $query_array = array();
  650. $pairs = explode('&', $query);
  651. foreach ($pairs as $pair) {
  652. $name_value = explode('=', $pair, 2);
  653. $name = urldecode($name_value[0]);
  654. $value = isset($name_value[1]) ? urldecode($name_value[1]) : NULL;
  655. $query_array[$name] = $value;
  656. }
  657. return $query_array;
  658. }
  659. /**
  660. * Implements hook_theme().
  661. */
  662. function link_theme() {
  663. return array(
  664. 'link_formatter_link_default' => array(
  665. 'variables' => array('element' => NULL, 'field' => NULL),
  666. ),
  667. 'link_formatter_link_plain' => array(
  668. 'variables' => array('element' => NULL, 'field' => NULL),
  669. ),
  670. 'link_formatter_link_host' => array(
  671. 'variables' => array('element' => NULL),
  672. ),
  673. 'link_formatter_link_absolute' => array(
  674. 'variables' => array('element' => NULL, 'field' => NULL),
  675. ),
  676. 'link_formatter_link_domain' => array(
  677. 'variables' => array('element' => NULL, 'display' => NULL, 'field' => NULL),
  678. ),
  679. 'link_formatter_link_title_plain' => array(
  680. 'variables' => array('element' => NULL, 'field' => NULL),
  681. ),
  682. 'link_formatter_link_url' => array(
  683. 'variables' => array('element' => NULL, 'field' => NULL),
  684. ),
  685. 'link_formatter_link_short' => array(
  686. 'variables' => array('element' => NULL, 'field' => NULL),
  687. ),
  688. 'link_formatter_link_label' => array(
  689. 'variables' => array('element' => NULL, 'field' => NULL),
  690. ),
  691. 'link_formatter_link_separate' => array(
  692. 'variables' => array('element' => NULL, 'field' => NULL),
  693. ),
  694. 'link_field' => array(
  695. 'render element' => 'element',
  696. ),
  697. );
  698. }
  699. /**
  700. * Formats a link field widget.
  701. */
  702. function theme_link_field($vars) {
  703. drupal_add_css(drupal_get_path('module', 'link') . '/link.css');
  704. $element = $vars['element'];
  705. // Prefix single value link fields with the name of the field.
  706. if (empty($element['#field']['multiple'])) {
  707. if (isset($element['url']) && !isset($element['title'])) {
  708. $element['url']['#title_display'] = 'invisible';
  709. }
  710. }
  711. $output = '';
  712. $output .= '<div class="link-field-subrow clearfix">';
  713. if (isset($element['title'])) {
  714. $output .= '<div class="link-field-title link-field-column">' . drupal_render($element['title']) . '</div>';
  715. }
  716. $output .= '<div class="link-field-url' . (isset($element['title']) ? ' link-field-column' : '') . '">' . drupal_render($element['url']) . '</div>';
  717. $output .= '</div>';
  718. if (!empty($element['attributes']['target'])) {
  719. $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['target']) . '</div>';
  720. }
  721. if (!empty($element['attributes']['title'])) {
  722. $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['title']) . '</div>';
  723. }
  724. if (!empty($element['attributes']['class'])) {
  725. $output .= '<div class="link-attributes">' . drupal_render($element['attributes']['class']) . '</div>';
  726. }
  727. $output .= drupal_render_children($element);
  728. return $output;
  729. }
  730. /**
  731. * Implements hook_element_info().
  732. */
  733. function link_element_info() {
  734. $elements = array();
  735. $elements['link_field'] = array(
  736. '#input' => TRUE,
  737. '#process' => array('link_field_process'),
  738. '#theme' => 'link_field',
  739. '#theme_wrappers' => array('form_element'),
  740. );
  741. return $elements;
  742. }
  743. /**
  744. * Returns the default attributes and their values.
  745. */
  746. function _link_default_attributes() {
  747. return array(
  748. 'target' => LINK_TARGET_DEFAULT,
  749. 'class' => '',
  750. 'rel' => '',
  751. );
  752. }
  753. /**
  754. * Processes the link type element before displaying the field.
  755. *
  756. * Build the form element. When creating a form using FAPI #process,
  757. * note that $element['#value'] is already set.
  758. *
  759. * The $fields array is in $complete_form['#field_info'][$element['#field_name']].
  760. */
  761. function link_field_process($element, $form_state, $complete_form) {
  762. $instance = field_widget_instance($element, $form_state);
  763. $settings = $instance['settings'];
  764. $element['url'] = array(
  765. '#type' => 'textfield',
  766. '#maxlength' => LINK_URL_MAX_LENGTH,
  767. '#title' => t('URL'),
  768. '#required' => ($element['#delta'] == 0 && $settings['url'] !== 'optional') ? $element['#required'] : FALSE,
  769. '#default_value' => isset($element['#value']['url']) ? $element['#value']['url'] : NULL,
  770. );
  771. if ($settings['title'] !== 'none' && $settings['title'] !== 'value') {
  772. // Figure out the label of the title field.
  773. if (!empty($settings['title_label_use_field_label'])) {
  774. // Use the element label as the title field label.
  775. $title_label = $element['#title'];
  776. // Hide the field label because there is no need for the duplicate labels.
  777. $element['#title_display'] = 'invisible';
  778. }
  779. else {
  780. $title_label = t('Title');
  781. }
  782. $element['title'] = array(
  783. '#type' => 'textfield',
  784. '#maxlength' => $settings['title_maxlength'],
  785. '#title' => $title_label,
  786. '#description' => t('The link title is limited to @maxlength characters maximum.', array('@maxlength' => $settings['title_maxlength'])),
  787. '#required' => ($settings['title'] == 'required' && (($element['#delta'] == 0 && $element['#required']) || !empty($element['#value']['url']))) ? TRUE : FALSE,
  788. '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
  789. );
  790. }
  791. // Initialize field attributes as an array if it is not an array yet.
  792. if (!is_array($settings['attributes'])) {
  793. $settings['attributes'] = array();
  794. }
  795. // Add default attributes.
  796. $settings['attributes'] += _link_default_attributes();
  797. $attributes = isset($element['#value']['attributes']) ? $element['#value']['attributes'] : $settings['attributes'];
  798. if (!empty($settings['attributes']['target']) && $settings['attributes']['target'] == LINK_TARGET_USER) {
  799. $element['attributes']['target'] = array(
  800. '#type' => 'checkbox',
  801. '#title' => t('Open URL in a New Window'),
  802. '#return_value' => LINK_TARGET_NEW_WINDOW,
  803. '#default_value' => isset($attributes['target']) ? $attributes['target'] : FALSE,
  804. );
  805. }
  806. if (!empty($settings['attributes']['configurable_title']) && $settings['attributes']['configurable_title'] == 1) {
  807. $element['attributes']['title'] = array(
  808. '#type' => 'textfield',
  809. '#title' => t('Link "title" attribute'),
  810. '#default_value' => isset($attributes['title']) ? $attributes['title'] : '',
  811. '#field_prefix' => 'title = "',
  812. '#field_suffix' => '"',
  813. );
  814. }
  815. if (!empty($settings['attributes']['configurable_class']) && $settings['attributes']['configurable_class'] == 1) {
  816. $element['attributes']['class'] = array(
  817. '#type' => 'textfield',
  818. '#title' => t('Custom link class'),
  819. '#default_value' => isset($attributes['class']) ? $attributes['class'] : '',
  820. '#field_prefix' => 'class = "',
  821. '#field_suffix' => '"',
  822. );
  823. }
  824. // If the title field is available or there are field accepts multiple values
  825. // then allow the individual field items display the required asterisk if needed.
  826. if (isset($element['title']) || isset($element['_weight'])) {
  827. // To prevent an extra required indicator, disable the required flag on the
  828. // base element since all the sub-fields are already required if desired.
  829. $element['#required'] = FALSE;
  830. }
  831. return $element;
  832. }
  833. /**
  834. * Implements hook_field_formatter_info().
  835. */
  836. function link_field_formatter_info() {
  837. return array(
  838. 'link_default' => array(
  839. 'label' => t('Title, as link (default)'),
  840. 'field types' => array('link_field'),
  841. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  842. ),
  843. 'link_title_plain' => array(
  844. 'label' => t('Title, as plain text'),
  845. 'field types' => array('link_field'),
  846. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  847. ),
  848. 'link_host' => array(
  849. 'label' => t('Host, as plain text'),
  850. 'field types' => array('link_field'),
  851. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  852. ),
  853. 'link_url' => array(
  854. 'label' => t('URL, as link'),
  855. 'field types' => array('link_field'),
  856. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  857. ),
  858. 'link_plain' => array(
  859. 'label' => t('URL, as plain text'),
  860. 'field types' => array('link_field'),
  861. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  862. ),
  863. 'link_absolute' => array(
  864. 'label' => t('URL, absolute'),
  865. 'field types' => array('link_field'),
  866. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  867. ),
  868. 'link_domain' => array(
  869. 'label' => t('Domain, as link'),
  870. 'field types' => array('link_field'),
  871. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  872. 'settings' => array(
  873. 'strip_www' => FALSE,
  874. ),
  875. ),
  876. 'link_short' => array(
  877. 'label' => t('Short, as link with title "Link"'),
  878. 'field types' => array('link_field'),
  879. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  880. ),
  881. 'link_label' => array(
  882. 'label' => t('Label, as link with label as title'),
  883. 'field types' => array('link_field'),
  884. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  885. ),
  886. 'link_separate' => array(
  887. 'label' => t('Separate title and URL'),
  888. 'field types' => array('link_field'),
  889. 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
  890. ),
  891. );
  892. }
  893. /**
  894. * Implements hook_field_formatter_settings_form().
  895. */
  896. function link_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  897. $display = $instance['display'][$view_mode];
  898. $settings = $display['settings'];
  899. $element = array();
  900. if ($display['type'] == 'link_domain') {
  901. $element['strip_www'] = array(
  902. '#title' => t('Strip www. from domain'),
  903. '#type' => 'checkbox',
  904. '#default_value' => $settings['strip_www'],
  905. );
  906. }
  907. return $element;
  908. }
  909. /**
  910. * Implements hook_field_formatter_settings_summary().
  911. */
  912. function link_field_formatter_settings_summary($field, $instance, $view_mode) {
  913. $display = $instance['display'][$view_mode];
  914. $settings = $display['settings'];
  915. if ($display['type'] == 'link_domain') {
  916. if ($display['settings']['strip_www']) {
  917. return t('Strip www. from domain');
  918. }
  919. else {
  920. return t('Leave www. in domain');
  921. }
  922. }
  923. return '';
  924. }
  925. /**
  926. * Implements hook_field_formatter_view().
  927. */
  928. function link_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  929. $elements = array();
  930. foreach ($items as $delta => $item) {
  931. $elements[$delta] = array(
  932. '#theme' => 'link_formatter_' . $display['type'],
  933. '#element' => $item,
  934. '#field' => $instance,
  935. '#display' => $display,
  936. );
  937. }
  938. return $elements;
  939. }
  940. /**
  941. * Formats a link.
  942. */
  943. function theme_link_formatter_link_default($vars) {
  944. $link_options = $vars['element'];
  945. unset($link_options['title']);
  946. unset($link_options['url']);
  947. if (isset($link_options['attributes']['class'])) {
  948. $link_options['attributes']['class'] = array($link_options['attributes']['class']);
  949. }
  950. // Display a normal link if both title and URL are available.
  951. if (!empty($vars['element']['title']) && !empty($vars['element']['url'])) {
  952. return l($vars['element']['title'], $vars['element']['url'], $link_options);
  953. }
  954. // If only a title, display the title.
  955. elseif (!empty($vars['element']['title'])) {
  956. return $link_options['html'] ? $vars['element']['title'] : check_plain($vars['element']['title']);
  957. }
  958. elseif (!empty($vars['element']['url'])) {
  959. return l($vars['element']['title'], $vars['element']['url'], $link_options);
  960. }
  961. }
  962. /**
  963. * Formats a link (or its title) as plain text.
  964. */
  965. function theme_link_formatter_link_plain($vars) {
  966. $link_options = $vars['element'];
  967. if (isset($link_options['title'])) {
  968. unset($link_options['title']);
  969. }
  970. else {
  971. $vars['element']['title'] = '';
  972. }
  973. unset($link_options['url']);
  974. return empty($vars['element']['url']) ? check_plain($vars['element']['title']) : url($vars['element']['url'], $link_options);
  975. }
  976. /**
  977. * Theme function for 'host' text field formatter.
  978. */
  979. function theme_link_formatter_link_host($vars) {
  980. $host = @parse_url($vars['element']['url']);
  981. return isset($host['host']) ? check_plain($host['host']) : '';
  982. }
  983. /**
  984. * Formats a link as an absolute URL.
  985. */
  986. function theme_link_formatter_link_absolute($vars) {
  987. $absolute = array('absolute' => TRUE);
  988. return empty($vars['element']['url']) ? '' : url($vars['element']['url'], $absolute + $vars['element']);
  989. }
  990. /**
  991. * Formats a link using the URL's domain for it's link text.
  992. */
  993. function theme_link_formatter_link_domain($vars) {
  994. $link_options = $vars['element'];
  995. unset($link_options['title']);
  996. unset($link_options['url']);
  997. $domain = parse_url($vars['element']['display_url'], PHP_URL_HOST);
  998. if (!empty($vars['display']['settings']['strip_www'])) {
  999. $domain = str_replace('www.', '', $domain);
  1000. }
  1001. return $vars['element']['url'] ? l($domain, $vars['element']['url'], $link_options) : '';
  1002. }
  1003. /**
  1004. * Formats a link's title as plain text.
  1005. */
  1006. function theme_link_formatter_link_title_plain($vars) {
  1007. return empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
  1008. }
  1009. /**
  1010. * Formats a link using an alternate display URL for its link text.
  1011. */
  1012. function theme_link_formatter_link_url($vars) {
  1013. $link_options = $vars['element'];
  1014. unset($link_options['title']);
  1015. unset($link_options['url']);
  1016. return $vars['element']['url'] ? l($vars['element']['display_url'], $vars['element']['url'], $link_options) : '';
  1017. }
  1018. /**
  1019. * Formats a link using "Link" as the link text.
  1020. */
  1021. function theme_link_formatter_link_short($vars) {
  1022. $link_options = $vars['element'];
  1023. unset($link_options['title']);
  1024. unset($link_options['url']);
  1025. return $vars['element']['url'] ? l(t('Link'), $vars['element']['url'], $link_options) : '';
  1026. }
  1027. /**
  1028. * Formats a link using the field's label as link text.
  1029. */
  1030. function theme_link_formatter_link_label($vars) {
  1031. $link_options = $vars['element'];
  1032. unset($link_options['title']);
  1033. unset($link_options['url']);
  1034. return $vars['element']['url'] ? l($vars['field']['label'], $vars['element']['url'], $link_options) : '';
  1035. }
  1036. /**
  1037. * Formats a link as separate title and URL elements.
  1038. */
  1039. function theme_link_formatter_link_separate($vars) {
  1040. $class = empty($vars['element']['attributes']['class']) ? '' : ' ' . $vars['element']['attributes']['class'];
  1041. unset($vars['element']['attributes']['class']);
  1042. $link_options = $vars['element'];
  1043. unset($link_options['title']);
  1044. unset($link_options['url']);
  1045. $title = empty($vars['element']['title']) ? '' : check_plain($vars['element']['title']);
  1046. // @TODO static html markup looks not very elegant
  1047. // needs smarter output solution and an optional title/url seperator
  1048. $url_parts = _link_parse_url($vars['element']['url']);
  1049. $output = '';
  1050. $output .= '<div class="link-item ' . $class . '">';
  1051. if (!empty($title)) {
  1052. $output .= '<div class="link-title">' . $title . '</div>';
  1053. }
  1054. $output .= '<div class="link-url">' . l($url_parts['url'], $vars['element']['url'], $link_options) . '</div>';
  1055. $output .= '</div>';
  1056. return $output;
  1057. }
  1058. /**
  1059. * Implements hook_token_list().
  1060. *
  1061. * @TODO: hook_token_list no longer exists - this should change to hook_token_info().
  1062. */
  1063. function link_token_list($type = 'all') {
  1064. if ($type === 'field' || $type === 'all') {
  1065. $tokens = array();
  1066. $tokens['link']['url'] = t("Link URL");
  1067. $tokens['link']['title'] = t("Link title");
  1068. $tokens['link']['view'] = t("Formatted html link");
  1069. return $tokens;
  1070. }
  1071. }
  1072. /**
  1073. * Implements hook_token_values().
  1074. *
  1075. * @TODO: hook_token_values no longer exists - this should change to hook_tokens().
  1076. */
  1077. function link_token_values($type, $object = NULL) {
  1078. if ($type === 'field') {
  1079. $item = $object[0];
  1080. $tokens['url'] = $item['url'];
  1081. $tokens['title'] = $item['title'];
  1082. $tokens['view'] = isset($item['view']) ? $item['view'] : '';
  1083. return $tokens;
  1084. }
  1085. }
  1086. /**
  1087. * Implements hook_views_api().
  1088. */
  1089. function link_views_api() {
  1090. return array(
  1091. 'api' => 2,
  1092. 'path' => drupal_get_path('module', 'link') . '/views',
  1093. );
  1094. }
  1095. /**
  1096. * Forms a valid URL if possible from an entered address.
  1097. *
  1098. * Trims whitespace and automatically adds an http:// to addresses without a
  1099. * protocol specified
  1100. *
  1101. * @param string $url
  1102. * The url entered by the user.
  1103. * @param string $protocol
  1104. * The protocol to be prepended to the url if one is not specified
  1105. */
  1106. function link_cleanup_url($url, $protocol = 'http') {
  1107. $url = trim($url);
  1108. $type = link_url_type($url);
  1109. if ($type === LINK_EXTERNAL) {
  1110. // Check if there is no protocol specified.
  1111. $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i", $url);
  1112. if (empty($protocol_match)) {
  1113. // But should there be? Add an automatic http:// if it starts with a domain name.
  1114. $LINK_DOMAINS = _link_domains();
  1115. $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(' . $LINK_DOMAINS . '|[a-z]{2}))/i', $url);
  1116. if (!empty($domain_match)) {
  1117. $url = $protocol . "://" . $url;
  1118. }
  1119. }
  1120. }
  1121. return $url;
  1122. }
  1123. /**
  1124. * Validates a URL.
  1125. *
  1126. * @param $text
  1127. * Url to be validated.
  1128. *
  1129. * @param $langcode
  1130. * An optional language code to look up the path in.
  1131. *
  1132. * @return boolean
  1133. * True if a valid link, FALSE otherwise.
  1134. */
  1135. function link_validate_url($text, $langcode = NULL) {
  1136. $text = link_cleanup_url($text);
  1137. $type = link_url_type($text);
  1138. if ($type && ($type == LINK_INTERNAL || $type == LINK_EXTERNAL)) {
  1139. $flag = valid_url($text, TRUE);
  1140. if (!$flag) {
  1141. $normal_path = drupal_get_normal_path($text, $langcode);
  1142. $parsed_link = parse_url($normal_path, PHP_URL_PATH);
  1143. if ($normal_path != $parsed_link) {
  1144. $normal_path = $parsed_link;
  1145. }
  1146. $flag = drupal_valid_path($normal_path);
  1147. }
  1148. if (!$flag) {
  1149. $flag = file_exists($normal_path);
  1150. }
  1151. if (!$flag) {
  1152. $uri = file_build_uri($normal_path);
  1153. $flag = file_exists($uri);
  1154. }
  1155. }
  1156. else {
  1157. $flag = (bool) $type;
  1158. }
  1159. return $flag;
  1160. }
  1161. /**
  1162. * Type check a URL.
  1163. *
  1164. * Accepts all URLs following RFC 1738 standard for URL formation and all e-mail
  1165. * addresses following the RFC 2368 standard for mailto address formation.
  1166. *
  1167. * @param string $text
  1168. * Url to be checked.
  1169. *
  1170. * @return mixed
  1171. * Returns boolean FALSE if the URL is not valid. On success, returns one of
  1172. * the LINK_(linktype) constants.
  1173. */
  1174. function link_url_type($text) {
  1175. // @TODO Complete letters.
  1176. $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array(
  1177. "&#x00E6;", // æ
  1178. "&#x00C6;", // Æ
  1179. "&#x00C0;", // À
  1180. "&#x00E0;", // à
  1181. "&#x00C1;", // Á
  1182. "&#x00E1;", // á
  1183. "&#x00C2;", // Â
  1184. "&#x00E2;", // â
  1185. "&#x00E5;", // å
  1186. "&#x00C5;", // Å
  1187. "&#x00E4;", // ä
  1188. "&#x00C4;", // Ä
  1189. "&#x00C7;", // Ç
  1190. "&#x00E7;", // ç
  1191. "&#x00D0;", // Ð
  1192. "&#x00F0;", // ð
  1193. "&#x00C8;", // È
  1194. "&#x00E8;", // è
  1195. "&#x00C9;", // É
  1196. "&#x00E9;", // é
  1197. "&#x00CA;", // Ê
  1198. "&#x00EA;", // ê
  1199. "&#x00CB;", // Ë
  1200. "&#x00EB;", // ë
  1201. "&#x00CE;", // Î
  1202. "&#x00EE;", // î
  1203. "&#x00CF;", // Ï
  1204. "&#x00EF;", // ï
  1205. "&#x00F8;", // ø
  1206. "&#x00D8;", // Ø
  1207. "&#x00F6;", // ö
  1208. "&#x00D6;", // Ö
  1209. "&#x00D4;", // Ô
  1210. "&#x00F4;", // ô
  1211. "&#x00D5;", // Õ
  1212. "&#x00F5;", // õ
  1213. "&#x0152;", // Œ
  1214. "&#x0153;", // œ
  1215. "&#x00FC;", // ü
  1216. "&#x00DC;", // Ü
  1217. "&#x00D9;", // Ù
  1218. "&#x00F9;", // ù
  1219. "&#x00DB;", // Û
  1220. "&#x00FB;", // û
  1221. "&#x0178;", // Ÿ
  1222. "&#x00FF;", // ÿ
  1223. "&#x00D1;", // Ñ
  1224. "&#x00F1;", // ñ
  1225. "&#x00FE;", // þ
  1226. "&#x00DE;", // Þ
  1227. "&#x00FD;", // ý
  1228. "&#x00DD;", // Ý
  1229. "&#x00BF;", // ¿
  1230. )), ENT_QUOTES, 'UTF-8');
  1231. $LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
  1232. "&#x00DF;", // ß
  1233. )), ENT_QUOTES, 'UTF-8');
  1234. $allowed_protocols = variable_get('filter_allowed_protocols', array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal'));
  1235. $LINK_DOMAINS = _link_domains();
  1236. // Starting a parenthesis group with (?: means that it is grouped, but is not captured.
  1237. $protocol = '((?:' . implode("|", $allowed_protocols) . '):\/\/)';
  1238. $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w" . $LINK_ICHARS . "\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
  1239. $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*(' . $LINK_DOMAINS . '|[a-z]{2}))?)';
  1240. $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
  1241. $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
  1242. $port = '(?::([0-9]{1,5}))';
  1243. // Pattern specific to external links.
  1244. $external_pattern = '/^' . $protocol . '?' . $authentication . '?(' . $domain . '|' . $ipv4 . '|' . $ipv6 . ' |localhost)' . $port . '?';
  1245. // Pattern specific to internal links.
  1246. $internal_pattern = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\] ]+)";
  1247. $internal_pattern_file = "/^(?:[a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \(\)][a-z0-9" . $LINK_ICHARS . "_\-+\[\]\. \/\(\)]+)$/i";
  1248. $directories = "(?:\/[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'#!():;*@\[\]]*)*";
  1249. // Yes, four backslashes == a single backslash.
  1250. $query = "(?:\/?\?([?a-z0-9" . $LINK_ICHARS . "+_|\-\.~\/\\\\%=&,$'!():;*@\[\]{} ]*))";
  1251. $anchor = "(?:#[a-z0-9" . $LINK_ICHARS . "_\-\.~+%=&,$'():;*@\[\]\/\?]*)";
  1252. // The rest of the path for a standard URL.
  1253. $end = $directories . '?' . $query . '?' . $anchor . '?' . '$/i';
  1254. $message_id = '[^@].*@' . $domain;
  1255. $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
  1256. $news_pattern = '/^news:(' . $newsgroup_name . '|' . $message_id . ')$/i';
  1257. $user = '[a-zA-Z0-9' . $LINK_ICHARS . '_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
  1258. $email_pattern = '/^mailto:' . $user . '@' . '(?:' . $domain . '|' . $ipv4 . '|' . $ipv6 . '|localhost)' . $query . '?$/';
  1259. if (strpos($text, '<front>') === 0) {
  1260. return LINK_FRONT;
  1261. }
  1262. if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
  1263. return LINK_EMAIL;
  1264. }
  1265. if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
  1266. return LINK_NEWS;
  1267. }
  1268. if (preg_match($internal_pattern . $end, $text)) {
  1269. return LINK_INTERNAL;
  1270. }
  1271. if (preg_match($external_pattern . $end, $text)) {
  1272. return LINK_EXTERNAL;
  1273. }
  1274. if (preg_match($internal_pattern_file, $text)) {
  1275. return LINK_INTERNAL;
  1276. }
  1277. return FALSE;
  1278. }
  1279. /**
  1280. * Returns the list of allowed domains.
  1281. *
  1282. * If the variable link_allowed_domains is set, restrict allowed domains to the
  1283. * strings in that array. If the variable link_allowed_domains is not set, allow
  1284. * all domains between 2 and 63 characters in length.
  1285. * See https://tools.ietf.org/html/rfc1034.
  1286. */
  1287. function _link_domains() {
  1288. $link_allowed_domains = variable_get('link_allowed_domains', array());
  1289. return empty($link_allowed_domains) ? '[a-z][a-z0-9-]{1,62}' : implode('|', $link_allowed_domains);
  1290. }
  1291. /**
  1292. * Implements hook_migrate_field_alter().
  1293. */
  1294. function link_content_migrate_field_alter(&$field_value, $instance_value) {
  1295. if ($field_value['type'] == 'link') {
  1296. // Adjust the field type.
  1297. $field_value['type'] = 'link_field';
  1298. // Remove settings that are now on the instance.
  1299. foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
  1300. unset($field_value['settings'][$setting]);
  1301. }
  1302. }
  1303. }
  1304. /**
  1305. * Implements hook_migrate_instance_alter().
  1306. *
  1307. * Widget type also changed to link_field.
  1308. */
  1309. function link_content_migrate_instance_alter(&$instance_value, $field_value) {
  1310. if ($field_value['type'] == 'link') {
  1311. // Grab settings that were previously on the field.
  1312. foreach (array('attributes', 'display', 'url', 'title', 'title_value', 'enable_tokens', 'validate_url') as $setting) {
  1313. if (isset($field_value['settings'][$setting])) {
  1314. $instance_value['settings'][$setting] = $field_value['settings'][$setting];
  1315. }
  1316. }
  1317. // Adjust widget type.
  1318. if ($instance_value['widget']['type'] == 'link') {
  1319. $instance_value['widget']['type'] = 'link_field';
  1320. }
  1321. // Adjust formatter types.
  1322. foreach ($instance_value['display'] as $context => $settings) {
  1323. if (in_array($settings['type'], array('default', 'title_plain', 'url', 'plain', 'short', 'label', 'separate'))) {
  1324. $instance_value['display'][$context]['type'] = 'link_' . $settings['type'];
  1325. }
  1326. }
  1327. }
  1328. }
  1329. /**
  1330. * Implements hook_field_settings_form().
  1331. */
  1332. function link_field_settings_form() {
  1333. return array();
  1334. }
  1335. /**
  1336. * Additional callback to adapt the property info of link fields.
  1337. *
  1338. * @see entity_metadata_field_entity_property_info()
  1339. */
  1340. function link_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  1341. $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  1342. // Define a data structure so it's possible to deal with both the link title
  1343. // and URL.
  1344. $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  1345. $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  1346. // Auto-create the field item as soon as a property is set.
  1347. $property['auto creation'] = 'link_field_item_create';
  1348. $property['property info'] = link_field_item_property_info();
  1349. $property['property info']['url']['required'] = !$instance['settings']['url'];
  1350. $property['property info']['title']['required'] = ($instance['settings']['title'] == 'required');
  1351. if ($instance['settings']['title'] == 'none') {
  1352. unset($property['property info']['title']);
  1353. }
  1354. unset($property['query callback']);
  1355. }
  1356. /**
  1357. * Callback for creating a new, empty link field item.
  1358. *
  1359. * @see link_field_property_info_callback()
  1360. */
  1361. function link_field_item_create() {
  1362. return array('title' => NULL, 'url' => NULL);
  1363. }
  1364. /**
  1365. * Defines info for the properties of the link-field item data structure.
  1366. */
  1367. function link_field_item_property_info() {
  1368. $properties['title'] = array(
  1369. 'type' => 'text',
  1370. 'label' => t('The title of the link.'),
  1371. 'setter callback' => 'entity_property_verbatim_set',
  1372. );
  1373. $properties['url'] = array(
  1374. 'type' => 'uri',
  1375. 'label' => t('The URL of the link.'),
  1376. 'setter callback' => 'entity_property_verbatim_set',
  1377. );
  1378. $properties['attributes'] = array(
  1379. 'type' => 'struct',
  1380. 'label' => t('The attributes of the link.'),
  1381. 'setter callback' => 'entity_property_verbatim_set',
  1382. 'getter callback' => 'link_attribute_property_get',
  1383. );
  1384. return $properties;
  1385. }
  1386. /**
  1387. * Entity property info getter callback for link attributes.
  1388. */
  1389. function link_attribute_property_get($data, array $options, $name, $type, $info) {
  1390. return isset($data[$name]) ? array_filter($data[$name]) : array();
  1391. }
  1392. /**
  1393. * Implements hook_field_update_instance().
  1394. */
  1395. function link_field_update_instance($instance, $prior_instance) {
  1396. if (function_exists('i18n_string_update') && $instance['widget']['type'] == 'link_field' && $prior_instance['settings']['title_value'] != $instance['settings']['title_value']) {
  1397. $i18n_string_name = "field:{$instance['field_name']}:{$instance['bundle']}:title_value";
  1398. i18n_string_update($i18n_string_name, $instance['settings']['title_value']);
  1399. }
  1400. }
  1401. /**
  1402. * Implements hook_i18n_string_list_TEXTGROUP_alter().
  1403. */
  1404. function link_i18n_string_list_field_alter(&$strings, $type = NULL, $object = NULL) {
  1405. if ($type != 'field_instance' || !is_array($object) || !isset($object['widget']['type'])) {
  1406. return;
  1407. }
  1408. if ($object['widget']['type'] == 'link_field' && isset($object['settings']['title_value'])) {
  1409. $strings['field'][$object['field_name']][$object['bundle']]['title_value']['string'] = $object['settings']['title_value'];
  1410. }
  1411. }