scanner.module

  1. cis7 sites/all/modules/ulmus/scanner/scanner.module
  2. cle7 sites/all/modules/ulmus/scanner/scanner.module
  3. ecd7 sites/all/modules/ulmus/scanner/scanner.module
  4. elmsmedia7 sites/all/modules/ulmus/scanner/scanner.module
  5. harmony7 sites/all/modules/ulmus/scanner/scanner.module
  6. icor7 sites/all/modules/ulmus/scanner/scanner.module
  7. meedjum_blog7 sites/all/modules/ulmus/scanner/scanner.module
  8. mooc7 sites/all/modules/ulmus/scanner/scanner.module

Search and Replace Scanner - works on all nodes text content.

The Search and Replace Scanner can do regular expression matches against the title, body and CCK text content fields on all nodes in your system. This is useful for finding html strings that Drupal's normal search will ignore. And it can replace the matched text. Very handy if you are changing the name of your company, or are changing the URL of a link included multiple times in multiple nodes.

The module allow you to configure which fields and tables to work with, and also to add in custom tables and fields for modules that don't use CCK.

Limitations: Only works with Mysql

Warning: This is a very powerful tool, and as such is very dangerous. You can easy distroy your entire site with it. Be sure to backup your database before using it. No, really.

Todo: Provide better highlighting for search results

  • right now there's a known bug where multiple search terms on the same line aren't all highlighted. (The hit count is correct, though, and all items are replaced correctly.)

Credits: Version 5.x-1.0 by:

Version 5.x-2.0 by:

Version 7.x-1.0 by:

Functions

Namesort descending Description
scanner_admin_form Search and Replace Settings form.
scanner_confirm_form Scanner confirmation form to prevent people from accidentally replacing things they don't intend to.
scanner_confirm_form_submit Submission handling for scanner confirmation form.
scanner_execute Handles the actual search and replace.
scanner_form The search and replace form.
scanner_form_submit Handles submission of the search and replace form.
scanner_form_validate Validate form input.
scanner_menu Implements hook_menu().
scanner_permission Implements hook_permission().
scanner_theme @todo Please document this function.
scanner_undo_confirm_form @todo Please document this function.
scanner_undo_confirm_form_submit @todo Please document this function.
scanner_undo_page @todo Please document this function.
scanner_view Menu callback; presents the scan form and results.
theme_scanner_item Theme each search result hit.
theme_scanner_replace_item @todo Please document this function.
theme_scanner_replace_results @todo Please document this function.
theme_scanner_results The the search results.
_scanner_change_env Attempt to stretch the amount of time available for processing so that timeouts don't interrupt search and replace actions.
_scanner_get_all_tables_map Get all text fields. This is all very fragle based on how CCK stores fields. Works for CCK 1.6.
_scanner_get_selected_tables_map Get the fields that have been selected for scanning.

Constants

File

