destinations.ftp.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /**
  3. * @file
  4. * Functions to handle the FTP backup destination.
  5. */
  6. /**
  7. * A destination for sending database backups to an FTP server.
  8. *
  9. * @ingroup backup_migrate_destinations
  10. */
  11. class backup_migrate_destination_ftp extends backup_migrate_destination_remote {
  12. public $supported_ops = array('scheduled backup', 'manual backup', 'remote backup', 'restore', 'list files', 'configure', 'delete');
  13. public $ftp = NULL;
  14. /**
  15. * Save to the ftp destination.
  16. */
  17. public function _save_file($file, $settings) {
  18. $ftp = $this->ftp_object();
  19. if (drupal_ftp_file_to_ftp($file->filepath(), $file->filename(), '.', $ftp)) {
  20. return $file;
  21. }
  22. return FALSE;
  23. }
  24. /**
  25. * Load from the ftp destination.
  26. */
  27. public function load_file($file_id) {
  28. backup_migrate_include('files');
  29. $file = new backup_file(array('filename' => $file_id));
  30. $this->ftp_object();
  31. if (drupal_ftp_ftp_to_file($file->filepath(), $file_id, '.', $this->ftp)) {
  32. return $file;
  33. }
  34. return FALSE;
  35. }
  36. /**
  37. * Delete from the ftp destination.
  38. */
  39. public function _delete_file($file_id) {
  40. $this->ftp_object();
  41. drupal_ftp_delete_file($file_id, $this->ftp);
  42. }
  43. public function _list_files() {
  44. backup_migrate_include('files');
  45. $files = array();
  46. $this->ftp_object();
  47. $ftp_files = (array) drupal_ftp_file_list('.', $this->ftp);
  48. foreach ($ftp_files as $file) {
  49. $files[$file['filename']] = new backup_file($file);
  50. }
  51. return $files;
  52. }
  53. /**
  54. * Get the form for the settings for this filter.
  55. */
  56. public function edit_form() {
  57. $form = parent::edit_form();
  58. $form['scheme']['#type'] = 'value';
  59. $form['scheme']['#value'] = 'ftp';
  60. $form['port'] = array(
  61. "#type" => "textfield",
  62. "#title" => t("Port"),
  63. "#default_value" => @$this->dest_url['port'] ? $this->dest_url['port'] : '21',
  64. "#weight" => 15,
  65. );
  66. $form['pasv'] = array(
  67. '#type' => 'checkbox',
  68. '#title' => t('Use PASV transfers'),
  69. '#default_value' => $this->get_pasv(),
  70. '#weight' => 50,
  71. );
  72. return $form;
  73. }
  74. public function set_pasv($value) {
  75. $this->settings['pasv'] = (bool) $value;
  76. }
  77. public function get_pasv() {
  78. return isset($this->settings['pasv']) ? $this->settings['pasv'] : FALSE;
  79. }
  80. public function ftp_object() {
  81. if (!$this->ftp) {
  82. $this->dest_url['port'] = empty($this->dest_url['port']) ? '21' : $this->dest_url['port'];
  83. $this->dest_url['pasv'] = $this->get_pasv();
  84. $this->ftp = drupal_ftp_ftp_object($this->dest_url['host'], $this->dest_url['port'], $this->dest_url['user'], $this->dest_url['pass'], $this->dest_url['path'], $this->dest_url['pasv']);
  85. }
  86. return $this->ftp;
  87. }
  88. }
  89. // The FTP code below was taken from the ftp module by Aaron Winborn.
  90. // Inspired by http://www.devarticles.com/c/a/PHP/My-FTP-Wrapper-Class-for-PHP/
  91. // It's been drupalized, however, and most of the bugs from that example have been fixed.
  92. // - winborn 2007-06-22 - 2007-06-28
  93. define('DRUPAL_FTP_FT_DIRECTORY', 0);
  94. define('DRUPAL_FTP_FT_FILE', 1);
  95. /**
  96. * Creates a new ftp object. if any elements of ftp_map are missing, they'll be filled with the server defaults.
  97. */
  98. function drupal_ftp_ftp_object($server, $port, $user, $pass, $dir, $pasv) {
  99. $ftp = new stdClass();
  100. $ftp->__server = $server;
  101. $ftp->__port = $port;
  102. $ftp->__user = $user;
  103. $ftp->__password = $pass;
  104. $ftp->__directory = $dir;
  105. $ftp->__pasv = $pasv;
  106. return $ftp;
  107. }
  108. /**
  109. * The drupal_ftp_connect function
  110. * This function connects to an FTP server and attempts to change into the directory specified by
  111. * the fourth parameter, $directory.
  112. */
  113. function drupal_ftp_connect(&$ftp) {
  114. if (is_null($ftp)) {
  115. $ftp = drupal_ftp_ftp_object();
  116. }
  117. if (!$ftp->__conn && !drupal_ftp_connected($ftp)) {
  118. // Attempt to connect to the remote server.
  119. $ftp->__conn = @ftp_connect($ftp->__server, $ftp->__port);
  120. if (!$ftp->__conn) {
  121. _backup_migrate_message('FTP Error: Couldn\'t connect to server @server', array('@server' => $ftp->__server), 'error');
  122. return FALSE;
  123. }
  124. // Attempt to login to the remote server.
  125. $ftp->__login = @ftp_login($ftp->__conn, $ftp->__user, $ftp->__password);
  126. if (!$ftp->__login) {
  127. _backup_migrate_message('FTP Error: Couldn\'t login as user @ftp_user to @server', array('@ftp_user' => $ftp->__user, '@server' => $ftp->__server), 'error');
  128. return FALSE;
  129. }
  130. // Attempt to change into the working directory.
  131. $chdir = @ftp_chdir($ftp->__conn, $ftp->__directory);
  132. if (!$chdir) {
  133. _backup_migrate_message('FTP Error: Couldn\'t change into the @directory directory', array('@directory' => $ftp->__directory), 'error');
  134. return FALSE;
  135. }
  136. // Set PASV - if needed.
  137. if ($ftp->__pasv) {
  138. $pasv = @ftp_pasv($ftp->__conn, TRUE);
  139. if (!$pasv) {
  140. _backup_migrate_message('FTP Error: Couldn\'t set PASV mode', array(), 'error');
  141. return FALSE;
  142. }
  143. }
  144. }
  145. // Everything worked OK, return TRUE.
  146. return TRUE;
  147. }
  148. /**
  149. * The drupal_ftp_connected function
  150. * This function queries the FTP server with the ftp_systype command to check if the connection is still alive.
  151. * It returns TRUE on success or FALSE on disconnection.
  152. */
  153. function drupal_ftp_connected(&$ftp) {
  154. // Attempt to call the ftp_systype to see if the connect
  155. // to the FTP server is still alive and kicking.
  156. if (is_null($ftp)) {
  157. $ftp = drupal_ftp_ftp_object();
  158. return FALSE;
  159. }
  160. if (!@ftp_systype($ftp->__conn)) {
  161. // The connection is dead.
  162. return FALSE;
  163. }
  164. else {
  165. // The connection is still alive.
  166. return TRUE;
  167. }
  168. }
  169. /**
  170. * This function tries to retrieve the contents of a file from the FTP server.
  171. * Firstly it changes into the $directory directory, and then attempts to download the file $filename.
  172. * The file is saved locally and its contents are returned to the caller of the function.
  173. */
  174. function drupal_ftp_ftp_to_file($file, $filename, $directory, &$ftp) {
  175. // Change into the remote directory and retrieve the content
  176. // of a file. Once retrieve, return this value to the caller.
  177. if (!@drupal_ftp_connect($ftp)) {
  178. return FALSE;
  179. }
  180. // We are now connected, so let's retrieve the file contents.
  181. // Firstly, we change into the directory.
  182. $chdir = @ftp_chdir($ftp->__conn, $directory);
  183. if (!$chdir) {
  184. _backup_migrate_message('FTP Error: Couldn\'t change into directory: @directory', array('@directory' => $directory), 'error');
  185. return FALSE;
  186. }
  187. // We have changed into the directory, let's attempt to get the file.
  188. $fp = @fopen($file, 'wb');
  189. $get_file = @ftp_fget($ftp->__conn, $fp, $filename, FTP_BINARY);
  190. fclose($fp);
  191. $fp = NULL;
  192. if (!$get_file) {
  193. _backup_migrate_message('FTP Error: Unable to download file: @filename from @directory', array('@filename' => $filename, '@directory' => $directory), 'error');
  194. return FALSE;
  195. }
  196. return TRUE;
  197. }
  198. /**
  199. * Send a file to an FTP server.
  200. */
  201. function drupal_ftp_file_to_ftp($file, $ftp_filename, $ftp_directory, &$ftp) {
  202. if (!@drupal_ftp_connect($ftp)) {
  203. return FALSE;
  204. }
  205. if ($source = drupal_realpath($file)) {
  206. // Now we can try to write to the remote file.
  207. $complete_filename = $ftp_directory . '/' . $ftp_filename;
  208. $put_file = @ftp_put($ftp->__conn, $complete_filename, $source, FTP_BINARY);
  209. if (!$put_file) {
  210. _backup_migrate_message('FTP Error: Couldn\'t write to @complete_filename when trying to save file on the ftp server.', array('@complete_filename' => $complete_filename), 'error');
  211. return FALSE;
  212. }
  213. // Everything worked OK.
  214. return TRUE;
  215. }
  216. else {
  217. _backup_migrate_message('FTP Error: Couldn\'t find @file.', array('@file'), 'error');
  218. return FALSE;
  219. }
  220. }
  221. /**
  222. * The drupal_ftp_change_directory Function
  223. * This function simply changes into the $directory folder on the FTP server.
  224. * If a connection or permission error occurs then _backup_migrate_message() will contain the error message.
  225. */
  226. function drupal_ftp_change_directory($directory, &$ftp) {
  227. // Switch to another directory on the web server. If we don't
  228. // have permissions then an error will occur.
  229. if (!@drupal_ftp_connect($ftp)) {
  230. return FALSE;
  231. }
  232. // Try and change into another directory.
  233. $chdir = ftp_chdir($ftp->__conn, $directory);
  234. if (!$chdir) {
  235. _backup_migrate_message('FTP Error: Couldn\'t change into directory: @directory', array('@directory', $directory), 'error');
  236. return FALSE;
  237. }
  238. else {
  239. // Changing directories worked OK.
  240. return TRUE;
  241. }
  242. }
  243. /**
  244. * The drupal_ftp_file_list Function
  245. * This function will change into the $directory folder and get a list of files and directories contained in that folder.
  246. * This function still needs a lot of work, but should work in most cases.
  247. */
  248. function drupal_ftp_file_list($directory, &$ftp) {
  249. // This function will attempt to change into the specified
  250. // directory and retrieve a list of files as an associative
  251. // array. This list will include file name, size and date last modified.
  252. $file_array = array();
  253. // Can we switch to the desired directory?
  254. if (!drupal_ftp_change_directory($directory, $ftp)) {
  255. return FALSE;
  256. }
  257. // We are in the directory, let's retrieve a list of files
  258. // This is slower than parsing the raw return values, but it is faster.
  259. $file_list = ftp_nlist($ftp->__conn, $directory);
  260. // Save the list of files.
  261. if (@is_array($file_list)) {
  262. // Interate through the array.
  263. foreach ($file_list as $file) {
  264. $file_array[] = array(
  265. 'filename' => $file,
  266. 'filesize' => ftp_size($ftp->__conn, $directory . "/" . $file),
  267. 'filetime' => ftp_mdtm($ftp->__conn, $directory . "/" . $file),
  268. );
  269. }
  270. }
  271. sort($file_array);
  272. return $file_array;
  273. }
  274. /**
  275. * The drupal_ftp_create_directory Function
  276. * This function tries to make a new directory called $folder_name on the FTP server.
  277. * If it can create the folder, then the folder is given appropriate rights with the CHMOD command.
  278. */
  279. function drupal_ftp_create_directory($folder_name, &$ftp) {
  280. // Makes a new folder on the web server via FTP.
  281. if (!@drupal_ftp_connect($ftp)) {
  282. return FALSE;
  283. }
  284. $create_result = @ftp_mkdir($ftp->__conn, $folder_name);
  285. if ($create_result == TRUE) {
  286. // Can we change the files permissions?
  287. $exec_result = @ftp_site($ftp->__conn, 'chmod 0777 ' . $folder_name . '/');
  288. if ($exec_result == TRUE) {
  289. return TRUE;
  290. }
  291. else {
  292. _backup_migrate_message('FTP Error: Couldn\'t set owner permissions on @folder.', array('@folder', $folder_name), 'error');
  293. return FALSE;
  294. }
  295. }
  296. else {
  297. _backup_migrate_message('FTP Error: Couldn\'t create new folder @folder.', array('@folder', $folder_name), 'error');
  298. return FALSE;
  299. }
  300. }
  301. /**
  302. * The drupal_ftp_delete_file Function
  303. * This function attempts to delete a file called $filename from the FTP server.
  304. */
  305. function drupal_ftp_delete_file($filename, &$ftp) {
  306. // Remove the specified file from the FTP server.
  307. if (!@drupal_ftp_connect($ftp)) {
  308. return FALSE;
  309. }
  310. $delete_result = @ftp_delete($ftp->__conn, $filename);
  311. if ($delete_result == TRUE) {
  312. // The file/folder was renamed successfully.
  313. return TRUE;
  314. }
  315. else {
  316. // Couldn't delete the selected file.
  317. _backup_migrate_message('FTP Error: Couldn\'t delete the selected file: @filename', array('@filename' => $filename), 'error');
  318. return FALSE;
  319. }
  320. }
  321. /**
  322. * The drupal_ftp_delete_folder Function
  323. * This function was one of the hardest to write. It recursively deletes all files and folders from a directory called $folder_name.
  324. */
  325. function drupal_ftp_delete_folder($folder_name, &$ftp) {
  326. if (!@drupal_ftp_connect($ftp)) {
  327. return FALSE;
  328. }
  329. @ftp_chdir($ftp->__conn, $folder_name);
  330. $location = @ftp_pwd($ftp->__conn);
  331. $directories = array();
  332. $files = array();
  333. $dir_counter = 0;
  334. $file_counter = 0;
  335. $content = @ftp_nlist($ftp->__conn, ".");
  336. for ($i = 0; $i < sizeof($content); $i++) {
  337. // If we can change into this then it's a directory.
  338. // If not, it's a file.
  339. if ($content[$i] != "." && $content[$i] != "..") {
  340. if (@ftp_chdir($ftp->__conn, $content[$i])) {
  341. // We have a directory.
  342. $directories[] = $content[$i];
  343. $dir_counter++;
  344. @ftp_cdup($ftp->__conn);
  345. }
  346. else {
  347. // We have a file.
  348. $files[] = $content[$i];
  349. $file_counter++;
  350. }
  351. }
  352. }
  353. for ($j = 0; $j < $file_counter; $j++) {
  354. @ftp_delete($ftp->__conn, $files[$j]);
  355. }
  356. for ($j = 0; $j < $dir_counter; $j++) {
  357. if ($directories[$j] != "." OR $directories[$j] != "..") {
  358. $location = ftp_pwd($ftp->__conn);
  359. drupal_ftp_delete_folder($directories[$j], $ftp);
  360. @ftp_cdup($ftp->__conn);
  361. @ftp_rmdir($ftp->__conn, $directories[$j]);
  362. }
  363. }
  364. // Lastly, we change into the directory that we want to delete and see
  365. // if we can cdup. If we can, we delete it.
  366. @ftp_chdir($ftp->__conn, $folder_name);
  367. @ftp_cdup($ftp->__conn);
  368. @ftp_rmdir($ftp->__conn, $folder_name);
  369. // Did the recursive folder/file deletion work?
  370. return TRUE;
  371. }