linkit.module 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  1. <?php
  2. /**
  3. * @file
  4. * Main file for Linkit module.
  5. */
  6. /**
  7. * Define that urls should be constructed as it is (raw).
  8. */
  9. define('LINKIT_URL_METHOD_RAW', 1);
  10. /**
  11. * Define that urls should be constructed as it is (raw) with a prepending
  12. * slash.
  13. */
  14. define('LINKIT_URL_METHOD_RAW_SLASH', 2);
  15. /**
  16. * Define that paths should be constructed as an alias.
  17. */
  18. define('LINKIT_URL_METHOD_ALIAS', 3);
  19. /**
  20. * Define which query string key Better autocomplete will just.
  21. */
  22. define('LINKIT_BAC_QUERY_KEY', 's');
  23. /**
  24. * Define the minimum number of characters to perform server polling.
  25. */
  26. define('LINKIT_CHAR_LIMIT', 3);
  27. /**
  28. * Define the time from last key press to poll the server.
  29. */
  30. define('LINKIT_WAIT', 350);
  31. /**
  32. * Define the client side timeout for a request to the server.
  33. */
  34. define('LINKIT_REMOTE_TIMEOUT', 10000);
  35. /**
  36. * Define the profile type editor.
  37. */
  38. define('LINKIT_PROFILE_TYPE_EDITOR', 1);
  39. /**
  40. * Define the profile type field.
  41. */
  42. define('LINKIT_PROFILE_TYPE_FIELD', 2);
  43. // Include the file with the field functions.
  44. require_once dirname(__FILE__) . '/linkit.field.inc';
  45. /**
  46. * Implements hook_permission().
  47. */
  48. function linkit_permission() {
  49. return array(
  50. 'administer linkit' => array(
  51. 'title' => t('Administer Linkit'),
  52. 'description' => t('Perform administration tasks for Linkit.'),
  53. ),
  54. );
  55. }
  56. /**
  57. * Implements hook_ctools_plugin_directory().
  58. */
  59. function linkit_ctools_plugin_directory($module, $plugin) {
  60. if ($module == 'ctools' && !empty($plugin)) {
  61. return "plugins/" . $plugin;
  62. }
  63. if ($module == 'linkit' && !empty($plugin)) {
  64. return "plugins/" . $plugin;
  65. }
  66. }
  67. /**
  68. * Implements hook_ctools_plugin_TYPE() to inform the plugin system that Linkit
  69. * owns Linkit plugin types.
  70. */
  71. function linkit_ctools_plugin_type() {
  72. $plugins['linkit_search'] = array(
  73. 'child plugins' => TRUE,
  74. 'classes' => array('handler'),
  75. );
  76. $plugins['linkit_insert'] = array(
  77. 'process' => array(
  78. 'function' => 'linkit_insert_plugin_process',
  79. ),
  80. );
  81. $plugins['linkit_attribute'] = array();
  82. return $plugins;
  83. }
  84. /**
  85. * Provide defaults for insert plugins.
  86. */
  87. function linkit_insert_plugin_process(&$plugin, $info) {
  88. // The plugin javascript can be just a filename or a full path,
  89. // in case it's just a filename, add the plugin path.
  90. if (!file_exists($plugin['javascript'])) {
  91. $plugin['javascript'] = $plugin['path'] . '/' . $plugin['javascript'];
  92. }
  93. }
  94. /**
  95. * Load a single Linkit profile.
  96. *
  97. * @param $name
  98. * A string with the name of the profile to load.
  99. *
  100. * @return
  101. * A LinkitProfile object or FALSE if no profile is found.
  102. */
  103. function linkit_profile_load($name) {
  104. ctools_include('export');
  105. $result = ctools_export_load_object('linkit_profiles', 'names', array($name));
  106. if (isset($result[$name])) {
  107. return $result[$name];
  108. }
  109. return FALSE;
  110. }
  111. /**
  112. * Load all Linkit profiles.
  113. *
  114. * @return
  115. * An array with LinkitProfile objects.
  116. */
  117. function linkit_profile_load_all() {
  118. ctools_include('export');
  119. $profiles = ctools_export_load_object('linkit_profiles');
  120. return $profiles;
  121. }
  122. /**
  123. * Load all Linkit profiles that is for fields.
  124. *
  125. * @return
  126. * An array with LinkitProfile objects.
  127. */
  128. function linkit_profile_field_load_all() {
  129. $profiles = linkit_profile_load_all();
  130. foreach ($profiles as &$profile) {
  131. if ($profile->profile_type != LINKIT_PROFILE_TYPE_FIELD) {
  132. $profile = FALSE;
  133. }
  134. }
  135. return array_filter($profiles);
  136. }
  137. /**
  138. * Load all Linkit profiles that is for editors.
  139. *
  140. * @return
  141. * An array with LinkitProfile objects.
  142. */
  143. function linkit_profile_editor_load_all() {
  144. $profiles = linkit_profile_load_all();
  145. foreach ($profiles as &$profile) {
  146. if ($profile->profile_type != LINKIT_PROFILE_TYPE_EDITOR) {
  147. $profile = FALSE;
  148. }
  149. }
  150. return array_filter($profiles);
  151. }
  152. /**
  153. * Temporary saves the active profile. Active means that the user is working
  154. * with the profile in the dialog.
  155. *
  156. * @param string LinkitProfile $profile
  157. * A LinkitProfile object.
  158. */
  159. function linkit_set_active_profile(LinkitProfile $profile) {
  160. $active_profile = &drupal_static('linkit_active_profile');
  161. $active_profile = $profile;
  162. }
  163. /**
  164. * Get the currently active profile.
  165. *
  166. * @return
  167. * A LinkitProfile object if there is one set.
  168. *
  169. * @see linkit_set_active_profile()
  170. */
  171. function linkit_get_active_profile() {
  172. return drupal_static('linkit_active_profile');
  173. }
  174. /**
  175. * Fetch metadata for all Linkit search plugins.
  176. *
  177. * @return
  178. * An array of arrays with information about all available Linkit search
  179. * plugins.
  180. */
  181. function linkit_search_plugin_load_all() {
  182. ctools_include('plugins');
  183. $plugins = ctools_get_plugins('linkit', 'linkit_search');
  184. // If you alter the plugin handler, be sure the new handler is registerd or
  185. // you include it in some other way.
  186. drupal_alter('linkit_search_plugins', $plugins);
  187. return $plugins;
  188. }
  189. /**
  190. * Function used by uasort to sort plugins by weight.
  191. */
  192. function linkit_sort_plugins_by_weight($a, $b) {
  193. return $a["weight"] >= $b["weight"];
  194. }
  195. /**
  196. * Fetch metadata for one Linkit search plugin by the given name.
  197. *
  198. * @param $plugin_name
  199. * A string with the name of the plugin to load.
  200. *
  201. * @return
  202. * An array with information about the search plugin.
  203. */
  204. function linkit_search_plugin_load($plugin_name) {
  205. ctools_include('plugins');
  206. $plugin = ctools_get_plugins('linkit', 'linkit_search', $plugin_name);
  207. // If you alter the plugin handler, be sure the new handler is registerd or
  208. // you include it in some other way.
  209. drupal_alter('linkit_search_plugin', $plugin);
  210. return $plugin;
  211. }
  212. /**
  213. * Fetch metadata for all Linkit insert plugins.
  214. *
  215. * @return
  216. * An array of arrays with information about all available insert plugins.
  217. */
  218. function linkit_insert_plugin_load_all() {
  219. ctools_include('plugins');
  220. // Load all insert plugins.
  221. $plugins = ctools_get_plugins('linkit', 'linkit_insert');
  222. return $plugins;
  223. }
  224. /**
  225. * Fetch metadata for one Linkit insert plugin by the given name.
  226. *
  227. * @param $plugin_name
  228. * A string with the name of the plugin to load.
  229. *
  230. * @return
  231. * An array with information about the insert plugin.
  232. */
  233. function linkit_insert_plugin_load($plugin_name) {
  234. ctools_include('plugins');
  235. // Load all insert plugins.
  236. $plugins = ctools_get_plugins('linkit', 'linkit_insert', $plugin_name);
  237. return $plugins;
  238. }
  239. /**
  240. * Fetch metadata for all Linkit attribute plugins.
  241. *
  242. * @return
  243. * An array of arrays with information about all available attribute plugins.
  244. */
  245. function linkit_attribute_plugin_load_all() {
  246. ctools_include('plugins');
  247. // Load all attribute plugins.
  248. $attributes = ctools_get_plugins('linkit', 'linkit_attribute');
  249. return $attributes;
  250. }
  251. function linkit_attribute_plugin_load($plugin_name) {
  252. ctools_include('plugins');
  253. // Load all insert plugins.
  254. $plugins = ctools_get_plugins('linkit', 'linkit_attribute', $plugin_name);
  255. return $plugins;
  256. }
  257. /**
  258. * Implements hook_theme().
  259. */
  260. function linkit_theme($existing, $type, $theme, $path) {
  261. return array(
  262. 'linkit_plugin_form_table' => array(
  263. 'render element' => 'form',
  264. 'file' => 'includes/theme.inc',
  265. ),
  266. );
  267. }
  268. /**
  269. * Implements hook_menu_alter().
  270. */
  271. function linkit_menu_alter(&$items) {
  272. // Override the default titles that ctools export_ui sets.
  273. // This way there is less code compared to define this in the plugin array.
  274. $items['admin/config/content/linkit/add']['title'] = 'Add new profile';
  275. $items['admin/config/content/linkit/import']['title'] = 'Import profiles';
  276. // Make tabs instead of action links.
  277. $items['admin/config/content/linkit/add']['type'] = MENU_LOCAL_TASK;
  278. $items['admin/config/content/linkit/import']['type'] = MENU_LOCAL_TASK;
  279. }
  280. /**
  281. * Implements hook_module_implements_alter().
  282. *
  283. * @TODO: Document why we are doing this.
  284. * @see linkit_element_info_alter()
  285. */
  286. function linkit_module_implements_alter(&$implementations, $hook) {
  287. if ($hook == 'element_info_alter') {
  288. $group = $implementations['linkit'];
  289. unset($implementations['linkit']);
  290. $implementations['linkit'] = $group;
  291. }
  292. }
  293. /**
  294. * Implements hook_element_info_alter().
  295. */
  296. function linkit_element_info_alter(&$types) {
  297. // Append a process function for the field integration.
  298. foreach (linkit_get_allowed_field_elements() as $element) {
  299. if (isset($types[$element])) {
  300. $types[$element]['#process'][] = 'linkit_process_field_element';
  301. }
  302. }
  303. // Used when using ckeditor module.
  304. if (isset($types['text_format']['#pre_render']) && is_array($types['text_format']['#pre_render'])) {
  305. if (in_array('ckeditor_pre_render_text_format', $types['text_format']['#pre_render'])) {
  306. $types['text_format']['#pre_render'][] = 'linkit_pre_render_editor_element';
  307. // Add the linkit library for the editor.
  308. drupal_add_library('linkit', 'ckeditor');
  309. }
  310. }
  311. // Used when using wysiwyg module.
  312. if (isset($types['text_format']['#pre_render']) && is_array($types['text_format']['#pre_render'])) {
  313. if (in_array('wysiwyg_pre_render_text_format', $types['text_format']['#pre_render'])) {
  314. $types['text_format']['#process'][] = 'linkit_pre_render_editor_element';
  315. }
  316. }
  317. }
  318. /**
  319. * Implements hook_library().
  320. */
  321. function linkit_library() {
  322. $path = drupal_get_path('module', 'linkit');
  323. $common = array(
  324. 'website' => 'http://drupal.org/project/linkit',
  325. 'version' => '7.3',
  326. );
  327. // Linkit base
  328. $libraries['base'] = array(
  329. 'title' => 'Linkit base',
  330. 'js' => array(
  331. $path . '/js/linkit.js' => array('group' => JS_DEFAULT, 'weight' => -1),
  332. // Add global settings for Linkit.
  333. array(
  334. 'type' => 'setting',
  335. // ___profile___ is just a placeholder.
  336. 'data' => array(
  337. 'linkit' => array(
  338. 'autocompletePath' => url('linkit/autocomplete/___profile___', array('query' => array(LINKIT_BAC_QUERY_KEY => ''), 'absolute' => TRUE)),
  339. 'dashboardPath' => url('linkit/dashboard/'),
  340. 'currentInstance' => new stdClass(),
  341. ),
  342. ),
  343. ),
  344. ),
  345. 'dependencies' => array(
  346. array('system', 'ui.dialog'),
  347. array('system', 'drupal.ajax'),
  348. ),
  349. );
  350. // Linkit field ui script.
  351. $libraries['field'] = array(
  352. 'title' => 'Linkit Field UI',
  353. 'js' => array(
  354. $path . '/js/linkit.field.js' => array('group' => JS_DEFAULT),
  355. ),
  356. 'dependencies' => array(
  357. array('linkit', 'base'),
  358. ),
  359. );
  360. // Linkit ckeditor dialog script.
  361. $libraries['ckeditor'] = array(
  362. 'title' => 'Linkit CKeditor',
  363. 'js' => array(
  364. $path . '/editors/ckeditor/linkitDialog.js' => array('group' => JS_DEFAULT),
  365. ),
  366. 'dependencies' => array(
  367. array('linkit', 'base'),
  368. ),
  369. );
  370. // Linkit tinymce dialog script.
  371. $libraries['tinymce'] = array(
  372. 'title' => 'Linkit TinyMCE',
  373. 'js' => array(
  374. $path . '/editors/tinymce/linkitDialog.js' => array('group' => JS_DEFAULT),
  375. ),
  376. 'dependencies' => array(
  377. array('linkit', 'base'),
  378. ),
  379. );
  380. foreach ($libraries as &$library) {
  381. $library += $common;
  382. }
  383. // Linkit BAC
  384. $libraries['bac'] = array(
  385. 'website' => 'https://github.com/betamos/Better-Autocomplete',
  386. 'version' => '1.0',
  387. 'title' => 'Better autocomplete',
  388. 'js' => array(
  389. $path . '/better-autocomplete/jquery.better-autocomplete.js' => array('group' => JS_LIBRARY),
  390. ),
  391. 'css' => array(
  392. $path . '/better-autocomplete/better-autocomplete.css' => array(
  393. 'group' => CSS_DEFAULT,
  394. 'preprocess' => FALSE,
  395. ),
  396. ),
  397. );
  398. return $libraries;
  399. }
  400. /**
  401. * Implements hook_menu().
  402. */
  403. function linkit_menu() {
  404. $items = array();
  405. // This is the Linkit dashboard menu callback.
  406. $items['linkit/dashboard/%linkit_profile'] = array(
  407. 'title' => 'Linkit',
  408. 'description' => 'Dashboard',
  409. 'delivery callback' => 'ajax_deliver',
  410. 'page callback' => 'linkit_dashboard_page',
  411. 'page arguments' => array(2),
  412. 'access callback' => TRUE,
  413. 'theme callback' => 'ajax_base_page_theme',
  414. 'type' => MENU_CALLBACK,
  415. 'file path' => 'includes',
  416. 'file' => 'form.inc',
  417. );
  418. // The autocomplete callback, the search_string is found in the $_GET array
  419. // so dont pass that to the page callback.
  420. $items['linkit/autocomplete/%linkit_profile'] = array(
  421. 'title' => 'Linkit autocomplete response function',
  422. 'page callback' => 'linkit_autocomplete',
  423. 'page arguments' => array(2),
  424. 'access callback' => TRUE,
  425. 'type' => MENU_CALLBACK,
  426. );
  427. return $items;
  428. };
  429. /**
  430. * Creates the dialog dashboard.
  431. *
  432. * We don't call drupal_get_form() in the menu callback as we don't want to
  433. * return the rendered form, we just want to print it as it is.
  434. *
  435. * @param $profile
  436. * A LinkitProfile object.
  437. */
  438. function linkit_dashboard_page(LinkitProfile $profile) {
  439. // Set the active Linkit profile.
  440. linkit_set_active_profile($profile);
  441. // The dashboard isn't really a form.
  442. // See comment in linkit_dashboard_form() for more info.
  443. $form = drupal_get_form('linkit_dashboard_form');
  444. //$change_profile = drupal_get_form('linkit_dashboad_profile_change_form');
  445. return array(
  446. '#type' => 'ajax',
  447. '#commands' => array(
  448. ajax_command_html('#linkit-modal', drupal_render($form)),
  449. ajax_command_prepend('#linkit-modal', theme('status_messages')),
  450. ),
  451. );
  452. }
  453. /**
  454. * Ajax Form callback;
  455. *
  456. * Returns the Linkit "form" when changing profile.
  457. */
  458. function linkit_change_profile($form, $form_state) {
  459. return $form['linkit_container'];
  460. }
  461. /**
  462. * Create the dashboard page.
  463. */
  464. function linkit_dashboard_form($form, &$form_state) {
  465. // The use of a form here is insignificant as we are not submitting any data.
  466. // A form here just cause trouble because it can be submitted and will do new
  467. // request to the action of the form.
  468. // In normal cases this isn't really a problem as the javascript is preventing
  469. // the form to be submitted, but in case of a javascript error, this event
  470. // can't be prevented.
  471. // We still have to use the FAPI to build this "form", as we want proper
  472. // classes and ids on the elements and take advantage of all the good parts of
  473. // the form generation.
  474. // So, just make sure the form tags dont get rendered.
  475. //$form['#theme_wrappers'] = array();
  476. // Get the active Linkit profile.
  477. if (!empty($form_state['values']['profile'])) {
  478. linkit_set_active_profile(linkit_profile_load($form_state['values']['profile']));
  479. }
  480. $active_profile = linkit_get_active_profile();
  481. $profiles = linkit_profile_editor_load_all();
  482. $_profiles = array();
  483. foreach ($profiles as $profile) {
  484. $_profiles[$profile->name] = check_plain($profile->admin_title);
  485. }
  486. // Attach css to the form.
  487. $form['#attached']['css'][drupal_get_path('module', 'linkit') . '/css/linkit.css'] = array(
  488. 'preprocess' => FALSE,
  489. );
  490. // Attach js to the form.
  491. $form['#attached']['js'] = array(
  492. drupal_get_path('module', 'linkit') . '/js/linkit.dashboard.js',
  493. );
  494. if ($active_profile->profile_type == LINKIT_PROFILE_TYPE_EDITOR) {
  495. $form['profile'] = array(
  496. '#type' => 'radios',
  497. '#title' => t('Select profile to use'),
  498. '#default_value' => $active_profile->name,
  499. '#weight' => -100,
  500. '#options' => $_profiles,
  501. '#ajax' => array(
  502. 'callback' => 'linkit_change_profile',
  503. 'wrapper' => 'linkit-dashbaord-ajax-wrapper',
  504. 'method' => 'replaceWith',
  505. 'effect' => 'fade',
  506. 'event' => 'click',
  507. ),
  508. '#prefix' => '<div id="linkit-profile-changer">',
  509. '#suffix' => '</div>',
  510. );
  511. foreach ($profiles as $profile) {
  512. $form['profile'] += array(
  513. $profile->name => array(
  514. '#description' => check_markup($profile->admin_description),
  515. )
  516. );
  517. }
  518. }
  519. $form['linkit_container'] = array(
  520. '#type' => 'container',
  521. '#weight' => 100,
  522. '#prefix' => '<div id="linkit-dashbaord-ajax-wrapper">',
  523. '#suffix' => '</div>',
  524. '#attached' => array(
  525. 'js' => array(
  526. array(
  527. 'data' => array(
  528. 'linkit' => array(
  529. 'currentInstance' => array(
  530. 'profile' => $active_profile->name,
  531. 'autocomplete' => array_filter($active_profile->data['autocomplete']),
  532. ),
  533. ),
  534. ),
  535. 'type' => 'setting',
  536. )
  537. ),
  538. ),
  539. );
  540. $form['linkit_container']['linkit_search'] = array(
  541. '#type' => 'textfield',
  542. '#title' => t('Search for content.'),
  543. '#description' => t('Start typing to find content or paste a URL.'),
  544. '#maxlength' => 255,
  545. '#size' => 60,
  546. '#default_value' => '',
  547. '#weight' => -10,
  548. '#attributes' => array(
  549. 'class' => array(
  550. 'linkit-search-element',
  551. ),
  552. ),
  553. '#attached' => array(
  554. 'library' => array(
  555. array('linkit', 'bac'),
  556. ),
  557. ),
  558. );
  559. $form['linkit_container']['linkit_path'] = array(
  560. '#type' => 'textfield',
  561. '#title' => t('Link URL'),
  562. '#description' => t('This will be populated by the search, or you can fill it in yourself.'),
  563. '#required' => TRUE,
  564. '#maxlength' => NULL,
  565. '#size' => 60,
  566. '#default_value' => '',
  567. '#weight' => -1,
  568. '#attributes' => array(
  569. 'class' => array(
  570. 'linkit-path-element',
  571. ),
  572. ),
  573. );
  574. // If we have enabled attributes, lets put them inside a fieldset.
  575. if (count($active_profile->getEnabledAttributePlugins())) {
  576. // Create the container fieldset.
  577. $form['linkit_container']['linkit_attributes'] = array(
  578. '#type' => 'fieldset',
  579. '#title' => t('Options'),
  580. '#collapsible' => TRUE,
  581. '#collapsed' => TRUE,
  582. '#weight' => 10,
  583. '#attributes' => array(
  584. 'class' => array(
  585. 'linkit-attributes',
  586. ),
  587. ),
  588. );
  589. // Append the attributes info the fieldset.
  590. foreach ($active_profile->getEnabledAttributePlugins() AS $name => $attribute) {
  591. // Add a class to all items.
  592. $attribute['#attributes']['class'][] = 'linkit-attribute-' . $name;
  593. // Add 'linkit_' prefix to ensure that is unique.
  594. $form['linkit_container']['linkit_attributes']['linkit_' . $name] = $attribute;
  595. }
  596. }
  597. $form['linkit_container']['linkit_insert'] = array(
  598. '#type' => 'button',
  599. '#value' => t('Insert link'),
  600. '#suffix' => '<a id="linkit-cancel" href="#">' . t('Cancel') . '</a>',
  601. '#weight' => 100,
  602. '#attributes' => array(
  603. 'class' => array(
  604. 'linkit-insert',
  605. ),
  606. ),
  607. );
  608. return $form;
  609. }
  610. /**
  611. * Autocomplete callback function.
  612. *
  613. * @param object $profile
  614. * A LinkitProfile object.
  615. */
  616. function linkit_autocomplete(LinkitProfile $profile) {
  617. // Set the active Linkit profile.
  618. linkit_set_active_profile($profile);
  619. // This is not sanitized until it is used within output.
  620. $search_string = $_GET[LINKIT_BAC_QUERY_KEY];
  621. $results = array();
  622. // Special for link to frontpage.
  623. if (strpos($search_string, 'front') !== FALSE) {
  624. $results = array_merge($results, array(
  625. 'title' => t('Frontpage'),
  626. 'description' => 'The frontpage for this site.',
  627. 'path' => url('<front>'),
  628. 'group' => t('System'),
  629. ));
  630. }
  631. // Let the Linkit search plugins do their job.
  632. $results = array_merge($results, linkit_autocomplete_search_plugins($search_string));
  633. // If there is results from the Linkit search plugins, don't care about
  634. // searching for absolute URL's results.
  635. if (!count($results)) {
  636. // Try to parse the string as an URL.
  637. // We will not use the drupal wrapper function drupal_pasre_url() as that
  638. // function should only be used for URL's that have been generated by the
  639. // system, and we can't be sure that this is the case here.
  640. // Check for an e-mail address then return an e-mail result and create a
  641. // mail-to link if appropriate.
  642. if (filter_var($search_string, FILTER_VALIDATE_EMAIL)) {
  643. $results = array(array(
  644. 'title' => t('E-mail @email', array('@email' => $search_string)),
  645. 'path' => 'mailto:'. check_plain($search_string),
  646. 'description' => t('Open your mail client ready to e-mail @email', array('@email' => $search_string)),
  647. 'addClass' => 'status-notice',
  648. ));
  649. }
  650. else {
  651. $parts = parse_url(trim($search_string, '/'));
  652. // This seems to be an absolute URL.
  653. if (isset($parts['scheme']) || isset($parts['host'])) {
  654. $results = array_merge($results, linkit_autocomplete_absolute_url($search_string, $parts));
  655. }
  656. }
  657. }
  658. // If there is still no results, return a "no results" array.
  659. if (!count($results)) {
  660. $results = array(
  661. array(
  662. 'title' => t('No results'),
  663. 'addClass' => 'status-notice',
  664. 'disabled' => TRUE,
  665. )
  666. );
  667. }
  668. print drupal_json_output($results);
  669. drupal_exit();
  670. }
  671. /**
  672. * Perform autocomplete search with the Linkit search plugins.
  673. *
  674. * @param $search_string
  675. * The search string.
  676. *
  677. * @return
  678. * An array with the results objects, or an empty array if no results.
  679. */
  680. function linkit_autocomplete_search_plugins($search_string) {
  681. $matches = array();
  682. $profile = linkit_get_active_profile();
  683. // Get matches from all search plugins.
  684. foreach ($profile->getEnabledsearchPlugins() as $plugin_name => $plugin) {
  685. $matches = array_merge($matches, $plugin->fetchResults($search_string));
  686. }
  687. return $matches;
  688. }
  689. /**
  690. * Retrieve relevant information about a URL. Specifically this function is
  691. * usable for internal (absolute) URL:s, but it also works for external URL:s.
  692. *
  693. * @param $url
  694. * The search string (URL) that should be scanned.
  695. *
  696. * @param $parts
  697. * An array of URL parts from parse_url().
  698. *
  699. * @return
  700. * An associative array containing:
  701. * - url: The same as the argument $url, untouched.
  702. * - target: Either "internal" or "external".
  703. * - requested_path: If internal, the path requested relative to Drupal root.
  704. * The only exception is when frontpage is referred directly, then it will
  705. * be whatever the frontpage is set to.
  706. * - system_path: If internal and the path is valid, the Drupal system path,
  707. * e.g. "node/23".
  708. * - query_fragment: If internal, the query and fragment of the url.
  709. * Typically it is not needed for searching and is just reappended back
  710. * when processing of the path is done. It could e.g. look like
  711. * "?foo=bar#anchor".
  712. */
  713. function linkit_parse_url($url, $parts) {
  714. global $base_url;
  715. // Make a new array, this will hold the components from parse_url() and our
  716. // own "Linkit" components.
  717. $path_info = array();
  718. // Append the original components from parse_url() to our array.
  719. $path_info += $parts;
  720. // Save the whole URL.
  721. $path_info['url'] = $url;
  722. if (!isset($path_info['query'])) {
  723. $path_info['query'] = '';
  724. }
  725. // Convert the query string to an array as Drupal can only handle querys as
  726. // arrays.
  727. // @see http://api.drupal.org/drupal_http_build_query
  728. parse_str($path_info['query'], $path_info['query']);
  729. // The 'q' parameter contains the path of the current page if clean URLs are
  730. // disabled. It overrides the 'path' of the URL when present, even if clean
  731. // URLs are enabled, due to how Apache rewriting rules work.
  732. if (isset($path_info['query']['q'])) {
  733. $path_info['path'] = $path_info['query']['q'];
  734. unset($path_info['query']['q']);
  735. }
  736. // Load all local stream wrappers. The $path_info['scheme'] will be tested
  737. // against this later to ensure it is local.
  738. $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
  739. // Internal URL.
  740. // We can not use the url_is_external() as it treats all absolute links as
  741. // external and treats all stream wrappers as internal.
  742. $local_url = trim($path_info['scheme'] . '://' . $path_info['host'] . base_path(), '/') == $base_url;
  743. $local_stream_wrapper = isset($local_stream_wrappers[$path_info['scheme']]);
  744. //@TODO: maybe file_valid_uri() ?
  745. if ($local_url || $local_stream_wrapper) {
  746. // Set target as internal.
  747. $path_info['target'] = 'internal';
  748. // If this is seems to be a valid local stream wrapper string, force the
  749. // $path_info['path'] to be set to the file_url.
  750. if ($local_stream_wrapper) {
  751. $path_info = array_merge($path_info, parse_url(file_create_url($path_info['url'])));
  752. }
  753. // Trim the path from slashes.
  754. $path_info['path'] = trim($path_info['path'], '/');
  755. // If we have an empty path, and an internal target, we can assume that the
  756. // URL should go the the frontpage.
  757. if (empty($path_info['path'])) {
  758. $path_info['frontpage'] = TRUE;
  759. $path_info['path'] = variable_get('site_frontpage', 'node');
  760. }
  761. // Try converting the path to an internal Drupal path.
  762. $internal_url = drupal_get_normal_path($path_info['path']);
  763. // Add the "real" system path (not the alias) if the current user have
  764. // access to the URL.
  765. $path_info['system_path'] = drupal_valid_path($internal_url) ? $internal_url : FALSE;
  766. $menu_item = menu_get_item($path_info['system_path']);
  767. if ($menu_item) {
  768. $path_info['menu']['path'] = $path_info['system_path'];
  769. $path_info['menu']['description'] = check_plain($menu_item['description']);
  770. $path_info['menu']['title'] = check_plain($menu_item['title']);
  771. }
  772. // If we have a valid stream wrapper URL, find out the internal url.
  773. if ($local_stream_wrapper) {
  774. $path_info['system_path'] = $path_info['path'];
  775. $path_info['menu']['path'] = $path_info['path'];
  776. $path_info['menu']['description'] = 'This funciton is not fully integrated yet.';
  777. $path_info['menu']['title'] = 'This funciton is not fully integrated yet.';
  778. }
  779. }
  780. else {
  781. // Set target as external.
  782. $path_info['target'] = 'external';
  783. }
  784. return $path_info;
  785. }
  786. /**
  787. * Retrieve the result object from an absolute URL. Both internal and external
  788. * paths work.
  789. *
  790. * @param $url
  791. * The search string which seems to be an URL.
  792. *
  793. * @param $parts
  794. * An array of URL parts from parse_url().
  795. *
  796. * @return
  797. * A result object. This is an associative array which consists of:
  798. * - title: The title of the result.
  799. * - description: The description of the result (may contain HTML).
  800. * - path: The target path which will be inserted as the href in the link.
  801. * - addClass: A CSS class that will be added to the DOM result item.
  802. */
  803. function linkit_autocomplete_absolute_url($url, $parts) {
  804. $result = array();
  805. // Retrieve relevant information about the search string and if its a URL.
  806. $path_info = linkit_parse_url($url, $parts);
  807. // Set default options to pass with the url() function if the URL is internal.
  808. $url_options = array();
  809. // Add the URL frament.
  810. $url_options['fragment'] = isset($path_info['fragment']) ? $path_info['fragment'] : '';
  811. // Add the URL query.
  812. $url_options['query'] = isset($path_info['query']) ? $path_info['query'] : '';
  813. // The URL is registerd by Drupal (Internal).
  814. if (isset($path_info['menu']) && $path_info['system_path'] !== FALSE) {
  815. $result = array(
  816. 'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['menu']['path'], $url_options),
  817. 'title' => $path_info['menu']['title'] ? check_plain($path_info['menu']['title']) : check_plain($path_info['menu']['path']),
  818. 'description' => check_plain($path_info['menu']['description']) . ' ' . t('This is an internal path.'),
  819. 'addClass' => 'status-ok',
  820. );
  821. }
  822. // No internal menu result, but the URL seems to be internal. Either we do not
  823. // have access to it or it does not exists.
  824. elseif ($path_info['target'] == 'internal') {
  825. $result = array(
  826. 'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['path'], $url_options),
  827. 'title' => t('Page not found'),
  828. 'description' => t('This page does not exist or you do not have access to it.'),
  829. 'addClass' => 'status-warning',
  830. );
  831. }
  832. // The URL seems to be external.
  833. elseif ($path_info['target'] == 'external') {
  834. $result = array(
  835. 'title' => t('No information available'),
  836. 'description' => t('This is an external URL, but we don\'t know where it leads.'),
  837. 'path' => $path_info['url'],
  838. 'addClass' => 'status-notice',
  839. );
  840. }
  841. // Return the results in an array as BAC will need to have it that way.
  842. return array($result);
  843. }
  844. /**
  845. * Implements hook_image_default_styles().
  846. *
  847. * @return
  848. * An array of image styles, keyed by the style name.
  849. */
  850. function linkit_image_default_styles() {
  851. $styles = array();
  852. $styles['linkit_thumb'] = array(
  853. 'effects' => array(
  854. array(
  855. 'name' => 'image_scale',
  856. 'data' => array(
  857. 'width' => 50,
  858. 'height' => 50,
  859. 'upscale' => 0,
  860. ),
  861. 'weight' => 0,
  862. ),
  863. ),
  864. );
  865. return $styles;
  866. }
  867. /**
  868. * Implements hook_wysiwyg_plugin().
  869. */
  870. function linkit_wysiwyg_plugin($editor, $version) {
  871. $plugin = array();
  872. $plugin['linkit'] = array(
  873. 'path' => drupal_get_path('module', 'linkit') . '/editors/' . $editor,
  874. 'buttons' => array('linkit' => t('Linkit')),
  875. 'url' => 'http://drupal.org/project/linkit',
  876. 'load' => TRUE,
  877. );
  878. // TinyMCE needs to know the filename.
  879. if ($editor == 'tinymce') {
  880. $plugin['linkit']['filename'] = 'editor_plugin.js';
  881. }
  882. // Add the linkit library for the editor.
  883. drupal_add_library('linkit', $editor);
  884. return $plugin;
  885. }
  886. /**
  887. * Implements hook_wysiwyg_editor_settings_alter().
  888. */
  889. function linkit_wysiwyg_editor_settings_alter(&$settings, $context) {
  890. if ($context['profile']->editor == 'ckeditor') {
  891. $button_exists = isset($context['profile']->settings['buttons']['linkit']);
  892. if (!empty($context['profile']->settings['default_toolbar_grouping']) && $button_exists) {
  893. foreach ($settings['toolbar'] as &$group) {
  894. if ($group['name'] == 'links') {
  895. array_unshift($group['items'], 'linkit');
  896. }
  897. if ($group['name'] == 'other') {
  898. if (in_array('linkit', $group['items'])) {
  899. unset($group['items'][array_search('linkit', $group['items'])]);
  900. // Regenerate the keys.
  901. $group['items'] = array_values($group['items']);
  902. }
  903. }
  904. }
  905. }
  906. }
  907. }
  908. /**
  909. * Implements hook_ckeditor_plugin().
  910. */
  911. function linkit_ckeditor_plugin() {
  912. return array(
  913. 'linkit' => array(
  914. // Name of the plugin used to write it
  915. 'name' => 'linkit',
  916. // Description of plugin - it would appear in plugins managment of profile settings
  917. 'desc' => t('Support for Linkit module'),
  918. // The full path to the CKEditor plugin directory, with trailing slash.
  919. 'path' => drupal_get_path('module', 'linkit') . '/editors/ckeditor/',
  920. // Buttons to show up in the toolbar config area.
  921. 'buttons' => array(
  922. 'linkit' => array(
  923. 'label' => 'Linkit',
  924. 'icon' => 'icons/linkit.png',
  925. ),
  926. ),
  927. ),
  928. );
  929. }
  930. /**
  931. * Add Linkit settings to pages where we have an editor element.
  932. */
  933. function linkit_pre_render_editor_element($element) {
  934. static $processed = array();
  935. if (!isset($processed[$element['#id']])) {
  936. // Load the first profile that is assign to this text format.
  937. $profile = linkit_profile_load_by_format($element['#format']);
  938. // If we dont have any profile, lets just return the element.
  939. if (!$profile) {
  940. return $element;
  941. }
  942. // Set the field ID.
  943. if (isset($element['value']['#id'])) {
  944. $field_id = $element['value']['#id'];
  945. }
  946. else {
  947. $field_id = $element['#id'] . '-value';
  948. }
  949. $field_js = array(
  950. 'data' => array(
  951. 'linkit' => array(
  952. 'fields' => array(
  953. $field_id => array(
  954. 'profile' => $profile->name,
  955. ),
  956. ),
  957. ),
  958. ),
  959. 'type' => 'setting',
  960. );
  961. // Configure Linkit to have an IMCE selector.
  962. if (module_invoke('imce', 'access') && isset($profile->data['imce']) && $profile->data['imce'] == 1) {
  963. $field_js['data']['linkit']['IMCEurl'] = url('imce', array('query' => array(
  964. 'app' => 'Linkit|sendto@Drupal.linkit.IMCECallback',
  965. 'absolute' => TRUE,
  966. )));
  967. // We will only serv public files with IMCE.
  968. $field_js['data']['linkit']['publicFilesDirectory'] = variable_get('file_public_path', conf_path() . '/files');
  969. }
  970. // Attach the linkit_base librar
  971. $element['#attached']['library'][] = array('linkit', 'base');
  972. $element['#attached']['js'][] = $field_js;
  973. $processed[$element['#id']] = TRUE;
  974. }
  975. return $element;
  976. }
  977. /**
  978. * Load the first profile assigned to a text format.
  979. *
  980. * @param $format
  981. * A string with the format machine name.
  982. *
  983. * @return
  984. * A LinkitProfile object or FALSE if no profile is found.
  985. */
  986. function linkit_profile_load_by_format($format) {
  987. $profiles = linkit_profile_editor_load_all();
  988. foreach ($profiles as $profile) {
  989. if (is_array($profile->data['text_formats']) && in_array($format, array_filter($profile->data['text_formats']))) {
  990. return $profile;
  991. }
  992. }
  993. return FALSE;
  994. }
  995. /**
  996. * Get profile type.
  997. */
  998. function linkit_get_profile_type($type) {
  999. switch ($type) {
  1000. case LINKIT_PROFILE_TYPE_EDITOR:
  1001. return t('Editor');
  1002. case LINKIT_PROFILE_TYPE_FIELD:
  1003. return t('Field');
  1004. default:
  1005. return t('Can not find the profile type');
  1006. }
  1007. }
  1008. /**
  1009. * Gets the path processed by the inputfilter choice.
  1010. *
  1011. * @param LinkitProfile $profile
  1012. * The Linkit profile to use.
  1013. *
  1014. * @param $uri
  1015. * The uri to act on.
  1016. *
  1017. * @param $options
  1018. * An array of options to the URL function if its used.
  1019. *
  1020. * @return The processed uri.
  1021. */
  1022. function linkit_get_insert_plugin_processed_path(LinkitProfile $profile, $uri, $options = array()) {
  1023. switch ($profile->data['insert_plugin']['url_method']) {
  1024. case LINKIT_URL_METHOD_RAW:
  1025. $path = $uri;
  1026. break;
  1027. case LINKIT_URL_METHOD_RAW_SLASH:
  1028. $options['alias'] = TRUE;
  1029. $path = url($uri, $options);
  1030. break;
  1031. case LINKIT_URL_METHOD_ALIAS:
  1032. $path = url($uri, $options);
  1033. break;
  1034. }
  1035. return $path;
  1036. }