sites/all/modules/ulmus/scanner/scanner.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Search and Replace Scanner - works on all nodes text content.
  5. *
  6. * The Search and Replace Scanner can do regular expression matches
  7. * against the title, body and CCK text content fields on all nodes in your system.
  8. * This is useful for finding html strings that Drupal's normal search will
  9. * ignore. And it can replace the matched text. Very handy if you are changing
  10. * the name of your company, or are changing the URL of a link included
  11. * multiple times in multiple nodes.
  12. *
  13. * The module allow you to configure which fields and tables to work with,
  14. * and also to add in custom tables and fields for modules that don't use CCK.
  15. *
  16. * Limitations:
  17. * Only works with Mysql
  18. *
  19. * Warning:
  20. * This is a very powerful tool, and as such is very dangerous. You can
  21. * easy distroy your entire site with it. Be sure to backup your database
  22. * before using it. No, really.
  23. *
  24. * Todo:
  25. * Provide better highlighting for search results
  26. * - right now there's a known bug where multiple search terms
  27. * on the same line aren't all highlighted. (The hit count
  28. * is correct, though, and all items are replaced correctly.)
  29. *
  30. * Credits:
  31. * Version 5.x-1.0 by:
  32. * - Tao Starbow http://www.starbowconsulting.com
  33. * Drupal username: starbow
  34. * Version 5.x-2.0 by:
  35. * - Amit Asaravala http://www.returncontrol.com
  36. * Drupal username: aasarava
  37. * - Jason Salter jason http://www.fivepaths.com
  38. * Drupal username: jpsalter
  39. * - Sponsored by Five Paths Consulting http://www.fivepaths.com
  40. * Version 7.x-1.0 by:
  41. * - Michael Rossetti - http://www.mit.edu
  42. * Drupal username: MikeyR
  43. */
  44. //The special characters to escape if a search string is not a regex string:
  45. define('SCANNER_REGEX_CHARS', '.\/+*?[^]$() {}=!<>|:');
  46. //The modes that the search-and-replace process can be in.
  47. //We need to track the modes to prevent accidentally starting a replacement
  48. // or a long search if a user leaves mid-way through the process
  49. // and comes back again w/ the same session variables.
  50. define('SCANNER_STATUS_GO_SEARCH', 1);
  51. define('SCANNER_STATUS_GO_CONFIRM', 2);
  52. define('SCANNER_STATUS_GO_REPLACE', 3);
  53. /**
  54. * Implements hook_menu().
  55. */
  56. function scanner_menu() {
  57. $items['admin/content/scanner'] = array(
  58. 'title' => 'Search and Replace Scanner',
  59. 'description' => 'Find (and replace) keywords in all your content.',
  60. 'page callback' => 'scanner_view',
  61. 'type' => MENU_LOCAL_TASK,
  62. 'access arguments' => array('perform search and replace'),
  63. );
  64. $items['admin/content/scanner/scan'] = array(
  65. 'title' => 'Search',
  66. 'access arguments' => array('perform search and replace'),
  67. 'type' => MENU_DEFAULT_LOCAL_TASK,
  68. );
  69. $items['admin/content/scanner/scan/confirm'] = array(
  70. 'title' => 'Confirm Replace',
  71. 'access arguments' => array('perform search and replace'),
  72. 'page callback' => 'drupal_get_form',
  73. 'page arguments' => array('scanner_confirm_form'),
  74. 'type' => MENU_CALLBACK,
  75. );
  76. $items['admin/content/scanner/undo/confirm'] = array(
  77. 'title' => 'Confirm Undo',
  78. 'access arguments' => array('perform search and replace'),
  79. 'page callback' => 'drupal_get_form',
  80. 'page arguments' => array('scanner_undo_confirm_form'),
  81. 'type' => MENU_CALLBACK,
  82. );
  83. $items['admin/content/scanner/settings'] = array(// Shows up on scanner page as tab.
  84. 'title' => 'Settings',
  85. 'page callback' => 'drupal_get_form',
  86. 'page arguments' => array('scanner_admin_form'),
  87. 'access arguments' => array('administer scanner settings'),
  88. 'type' => MENU_LOCAL_TASK,
  89. 'weight' => 1,
  90. );
  91. $items['admin/content/scanner/undo'] = array(// Shows up on scanner page as tab.
  92. 'title' => 'Undo',
  93. 'page callback' => 'scanner_undo_page',
  94. 'access arguments' => array('perform search and replace'),
  95. 'type' => MENU_LOCAL_TASK,
  96. );
  97. $items['admin/config/scanner'] = array(// Shows up on admin page.
  98. 'title' => 'Search and Replace Scanner',
  99. 'description' => 'Configure defaults and what fields can be searched and replaced.',
  100. 'page callback' => 'drupal_get_form',
  101. 'page arguments' => array('scanner_admin_form'),
  102. 'access arguments' => array('administer scanner settings'),
  103. );
  104. return $items;
  105. }
  106. /**
  107. * @todo Please document this function.
  108. * @see http://drupal.org/node/1354
  109. */
  110. function scanner_theme() {
  111. return array(
  112. 'scanner_results' => array(
  113. 'file' => 'scanner.module',
  114. 'variables' => array(
  115. 'results' => NULL,
  116. ),
  117. ),
  118. 'scanner_item' => array(
  119. 'file' => 'scanner.module',
  120. 'variables' => array(
  121. 'item' => NULL,
  122. ),
  123. ),
  124. 'scanner_replace_results' => array(
  125. 'file' => 'scanner.module',
  126. 'variables' => array(
  127. 'results' => NULL,
  128. ),
  129. ),
  130. 'scanner_replace_item' => array(
  131. 'file' => 'scanner.module',
  132. 'variables' => array(
  133. 'item' => NULL,
  134. ),
  135. ),
  136. );
  137. }
  138. /**
  139. * Implements hook_permission().
  140. */
  141. function scanner_permission() {
  142. return array(
  143. 'administer scanner settings' => array(
  144. 'title' => t('administer scanner settings'),
  145. 'description' => t('TODO Add a description for \'administer scanner settings\''),
  146. ),
  147. 'perform search and replace' => array(
  148. 'title' => t('perform search and replace'),
  149. 'description' => t('TODO Add a description for \'perform search and replace\''),
  150. ),
  151. );
  152. }
  153. /**
  154. * Menu callback; presents the scan form and results.
  155. */
  156. function scanner_view() {
  157. $output='';
  158. //using set_html_head because it seems unecessary to load a separate css
  159. // file for just two simple declarations:
  160. drupal_add_css('
  161. #scanner-form .form-submit { margin-top:0; }
  162. #scanner-form .form-item { margin-bottom:0; }
  163. ', array('type' => 'inline'));
  164. //javascript checks to make sure user has entered some search text:
  165. drupal_add_js("
  166. $(document).ready(function() {
  167. $('input[@type=submit][@value=Search]').click(function() {
  168. var searchfield = $('#edit-search');
  169. var chars = searchfield.val().length;
  170. if (chars == 0) {
  171. alert('Please provide some search text and try again.');
  172. searchfield.addClass('error');
  173. searchfield[0].focus();
  174. return FALSE;
  175. } else if (chars < 3) {
  176. return confirm('Searching for a keyword that has fewer than three characters could take a long time. Are you sure you want to continue?');
  177. }
  178. return TRUE;
  179. });
  180. });
  181. ", array('type' => 'inline', 'scope' => JS_DEFAULT));
  182. if (isset($_SESSION['scanner_search'])) {
  183. $search = $_SESSION['scanner_search'];
  184. }
  185. else{
  186. $search = NULL;
  187. }
  188. if (isset($_SESSION['scanner_status'])) {
  189. $status = $_SESSION['scanner_status'];
  190. }
  191. else{
  192. $status = NULL;
  193. }
  194. if (!is_NULL($search) && $status >= SCANNER_STATUS_GO_SEARCH) {
  195. if ($status == SCANNER_STATUS_GO_CONFIRM) {
  196. drupal_goto('admin/content/scanner/scan/confirm');
  197. }
  198. elseif ($status == SCANNER_STATUS_GO_REPLACE) {
  199. $resulttxt = '<a name="results"></a>' . t('Replacement Results');
  200. $results = scanner_execute('replace');
  201. }
  202. else {
  203. $resulttxt = t('Search Results');
  204. $results = scanner_execute('search');
  205. }
  206. if ($results) {
  207. // TODO Please change this theme call to use an associative array for the $variables parameter.
  208. $results = '<a name="results"></a><div><h2>' . $resulttxt . '</h2>' . $results;
  209. }
  210. else {
  211. // TODO Please change this theme call to use an associative array for the $variables parameter.
  212. $results = t('Your search yielded no results.');
  213. }
  214. $scanner_form = drupal_get_form('scanner_form');
  215. $output = drupal_render($scanner_form);
  216. $output .= $results;
  217. //clear any old search form input:
  218. unset($_SESSION['scanner_search']);
  219. unset($_SESSION['scanner_replace']);
  220. unset($_SESSION['scanner_preceded']);
  221. unset($_SESSION['scanner_followed']);
  222. unset($_SESSION['scanner_mode']);
  223. unset($_SESSION['scanner_wholeword']);
  224. unset($_SESSION['scanner_published']);
  225. unset($_SESSION['scanner_regex']);
  226. unset($_SESSION['scanner_terms']);
  227. //clear old status:
  228. unset($_SESSION['scanner_status']);
  229. return $output;
  230. }
  231. $scanner_form = drupal_get_form('scanner_form');
  232. $output = drupal_render($scanner_form);
  233. return $output;
  234. }
  235. /**
  236. * The search and replace form.
  237. *
  238. * @param str $search - regex to search for.
  239. * @param str $replace - string to substitute.
  240. * @return $form
  241. */
  242. function scanner_form($node, &$form_state) {
  243. $form = array();
  244. if (isset($_SESSION['scanner_search'])) {
  245. $search = $_SESSION['scanner_search'];
  246. }
  247. else{
  248. $search = NULL;
  249. }
  250. if (isset($_SESSION['scanner_replace'])) {
  251. $replace = $_SESSION['scanner_replace'];
  252. }
  253. else{
  254. $replace = NULL;
  255. }
  256. if (isset($_SESSION['scanner_preceded'])) {
  257. $preceded = $_SESSION['scanner_preceded'];
  258. }
  259. else{
  260. $preceded = NULL;
  261. }
  262. if (isset($_SESSION['scanner_followed'])) {
  263. $followed = $_SESSION['scanner_followed'];
  264. }
  265. else{
  266. $followed = NULL;
  267. }
  268. $mode = isset($_SESSION['scanner_mode']) ? $_SESSION['scanner_mode'] : variable_get('scanner_mode', 0);
  269. $wholeword = isset($_SESSION['scanner_wholeword']) ? $_SESSION['scanner_wholeword'] : variable_get('scanner_wholeword', 0);
  270. $published = isset($_SESSION['scanner_published']) ? $_SESSION['scanner_published'] : variable_get('scanner_published', 1);
  271. $regex = isset($_SESSION['scanner_regex']) ? $_SESSION['scanner_regex'] : variable_get('scanner_regex', 0);
  272. if (isset($_SESSION['scanner_terms'])) {
  273. $terms = $_SESSION['scanner_terms'];
  274. }
  275. else{
  276. $terms = NULL;
  277. }
  278. $form['search'] = array(
  279. '#type' => 'textfield',
  280. '#default_value' => $search,
  281. '#title' => t('Step 1: Search for'),
  282. '#maxlength' => 256,
  283. );
  284. $form['submit_search'] = array(
  285. '#type' => 'submit',
  286. '#value' => t('Search'),
  287. );
  288. $form['replace'] = array(
  289. '#type' => 'textfield',
  290. '#default_value' => $replace,
  291. '#title' => t('Step 2: Replace with'),
  292. '#maxlength' => 256,
  293. );
  294. $form['submit_replace'] = array(
  295. '#type' => 'submit',
  296. '#value' => t('Replace'),
  297. );
  298. $form['options'] = array(
  299. '#type' => 'fieldset',
  300. '#title' => t('Search Options'),
  301. '#collapsible' => TRUE,
  302. '#collapsed' => FALSE,
  303. );
  304. $form['options']['surrounding'] = array(
  305. '#type' => 'fieldset',
  306. '#title' => t('Surrounding Text'),
  307. '#collapsible' => FALSE,
  308. '#description' => t('You can limit matches by providing the text that should appear immediately before or after the search text. Remember to account for spaces. Note: Case sensitivity and regular expression options will all apply here, too. Whole word is not recommended.'),
  309. );
  310. $form['options']['surrounding']['preceded'] = array(
  311. '#type' => 'textfield',
  312. '#title' => t('Preceded by'),
  313. '#default_value' => $preceded,
  314. '#maxlength' => 256,
  315. );
  316. /* TODO: for possible future implementation...
  317. * Depends on whether negative lookahead and negative lookbehind
  318. * can accurately be approximated in MySQL...
  319. $form['options']['surrounding']['notpreceded'] = array(
  320. '#type' => 'checkbox',
  321. '#title' => t('NOT preceded by the text above'),
  322. '#default_value' => $notpreceded,
  323. );
  324. */
  325. $form['options']['surrounding']['followed'] = array(
  326. '#type' => 'textfield',
  327. '#title' => t('Followed by'),
  328. '#default_value' => $followed,
  329. '#maxlength' => 256,
  330. );
  331. /* TODO: for possible future implementation...
  332. * Depends on whether negative lookahead and negative lookbehind
  333. * can accurately be approximated in MySQL...
  334. $form['options']['surrounding']['notfollowed'] = array(
  335. '#type' => 'checkbox',
  336. '#title' => t('NOT followed by the text above'),
  337. '#default_value' => $notfollowed,
  338. );
  339. */
  340. $form['options']['mode'] = array(
  341. '#type' => 'checkbox',
  342. '#title' => t('Case sensitive search'),
  343. '#default_value' => $mode,
  344. '#description' => t("Check this if the search should only return results that exactly match the capitalization of your search terms."),
  345. );
  346. $form['options']['wholeword'] = array(
  347. '#type' => 'checkbox',
  348. '#title' => t('Match whole word'),
  349. '#default_value' => $wholeword,
  350. '#description' => t("Check this if you don't want the search to match any partial words. For instance, if you search for 'run', a whole word search will <em>not</em> match 'running'."),
  351. );
  352. $form['options']['regex'] = array(
  353. '#type' => 'checkbox',
  354. '#title' => t('Use regular expressions in search'),
  355. '#default_value' => $regex,
  356. '#description' => t('Check this if you want to use regular expressions in your search terms.'),
  357. );
  358. $form['options']['published'] = array(
  359. '#type' => 'checkbox',
  360. '#title' => t('Published nodes only'),
  361. '#default_value' => $published,
  362. '#description' => t('Check this if you only want your search and replace to affect fields in nodes that are published.'),
  363. );
  364. $scanner_vocabularies = array_filter(variable_get('scanner_vocabulary', array()));
  365. if (count($scanner_vocabularies)) {
  366. $vocabularies = taxonomy_get_vocabularies();
  367. $options = array();
  368. foreach ($vocabularies as $vid => $vocabulary) {
  369. if (in_array($vid, $scanner_vocabularies) ) {
  370. $tree = taxonomy_get_tree($vid);
  371. if ($tree && (count($tree) > 0)) {
  372. $options[$vocabulary->name] = array();
  373. foreach ($tree as $term) {
  374. $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
  375. }
  376. }
  377. }
  378. }
  379. $form['options']['terms'] = array(
  380. '#type' => 'select',
  381. '#title' => t('Only match nodes with these terms'),
  382. '#options' => $options,
  383. '#default_value' => $terms,
  384. '#multiple' => TRUE,
  385. );
  386. }
  387. return $form;
  388. }
  389. /**
  390. * Validate form input.
  391. */
  392. function scanner_form_validate($form, &$form_state) {
  393. $search = trim($form_state['values']['search']);
  394. if ($search == '') {
  395. form_set_error('search', t('Please enter some keywords.'));
  396. }
  397. }
  398. /**
  399. * Handles submission of the search and replace form.
  400. *
  401. * @param $form
  402. * @param $form_state
  403. * @return the new path that will be goto'ed.
  404. */
  405. function scanner_form_submit($form, &$form_state) {
  406. //save form input:
  407. $_SESSION['scanner_search'] = $form_state['values']['search'];
  408. $_SESSION['scanner_preceded'] = $form_state['values']['preceded'];
  409. //$_SESSION['scanner_notpreceded'] = $form_state['values']['notpreceded'];
  410. $_SESSION['scanner_followed'] = $form_state['values']['followed'];
  411. //$_SESSION['scanner_notfollowed'] = $form_state['values']['notfollowed'];
  412. $_SESSION['scanner_mode'] = $form_state['values']['mode'];
  413. $_SESSION['scanner_wholeword'] = $form_state['values']['wholeword'];
  414. $_SESSION['scanner_published'] = $form_state['values']['published'];
  415. $_SESSION['scanner_regex'] = $form_state['values']['regex'];
  416. if (isset($form_state['values']['terms'])) {
  417. $_SESSION['scanner_terms'] = $form_state['values']['terms'];
  418. }
  419. $_SESSION['scanner_replace'] = $form_state['values']['replace'];
  420. /* TODO The 'op' element in the form values is deprecated.
  421. Each button can have #validate and #submit functions associated with it.
  422. Thus, there should be one button that submits the form and which invokes
  423. the normal form_id_validate and form_id_submit handlers. Any additional
  424. buttons which need to invoke different validate or submit functionality
  425. should have button-specific functions. */
  426. if ($form_state['values']['op'] == 'Replace') {
  427. $_SESSION['scanner_status'] = SCANNER_STATUS_GO_CONFIRM;
  428. }
  429. else {
  430. $_SESSION['scanner_status'] = SCANNER_STATUS_GO_SEARCH;
  431. }
  432. $form_state['redirect'] = 'admin/content/scanner';
  433. }
  434. /**
  435. * Scanner confirmation form to prevent people from accidentally
  436. * replacing things they don't intend to.
  437. */
  438. function scanner_confirm_form($form, &$form_state) {
  439. //using set_html_head because it seems unecessary to load a separate css
  440. // file for just one declaration:
  441. //you can override the styles by declaring with something "higher up"
  442. // the chain, like: #wrapper #scanner-confirm-form .scanner-buttons .scanner-button-msg {...}
  443. drupal_add_css('
  444. #scanner-confirm-form .scanner-buttons .scanner-button-msg {
  445. position:absolute;
  446. top:0; left:0; z-index:100;
  447. width:100%; height:100%;
  448. background-color:#000; opacity:0.75;
  449. font-size:1.2em;
  450. }
  451. #scanner-confirm-form .scanner-buttons .scanner-button-msg p {
  452. color:#fff;
  453. }
  454. ', array('type' => 'inline'));
  455. //javascript to prevent further clicks on confirmation button after it's clicked once.
  456. //unfortunately we can't just use css disable to disable the button because then
  457. // the op values aren't sent to drupal correctly.
  458. drupal_add_js("
  459. $(document).ready(function() {
  460. $('input[@type=submit][@value=Yes, Continue]').click(function() {
  461. $('.scanner-buttons').css('position','relative')
  462. .append('<div class=\"scanner-button-msg\"><p>Replacing items... please wait...</p></div>')
  463. $('.scanner-button-msg').click(function() { return false; });
  464. return true;
  465. });
  466. });
  467. ", array('type' => 'inline', 'scope' => JS_DEFAULT));
  468. $form = array();
  469. $search = $_SESSION['scanner_search'];
  470. $replace = $_SESSION['scanner_replace'];
  471. $preceded = $_SESSION['scanner_preceded'];
  472. $followed = $_SESSION['scanner_followed'];
  473. $wholeword = $_SESSION['scanner_wholeword'];
  474. $regex = $_SESSION['scanner_regex'];
  475. $mode = $_SESSION['scanner_mode'];
  476. $modetxt = ($mode) ? t('Case sensitive') : t('Not case sensitive: will replace any matches regardless of capitalization.');
  477. $msg = (
  478. '<p>' . t('Are you sure you want to make the following replacement?') . '</p>' .
  479. '<div class="scanner-confirm">' .
  480. ' <label>' . t('Search for') . ':</label> [' . check_plain($search) . ']' .
  481. '</div>'
  482. );
  483. if ($preceded) {
  484. $msg .= (
  485. '<div class="scanner-confirm">' .
  486. ' <label>' . t('Preceded by') . ':</label> [' . check_plain($preceded) . ']' .
  487. '</div>'
  488. );
  489. }
  490. if ($followed) {
  491. $msg .= (
  492. '<div class="scanner-confirm">' .
  493. ' <label>' . t('Followed by') . ':</label> [' . check_plain($followed) . ']' .
  494. '</div>'
  495. );
  496. }
  497. $msg .= (
  498. '<div class="scanner-confirm">' .
  499. ' <label>' . t('Replace with') . ':</label> [' . check_plain($replace) . ']'
  500. );
  501. if ($replace === '') {
  502. $msg .= ' <span class="warning">This will delete any occurences of the search terms!</span>';
  503. }
  504. $msg .= (
  505. '</div>' .
  506. '<div class="scanner-confirm">' .
  507. ' <label>' . t('Mode') . ':</label> ' . $modetxt .
  508. '</div>'
  509. );
  510. if ($wholeword) {
  511. $msg .= (
  512. '<div class="scanner-confirm">' .
  513. ' <label>' . t('Match whole word') . ':</label> ' . t('Yes') .
  514. '</div>'
  515. );
  516. }
  517. if ($regex) {
  518. $msg .= (
  519. '<div class="scanner-confirm">' .
  520. ' <label>' . t('Use regular expressions') . ':</label> ' . t('Yes') .
  521. '</div>'
  522. );
  523. }
  524. $form['warning'] = array(
  525. '#type' => 'item',
  526. '#markup' => $msg,
  527. );
  528. $form['confirm'] = array(
  529. '#type' => 'submit',
  530. '#value' => t('Yes, Continue'),
  531. '#prefix' => '<div class="scanner-buttons">', //see suffix in cancel button element
  532. );
  533. $form['cancel'] = array(
  534. '#type' => 'submit',
  535. '#value' => t('No, Cancel'),
  536. '#suffix' => '</div>', //see prefix in confirm button element
  537. );
  538. return $form;
  539. }
  540. /**
  541. * Submission handling for scanner confirmation form.
  542. */
  543. function scanner_confirm_form_submit($form, &$form_state) {
  544. /* TODO The 'op' element in the form values is deprecated.
  545. Each button can have #validate and #submit functions associated with it.
  546. Thus, there should be one button that submits the form and which invokes
  547. the normal form_id_validate and form_id_submit handlers. Any additional
  548. buttons which need to invoke different validate or submit functionality
  549. should have button-specific functions. */
  550. if ($form_state['values']['op'] == t('Yes, Continue')) {
  551. $_SESSION['scanner_status'] = SCANNER_STATUS_GO_REPLACE;
  552. }
  553. else {
  554. unset($_SESSION['scanner_status']);
  555. }
  556. $form_state['redirect'] = 'admin/content/scanner';
  557. }
  558. /**
  559. * @todo Please document this function.
  560. * @see http://drupal.org/node/1354
  561. */
  562. function scanner_undo_page() {
  563. $header = array(t('Date'), t('Searched'), t('Replaced'), t('Count'), t('Operation'));
  564. $undoQuery = db_select('scanner', 's');
  565. $undoQuery->fields('s', array('undo_id', 'time', 'searched', 'replaced', 'count', 'undone'))
  566. ->orderBy('undo_id', 'DESC');
  567. $sandrs = $undoQuery->execute();
  568. $rows = array();
  569. foreach ($sandrs as $sandr) {
  570. if ($sandr->undone) {
  571. $operation = l(t('Redo'), 'admin/content/scanner/undo/confirm', array('query' => array('undo_id' => $sandr->undo_id)));
  572. }
  573. else {
  574. $operation = l(t('Undo'), 'admin/content/scanner/undo/confirm', array('query' => array('undo_id' => $sandr->undo_id)));
  575. }
  576. $rows[] = array(
  577. format_date($sandr->time),
  578. check_plain($sandr->searched),
  579. check_plain($sandr->replaced),
  580. $sandr->count,
  581. $operation,
  582. );
  583. }
  584. return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => NULL, 'caption' => 'Prior Search and Replace Events'));
  585. }
  586. /**
  587. * @todo Please document this function.
  588. * @see http://drupal.org/node/1354
  589. */
  590. function scanner_undo_confirm_form($form, &$form_state) {
  591. $undo_id = $_GET['undo_id'];
  592. if ($undo_id > 0) {
  593. $query = db_select('scanner', 's');
  594. $query->fields('s', array('undo_id', 'searched', 'replaced'))
  595. ->condition('undo_id', $undo_id, '=');
  596. $result = $query->execute();
  597. foreach ($result as $undo) {
  598. $undo = $undo;
  599. }
  600. }
  601. if ($undo->undo_id > 0) {
  602. $form['info'] = array(
  603. '#markup' => '<h2>' . t('Do you want to undo:') . '</h2>' .
  604. '<h3>' . t('Searched for:') . '</h3>' .
  605. '<p>[<em>' . check_plain($undo->searched) . '</em>]</p>' .
  606. '<h3>' . t('Replaced with:') . '</h3>' .
  607. '<p>[<em>' . check_plain($undo->replaced) . '</em>]</p>',
  608. );
  609. $form['undo_id'] = array(
  610. '#type' => 'hidden',
  611. '#value' => $undo->undo_id,
  612. );
  613. $form['confirm'] = array(
  614. '#type' => 'submit',
  615. '#value' => t('Yes, Continue'),
  616. );
  617. $form['cancel'] = array(
  618. '#type' => 'submit',
  619. '#value' => t('No, Cancel'),
  620. );
  621. }
  622. else {
  623. $form['info'] = array(
  624. '#value' => '<h2>' . t('No undo event was found') . '</h2>',
  625. );
  626. }
  627. return $form;
  628. }
  629. /**
  630. * @todo Please document this function.
  631. * @see http://drupal.org/node/1354
  632. */
  633. function scanner_undo_confirm_form_submit($form, &$form_state) {
  634. /* TODO The 'op' element in the form values is deprecated.
  635. Each button can have #validate and #submit functions associated with it.
  636. Thus, there should be one button that submits the form and which invokes
  637. the normal form_id_validate and form_id_submit handlers. Any additional
  638. buttons which need to invoke different validate or submit functionality
  639. should have button-specific functions. */
  640. if ($form_state['values']['op'] == t('Yes, Continue')) {
  641. $query = db_select('scanner', 's');
  642. $query->fields('s', array('undo_data', 'undone'))
  643. ->condition('undo_id', $form_state['values']['undo_id'], '=');
  644. $results = $query->execute();
  645. foreach ($results as $undo) {
  646. $undo = $undo;
  647. }
  648. $undos = unserialize($undo->undo_data);
  649. $count = NULL;
  650. foreach ($undos as $nid => $sandr_event) {
  651. if ($undo->undone == 0) {
  652. $vid = $sandr_event['old_vid'];
  653. $undone = 1;
  654. }
  655. else {
  656. $vid = $sandr_event['new_vid'];
  657. $undone = 0;
  658. }
  659. $node = node_load($nid, $vid);
  660. $node->revision = TRUE;
  661. $node->log = t('Copy of the revision from %date via Search and Replace Undo', array('%date' => format_date($node->revision_timestamp)));
  662. node_save($node);
  663. ++$count;
  664. }
  665. drupal_set_message($count . ' ' . t('Nodes reverted'));
  666. // TODO Please review the conversion of this statement to the D7 database API syntax.
  667. db_update('scanner')
  668. ->fields(array(
  669. 'undone' => $undone,
  670. ))
  671. ->condition('undo_id', $form_state['values']['undo_id'])
  672. ->execute();
  673. }
  674. else {
  675. drupal_set_message(t('Undo / Redo canceled'));
  676. }
  677. $form_state['redirect'] = 'admin/content/scanner/undo';
  678. $form_state['nid'] = $node->nid;
  679. }
  680. /**
  681. * Handles the actual search and replace.
  682. *
  683. * @param str $searchtype - either 'search', or 'replace'
  684. * @return The themed results.
  685. */
  686. function scanner_execute($searchtype = 'search') {
  687. global $user;
  688. // variables to monitor possible timeout
  689. $max_execution_time = ini_get('max_execution_time');
  690. $start_time = REQUEST_TIME;
  691. $expanded = FALSE;
  692. // get process and undo data if saved from timeout
  693. $processed = variable_get('scanner_partially_processed_' . $user->uid, array());
  694. $undo_data = variable_get('scanner_partial_undo_' . $user->uid, array());
  695. unset($_SESSION['scanner_status']);
  696. $results = NULL;
  697. $search = $_SESSION['scanner_search'];
  698. $replace = $_SESSION['scanner_replace'];
  699. $preceded = $_SESSION['scanner_preceded'];
  700. //$notpreceded = $_SESSION['scanner_notpreceded'];
  701. $followed = $_SESSION['scanner_followed'];
  702. //$notfollowed = $_SESSION['scanner_notfollowed'];
  703. $mode = $_SESSION['scanner_mode'];
  704. $wholeword = $_SESSION['scanner_wholeword'];
  705. $published = $_SESSION['scanner_published'];
  706. $regex = $_SESSION['scanner_regex'];
  707. if (isset($_SESSION['scanner_terms'])) {
  708. $terms = $_SESSION['scanner_terms'];
  709. }
  710. else{
  711. $terms = NULL;
  712. }
  713. if ($searchtype == 'search') {
  714. drupal_set_message(t('Scanning for: [%search] ...', array('%search' => $search)));
  715. }
  716. else { //searchtype == 'replace'
  717. drupal_set_message(t('Replacing [%search] with [%replace] ...', array('%search' => $search, '%replace' => $replace)));
  718. }
  719. if ($mode) { // Case Sensitive
  720. $flag = NULL;
  721. }
  722. else { // Case Insensitive
  723. $flag = 'i'; //ci flag for use in php preg_search and preg_replace
  724. }
  725. $preceded_php = '';
  726. if (!empty($preceded)) {
  727. if (!$regex) {
  728. $preceded = addcslashes($preceded, SCANNER_REGEX_CHARS);
  729. }
  730. $preceded_php = '(?<=' . $preceded . ')';
  731. }
  732. $followed_php = '';
  733. if (!empty($followed)) {
  734. if (!$regex) {
  735. $followed = addcslashes($followed, SCANNER_REGEX_CHARS);
  736. }
  737. $followed_php = '(?=' . $followed . ')';
  738. }
  739. //Case 1:
  740. if ($wholeword && $regex) {
  741. $where = "[[:<:]]" . $search . "[[:>:]]";
  742. $search_db = $preceded . $search . $followed;
  743. $search_php = '\b' . $preceded_php . $search . $followed_php . '\b';
  744. }
  745. //Case 2:
  746. elseif ($wholeword && !$regex) {
  747. $where = "[[:<:]]" . $search . "[[:>:]]";
  748. $search_db = $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed;
  749. $search_php = '\b' . $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php . '\b';
  750. }
  751. //Case 3:
  752. elseif (!$wholeword && $regex) {
  753. $where = $search;
  754. $search_db = $preceded . $search . $followed;
  755. $search_php = $preceded_php . $search . $followed_php;
  756. }
  757. //Case 4:
  758. else { //!wholeword and !regex:
  759. $where = $search;
  760. $search_db = $preceded . addcslashes($search, SCANNER_REGEX_CHARS) . $followed;
  761. $search_php = $preceded_php . addcslashes($search, SCANNER_REGEX_CHARS) . $followed_php;
  762. }
  763. //if terms selected, then put together extra join and where clause:
  764. $join = '';
  765. if (is_array($terms) && count($terms)) {
  766. $terms_where = array();
  767. $terms_params = array();
  768. foreach ($terms as $term) {
  769. $terms_where[] = 'tn.tid = %d';
  770. $terms_params[] = $term;
  771. }
  772. $join = 'INNER JOIN {taxonomy_term_node} tn ON t.nid = tn.nid';
  773. $where .= ' AND (' . implode(' OR ', $terms_where) . ')';
  774. }
  775. $tables_map = _scanner_get_selected_tables_map();
  776. foreach ( $tables_map as $map ) {
  777. $table = $map['table'];
  778. $field = $map['field'];
  779. $type = $map['type'];
  780. $query_params = array($field, $table, $type, $field, $search_db);
  781. if (!empty($join)) {
  782. $query_params = array_merge($query_params, $terms_params);
  783. }
  784. // TODO Please convert this statement to the D7 database API syntax.
  785. $query = db_select($table, 't');
  786. if ($table == 'node_revision') {
  787. $nid = 'nid';
  788. $vid = 'vid';
  789. }
  790. else{
  791. $field = $field . '_value';
  792. $nid = 'entity_id';
  793. $vid = 'revision_id';
  794. }
  795. $query->join('node', 'n', 't.' . $vid . ' = n.vid'); //Must use vid and revision_id here. Make sure it saves as new revision.
  796. if (is_array($terms) && count($terms)) {
  797. $db_or = db_or();
  798. $query->join('taxonomy_index', 'tx', 't.' . $nid . ' = tx.nid');
  799. foreach ($terms as $term) {
  800. $db_or->condition('tx.tid', $term);
  801. }
  802. $query->condition($db_or);
  803. }
  804. $query->addField('t', $field, 'content');
  805. $query->fields('n', array('nid', 'title'));
  806. $query->condition('n.type', $type, '=');
  807. if ($mode) {
  808. $query->condition('t.' . $field, $search_db, 'REGEXP BINARY');
  809. }
  810. else{
  811. $query->condition('t.' . $field, $search_db, 'REGEXP');
  812. }
  813. if ($published) {
  814. $query->condition('n.status', '1', '=');
  815. }
  816. $result = $query->execute();
  817. $shutting_down = FALSE;
  818. foreach ($result as $row) {
  819. $content = $row->content;
  820. $matches = array();
  821. $text = '';
  822. // checking for possible timeout
  823. // if within 5 seconds of timeout - attempt to expand environment
  824. if (REQUEST_TIME >= ($start_time + $max_execution_time - 5)) {
  825. if (!$expanded) {
  826. if ($user->uid > 0) {
  827. $verbose = TRUE;
  828. }
  829. else {
  830. $verbose = FALSE;
  831. }
  832. if (_scanner_change_env('max_execution_time', '600', $verbose)) {
  833. drupal_set_message(t('Default max_execution_time too small and changed to 10 minutes.'), 'error');
  834. $max_execution_time = 600;
  835. }
  836. $expanded = TRUE;
  837. }
  838. // if expanded environment still running out of time - shutdown process
  839. else {
  840. $shutting_down = TRUE;
  841. variable_set('scanner_partially_processed_' . $user->uid, $processed);
  842. variable_set('scanner_partial_undo_' . $user->uid, $undo_data);
  843. if ($searchtype == 'search') {
  844. drupal_set_message(t('Did not have enough time to complete search.'), 'error');
  845. }
  846. else {
  847. drupal_set_message(t('Did not have enough time to complete. Please re-submit replace'), 'error');
  848. }
  849. break 2;
  850. }
  851. }
  852. /*
  853. * SEARCH
  854. */
  855. if ($searchtype == 'search') {
  856. //pull out the terms and highlight them for display in search results:
  857. $regexstr = "/(.{0,130}?)($search_php)(.{0,130})/$flag";
  858. $hits = preg_match_all($regexstr, $content, $matches, PREG_SET_ORDER);
  859. if ($hits > 0) {
  860. foreach ( $matches as $match ) {
  861. if ( $match[1] ) {
  862. $text .= '...' . htmlentities($match[1], ENT_COMPAT, 'UTF-8');
  863. }
  864. $text .= '<strong>' . htmlentities($match[2], ENT_COMPAT, 'UTF-8') . '</strong>';
  865. if ( $match[3] ) {
  866. $text .= htmlentities($match[3], ENT_COMPAT, 'UTF-8') . '...';
  867. }
  868. }
  869. }
  870. else {
  871. $text = "<div class='warning'>" . t("Can't display search result due to conflict between search term and internal preg_match_all function.") . '</div>';
  872. }
  873. $results[] = array(
  874. 'title' => $row->title,
  875. 'type' => $type,
  876. 'count' => $hits,
  877. 'field' => $field,
  878. 'nid' => $row->nid,
  879. 'text' => $text,
  880. );
  881. }
  882. /*
  883. * REPLACE
  884. * + check to see if already processed
  885. */
  886. elseif (!isset($processed[$field][$row->nid])) {
  887. $hits = 0;
  888. $newcontent = preg_replace("/$search_php/$flag", $replace, $content, -1, $hits);
  889. $thenode = node_load($row->nid);
  890. //see if we're dealing with a CCK text field and therefore need to strip the
  891. // "_value" off the end:
  892. preg_match('/(.+)_value$/', $field, $matches);
  893. if (empty($matches[0])) { //if not CCK text field:
  894. $thenode->$field = $newcontent;
  895. }
  896. else {
  897. //Is this the best way to copy the new content back into the node's CCK field???
  898. //$tmpstr = ('$thenode->' . $matches[1] . '[$thenode->language][0]["value"] = $newcontent;');
  899. //eval($tmpstr);
  900. //This is a better way
  901. $thenode->{$matches[1]}[$thenode->language][0]["value"] = $newcontent;
  902. }
  903. // NOTE: a revision only created for the first change of the node.
  904. // subsequent changes of the same node do not generate additional revisions:
  905. if (!isset($undo_data[$thenode->nid]['new_vid'])) {
  906. $thenode->revision = TRUE;
  907. $thenode->log = t('@name replaced %search with %replace via Scanner Search and Replace module.', array('@name' => $user->name, '%search' => $search, '%replace' => $replace));
  908. $undo_data[$thenode->nid]['old_vid'] = $thenode->vid;
  909. }
  910. /* TODO MAKE THIS WORK WITH SUMMARY, TEASERS DON'T EXIST
  911. if (variable_get('scanner_rebuild_teasers', 1)) {
  912. $thenode->teaser = node_teaser($thenode->body, $thenode->format);
  913. }
  914. */
  915. node_save($thenode);
  916. // array to log completed fields in case of shutdown
  917. $processed[$field][$row->nid] = TRUE;
  918. // undo data construction
  919. $undo_data[$thenode->nid]['new_vid'] = $thenode->vid; //now set to updated vid after node_save()
  920. $results[] = array(
  921. 'title' => $thenode->title,
  922. 'type' => $thenode->type,
  923. 'count' => $hits,
  924. 'field' => $field,
  925. 'nid' => $thenode->nid,
  926. );
  927. }
  928. } //end foreach
  929. } //end foreach
  930. // if completed
  931. if (!$shutting_down) {
  932. variable_del('scanner_partially_processed_' . $user->uid);
  933. variable_del('scanner_partial_undo_' . $user->uid);
  934. }
  935. if ($searchtype == 'search') {
  936. return theme('scanner_results', array('results' => $results));
  937. }
  938. else { //searchtype == 'replace'
  939. if (count($undo_data) && !$shutting_down) {
  940. // TODO Please review the conversion of this statement to the D7 database API syntax.
  941. $id = db_insert('scanner')
  942. ->fields(array(
  943. 'undo_data' => serialize($undo_data),
  944. 'undone' => 0,
  945. 'searched' => $search,
  946. 'replaced' => $replace,
  947. 'count' => count($undo_data),
  948. 'time' => REQUEST_TIME,
  949. ))
  950. ->execute();
  951. }
  952. return theme('scanner_replace_results', array('results' => $results));
  953. }
  954. }
  955. // ***************************************************************************
  956. // Settings ******************************************************************
  957. // ***************************************************************************
  958. /**
  959. * Search and Replace Settings form.
  960. *
  961. * @return $form
  962. */
  963. function scanner_admin_form($node, &$form_state) {
  964. drupal_set_title(t('Scanner Settings'));
  965. $output= '';
  966. $table_map = _scanner_get_selected_tables_map();
  967. if ($table_map) {
  968. $output = '<p>Fields that will be searched (in [nodetype: fieldname] order):</p><ul>' . $output . '</ul>';
  969. sort($table_map);
  970. foreach ($table_map as $item) {
  971. $output .= '<li><b>' . $item['type'] . ':</b> ' . $item['field'] . '</li>';
  972. }
  973. }
  974. else{
  975. $output = '<p>There are currently no selected elements to be scanned</p>';
  976. }
  977. $form['selected'] = array(
  978. '#type' => 'fieldset',
  979. '#title' => t('Current Settings'),
  980. '#collapsible' => TRUE,
  981. '#description' => filter_xss($output, $allowed_tags = array('b', 'ul', 'li', 'p')),
  982. );
  983. $form['settings'] = array(
  984. '#type' => 'fieldset',
  985. '#title' => t('Scanner Options'),
  986. '#collapsible' => TRUE,
  987. );
  988. $form['settings']['scanner_mode'] = array(
  989. '#type' => 'checkbox',
  990. '#title' => t('Default: Case Sensitive Search Mode'),
  991. '#default_value' => variable_get('scanner_mode', 0),
  992. );
  993. $form['settings']['scanner_wholeword'] = array(
  994. '#type' => 'checkbox',
  995. '#title' => t('Default: Match Whole Word'),
  996. '#default_value' => variable_get('scanner_wholeword', 0),
  997. );
  998. $form['settings']['scanner_regex'] = array(
  999. '#type' => 'checkbox',
  1000. '#title' => t('Default: Regular Expression Search'),
  1001. '#default_value' => variable_get('scanner_regex', 0),
  1002. );
  1003. $form['settings']['scanner_published'] = array(
  1004. '#type' => 'checkbox',
  1005. '#title' => t('Default: Search Published Nodes Only'),
  1006. '#default_value' => variable_get('scanner_published', 1),
  1007. );
  1008. /* TURN OFF TEASER (SUMMARY) REBUILD FOR NOW
  1009. $form['settings']['scanner_rebuild_teasers'] = array(
  1010. '#type' => 'checkbox',
  1011. '#title' => t('Rebuild Teasers on Replace'),
  1012. '#default_value' => variable_get('scanner_rebuild_teasers', 1),
  1013. '#description' => t('If this box is checked: The teasers for any nodes that are modified in a search-and-replace action will be rebuilt to reflect the replacements in other fields; you do not need to check any teaser fields for nodes in the "Fields" section below. If this box is unchecked: Teasers will remain untouched; you can select specific teaser fields below to include in search-and-replaces.'),
  1014. );
  1015. */
  1016. if (module_exists('taxonomy')) {
  1017. $vocabularies = taxonomy_get_vocabularies();
  1018. if (count($vocabularies)) {
  1019. $options = array();
  1020. foreach ($vocabularies as $vocabulary) {
  1021. $options[$vocabulary->vid] = $vocabulary->name;
  1022. }
  1023. $form['settings']['scanner_vocabulary'] = array(
  1024. '#type' => 'checkboxes',
  1025. '#title' => t("Allow restrictions by terms in a vocabulary"),
  1026. '#options' => $options,
  1027. '#default_value' => variable_get('scanner_vocabulary', array()),
  1028. );
  1029. }
  1030. }
  1031. $form['tables'] = array(
  1032. '#type' => 'fieldset',
  1033. '#title' => t('Fields that can be searched'),
  1034. '#description' => t('Fields are listed in [nodetype: fieldname] order:'),
  1035. '#collapsible' => TRUE,
  1036. );
  1037. $table_map = _scanner_get_all_tables_map();
  1038. sort($table_map);
  1039. foreach ($table_map as $item) {
  1040. $key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type'];
  1041. $form['tables'][$key] = array(
  1042. '#type' => 'checkbox',
  1043. '#title' => filter_xss('<b>' . $item['type'] . ':</b> ' . $item['field'], $allowed_values = array('b', 'p')),
  1044. '#default_value' => variable_get($key, FALSE), // default to not checked
  1045. );
  1046. }
  1047. return system_settings_form($form);
  1048. }
  1049. // ***************************************************************************
  1050. // Internal Utility Functions ************************************************
  1051. // ***************************************************************************
  1052. /**
  1053. * Get all text fields.
  1054. * This is all very fragle based on how CCK stores fields.
  1055. * Works for CCK 1.6.
  1056. *
  1057. * @return map of fields and tables.
  1058. */
  1059. function _scanner_get_all_tables_map() {
  1060. //note, each array in the multidim array that is returned should be in the
  1061. // following order: type, field, table.
  1062. //this ensures that we can use the sort() function to easily sort the array
  1063. // based on the nodetype.
  1064. //build list of title, body, teaser fields for all node types:
  1065. $ntypes = node_type_get_types();
  1066. foreach ($ntypes as $type) {
  1067. if ($type->has_title) {
  1068. $tables_map[] = array(
  1069. 'type' => $type->type,
  1070. 'field' => 'title',
  1071. 'table' => 'node_revision',
  1072. );
  1073. }
  1074. }
  1075. if (module_exists('field')) {
  1076. $query = db_select('field_config_instance', 'f');
  1077. $query->join('field_config', 'fc', 'f.field_name = fc.field_name');
  1078. $query->fields('f', array('field_name', 'bundle'))
  1079. ->condition('f.entity_type', 'node')
  1080. ->condition('fc.module', 'text', '=')
  1081. ->orderBy('bundle', 'ASC');
  1082. $result = $query->execute();
  1083. foreach ($result as $record) {
  1084. $table = 'field_revision_' . $record->field_name;
  1085. $tables_map[] = array(
  1086. 'type' => $record->bundle,
  1087. 'field' => $record->field_name,
  1088. 'table' => $table,
  1089. );
  1090. }
  1091. }
  1092. return $tables_map;
  1093. }
  1094. /**
  1095. * Get the fields that have been selected for scanning.
  1096. *
  1097. * @return map of selected fields and tables.
  1098. */
  1099. function _scanner_get_selected_tables_map() {
  1100. $tables_map = _scanner_get_all_tables_map();
  1101. foreach ($tables_map as $i => $item) {
  1102. $key = 'scanner_' . $item['field'] . '_' . $item['table'] . '_' . $item['type'];
  1103. if (!variable_get($key, FALSE)) {
  1104. unset($tables_map[$i]);
  1105. }
  1106. }
  1107. return $tables_map;
  1108. }
  1109. /**
  1110. * Attempt to stretch the amount of time available for processing so
  1111. * that timeouts don't interrupt search and replace actions.
  1112. *
  1113. * This only works in hosting environments where changing PHP and
  1114. * Apache settings on the fly is allowed.
  1115. */
  1116. function _scanner_change_env($setting, $value, $verbose) {
  1117. $old_value = ini_get($setting);
  1118. if ($old_value != $value && $old_value != 0) {
  1119. if (ini_set($setting, $value)) {
  1120. if ($verbose) {
  1121. drupal_set_message(t('%setting changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value)));
  1122. }
  1123. return TRUE;
  1124. }
  1125. else {
  1126. if ($verbose) {
  1127. drupal_set_message(t('%setting could not be changed from %old_value to %value.', array('%setting' => $setting, '%old_value' => $old_value, '%value' => $value)), 'error');
  1128. }
  1129. return FALSE;
  1130. }
  1131. }
  1132. }
  1133. // ***************************************************************************
  1134. // Theme Functions ***********************************************************
  1135. // ***************************************************************************
  1136. /**
  1137. * The the search results.
  1138. *
  1139. * @param map $results
  1140. * @return html str.
  1141. */
  1142. function theme_scanner_results($variables) {
  1143. $results = $variables['results'];
  1144. $output = NULL;
  1145. if (is_array($results)) {
  1146. $total = count($results);
  1147. drupal_set_message(filter_xss('Found matches in ' . $total . ' fields. <a href="#results">See below</a> for details.', $allowed_tags = array('a')));
  1148. $output = '<p>Found matches in ' . $total . ' fields:</p>';
  1149. $output .= '<ol class="scanner-results scanner-search-results">';
  1150. foreach ($results as $item) {
  1151. $output .= theme('scanner_item', array('item' => $item));
  1152. }
  1153. $output .= '</ol>';
  1154. //TO DO: use pager to split up results
  1155. }
  1156. else {
  1157. drupal_set_message(t('Sorry, we found no matches.'));
  1158. }
  1159. return $output;
  1160. }
  1161. /**
  1162. * Theme each search result hit.
  1163. *
  1164. * @param map $item.
  1165. * @return html str.
  1166. */
  1167. function theme_scanner_item($variables) {
  1168. $output = '';
  1169. $item = $variables['item'];
  1170. $item['count'] = $item['count'] > 0 ? $item['count'] : 'One or more';
  1171. $output .= '<li class="scanner-result">';
  1172. $output .= '<span class="scanner-title">' . l($item['title'], 'node/' . $item['nid']) . '</span><br />';
  1173. $output .= '<span class="scanner-info">[' . $item['count'] . ' matches in ' . $item['type'] . ' ' . $item['field'] . 'field:]</span><br />';
  1174. $output .= '<span class="scanner-text">' . $item['text'] . '</span>';
  1175. $output .= '</li>';
  1176. return $output;
  1177. }
  1178. /**
  1179. * @todo Please document this function.
  1180. * @see http://drupal.org/node/1354
  1181. */
  1182. function theme_scanner_replace_results($variables) {
  1183. $results = $variables['results'];
  1184. $output = '';
  1185. if (is_array($results)) {
  1186. drupal_set_message(filter_xss('Replaced items in ' . count($results) . ' fields. <a href="#results">See below</a> for details.', $allowed_tags = array('a')));
  1187. $output = '<p>Replaced items in ' . count($results) . ' fields:</p>';
  1188. $output .= '<ol class="scanner-results scanner-replace-results">';
  1189. foreach ($results as $item) {
  1190. $output .= theme('scanner_replace_item', array('item' => $item));
  1191. }
  1192. $output .= '</ol>';
  1193. //TO DO: use pager to split up results
  1194. }
  1195. else {
  1196. drupal_set_message(t('Sorry, we found 0 matches.'));
  1197. }
  1198. return $output;
  1199. }
  1200. /**
  1201. * @todo Please document this function.
  1202. * @see http://drupal.org/node/1354
  1203. */
  1204. function theme_scanner_replace_item($variables) {
  1205. $output = '';
  1206. $item = $variables['item'];
  1207. $item['count'] = $item['count'] > 0 ? $item['count'] : 'One or more';
  1208. $output .= '<li class="scanner-result">';
  1209. $output .= '<span class="scanner-title">' . l($item['title'], 'node/' . $item['nid']) . '</span><br />';
  1210. $output .= '<span class="scanner-info">[' . $item['count'] . ' replacements in ' . $item['type'] . ' ' . $item['field'] . ' field]</span>';
  1211. $output .= '</li>';
  1212. return $output;
  1213. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.