wysiwyg.admin.inc 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. <?php
  2. /**
  3. * @file
  4. * Integrate Wysiwyg editors into Drupal.
  5. */
  6. /**
  7. * Form builder for Wysiwyg profile form.
  8. */
  9. function wysiwyg_profile_form($form, &$form_state, $profile) {
  10. // Merge in defaults.
  11. $editor = wysiwyg_get_editor($profile->editor);
  12. if (!$editor) {
  13. // A missing library or unrecognized version, no edit link is shown so users
  14. // should not get here.
  15. drupal_goto('admin/config/content/wysiwyg');
  16. }
  17. if (!$profile->settings) {
  18. $profile->settings = array();
  19. }
  20. // Editor settings.
  21. $settings = &$profile->settings;
  22. $settings += array(
  23. 'theme' => '',
  24. 'language' => 'en',
  25. 'buttons' => array(),
  26. 'css_setting' => 'theme',
  27. 'css_theme' => variable_get('node_admin_theme') ? variable_get('admin_theme') : variable_get('theme_default', 'bartik'),
  28. 'css_path' => NULL,
  29. );
  30. if (!isset($profile->preferences)) {
  31. $profile->preferences = array();
  32. }
  33. // Wysiwyg profile preferences, not used directly by the editor.
  34. $preferences = &$profile->preferences;
  35. $preferences += array(
  36. 'add_to_summaries' => TRUE,
  37. 'default' => TRUE,
  38. 'show_toggle' => TRUE,
  39. 'user_choose' => FALSE,
  40. 'version' => NULL,
  41. );
  42. $installed_version = $editor['installed version'];
  43. $profile_version = $preferences['version'];
  44. $formats = filter_formats();
  45. drupal_set_title(t('%editor profile for %format', array('%editor' => $editor['title'], '%format' => $formats[$profile->format]->name)), PASS_THROUGH);
  46. $form['basic'] = array(
  47. '#type' => 'fieldset',
  48. '#title' => t('Basic setup'),
  49. '#collapsible' => TRUE,
  50. '#collapsed' => TRUE,
  51. '#group' => 'advanced',
  52. );
  53. $form['basic']['info'] = array(
  54. 'editor_version' => array(
  55. '#markup' => t('Installed editor library version: @installed_version', array('@installed_version' => (!empty($installed_version) ? $installed_version : t('Unknown')))),
  56. ),
  57. 'profile_version' => array(
  58. '#prefix' => '<br />',
  59. '#markup' => t('Version expected by profile: @profile_version', array('@profile_version' => (!empty($profile_version) ? $profile_version : t('Unknown')))),
  60. ),
  61. );
  62. $version_verified = $editor['installed version verified'];
  63. $migrate_message = '';
  64. $migrate_status = 'status';
  65. if (!empty($editor['verified version range'])) {
  66. $verified_range = $editor['verified version range'];
  67. $form['basic']['info']['verified_range'] = array(
  68. '#prefix' => '<br />',
  69. '#markup' => t('<a href="!url" target="_blank" title="Supported editor versions">Verified version range: @min_version to @max_version</a>', array(
  70. '@min_version' => $verified_range[0],
  71. '@max_version' => $verified_range[1],
  72. '!url' => url('https://www.drupal.org/node/596966'),
  73. )),
  74. );
  75. if (!$version_verified) {
  76. $migrate_status = 'warning';
  77. $form['basic']['info']['editor_version']['#markup'] .= ' <span class="warning">' . t('Untested!') . '</span>';
  78. }
  79. $version_diff = version_compare((empty($profile_version) ? $installed_version : $profile_version), $installed_version);
  80. if ($version_diff !== 0) {
  81. $migrate_message = t('The installed editor library version was changed from @profile_version to @installed_version since this profile was last saved.', array('@profile_version' => $profile_version, '@installed_version' => $installed_version));
  82. // If the editor integration supports migration between versions, attempt
  83. // it before widgets for settings are rendered.
  84. if (isset($editor['migrate settings callback']) && function_exists($editor['migrate settings callback'])) {
  85. // Get the version migrated to. MUST be in the verified version range or
  86. // FALSE if no migration was possible.
  87. $migrated_version = $editor['migrate settings callback']($settings, $editor, $profile_version, $installed_version);
  88. $profile->changed = ($migrated_version !== TRUE && $migrated_version != $installed_version);
  89. if ($migrated_version === FALSE) {
  90. $migrate_message .= ' ' . t('Wysiwyg is not able to automatically adapt the profile to the installed editor version. It is recommended to start over with a new profile.');
  91. $migrate_status = 'error';
  92. }
  93. // Migrations are likely not needed for every library update, as long
  94. // as the installed version is within the verified range, we're good.
  95. elseif ($version_verified) {
  96. $migrate_message .= ' ' . ($profile->changed ? t('The profile was automatically adapted to better match the installed editor version, but please check all settings before proceeding.') : t('No known changes to the profile were needed, but please check all settings before proceeding'));
  97. }
  98. else {
  99. $closest_version = ($version_diff === 1 ? $verified_range[0] : $verified_range[1]);
  100. $migrate_message .= ' ' . t('Wysiwyg was only able to adapt the profile to editor version %version, any additional differences to the installed version will not be accounted for and the editor may not work as expected.', array('%version' => $closest_version));
  101. $migrate_status = 'warning';
  102. }
  103. }
  104. }
  105. }
  106. if (!$version_verified) {
  107. $migrate_message .= (empty($migrate_message) ? '' : ' ') . t('The installed version has not been tested, the editor may not work as expected.');
  108. $migrate_status = 'warning';
  109. }
  110. if (!empty($migrate_message)) {
  111. $form['basic']['info'][''] = array(
  112. '#prefix' => '<div class="messages ' . $migrate_status . '">',
  113. '#markup' => $migrate_message . ' ' . t('This message is shown until the profile is saved while a verified version is installed.'),
  114. '#suffix' => '</div>',
  115. );
  116. }
  117. if (empty($profile->new) && !empty($profile->changed)) {
  118. drupal_set_message(t('All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard any changes.'), 'warning');
  119. }
  120. if (isset($profile->locked) && is_object($profile->locked)) {
  121. $form['locked'] = array(
  122. '#theme_wrappers' => array('container'),
  123. '#attributes' => array('class' => array('profile-locked', 'messages', 'warning')),
  124. '#markup' => t('This profile is being edited by user !user, and is therefore locked from editing by others. This lock is !age old. Click here to <a href="!break">break this lock</a>.', array('!user' => theme('username', array('account' => user_load($profile->locked->uid))), '!age' => format_interval(REQUEST_TIME - $profile->locked->updated), '!break' => url('admin/config/content/wysiwyg/profile/' . $profile->format. '/break-lock'))),
  125. );
  126. }
  127. $form['basic']['default'] = array(
  128. '#type' => 'checkbox',
  129. '#title' => t('Enabled by default'),
  130. '#default_value' => $preferences['default'],
  131. '#return_value' => 1,
  132. '#description' => t('The default editor state for users having access to this profile. Users are able to override this state if the next option is enabled.'),
  133. '#parents' => array('preferences', 'default'),
  134. );
  135. $form['basic']['user_choose'] = array(
  136. '#type' => 'checkbox',
  137. '#title' => t('Allow users to choose default'),
  138. '#default_value' => $preferences['user_choose'],
  139. '#return_value' => 1,
  140. '#description' => t('If allowed, users will be able to choose their own editor default state in their user account settings.'),
  141. '#parents' => array('preferences', 'user_choose'),
  142. );
  143. $form['basic']['show_toggle'] = array(
  144. '#type' => 'checkbox',
  145. '#title' => t('Show <em>enable/disable rich text</em> toggle link'),
  146. '#default_value' => $preferences['show_toggle'],
  147. '#return_value' => 1,
  148. '#description' => t('Whether or not to show the <em>enable/disable rich text</em> toggle link below a textarea. If disabled, the user setting or global default is used (see above).'),
  149. '#parents' => array('preferences', 'show_toggle'),
  150. );
  151. $form['basic']['add_to_summaries'] = array(
  152. '#type' => 'checkbox',
  153. '#title' => t('Enable on summary fields'),
  154. '#default_value' => $preferences['add_to_summaries'],
  155. '#return_value' => 1,
  156. '#description' => t('Attach the editor to summary fields of textareas.'),
  157. '#parents' => array('preferences', 'add_to_summaries'),
  158. );
  159. $form['buttons'] = array(
  160. '#type' => 'fieldset',
  161. '#title' => t('Buttons and plugins'),
  162. '#collapsible' => FALSE,
  163. '#tree' => TRUE,
  164. '#theme' => 'wysiwyg_admin_button_table',
  165. );
  166. $plugins = wysiwyg_get_plugins($profile->editor);
  167. // Generate the button list.
  168. foreach ($plugins as $name => $meta) {
  169. if (isset($meta['buttons']) && is_array($meta['buttons'])) {
  170. foreach ($meta['buttons'] as $button => $title) {
  171. $icon = '';
  172. if (!empty($meta['path'])) {
  173. // @todo Button icon locations are different in editors, editor versions,
  174. // and contrib/custom plugins (like Image Assist, f.e.).
  175. $img_src = $meta['path'] . "/images/$name.gif";
  176. // Handle plugins that have more than one button.
  177. if (!file_exists($img_src)) {
  178. $img_src = $meta['path'] . "/images/$button.gif";
  179. }
  180. $icon = file_exists($img_src) ? '<img src="' . base_path() . $img_src . '" title="' . $button . '" style="border: 1px solid grey; vertical-align: middle;" />' : '';
  181. }
  182. $title = (!empty($icon) ? $icon . ' ' . check_plain($title) : check_plain($title));
  183. $form['buttons'][$name][$button] = array(
  184. '#type' => 'checkbox',
  185. '#title' => $title,
  186. '#default_value' => !empty($settings['buttons'][$name][$button]) ? $settings['buttons'][$name][$button] : FALSE,
  187. '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
  188. );
  189. }
  190. }
  191. elseif (isset($meta['extensions']) && is_array($meta['extensions'])) {
  192. foreach ($meta['extensions'] as $extension => $title) {
  193. $form['buttons'][$name][$extension] = array(
  194. '#type' => 'checkbox',
  195. '#title' => check_plain($title),
  196. '#default_value' => !empty($settings['buttons'][$name][$extension]) ? $settings['buttons'][$name][$extension] : FALSE,
  197. '#description' => isset($meta['url']) ? l($meta['url'], $meta['url']) : NULL,
  198. );
  199. }
  200. }
  201. }
  202. $form['appearance'] = array(
  203. '#type' => 'fieldset',
  204. '#title' => t('Editor appearance'),
  205. '#collapsible' => TRUE,
  206. '#collapsed' => TRUE,
  207. '#group' => 'advanced',
  208. );
  209. $form['appearance']['theme'] = array(
  210. '#type' => 'hidden',
  211. '#value' => $settings['theme'],
  212. );
  213. $form['appearance']['language'] = array(
  214. '#type' => 'select',
  215. '#title' => t('Interface language'),
  216. '#default_value' => $settings['language'],
  217. );
  218. // @see _locale_prepare_predefined_list()
  219. require_once DRUPAL_ROOT . '/includes/iso.inc';
  220. $predefined = _locale_get_predefined_list();
  221. foreach ($predefined as $key => $value) {
  222. // Include native name in output, if possible.
  223. if (count($value) > 1) {
  224. $tname = t($value[0]);
  225. $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])";
  226. }
  227. else {
  228. $predefined[$key] = t($value[0]);
  229. }
  230. }
  231. asort($predefined);
  232. $form['appearance']['language']['#options'] = $predefined;
  233. $form['output'] = array(
  234. '#type' => 'fieldset',
  235. '#title' => t('Cleanup and output'),
  236. '#collapsible' => TRUE,
  237. '#collapsed' => TRUE,
  238. '#group' => 'advanced',
  239. );
  240. $form['css'] = array(
  241. '#type' => 'fieldset',
  242. '#title' => t('CSS'),
  243. '#collapsible' => TRUE,
  244. '#collapsed' => TRUE,
  245. '#group' => 'advanced',
  246. );
  247. $form['css']['css_setting'] = array(
  248. '#type' => 'select',
  249. '#title' => t('Editor CSS'),
  250. '#default_value' => $settings['css_setting'],
  251. '#options' => array('theme' => t('Use theme CSS'), 'self' => t('Define CSS'), 'none' => t('Editor default CSS')),
  252. '#description' => t('Defines the CSS to be used in the editor area.<br />Use theme CSS - loads stylesheets from current site theme.<br/>Define CSS - enter path for stylesheet files below.<br />Editor default CSS - uses default stylesheets from editor.'),
  253. );
  254. $themes = list_themes();
  255. $theme_list = array(
  256. '' => t('Active theme'),
  257. 'wysiwyg_theme_admin' => t('Admin theme'),
  258. 'wyiswyg_theme_default' => t('Default theme'),
  259. // Set an optgroup 'Other'.
  260. t('Other') => array(),
  261. );
  262. foreach ($themes as $theme) {
  263. if ($theme->status) {
  264. $theme_list[t('Other')][$theme->name] = $theme->info['name'];
  265. }
  266. }
  267. $form['css']['css_theme'] = array(
  268. '#type' => 'select',
  269. '#title' => t('Theme'),
  270. '#default_value' => $profile->settings['css_theme'],
  271. '#description' => t("Select which theme's CSS to apply to the content while in the editor.<br />Beware that most theme styling may not apply well without all wrapping elements in place. Users may see different themes depending on their permissions.<br />Note: This is only applied when the 'Editor CSS' field above is set to 'Use theme CSS'."),
  272. '#options' => $theme_list,
  273. '#states' => array(
  274. 'visible' => array('select[name="css_setting"]' => array('value' => 'theme')),
  275. ),
  276. );
  277. $form['css']['css_path'] = array(
  278. '#type' => 'textfield',
  279. '#title' => t('CSS path'),
  280. '#default_value' => $settings['css_path'],
  281. '#size' => 40,
  282. '#maxlength' => 255,
  283. '#description' => t('If "Define CSS" was selected above, enter path to a CSS file or a list of CSS files separated by a comma.') . '<br />' . t('Available tokens: <code>%b</code> (base path, eg: <code>/</code>), <code>%t</code> (path to theme, eg: <code>themes/garland</code>), <code>%q</code> (<code>css_js_query_string</code> variable, used for cache-busting)') . '<br />' . t('Example:') . ' css/editor.css,/themes/garland/style.css,%b%t/style.css?%q,http://example.com/external.css',
  284. '#states' => array(
  285. 'visible' => array('select[name="css_setting"]' => array('value' => 'self')),
  286. ),
  287. );
  288. $form['advanced'] = array(
  289. '#type' => 'vertical_tabs',
  290. '#weight' => 50,
  291. );
  292. $form['advanced']['settings_notes'] = array(
  293. '#type' => 'fieldset',
  294. '#weight' => 55,
  295. '#title' => '&star;&nbsp;' . t('Settings notes'),
  296. '#collapsible' => TRUE,
  297. '#collapsed' => TRUE,
  298. );
  299. $form['advanced']['settings_notes']['json']['#markup'] = t('Some settings use <a href="!json_url" title="JavaScript Object Notation">JSON</a> syntax to allow complex data structures. Compared to the JSON specification, Wysiwyg module is a bit more flexible with which syntax it accepts to make it easier to copy sample configurations from editor documentation. JSON requires properties and String values to be enclosed in double quotes. Wysiwyg will try to add missing double quotes and replace single quotes, but it only handles simple cases. JSON syntax only allows Objects, Arrays, Strings, Numbers, and Booleans as values, but Wysiwyg can recognize and convert some objects with a specific structures to native JavaScript types otherwise not accepted by JSON.',
  300. array(
  301. '!json_url' => url('https://en.wikipedia.org/wiki/JSON'),
  302. )
  303. );
  304. $form['advanced']['settings_notes']['json_examples'] = array(
  305. '#type' => 'fieldset',
  306. '#title' => t('Serializing native types'),
  307. '#collapsible' => TRUE,
  308. '#collapsed' => TRUE,
  309. );
  310. $form['advanced']['settings_notes']['json_examples']['function']['#markup'] = t('Function reference')
  311. . ':<br /><pre>{' . "\n " . '"drupalWysiwygType" : "callback",' . "\n " . '"name" : "name_of_callback_or_method",' . "\n " . '"context" : "global.context.object"' . "\n}</pre>";
  312. $form['advanced']['settings_notes']['json_examples']['regexp']['#markup'] = t('RegExp')
  313. . ':<br /><pre>{' . "\n " . '"drupalWysiwygType" : "regexp",' . "\n " . '"regexp" : "\w+\d$",' . "\n " . '"modifiers" : "gi"' . "\n}</pre>";
  314. $form['advanced']['settings_notes']['json_examples']['modules']['#markup'] = t('Modules implementing hooks may use the helper functions starting with <em>wysiwyg_wrap_js_*</em> to create these objects, see the file <em>wysiwyg.api.php</em>.');
  315. $form['actions'] = array('#type' => 'actions');
  316. $form['actions']['submit'] = array(
  317. '#type' => 'submit',
  318. '#value' => t('Save'),
  319. );
  320. $form['actions']['cancel'] = array(
  321. '#type' => 'submit',
  322. '#value' => t('Cancel'),
  323. '#submit' => array('wysiwyg_profile_form_cancel_submit'),
  324. );
  325. // Supply contextual information for other callbacks and handlers.
  326. // @todo Modernize this form for D7+ and declare these earlier.
  327. // $profile is the primary object of this form, and as an entity, usually
  328. // expected to live in $form_state[$entity_type].
  329. $form_state['wysiwyg_profile'] = $profile;
  330. $form_state['wysiwyg']['editor'] = $editor;
  331. $form_state['wysiwyg']['plugins'] = $plugins;
  332. // Allow editor library specific changes to be made to the form.
  333. if (isset($editor['settings form callback'])) {
  334. $editor['settings form callback']($form, $form_state);
  335. }
  336. foreach (array('appearance', 'output') as $fieldset) {
  337. if (count(element_children($form[$fieldset])) == 0) {
  338. $form[$fieldset]['#access'] = FALSE;
  339. }
  340. }
  341. return $form;
  342. }
  343. /**
  344. * Submit callback for Wysiwyg profile form.
  345. *
  346. * @see wysiwyg_profile_form()
  347. */
  348. function wysiwyg_profile_form_submit($form, &$form_state) {
  349. $values = $form_state['values'];
  350. if (isset($values['buttons'])) {
  351. // Store only enabled buttons for each plugin.
  352. foreach ($values['buttons'] as $plugin => $buttons) {
  353. $values['buttons'][$plugin] = array_filter($values['buttons'][$plugin]);
  354. }
  355. // Store only enabled plugins.
  356. $values['buttons'] = array_filter($values['buttons']);
  357. }
  358. $editor = $form_state['wysiwyg']['editor'];
  359. $preferences = $values['preferences'];
  360. $version = $editor['installed version'];
  361. // If the installed version is newer than what is supported, drop the stored
  362. // number so future upgrades know where to start. Ignore older versions.
  363. if (!$editor['installed version verified'] && !empty($editor['verified version range'])) {
  364. $version_range = $editor['verified version range'];
  365. if (version_compare($version, $version_range[1], '>')) {
  366. $version = $version_range[1];
  367. }
  368. }
  369. $preferences['version'] = $version;
  370. // Remove FAPI values.
  371. // @see system_settings_form_submit()
  372. unset($values['preferences'], $values['submit'], $values['cancel'], $values['form_id'], $values['op'], $values['form_token'], $values['form_build_id'], $values['advanced__active_tab']);
  373. $values['_profile_preferences'] = $preferences;
  374. $format = $form_state['wysiwyg_profile']->format;
  375. // Insert new profile data.
  376. db_merge('wysiwyg')
  377. ->key(array('format' => $format))
  378. ->fields(array(
  379. 'editor' => $form_state['wysiwyg_profile']->editor,
  380. 'settings' => serialize($values),
  381. ))
  382. ->execute();
  383. // Clear the editing caches.
  384. if (module_exists('ctools')) {
  385. ctools_include('object-cache');
  386. ctools_object_cache_clear('wysiwyg_profile', $form_state['wysiwyg_profile']->name);
  387. }
  388. else {
  389. cache_clear_all('wysiwyg_profile:' . $form_state['wysiwyg_profile']->name, 'cache');
  390. }
  391. wysiwyg_profile_cache_clear();
  392. $formats = filter_formats();
  393. drupal_set_message(t('Wysiwyg profile for %format has been saved.', array('%format' => $formats[$format]->name)));
  394. $form_state['redirect'] = 'admin/config/content/wysiwyg';
  395. }
  396. /**
  397. * Cancel submit handler for Wysiwyg profile form.
  398. *
  399. * @see wysiwyg_profile_form()
  400. */
  401. function wysiwyg_profile_form_cancel_submit($form, &$form_state) {
  402. $profile = $form_state['wysiwyg_profile'];
  403. // Clear the editing caches.
  404. if (module_exists('ctools')) {
  405. ctools_include('object-cache');
  406. ctools_object_cache_clear('wysiwyg_profile', $profile->name);
  407. }
  408. else {
  409. cache_clear_all('wysiwyg_profile:' . $profile->name, 'cache');
  410. }
  411. $form_state['redirect'] = 'admin/config/content/wysiwyg';
  412. }
  413. /**
  414. * Validation helper for JSON based elements.
  415. *
  416. * Ensures double quotes are used around property names and values.
  417. *
  418. * @param $element
  419. * The form element with a '#value' property to validate.
  420. * @param $form_state
  421. * The form state to update with the processed value.
  422. */
  423. function _wysiwyg_settings_form_validate_json_object($element, &$form_state) {
  424. $ret = array(
  425. 'valid' => TRUE,
  426. );
  427. if (!empty($element['#value'])) {
  428. $value = $element['#value'];
  429. // Ensure double quotes are used around property names.
  430. $value = preg_replace('/(?<=^|,|\n|{)(\s*)(\'?)(\w+)\2\s*:/', '$1"$3":', $value);
  431. // Ensure double quotes are used around string values.
  432. $value = preg_replace('/(?<=^|,|\n|{)\s*"(\w+)"\s*:\s*\'(.*?)\'\s*(?=$|\n|,|})/', '"$1":"$2"', $value);
  433. $ret['processed value'] = $value;
  434. form_set_value($element, $value, $form_state);
  435. $ret['valid'] = (json_decode($value) !== NULL);
  436. if (!$ret['valid']) {
  437. $ret['message'] = t('The value needs to be a valid JSON object.') . '&nbsp;&star;';
  438. }
  439. }
  440. return $ret;
  441. }
  442. /**
  443. * Layout for the buttons in the Wysiwyg Editor profile form.
  444. */
  445. function theme_wysiwyg_admin_button_table($variables) {
  446. $form = $variables['form'];
  447. $buttons = array();
  448. // Flatten forms array.
  449. foreach (element_children($form) as $name) {
  450. foreach (element_children($form[$name]) as $button) {
  451. $buttons[] = drupal_render($form[$name][$button]);
  452. }
  453. }
  454. // Split checkboxes into rows with 3 columns.
  455. $total = count($buttons);
  456. $rows = array();
  457. for ($i = 0; $i < $total; $i += 3) {
  458. $row = array();
  459. $row_buttons = array_slice($buttons, $i, 3) + array_fill(0, 3, array());
  460. foreach ($row_buttons as $row_button) {
  461. $row[] = array('data' => $row_button);
  462. }
  463. $rows[] = $row;
  464. }
  465. $output = theme('table', array('rows' => $rows, 'attributes' => array('width' => '100%')));
  466. return $output;
  467. }
  468. /**
  469. * Display overview of setup Wysiwyg Editor profiles; menu callback.
  470. */
  471. function wysiwyg_profile_overview($form, &$form_state) {
  472. include_once './includes/install.inc';
  473. // Check which wysiwyg editors are installed.
  474. $editors = wysiwyg_get_all_editors();
  475. // Sort editors by installed first, then by title.
  476. foreach ($editors as $key => $row) {
  477. $installed[$key] = $row['installed'];
  478. $title[$key] = drupal_strtolower($row['title']);
  479. }
  480. array_multisort($installed, SORT_DESC, $title, SORT_ASC, $editors);
  481. $count = count($editors);
  482. $status = array();
  483. $editor_options = array();
  484. // D7's seven theme displays links in table headers as block elements.
  485. drupal_add_css('table.system-status-report th a {display: inline;}', 'inline');
  486. foreach ($editors as $name => $editor) {
  487. $status[$name] = array(
  488. 'severity' => REQUIREMENT_INFO,
  489. 'title' => t('<a href="!vendor-url">@editor</a> (<a href="!download-url">Download</a>)', array(
  490. '!vendor-url' => $editor['vendor url'],
  491. '@editor' => $editor['title'],
  492. '!download-url' => $editor['download url'],
  493. )),
  494. 'value' => t('Not installed.'),
  495. 'description' => (isset($editor['error']) ? $editor['error'] : ''),
  496. );
  497. if (isset($editor['error'])) {
  498. $severity = REQUIREMENT_ERROR;
  499. }
  500. elseif ($editor['installed']) {
  501. $editor_options[$name] = check_plain($editor['title']) . ' ' . check_plain($editor['installed version']);
  502. if (!$editor['installed version verified']) {
  503. $status[$name]['severity'] = REQUIREMENT_WARNING;
  504. }
  505. else {
  506. $status[$name]['severity'] = REQUIREMENT_OK;
  507. }
  508. $status[$name]['value'] = $editor['installed version'];
  509. }
  510. else {
  511. // Build on-site installation instructions.
  512. // @todo Setup $library in wysiwyg_load_editor() already.
  513. $library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries']));
  514. $targs = array(
  515. '@editor-path' => $editor['editor path'],
  516. '@library-filepath' => $editor['library path'] . '/' . (isset($editor['libraries'][$library]['files'][0]) ? $editor['libraries'][$library]['files'][0] : key($editor['libraries'][$library]['files'])),
  517. );
  518. $instructions = '<p>' . t('Extract the archive and copy its contents into a new folder in the following location:<br /><code>@editor-path</code>', $targs) . '</p>';
  519. $instructions .= '<p>' . t('So the actual library can be found at:<br /><code>@library-filepath</code>', $targs) . '</p>';
  520. // Add any install notes.
  521. if (!empty($editor['install note callback']) && function_exists($editor['install note callback'])) {
  522. $instructions .= '<div class="editor-install-note">' . $editor['install note callback']() . '</div>';
  523. }
  524. $status[$name]['description'] .= $instructions;
  525. $count--;
  526. }
  527. if (!empty($editor['verified version range'])) {
  528. $status[$name]['value'] .= '<br />(' . $editor['verified version range'][0] . ' - ' . $editor['verified version range'][1] . ')*';
  529. }
  530. // In case there is an error, always show installation instructions.
  531. if (isset($editor['error'])) {
  532. $show_instructions = TRUE;
  533. }
  534. }
  535. if (!$count) {
  536. $show_instructions = TRUE;
  537. }
  538. $form['status'] = array(
  539. '#type' => 'fieldset',
  540. '#title' => t('Installation instructions'),
  541. '#collapsible' => TRUE,
  542. '#collapsed' => !isset($show_instructions),
  543. '#description' => t('A complete list of <a href="@url">supported editor versions</a> for all Wysiwyg module versions can be found on drupal.org.', array('@url' => url('https://www.drupal.org/node/596966'))) . (!$count ? ' ' . t('There are no editor libraries installed currently. The following list contains a list of currently supported editors:') : ''),
  544. '#weight' => 10,
  545. );
  546. $form['status']['report'] = array('#markup' => theme('status_report', array('requirements' => $status)));
  547. $form['status']['notes']['#markup'] = '* ' . t('The lowest and highest editor library version the current release of Wysiwyg module was last tested by the module maintainers.<br />Using a verified editor version is not a guaratee that any version of Wysiwyg module or an editor library is without problems. The verified version range only indicates a few manual tests have been performed on the first and last editor versions in the range together with the current Wysiwyg module version. <u>Always try the latest verified version before reporting bugs.</u> Unless otherwise noted; the testing was performed using the <strong>"default" or "full" package</strong> available for download from the editor\'s official website. Wysiwyg will <em>try</em> to use any recognized version above the verified version range, but proceed with caution. Editor versions lower than the verified range are <u>not supported at all</u>.<br /> More information about supported editor libraries and the verified versions can be found on the <a target="_blank" title="Supported editors" href="@project-url">wiki</a>.', array('@project-url' => url('https://www.drupal.org/node/596966')));
  548. if (!$count) {
  549. return $form;
  550. }
  551. $formats = filter_formats();
  552. wysiwyg_profile_cache_clear();
  553. $profiles = wysiwyg_profile_load_all();
  554. $form['formats'] = array(
  555. '#type' => 'item',
  556. '#description' => t('To assign a different editor to a text format, click "delete" to remove the existing first.'),
  557. '#tree' => TRUE,
  558. );
  559. $format_options = array();
  560. foreach ($formats as $id => $format) {
  561. if (($profile = (!empty($profiles[$id]) ? $profiles[$id] : FALSE))) {
  562. $form['formats'][$id]['name'] = array(
  563. '#markup' => check_plain($format->name),
  564. );
  565. $editor_name = $profile->editor;
  566. $installed = !empty($editors[$editor_name]['installed']);
  567. $profile_version = (isset($profile->preferences['version']) ? $profile->preferences['version'] : NULL);
  568. $editor_message = '';
  569. if ($installed) {
  570. if ($profile_version != $editors[$editor_name]['installed version']) {
  571. $editor_message = '<div class="messages warning">' . check_plain($editors[$editor_name]['title']) . ' (' . t('Expected version: %profile_version but found %installed_version.', array('%profile_version' => $profile_version, '%installed_version' => $editors[$editor_name]['installed version'])) . ')</div>';
  572. }
  573. elseif (!$editors[$editor_name]['installed version verified']) {
  574. $editor_message = '<div class="messages warning">' . check_plain($editors[$editor_name]['title']) . ' (' . t('Version %installed_version is not verified', array('%installed_version' => $editors[$editor_name]['installed version'])) . ')</div>';
  575. }
  576. else {
  577. $editor_message = t('@library @profile_version', array('@library' => $editors[$editor_name]['title'], '@profile_version' => $profile_version));
  578. }
  579. }
  580. else {
  581. $editor_message = '<div class="messages error">' . t('Unable to find @library. Expected version: %profile_version. Reinstall the library to edit this profile.', array('@library' => $editors[$editor_name]['title'], '%profile_version' => $profile_version)) . '</div>';
  582. }
  583. $form['formats'][$id]['editor']['#markup'] = $editor_message;
  584. if ($editors[$profile->editor]['installed']) {
  585. $form['formats'][$id]['edit'] = array(
  586. '#markup' => l(t('Edit'), "admin/config/content/wysiwyg/profile/$id"),
  587. );
  588. }
  589. $form['formats'][$id]['delete'] = array(
  590. '#markup' => l(t('Delete'), "admin/config/content/wysiwyg/profile/$id/delete"),
  591. );
  592. }
  593. else {
  594. $format_options[$id] = $format->name;
  595. }
  596. }
  597. $form['formats']['_new_profile']['name'] = array(
  598. '#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . t('Assign an editor profile to a format') . '</div>',
  599. '#suffix' => '</div>',
  600. );
  601. if (!empty($format_options)) {
  602. $form['formats']['_new_profile']['name'] += array(
  603. '#type' => 'select',
  604. '#title' => t('Select a format'),
  605. '#title_display' => 'invisible',
  606. '#empty_option' => t('- Select a format -'),
  607. '#default_value' => '',
  608. '#options' => $format_options,
  609. );
  610. }
  611. else {
  612. $form['formats']['_new_profile']['name'] += array(
  613. '#markup' => t('All formats have been assigned an editor profile'),
  614. );
  615. }
  616. if (!empty($editor_options)) {
  617. $form['formats']['_new_profile']['editor'] = array(
  618. '#type' => 'select',
  619. '#title' => t('Select an editor'),
  620. '#title_display' => 'invisible',
  621. '#empty_option' => t('- Select an editor -'),
  622. '#default_value' => '',
  623. '#options' => $editor_options,
  624. );
  625. }
  626. else {
  627. $form['formats']['_new_profile']['editor'] = array(
  628. '#markup' => t('No editors installed.'),
  629. );
  630. }
  631. $form['formats']['_new_profile']['editor']['#prefix'] = '<div class="add-new-placeholder">&nbsp;</div>';
  632. $form['formats']['_new_profile']['submit'] = array(
  633. '#type' => 'submit',
  634. '#value' => t('Create profile'),
  635. '#prefix' => '<div class="add-new-placeholder">&nbsp;</div>',
  636. );
  637. return $form;
  638. }
  639. /**
  640. * Return HTML for the Wysiwyg profile overview form.
  641. */
  642. function theme_wysiwyg_profile_overview($variables) {
  643. $form = $variables['form'];
  644. if (!isset($form['formats'])) {
  645. return;
  646. }
  647. $output = '';
  648. $header = array(t('Text format'), t('Editor'), array('data' => t('Operations'), 'colspan' => 2));
  649. $table_rows = array();
  650. foreach (element_children($form['formats']) as $form_row_name) {
  651. $form_row = &$form['formats'][$form_row_name];
  652. $editor_column_output = drupal_render($form_row['editor']);
  653. $table_row = array(
  654. 'data' => array(
  655. drupal_render($form_row['name']),
  656. $editor_column_output,
  657. ),
  658. );
  659. if ($form_row_name == '_new_profile') {
  660. // This is the "create profile" row.
  661. $table_row['data'][] = array(
  662. 'data' => drupal_render($form_row['submit']),
  663. 'colspan' => 2,
  664. );
  665. }
  666. else {
  667. $table_row['data'][] = isset($form_row['edit']) ? drupal_render($form_row['edit']) : '';
  668. $table_row['data'][] = isset($form_row['delete']) ? drupal_render($form_row['delete']) : '';
  669. };
  670. $table_rows[] = $table_row;
  671. }
  672. $form['formats']['table']['#markup'] = theme('table', array('header' => $header, 'rows' => $table_rows, 'caption' => t('Wysiwyg profiles')));
  673. $output .= drupal_render_children($form);
  674. return $output;
  675. }
  676. /**
  677. * Validate callback for Wysiwyg profile overview form.
  678. *
  679. * @see wysiwyg_profile_overview()
  680. */
  681. function wysiwyg_profile_overview_validate($form, &$form_state) {
  682. $new = $form_state['values']['formats']['_new_profile'];
  683. if (empty($new['name'])) {
  684. form_set_error('formats][new][name', t('You must select a format.'));
  685. }
  686. else {
  687. $profiles = wysiwyg_profile_load_all();
  688. if (isset($profiles[$new['name']]) && !empty($profiles[$new['name']]->editor)) {
  689. form_set_error('formats][new][name', t('An editor is already assigned to this format.'));
  690. }
  691. }
  692. if (empty($new['editor'])) {
  693. form_set_error('formats][new][editor', t('You must select an editor.'));
  694. }
  695. else {
  696. $editor = wysiwyg_get_editor($form_state['values']['formats']['_new_profile']['editor']);
  697. if (empty($editor)) {
  698. form_set_error('formats][new][editor', t('The selected editor was not found.'));
  699. }
  700. }
  701. }
  702. /**
  703. * Submit callback for Wysiwyg profile overview form.
  704. *
  705. * @see wysiwyg_profile_overview()
  706. */
  707. function wysiwyg_profile_overview_submit($form, &$form_state) {
  708. $editor = wysiwyg_get_editor($form_state['values']['formats']['_new_profile']['editor']);
  709. $format = $form_state['values']['formats']['_new_profile']['name'];
  710. $profile = array(
  711. 'name' => 'format' . $format,
  712. 'new' => TRUE,
  713. 'editing' => TRUE,
  714. 'format' => $format,
  715. 'editor' => $editor['name'],
  716. 'settings' => array(),
  717. 'preferences' => array(
  718. 'add_to_summaries' => TRUE,
  719. 'default' => TRUE,
  720. 'show_toggle' => TRUE,
  721. 'user_choose' => FALSE,
  722. 'version' => $editor['installed version'],
  723. ),
  724. );
  725. $profile = (object) $profile;
  726. // Cache the profile for editing.
  727. wysiwyg_ui_profile_cache_set($profile);
  728. // If there is a destination query, ensure we still redirect the user to the
  729. // edit view page, and then redirect the user to the destination.
  730. $destination = array();
  731. if (isset($_GET['destination'])) {
  732. $destination = drupal_get_destination();
  733. unset($_GET['destination']);
  734. }
  735. $form_state['redirect'] = array('admin/config/content/wysiwyg/profile/' . $format . '/edit', array('query' => $destination));
  736. }
  737. /**
  738. * Delete editor profile confirmation form.
  739. */
  740. function wysiwyg_profile_delete_confirm($form, &$form_state, $profile) {
  741. $formats = filter_formats();
  742. $format = $formats[$profile->format];
  743. $form_state['wysiwyg_profile'] = $profile;
  744. $form_state['format'] = $formats[$profile->format];
  745. return confirm_form(
  746. $form,
  747. t('Are you sure you want to remove the profile for %name?', array('%name' => $format->name)),
  748. 'admin/config/content/wysiwyg',
  749. t('This action cannot be undone.'), t('Remove'), t('Cancel')
  750. );
  751. }
  752. /**
  753. * Submit callback for Wysiwyg profile delete form.
  754. *
  755. * @see wysiwyg_profile_delete_confirm()
  756. */
  757. function wysiwyg_profile_delete_confirm_submit($form, &$form_state) {
  758. $profile = $form_state['wysiwyg_profile'];
  759. $format = $form_state['format'];
  760. wysiwyg_profile_delete($profile);
  761. if (module_exists('admin_menu')) {
  762. admin_menu_flush_caches();
  763. }
  764. drupal_set_message(t('Wysiwyg profile for %name has been deleted.', array('%name' => $format->name)));
  765. $form_state['redirect'] = 'admin/config/content/wysiwyg';
  766. }
  767. /**
  768. * Break lock confirmation form.
  769. */
  770. function wysiwyg_profile_break_lock_confirm($form, &$form_state, $profile) {
  771. $form_state['wysiwyg_profile'] = &$profile;
  772. $formats = filter_formats();
  773. $format = $formats[$profile->format];
  774. $form['format'] = array('#type' => 'value', '#value' => $format);
  775. if (empty($profile->locked)) {
  776. $form['message']['#markup'] = t('There is no lock on the profile for %name to break.', array('%name' => $format->name));
  777. return $form;
  778. }
  779. $account = user_load($profile->locked->uid);
  780. return confirm_form(
  781. $form,
  782. t('Are you sure you want to break the lock on the profile for %name?', array('%name' => $format->name)),
  783. 'admin/config/content/wysiwyg',
  784. t('By breaking this lock, any unsaved changes made by !user will be lost!.', array('!user' => theme('username', array('account' => $account)))),
  785. t('Break lock'), t('Cancel')
  786. );
  787. }
  788. /**
  789. * Submit handler to break_lock a profile.
  790. */
  791. function wysiwyg_profile_break_lock_confirm_submit(&$form, &$form_state) {
  792. // Only the CTools cache handles locks.
  793. if (module_exists('ctools')) {
  794. ctools_include('object-cache');
  795. ctools_object_cache_clear_all('wysiwyg_profile', $form_state['wysiwyg_profile']->name);
  796. }
  797. if (($profile = wysiwyg_profile_load($form_state['wysiwyg_profile']->format))) {
  798. $form_state['redirect'] = 'admin/config/content/wysiwyg/profile/' . $form_state['wysiwyg_profile']->format . '/edit';
  799. drupal_set_message(t('The lock has been broken and you may now edit this profile.'));
  800. }
  801. else {
  802. $form_state['redirect'] = 'admin/config/content/wysiwyg/profile';
  803. drupal_set_message(t('The lock has been broken and you may now create a new profile.'));
  804. }
  805. }