linkit.module 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  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 after_build function for the field integration.
  298. foreach (linkit_get_allowed_field_elements() as $element) {
  299. if (isset($types[$element])) {
  300. $types[$element]['#after_build'][] = 'linkit_field_element_after_build';
  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. $types['text_format']['#attached']['library'][] = array('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-dashboard-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-dashboard-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(
  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_hosts = array($base_url);
  743. // Give other modules a chance to add/remove/alter the local hosts.
  744. drupal_alter('linkit_local_hosts', $local_hosts);
  745. $local_url = in_array(trim($path_info['scheme'] . '://' . $path_info['host'] . base_path(), '/'), $local_hosts);
  746. $local_stream_wrapper = isset($local_stream_wrappers[$path_info['scheme']]);
  747. //@TODO: maybe file_valid_uri() ?
  748. if ($local_url || $local_stream_wrapper) {
  749. // Set target as internal.
  750. $path_info['target'] = 'internal';
  751. // If this is seems to be a valid local stream wrapper string, force the
  752. // $path_info['path'] to be set to the file_url.
  753. if ($local_stream_wrapper) {
  754. $path_info = array_merge($path_info, parse_url(file_create_url($path_info['url'])));
  755. }
  756. // Trim the path from slashes.
  757. $path_info['path'] = trim($path_info['path'], '/');
  758. // If we have an empty path, and an internal target, we can assume that the
  759. // URL should go the the frontpage.
  760. if (empty($path_info['path'])) {
  761. $path_info['frontpage'] = TRUE;
  762. $path_info['path'] = variable_get('site_frontpage', 'node');
  763. }
  764. // Try converting the path to an internal Drupal path.
  765. $internal_url = drupal_get_normal_path($path_info['path']);
  766. // Add the "real" system path (not the alias) if the current user have
  767. // access to the URL.
  768. $path_info['system_path'] = drupal_valid_path($internal_url) ? $internal_url : FALSE;
  769. $menu_item = menu_get_item($path_info['system_path']);
  770. if ($menu_item) {
  771. $path_info['menu']['path'] = $path_info['system_path'];
  772. $path_info['menu']['description'] = check_plain($menu_item['description']);
  773. $path_info['menu']['title'] = check_plain($menu_item['title']);
  774. }
  775. // If we have a valid stream wrapper URL, find out the internal url.
  776. if ($local_stream_wrapper) {
  777. $path_info['system_path'] = $path_info['path'];
  778. $path_info['menu']['path'] = $path_info['path'];
  779. $path_info['menu']['description'] = 'This funciton is not fully integrated yet.';
  780. $path_info['menu']['title'] = 'This funciton is not fully integrated yet.';
  781. }
  782. }
  783. else {
  784. // Set target as external.
  785. $path_info['target'] = 'external';
  786. }
  787. return $path_info;
  788. }
  789. /**
  790. * Retrieve the result object from an absolute URL. Both internal and external
  791. * paths work.
  792. *
  793. * @param $url
  794. * The search string which seems to be an URL.
  795. *
  796. * @param $parts
  797. * An array of URL parts from parse_url().
  798. *
  799. * @return
  800. * A result object. This is an associative array which consists of:
  801. * - title: The title of the result.
  802. * - description: The description of the result (may contain HTML).
  803. * - path: The target path which will be inserted as the href in the link.
  804. * - addClass: A CSS class that will be added to the DOM result item.
  805. */
  806. function linkit_autocomplete_absolute_url($url, $parts) {
  807. $result = array();
  808. // Retrieve relevant information about the search string and if its a URL.
  809. $path_info = linkit_parse_url($url, $parts);
  810. // Set default options to pass with the url() function if the URL is internal.
  811. $url_options = array();
  812. // Add the URL frament.
  813. $url_options['fragment'] = isset($path_info['fragment']) ? $path_info['fragment'] : '';
  814. // Add the URL query.
  815. $url_options['query'] = isset($path_info['query']) ? $path_info['query'] : '';
  816. // The URL is registerd by Drupal (Internal).
  817. if (isset($path_info['menu']) && $path_info['system_path'] !== FALSE) {
  818. $result = array(
  819. 'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['menu']['path'], $url_options),
  820. 'title' => $path_info['menu']['title'] ? check_plain($path_info['menu']['title']) : check_plain($path_info['menu']['path']),
  821. 'description' => check_plain($path_info['menu']['description']) . ' ' . t('This is an internal path.'),
  822. 'addClass' => 'status-ok',
  823. );
  824. }
  825. // No internal menu result, but the URL seems to be internal. Either we do not
  826. // have access to it or it does not exists.
  827. elseif ($path_info['target'] == 'internal') {
  828. $result = array(
  829. 'path' => linkit_get_insert_plugin_processed_path(linkit_get_active_profile(), $path_info['path'], $url_options),
  830. 'title' => t('Page not found'),
  831. 'description' => t('This page does not exist or you do not have access to it.'),
  832. 'addClass' => 'status-warning',
  833. );
  834. }
  835. // The URL seems to be external.
  836. elseif ($path_info['target'] == 'external') {
  837. $result = array(
  838. 'title' => t('No information available'),
  839. 'description' => t('This is an external URL, but we don\'t know where it leads.'),
  840. 'path' => $path_info['url'],
  841. 'addClass' => 'status-notice',
  842. );
  843. }
  844. // Return the results in an array as BAC will need to have it that way.
  845. return array($result);
  846. }
  847. /**
  848. * Implements hook_image_default_styles().
  849. *
  850. * @return
  851. * An array of image styles, keyed by the style name.
  852. */
  853. function linkit_image_default_styles() {
  854. $styles = array();
  855. $styles['linkit_thumb'] = array(
  856. 'effects' => array(
  857. array(
  858. 'name' => 'image_scale',
  859. 'data' => array(
  860. 'width' => 50,
  861. 'height' => 50,
  862. 'upscale' => 0,
  863. ),
  864. 'weight' => 0,
  865. ),
  866. ),
  867. );
  868. return $styles;
  869. }
  870. /**
  871. * Implements hook_wysiwyg_plugin().
  872. */
  873. function linkit_wysiwyg_plugin($editor, $version) {
  874. $plugin = array();
  875. $plugin['linkit'] = array(
  876. 'path' => drupal_get_path('module', 'linkit') . '/editors/' . $editor,
  877. 'buttons' => array('linkit' => t('Linkit')),
  878. 'url' => 'http://drupal.org/project/linkit',
  879. 'load' => TRUE,
  880. );
  881. // TinyMCE needs to know the filename.
  882. if ($editor == 'tinymce') {
  883. $plugin['linkit']['filename'] = 'editor_plugin.js';
  884. }
  885. // Add the linkit library for the editor.
  886. drupal_add_library('linkit', $editor);
  887. return $plugin;
  888. }
  889. /**
  890. * Implements hook_wysiwyg_editor_settings_alter().
  891. */
  892. function linkit_wysiwyg_editor_settings_alter(&$settings, $context) {
  893. if ($context['profile']->editor == 'ckeditor') {
  894. $button_exists = isset($context['profile']->settings['buttons']['linkit']);
  895. if (!empty($context['profile']->settings['default_toolbar_grouping']) && $button_exists) {
  896. foreach ($settings['toolbar'] as &$group) {
  897. if ($group['name'] == 'links') {
  898. array_unshift($group['items'], 'linkit');
  899. }
  900. if ($group['name'] == 'other') {
  901. if (in_array('linkit', $group['items'])) {
  902. unset($group['items'][array_search('linkit', $group['items'])]);
  903. // Regenerate the keys.
  904. $group['items'] = array_values($group['items']);
  905. }
  906. }
  907. }
  908. }
  909. }
  910. }
  911. /**
  912. * Implements hook_ckeditor_plugin().
  913. */
  914. function linkit_ckeditor_plugin() {
  915. return array(
  916. 'linkit' => array(
  917. // Name of the plugin used to write it
  918. 'name' => 'linkit',
  919. // Description of plugin - it would appear in plugins managment of profile settings
  920. 'desc' => t('Support for Linkit module'),
  921. // The full path to the CKEditor plugin directory, with trailing slash.
  922. 'path' => drupal_get_path('module', 'linkit') . '/editors/ckeditor/',
  923. // Buttons to show up in the toolbar config area.
  924. 'buttons' => array(
  925. 'linkit' => array(
  926. 'label' => 'Linkit',
  927. 'icon' => 'icons/linkit.png',
  928. ),
  929. ),
  930. ),
  931. );
  932. }
  933. /**
  934. * Add Linkit settings to pages where we have an editor element.
  935. */
  936. function linkit_pre_render_editor_element($element) {
  937. static $processed = array();
  938. if (!isset($processed[$element['#id']])) {
  939. global $user;
  940. $all_formats = array_keys(filter_formats($user));
  941. $profiles_formats = array();
  942. $use_imce = FALSE;
  943. // First we need to figure out which formats that the current user can access have active Linkit profiles for them.
  944. foreach ($all_formats as $format) {
  945. // Load the first profile that is assigned to this text format.
  946. $profile = linkit_profile_load_by_format($format);
  947. if ($profile) {
  948. if (isset($profile->data['imce']) && $profile->data['imce'] == 1) {
  949. $use_imce = TRUE;
  950. }
  951. $profiles_formats[$format] = array('profile' => $profile->name);
  952. }
  953. }
  954. if (!empty($profiles_formats)) {
  955. $field_js = array(
  956. 'data' => array(
  957. 'linkit' => array(
  958. 'formats' => $profiles_formats,
  959. ),
  960. ),
  961. 'type' => 'setting',
  962. );
  963. // Configure Linkit to have an IMCE selector.
  964. if (module_invoke('imce', 'access') && $use_imce) {
  965. $field_js['data']['linkit']['IMCEurl'] = url('imce', array('query' => array(
  966. 'app' => 'Linkit|sendto@Drupal.linkit.IMCECallback',
  967. 'absolute' => TRUE,
  968. )));
  969. // We will only serv public files with IMCE.
  970. $field_js['data']['linkit']['publicFilesDirectory'] = variable_get('file_public_path', conf_path() . '/files');
  971. }
  972. // Attach the linkit_base librar
  973. $element['#attached']['library'][] = array('linkit', 'base');
  974. $element['#attached']['js'][] = $field_js;
  975. $processed[$element['#id']] = TRUE;
  976. }
  977. }
  978. return $element;
  979. }
  980. /**
  981. * Load the first profile assigned to a text format.
  982. *
  983. * @param $format
  984. * A string with the format machine name.
  985. *
  986. * @return
  987. * A LinkitProfile object or FALSE if no profile is found.
  988. */
  989. function linkit_profile_load_by_format($format) {
  990. $profiles = linkit_profile_editor_load_all();
  991. foreach ($profiles as $profile) {
  992. if (is_array($profile->data['text_formats']) && in_array($format, array_filter($profile->data['text_formats']))) {
  993. return $profile;
  994. }
  995. }
  996. return FALSE;
  997. }
  998. /**
  999. * Get profile type.
  1000. */
  1001. function linkit_get_profile_type($type) {
  1002. switch ($type) {
  1003. case LINKIT_PROFILE_TYPE_EDITOR:
  1004. return t('Editor');
  1005. case LINKIT_PROFILE_TYPE_FIELD:
  1006. return t('Field');
  1007. default:
  1008. return t('Can not find the profile type');
  1009. }
  1010. }
  1011. /**
  1012. * Gets the path processed by the inputfilter choice.
  1013. *
  1014. * @param LinkitProfile $profile
  1015. * The Linkit profile to use.
  1016. *
  1017. * @param $uri
  1018. * The uri to act on.
  1019. *
  1020. * @param $options
  1021. * An array of options to the URL function if its used.
  1022. *
  1023. * @return The processed uri.
  1024. */
  1025. function linkit_get_insert_plugin_processed_path(LinkitProfile $profile, $uri, $options = array()) {
  1026. switch ($profile->data['insert_plugin']['url_method']) {
  1027. case LINKIT_URL_METHOD_RAW:
  1028. $path = $uri;
  1029. break;
  1030. case LINKIT_URL_METHOD_RAW_SLASH:
  1031. $options['alias'] = TRUE;
  1032. $path = url($uri, $options);
  1033. break;
  1034. case LINKIT_URL_METHOD_ALIAS:
  1035. $path = url($uri, $options);
  1036. break;
  1037. }
  1038. return $path;
  1039. }