destinations.ftp.inc 13 KB

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