PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/cp/expressionengine/third_party/assets/sources/base_source.php

https://bitbucket.org/sbeuken/artelux
PHP | 1274 lines | 701 code | 198 blank | 375 comment | 67 complexity | b08aadf84f32ffabf39d42398197675c MD5 | raw file
  1. <?php if (! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * Assets 2.0 source abstract class
  4. *
  5. * @package Assets
  6. * @author Andris Sevcenko <andris@pixelandtonic.com>
  7. * @copyright Copyright (c) 2011 Pixel & Tonic, Inc
  8. */
  9. abstract class Assets_base_source
  10. {
  11. /**
  12. * @var EE
  13. */
  14. public $EE;
  15. /**
  16. * @var string
  17. */
  18. protected $_source_id = '';
  19. /**
  20. * @var string
  21. */
  22. protected $_source_type = '';
  23. /**
  24. * @var array of Assets_base_file
  25. */
  26. private $files = array();
  27. /**
  28. * @var null|StdClass
  29. */
  30. protected $_source_settings = null;
  31. /**
  32. * @var null|StdClass
  33. */
  34. protected $_source_row = null;
  35. /**
  36. * Store index data for batch inserts
  37. * @var array
  38. */
  39. protected $_index_batch_entries = array();
  40. public function __construct()
  41. {
  42. // -------------------------------------------
  43. // Prepare Cache
  44. // -------------------------------------------
  45. $this->EE = get_instance();
  46. $this->EE->load->library('assets_lib');
  47. if (! isset($this->EE->session->cache['assets']))
  48. {
  49. $this->EE->session->cache['assets'] = array();
  50. }
  51. $this->cache =& $this->EE->session->cache['assets'];
  52. }
  53. /**
  54. * Returns TRUE if source is capable from performing the file move from another source as if the file being moved was
  55. * inside the new source already. For example - all EE operations and S3 operations, that take place on the same AWS account
  56. * @param Assets_base_source $source
  57. * @return boolean
  58. */
  59. abstract public function can_move_files_from(Assets_base_source $source);
  60. /**
  61. * Get the folder server path
  62. * @abstract
  63. * @param $folder
  64. * @param $folder_row
  65. * @return string
  66. */
  67. abstract public function get_folder_server_path($folder, $folder_row);
  68. /**
  69. * Create a folder at designated path for source
  70. * @abstract
  71. * @param $server_path
  72. * @return array
  73. */
  74. abstract protected function _create_source_folder($server_path);
  75. /**
  76. * Rename a folder
  77. * @abstract
  78. * @param $old_path
  79. * @param $new_path
  80. * @return bool|mixed
  81. */
  82. abstract protected function _rename_source_folder($old_path, $new_path);
  83. /**
  84. * Delete a folder
  85. * @abstract
  86. * @param $server_path
  87. * @param $source_row
  88. * @return mixed
  89. */
  90. abstract protected function _delete_source_folder($server_path, $source_row);
  91. /**
  92. * Delete a file
  93. * @abstract
  94. * @param $server_path
  95. * @param $source_row
  96. * @return mixed
  97. */
  98. abstract protected function _delete_source_file($server_path, $source_row);
  99. /**
  100. * Check if folder exists on source
  101. * @abstract
  102. * @param $server_path
  103. * @return array
  104. */
  105. abstract protected function _source_folder_exists($server_path);
  106. /**
  107. * Perform upload in the designated folder
  108. * @abstract
  109. * @param stdclass $folder_data holding the folder row data
  110. * @param string $temp_file_path file path on disk
  111. * @param string $original_name file original name (type checks need this)
  112. * @return mixed
  113. */
  114. abstract protected function _do_upload_in_folder($folder_data, $temp_file_path, $original_name);
  115. /**
  116. * Move a source file
  117. * @abstract
  118. * @param Assets_base_file $file
  119. * @param $previous_folder_row
  120. * @param $folder_row
  121. * @param $new_file_name
  122. * @return mixed
  123. */
  124. abstract protected function _move_source_file(Assets_base_file $file, $previous_folder_row, $folder_row, $new_file_name = '');
  125. /**
  126. * Starts an indexing session
  127. * @param $session_id
  128. * @return array
  129. */
  130. abstract public function start_index($session_id);
  131. /**
  132. * Starts a folder indexing session
  133. * @param $session_id
  134. * @param StdClass $folder_row
  135. * @return array
  136. */
  137. abstract public function start_folder_index($session_id, $folder_row);
  138. /**
  139. * Perform indexing
  140. * @param $session_id int
  141. * @param $offset
  142. * @abstract
  143. * @return boolean
  144. */
  145. abstract public function process_index($session_id, $offset);
  146. /**
  147. * Perform some actions that should be done after image upload
  148. * @abstract
  149. * @param $file_id
  150. * @param $image_path
  151. * @return mixed
  152. */
  153. abstract public function post_upload_image_actions($file_id, $image_path);
  154. /**
  155. * Perform some actions that should be done after deletion of an asset
  156. * @abstract
  157. * @param $file_id
  158. * @return mixed
  159. */
  160. abstract public function post_delete_actions($file_id);
  161. /**
  162. * Get a replacement name
  163. * @abstract
  164. * @param $folder_row
  165. * @param $file_name
  166. * @return mixed
  167. */
  168. abstract protected function _get_name_replacement($folder_row, $file_name);
  169. /**
  170. * Get a files server path
  171. * @abstract
  172. * @param $folder_row
  173. * @param $file_name
  174. * @return mixed
  175. */
  176. abstract protected function _get_file_server_path($folder_row, $file_name);
  177. /**
  178. * Return setting fields for this source
  179. * @return array
  180. */
  181. abstract public static function get_settings_field_list();
  182. /**
  183. * Get folder name for a path
  184. * @param $path
  185. * @return string
  186. */
  187. public function get_folder_name($path)
  188. {
  189. $components = explode('/', Assets_helper::normalize_path($path));
  190. return array_pop($components);
  191. }
  192. /**
  193. * Return source type
  194. * @return string
  195. */
  196. public function get_source_type()
  197. {
  198. return $this->_source_type;
  199. }
  200. /**
  201. * Return source id
  202. * @return string
  203. */
  204. public function get_source_id()
  205. {
  206. return $this->_source_id;
  207. }
  208. /**
  209. * Return settings for source
  210. * @return mixed
  211. */
  212. public function settings()
  213. {
  214. return $this->_source_settings;
  215. }
  216. /**
  217. * Clear file cache
  218. */
  219. public function clear_file_cache()
  220. {
  221. $this->files = array();
  222. }
  223. /**
  224. * Get all files in a folder
  225. * @param $folder_id
  226. * @param $keywords
  227. * @param string $search_type deep|shallow
  228. * @return array of Assets_base_file
  229. * TODO: the recursion here can be done away with by performing a select on assets_folders table on full_path column to get all children at once
  230. */
  231. public function get_files_in_folder($folder_id, $keywords, $search_type)
  232. {
  233. $search_type = $search_type == 'deep' ? 'deep' : 'shallow';
  234. $parameters = array(
  235. 'source_type' => $this->get_source_type(),
  236. 'folder_id' => $folder_id
  237. );
  238. $this->EE->db->select('file_id');
  239. $this->EE->db->where($parameters);
  240. if (! empty($keywords))
  241. {
  242. foreach ($keywords as $keyword)
  243. {
  244. $this->EE->db->like('search_keywords', $keyword);
  245. }
  246. }
  247. $file_ids = $this->EE->db->get('assets_files')->result();
  248. $output = array();
  249. foreach ($file_ids as $row)
  250. {
  251. $output[] = $this->get_file($row->file_id);
  252. }
  253. // recurse deeper, if we have to
  254. if ($search_type == 'deep' && ! empty($keywords))
  255. {
  256. ini_set('memory_limit', '64M');
  257. $child_rows = $this->EE->db->get_where('assets_folders', array('parent_id' => $folder_id))->result();
  258. foreach ($child_rows as $row)
  259. {
  260. $output = array_merge($output, $this->get_files_in_folder($row->folder_id, $keywords, $search_type));
  261. }
  262. }
  263. return $output;
  264. }
  265. /**
  266. * Create Folder at path, which consists is in form of "parent_id/folder_name"
  267. * @param $path
  268. * @throws Exception
  269. * @return array
  270. */
  271. public function create_folder($path)
  272. {
  273. if (substr_count($path, '/') !== 1)
  274. {
  275. throw new Exception(lang('invalid_folder_path'));
  276. }
  277. list ($parent_id, $folder_name) = explode('/', $path);
  278. $row = $this->EE->assets_lib->get_folder_row_by_id($parent_id);
  279. // swap whitespace with underscores
  280. $folder_name = preg_replace('/\s+/', '_', $folder_name);
  281. $source_type = $row->source_type;
  282. $parent_path = Assets_helper::normalize_path($row->full_path);
  283. $new_path = $parent_path . (substr($parent_path, -1) == '/' ? '' : '/') . $folder_name;
  284. // attempt to resolve, check if exists and create.
  285. if (! ($server_path = $this->get_folder_server_path($new_path, $row)))
  286. {
  287. throw new Exception(lang('invalid_folder_path'));
  288. }
  289. if ($this->_source_folder_exists($server_path))
  290. {
  291. throw new Exception(lang('file_already_exists'));
  292. }
  293. if ( ! $this->_create_source_folder($server_path))
  294. {
  295. throw new Exception(lang('invalid_folder_path'));
  296. }
  297. // created, insert in DB
  298. $data = array(
  299. 'source_type' => $source_type,
  300. 'folder_name' => $folder_name,
  301. 'full_path' => ltrim($new_path, '/') . '/',
  302. 'parent_id' => $parent_id,
  303. 'source_id' => $row->source_id,
  304. 'filedir_id' => $row->filedir_id
  305. );
  306. $this->EE->db->insert('assets_folders', $data);
  307. $this->EE->assets_lib->call_extension('assets_create_folder', array($this->EE->assets_lib->get_folder_row_by_id($this->EE->db->insert_id())));
  308. return array('success' => TRUE,
  309. 'folder_id' => $this->EE->db->insert_id(),
  310. 'parent_id' => $parent_id,
  311. 'folder_name' => $folder_name);
  312. }
  313. /**
  314. * Rename folder
  315. * @param $folder_id
  316. * @param $new_title
  317. * @throws Exception
  318. * @return array
  319. */
  320. public function rename_folder($folder_id, $new_title)
  321. {
  322. if (substr_count($new_title, '/') > 0)
  323. {
  324. throw new Exception(lang('invalid_folder_path'));
  325. }
  326. // swap whitespace with underscores
  327. $new_title = preg_replace('/\s+/', '_', $new_title);
  328. $source_folder = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  329. $parent_row = $this->EE->assets_lib->get_folder_row_by_id($source_folder->parent_id);
  330. $source_path = Assets_helper::normalize_path(substr($this->get_folder_server_path($source_folder->full_path, $source_folder), 0, -1));
  331. $parts = explode('/', $source_path);
  332. // remote storages don't have FS paths, so this really applies just to /long/paths/to/folder/anyway
  333. if (count($parts) !== 1)
  334. {
  335. array_pop($parts);
  336. array_push($parts, $new_title);
  337. $target = implode('/', $parts);
  338. }
  339. else
  340. {
  341. // we branch in here if the source path is like "X:path", where X is internal id for the source folder
  342. // so we have to drop the path, but leave the internal id intact.
  343. // This enforces the X:path syntax for remote sources though
  344. // this is very ugly, but at the top of my head can't figure out a better way to do this.
  345. $source_path = array_pop($parts);
  346. $remote_parts = explode(':', $source_path);
  347. // something went very wrong.
  348. if (count($remote_parts) !== 2)
  349. {
  350. throw new Exception(lang('invalid_folder_path'));
  351. }
  352. $target = $remote_parts[0] . ':' . $new_title;
  353. }
  354. if ( $this->_source_folder_exists($target) )
  355. {
  356. throw new Exception(lang('invalid_folder_path'));
  357. }
  358. if ( ! $this->_rename_source_folder($source_path, $target))
  359. {
  360. throw new Exception(lang('invalid_folder_path'));
  361. }
  362. $this->EE->assets_lib->call_extension('assets_rename_folder', array($source_folder, $new_title));
  363. $source_folder->folder_name = $new_title;
  364. $this->_update_folder_info($source_folder, $parent_row);
  365. return array('success' => TRUE, 'new_name' => $new_title);
  366. }
  367. /**
  368. * Move folder from one path to another
  369. * @param $folder_id
  370. * @param $new_parent
  371. * @param boolean $overwrite_target if TRUE will overwrite target folder
  372. * @return array
  373. */
  374. public function move_folder($folder_id, $new_parent, $overwrite_target = FALSE)
  375. {
  376. // why not.
  377. if ($folder_id == $new_parent)
  378. {
  379. return array('success' => TRUE);
  380. }
  381. $source_row = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  382. $new_parent_row = $this->EE->assets_lib->get_folder_row_by_id($new_parent);
  383. $new_parent_path = $this->get_folder_server_path($new_parent_row->full_path, $new_parent_row);
  384. $target = $new_parent_path . $source_row->folder_name;
  385. // if the folder exists, we see if the user has taken an action already
  386. $remove_from_tree = '';
  387. if ($this->_source_folder_exists(rtrim($target, '/') . '/'))
  388. {
  389. if ($overwrite_target)
  390. {
  391. if ( ! $this->EE->assets_lib->get_folder_id_by_parent_and_name($new_parent_row->folder_id, $source_row->folder_name))
  392. {
  393. $this->_delete_source_folder(
  394. $this->get_folder_server_path($new_parent_row->full_path . $source_row->full_path, $new_parent_row),
  395. $new_parent_row);
  396. }
  397. else
  398. {
  399. // pass this along as well, since this is a conflicting folder that must be removed from the tree
  400. $remove_from_tree = $this->EE->assets_lib->get_folder_id_by_parent_and_name($new_parent_row->folder_id, $source_row->folder_name);
  401. $this->delete_folder($remove_from_tree);
  402. }
  403. }
  404. else
  405. {
  406. return $this->_folder_prompt_result_array($source_row->folder_name, $folder_id);
  407. }
  408. }
  409. $this->EE->assets_lib->call_extension('assets_move_folder', array($source_row, $new_parent_row));
  410. // NOTE: this is needed, so we can create a progress bar - we need to split all the tasks in chunks
  411. //
  412. // transfer_list: array that describes file transfers needed
  413. // delete_list: list of folders to delete after move
  414. // changed_folder_ids: list of folder id changes
  415. $return = array(
  416. 'success' => TRUE,
  417. 'transfer_list' => array(),
  418. 'delete_list' => array($folder_id),
  419. 'changed_folder_ids' => array(),
  420. 'remove_from_tree' => $remove_from_tree
  421. );
  422. $mirroring_data = array(
  423. 'changed_folder_ids' => array(),
  424. );
  425. $this->_mirror_structure($new_parent_row, $source_row, $mirroring_data);
  426. $return['changed_folder_ids'] = $mirroring_data['changed_folder_ids'];
  427. $folder = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  428. $result = $this->EE->db->select('folder_id')->like('full_path', $folder->full_path, 'after')->get('assets_folders')->result();
  429. $folder_ids = array();
  430. foreach ($result as $row)
  431. {
  432. $folder_ids[] = $row->folder_id;
  433. }
  434. $this->EE->db->where_in('folder_id', $folder_ids);
  435. $result = $this->EE->db->get('assets_files')->result();
  436. foreach ($result as $row)
  437. {
  438. $return['transfer_list'][] = array(
  439. 'old_id' => $row->file_id,
  440. 'new_path' => $return['changed_folder_ids'][$row->folder_id]['new_id'] . '/' . $row->file_name);
  441. }
  442. return $return;
  443. }
  444. /**
  445. * Mirrors a subset of folder tree from one location to other
  446. * @param $target_row
  447. * @param $source_row
  448. * @param $changed_data
  449. * @throws Exception
  450. */
  451. private function _mirror_structure($target_row, $source_row, &$changed_data)
  452. {
  453. $result = $this->create_folder($target_row->folder_id . '/' . $source_row->folder_name);
  454. if (isset($result['success']))
  455. {
  456. $new_id = $result['folder_id'];
  457. $parent_id = $result['parent_id'];
  458. $changed_data['changed_folder_ids'][$source_row->folder_id] = array(
  459. 'new_id' => $new_id,
  460. 'new_parent_id' => $parent_id
  461. );
  462. $new_target_row = $this->EE->assets_lib->get_folder_row_by_id($new_id);
  463. $children = $this->EE->db->get_where('assets_folders', array('parent_id' => $source_row->folder_id));
  464. $children = $children->result();
  465. foreach ($children as $child)
  466. {
  467. $this->_mirror_structure($new_target_row, $child, $changed_data);
  468. }
  469. }
  470. else
  471. {
  472. throw new Exception(lang('exception_error'));
  473. }
  474. }
  475. /**
  476. * Delete folder
  477. * @param $folder_id
  478. * @throws Exception
  479. * @return array
  480. */
  481. public function delete_folder($folder_id)
  482. {
  483. $db = $this->EE->db;
  484. $source = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  485. $source_path = $this->get_folder_server_path($source->full_path, $source);
  486. // delete all files in this folder
  487. $files_to_delete = $db->get_where('assets_files', array('folder_id' => $folder_id));
  488. $rows = $files_to_delete->result();
  489. foreach ($rows as $file)
  490. {
  491. $this->delete_file($file->file_id, TRUE);
  492. }
  493. // delete all subfolders
  494. $folders_to_delete = $db->get_where('assets_folders', array('parent_id' => $folder_id));
  495. $rows = $folders_to_delete->result();
  496. foreach ($rows as $folder)
  497. {
  498. $this->delete_folder($folder->folder_id);
  499. }
  500. $this->EE->assets_lib->call_extension('assets_delete_folder', array($source));
  501. if ( ! $this->_delete_source_folder($source_path, $source))
  502. {
  503. throw new Exception(lang('invalid_source_path'));
  504. }
  505. $db->delete('assets_folders', array('folder_id' => $folder_id));
  506. return array('success' => TRUE);
  507. }
  508. /**
  509. * Upload a file into the folder with the id
  510. * @param $folder_id
  511. * @return array
  512. */
  513. public function upload_file($folder_id)
  514. {
  515. try
  516. {
  517. $folder_row = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  518. }
  519. catch (Exception $error)
  520. {
  521. return array('error' => $error->getMessage());
  522. }
  523. $server_path = $this->get_folder_server_path($folder_row->full_path, $folder_row);
  524. if ( ! $server_path)
  525. {
  526. return array('error' => lang('invalid_filedir_path'));
  527. }
  528. // upload the file and drop it in the temporary folder
  529. $uploader = new qqFileUploader();
  530. // make sure a file was uploaded
  531. if (! $uploader->file)
  532. {
  533. return array('error' => lang('no_files'));
  534. }
  535. $size = $uploader->file->getSize();
  536. // make sure the file isn't empty
  537. if (! $size)
  538. {
  539. return array('error' => lang('empty_file'));
  540. }
  541. $file_path = Assets_helper::get_temp_file();
  542. $uploader->file->save($file_path);
  543. // the file is being saved in a temporary location, so that the workflow here is manageable
  544. // if we didn't do this, that would mean that all sources must implement their own uploader as well
  545. // which would have been an overkill.
  546. $result = $this->_do_upload_in_folder($folder_row, $file_path, $uploader->file->getName());
  547. $return_prompt = FALSE;
  548. // naming conflict. create the new filename and ask user what to do
  549. if (isset($result['prompt']))
  550. {
  551. $new_file_name = $this->_get_name_replacement($folder_row, $uploader->file->getName());
  552. $return_prompt = $result;
  553. $result = $this->_do_upload_in_folder($folder_row, $file_path, $new_file_name);
  554. }
  555. if (isset($result['success']))
  556. {
  557. $filename = pathinfo($result['path'], PATHINFO_BASENAME);
  558. $data = array(
  559. 'folder_id' => $folder_id,
  560. 'source_type' => $folder_row->source_type,
  561. 'source_id' => $folder_row->source_id,
  562. 'filedir_id' => $folder_row->filedir_id,
  563. 'file_name' => $filename,
  564. 'kind' => Assets_helper::get_kind($filename)
  565. );
  566. $this->EE->db->insert('assets_files', $data);
  567. $file_id = $this->EE->db->insert_id();
  568. $file = $this->get_file($file_id);
  569. $this->update_file_info($file);
  570. @unlink($file_path);
  571. $this->EE->assets_lib->update_file_search_keywords($file_id);
  572. if ( ! $return_prompt)
  573. {
  574. return array('success' => TRUE, 'path' => $file_id);
  575. }
  576. else
  577. {
  578. $return_prompt['additional_info'] = $folder_id . ':' . $file_id;
  579. $return_prompt['new_file_id'] = $file_id;
  580. return $return_prompt;
  581. }
  582. }
  583. else
  584. {
  585. @unlink($file_path);
  586. return $result;
  587. }
  588. }
  589. /**
  590. * Updates file info in DB (width/height/size/date_modified) and performs additional actions for image files
  591. * @param Assets_base_file $file
  592. * @param string $file_path file location
  593. */
  594. public function update_file_info(Assets_base_file $file, $file_path = '')
  595. {
  596. $unlink_path = FALSE;
  597. if (empty($file_path))
  598. {
  599. $file_path = $file->get_local_copy();
  600. $unlink_path = TRUE;
  601. }
  602. if ($file->kind() == 'image')
  603. {
  604. $this->post_upload_image_actions($file->file_id(), $file_path);
  605. }
  606. $data = array(
  607. 'date_modified' => $file->date_modified(TRUE),
  608. 'size' => $file->size('', TRUE)
  609. );
  610. $this->EE->db->update('assets_files', $data, array('file_id' => $file->file_id()));
  611. if ($unlink_path)
  612. {
  613. @unlink($file_path);
  614. }
  615. return $file->file_id();
  616. }
  617. /**
  618. * Recursively updates folder of given id - sets the full path to the provided base + folder name
  619. * @param stdclass $target to update
  620. * @param stdclass $parent for source info
  621. */
  622. private function _update_folder_info($target, $parent)
  623. {
  624. $new_full_path = $parent->full_path . $target->folder_name . '/';
  625. $data = array(
  626. 'folder_name' => $target->folder_name,
  627. 'full_path' => $new_full_path,
  628. 'source_id' => $parent->source_id,
  629. 'filedir_id' => $parent->filedir_id
  630. );
  631. $this->EE->db->update('assets_folders', $data, array('folder_id' => $target->folder_id));
  632. $rows = $this->EE->db->get_where('assets_folders', array('parent_id' => $target->folder_id))->result();
  633. // have to do this so we an just pass along this object instead of selecting the data again
  634. $target->source_type = $parent->source_type;
  635. $target->source_id = $parent->source_id;
  636. $target->filedir_id = $parent->filedir_id;
  637. $target->full_path = $new_full_path;
  638. foreach ($rows as $row)
  639. {
  640. $this->_update_folder_info($row, $target);
  641. }
  642. }
  643. /**
  644. * Get a file by asset id
  645. * @param int $file_id
  646. * @param bool $return_missing if true return object even if file is missing
  647. * @return Assets_base_file
  648. */
  649. public function get_file($file_id, $return_missing = FALSE)
  650. {
  651. if (! isset($this->files[$file_id]))
  652. {
  653. $class_name = 'Assets_' . $this->get_source_type() .'_file';
  654. if (! class_exists($class_name))
  655. {
  656. require_once PATH_THIRD.'assets/blueprints/afile.php';
  657. require_once PATH_THIRD.'assets/sources/' . $this->get_source_type() . '/file.php';
  658. }
  659. $file = new $class_name($file_id, $this);
  660. $this->files[$file_id] = $file;
  661. }
  662. return ($this->files[$file_id] && ($return_missing OR $this->files[$file_id]->exists())) ? $this->files[$file_id] : FALSE;
  663. }
  664. /**
  665. * Move file from one path to another, if possible. Return false if not possible
  666. * @param Assets_base_source $previous_source
  667. * @param $file_id
  668. * @param $new_path
  669. * @param string $action action to take in case of naming conflict
  670. * @throws Exception
  671. * @return array
  672. */
  673. public function move_file_inside_source(Assets_base_source $previous_source, $file_id, $new_path, $action)
  674. {
  675. if (!$this->can_move_files_from($previous_source))
  676. {
  677. return FALSE;
  678. }
  679. // $path arrives as $folder_id/$file_name
  680. if (substr_count($new_path, '/') !== 1)
  681. {
  682. throw new Exception(lang('invalid_file_path'));
  683. }
  684. list ($folder_id, $file_name) = explode('/', $new_path);
  685. $file = $this->get_file($file_id);
  686. if (! $file)
  687. {
  688. throw new Exception(lang('invalid_file_path'));
  689. }
  690. if ($file->row_field('folder_id') == $folder_id && $file->filename() == $file_name)
  691. {
  692. return array('success' => TRUE, 'new_path' => $file_id);
  693. }
  694. $folder_row = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  695. $previous_folder_row = $this->EE->assets_lib->get_folder_row_by_id($file->row_field('folder_id'));
  696. // if this is not empty, we have a revisited conflict with some plan of action
  697. if ( ! empty($action))
  698. {
  699. switch ($action)
  700. {
  701. case Assets_helper::ACTIONS_REPLACE:
  702. if ( ! $this->EE->assets_lib->get_file_id_by_folder_id_and_name($folder_id, $file_name))
  703. {
  704. $this->_delete_source_file($this->_get_file_server_path($folder_row, $file_name), $folder_row);
  705. }
  706. else
  707. {
  708. $this->delete_file($this->EE->assets_lib->get_file_id_by_folder_id_and_name($folder_id, $file_name));
  709. }
  710. break;
  711. case Assets_helper::ACTIONS_KEEP_BOTH:
  712. $file_name = $this->_get_name_replacement($folder_row, $file_name);
  713. break;
  714. }
  715. }
  716. // now the source specific function has enough data to get to work
  717. $result = $this->_move_source_file($file, $previous_folder_row, $folder_row, $file_name, $action);
  718. if (isset($result['success']))
  719. {
  720. $data = $file->row();
  721. $data['folder_id'] = $folder_id;
  722. $data['file_name'] = $result['new_file_name'];
  723. $data['source_id'] = $folder_row->source_id;
  724. $data['filedir_id'] = $folder_row->filedir_id;
  725. $this->EE->db->update('assets_files', $data, array('file_id' => $file_id));
  726. }
  727. return $result;
  728. }
  729. /**
  730. * Delete file with the id
  731. * @param $file_id
  732. * @param $delete_missing boolean if TRUE will delete all reacords even if physical file cannot be found
  733. * @throws Exception
  734. * @return array
  735. */
  736. public function delete_file($file_id, $delete_missing = FALSE)
  737. {
  738. $file = $this->get_file($file_id, $delete_missing);
  739. if ( !$file OR is_dir($file->server_path()))
  740. {
  741. throw new Exception(lang('invalid_file_path'));
  742. }
  743. $this->EE->assets_lib->call_extension('assets_delete_file', array($file));
  744. try
  745. {
  746. $folder_row = $this->EE->assets_lib->get_folder_row_by_id($file->row_field('folder_id'));
  747. }
  748. catch (Exception $error)
  749. {
  750. return array('error' => $error->getMessage());
  751. }
  752. $this->_delete_source_file($file->server_path(), $folder_row);
  753. $this->EE->db->where('file_id', $file_id)
  754. ->delete('assets_files');
  755. $this->EE->db->where('file_id', $file_id)
  756. ->delete('assets_selections');
  757. $this->post_delete_actions($file_id);
  758. $this->_delete_generated_thumbs($file_id);
  759. return array('success' => TRUE);
  760. }
  761. /**
  762. * Transfers a file into this source from a file
  763. * @param string $source_location
  764. * @param string $destination_path
  765. * @param Assets_base_file $file
  766. * @param $action
  767. * @throws Exception
  768. * @return array
  769. */
  770. public function transfer_file_into_source($source_location, $destination_path, $file, $action)
  771. {
  772. // $path arrives as $folder_id/$file_name
  773. if (substr_count($destination_path, '/') !== 1)
  774. {
  775. throw new Exception(lang('invalid_file_path'));
  776. }
  777. list ($folder_id) = explode('/', $destination_path);
  778. $folder_row = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  779. $file_name = $file->filename();
  780. // swap whitespace with underscores
  781. $file_name = preg_replace('/\s+/', '_', $file_name);
  782. // if this is not empty, we have a revisited conflict with some plan of action
  783. if ( ! empty($action))
  784. {
  785. switch ($action)
  786. {
  787. case Assets_helper::ACTIONS_REPLACE:
  788. {
  789. if ( ! $this->EE->assets_lib->get_file_id_by_folder_id_and_name($folder_id, $file->filename()))
  790. {
  791. $this->_delete_source_file($this->_get_file_server_path($folder_row, $file_name), $folder_row);
  792. }
  793. else
  794. {
  795. $this->delete_file($this->EE->assets_lib->get_file_id_by_folder_id_and_name($folder_id, $file_name), TRUE);
  796. }
  797. break;
  798. }
  799. case Assets_helper::ACTIONS_KEEP_BOTH:
  800. {
  801. $file_name = $this->_get_name_replacement($folder_row, $file_name);
  802. break;
  803. }
  804. }
  805. }
  806. $result = $this->_do_upload_in_folder($folder_row, $source_location, $file_name, $action);
  807. if (isset($result['success']))
  808. {
  809. $file_row = $file->row();
  810. $file_row['source_type'] = $this->get_source_type();
  811. $file_row['source_id'] = $folder_row->source_id;
  812. $file_row['filedir_id'] = $folder_row->filedir_id;
  813. $file_row['folder_id'] = $folder_id;
  814. $file_row['file_name'] = $file_name;
  815. $this->EE->db->update('assets_files', $file_row, array('file_id' => $file->file_id()));
  816. return $result;
  817. }
  818. return $result;
  819. }
  820. /**
  821. * Finalize file transfer out of this source
  822. * @param Assets_base_file $file
  823. * @return array
  824. */
  825. public function finalize_outgoing_transfer(Assets_base_file $file)
  826. {
  827. $folder_row = $this->EE->assets_lib->get_folder_row_by_id($file->row_field('folder_id'));
  828. return $this->_delete_source_file($file->server_path(), $folder_row);
  829. }
  830. /**
  831. * Prepare a source folder for an incoming file transfer from another source
  832. * @param $parent_id
  833. * @param $folder_id
  834. * @throws Exception
  835. * @internal param $folder_name
  836. * @return array
  837. */
  838. public function prepare_folder_for_transfer($parent_id, $folder_id)
  839. {
  840. $folder_data = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  841. $result = $this->create_folder($parent_id . '/' . $folder_data->folder_name);
  842. if ( isset($result['error']))
  843. {
  844. throw new Exception($result['error']);
  845. }
  846. return $result;
  847. }
  848. /**
  849. * Finalize a folder transfer by deleting the source folder
  850. * @param $folder_id
  851. */
  852. public function finalize_folder_transfer($folder_id)
  853. {
  854. $this->delete_folder($folder_id);
  855. $folder_data = $this->EE->assets_lib->get_folder_row_by_id($folder_id);
  856. $this->_delete_source_folder(
  857. $this->get_folder_server_path($folder_data->full_path, $folder_data), $folder_data);
  858. }
  859. /**
  860. * Return a result array for prompting the user about filename conflicts
  861. * @param string $file_name the cause of all trouble
  862. * @return array
  863. */
  864. protected function _prompt_result_array($file_name)
  865. {
  866. return array(
  867. 'prompt' => $this->EE->functions->var_swap(lang('file_already_exists__title'), array('file' => $file_name)),
  868. 'file_name' => $file_name,
  869. 'choices' => array(
  870. array('value' => Assets_helper::ACTIONS_KEEP_BOTH, 'title' => lang('file_already_exists__keep_both')),
  871. array('value' => Assets_helper::ACTIONS_REPLACE, 'title' => lang('file_already_exists__replace')),
  872. array('value' => Assets_helper::ACTIONS_CANCEL, 'title' => lang('file_already_exists__cancel'))
  873. )
  874. );
  875. }
  876. /**
  877. * Return a result array for prompting the user about folder conflicts
  878. * @param string $folder_name the caused of all trouble
  879. * @param int $folder_id
  880. * @return array
  881. */
  882. protected function _folder_prompt_result_array($folder_name, $folder_id)
  883. {
  884. return array(
  885. 'prompt' => $this->EE->functions->var_swap(lang('folder_already_exists__title'), array('folder' => $folder_name)),
  886. 'file_name' => $folder_id,
  887. 'choices' => array(
  888. array('value' => Assets_helper::ACTIONS_REPLACE, 'title' => lang('folder_already_exists__replace')),
  889. array('value' => Assets_helper::ACTIONS_CANCEL, 'title' => lang('folder_already_exists__cancel'))
  890. )
  891. );
  892. }
  893. /**
  894. * Replace physical file
  895. * @param Assets_base_file $old_file
  896. * @param Assets_base_file $replace_with
  897. */
  898. public function replace_file(Assets_base_file $old_file, Assets_base_file $replace_with)
  899. {
  900. if ($old_file->kind() == 'image')
  901. {
  902. // we'll need this if replacing images
  903. $local_copy = $replace_with->get_local_copy();
  904. }
  905. $this->_delete_source_file($old_file->server_path(), $old_file->folder_row());
  906. $this->_delete_generated_thumbs($old_file->file_id());
  907. $this->_move_source_file($replace_with, $replace_with->folder_row(), $old_file->folder_row(), $old_file->filename());
  908. if ($old_file->kind() == 'image')
  909. {
  910. $this->post_upload_image_actions($old_file->file_id(), $local_copy);
  911. }
  912. $data = array(
  913. 'width' => $replace_with->width(),
  914. 'height' => $replace_with->height(),
  915. 'size' => $replace_with->size(),
  916. 'date_modified' => $replace_with->date_modified()
  917. );
  918. $this->EE->db->update('assets_files', $data, array('file_id' => $old_file->file_id()));
  919. }
  920. /**
  921. * Return source row
  922. * @return null|StdClass
  923. */
  924. public function get_source_row()
  925. {
  926. return $this->_source_row;
  927. }
  928. /**
  929. * Get folder preferences
  930. * @param $parameters
  931. * @return mixed
  932. */
  933. protected function _get_sources($parameters)
  934. {
  935. $result = $this->EE->db->get_where('assets_sources', $parameters)->result();
  936. return $result;
  937. }
  938. /**
  939. * Store folder data
  940. * @param $data
  941. * @return $insert_id
  942. */
  943. protected function _store_folder($data)
  944. {
  945. $this->EE->db->insert('assets_folders', $data);
  946. return $this->EE->db->insert_id();
  947. }
  948. /**
  949. * Finds folder row by parameters
  950. * @param $parameters
  951. * @return mixed
  952. */
  953. protected function _find_folder($parameters)
  954. {
  955. $result = $this->EE->db->get_where('assets_folders', $parameters)->result();
  956. if (empty($result))
  957. {
  958. return FALSE;
  959. }
  960. return $result[0];
  961. }
  962. /**
  963. * Delete thumbnails generated for this file by assets
  964. * @param $file_id
  965. */
  966. protected function _delete_generated_thumbs($file_id)
  967. {
  968. $thumb_path = Assets_helper::ensure_cache_path('assets/thumbs');
  969. $files = glob($thumb_path . '/' . $file_id . '/*');
  970. foreach ($files as $path)
  971. {
  972. @unlink($path);
  973. }
  974. }
  975. /**
  976. * Returns TRUE if extension is allowed by configurations
  977. * @param $extension
  978. * @return bool
  979. */
  980. protected function _is_extension_allowed($extension)
  981. {
  982. // check if file is valid according to config/mimes.php
  983. $valid_mime = TRUE;
  984. global $mimes;
  985. if (! is_array($mimes))
  986. {
  987. require_once(APPPATH.'config/mimes.php');
  988. }
  989. if (is_array($mimes) && ! isset($mimes[strtolower($extension)]))
  990. {
  991. $valid_mime = FALSE;
  992. }
  993. return $valid_mime;
  994. }
  995. /**
  996. * Store file data
  997. * @param $data
  998. * @return $insert_id
  999. */
  1000. protected function _store_file($data)
  1001. {
  1002. $this->EE->db->insert('assets_files', $data);
  1003. return $this->EE->db->insert_id();
  1004. }
  1005. /**
  1006. * Update file data
  1007. * @param $data
  1008. * @param $file_id
  1009. */
  1010. protected function _update_file($data, $file_id)
  1011. {
  1012. $this->EE->db->where('file_id', $file_id);
  1013. $this->EE->db->update('assets_files', $data);
  1014. $this->EE->assets_lib->update_file_search_keywords($file_id);
  1015. }
  1016. /**
  1017. * Store a index entry
  1018. * @param $session_id
  1019. * @param $source_type
  1020. * @param $source_id
  1021. * @param $offset
  1022. * @param $uri
  1023. * @param int $size
  1024. */
  1025. protected function _store_index_entry($session_id, $source_type, $source_id, $offset, $uri, $size = 0)
  1026. {
  1027. $this->_index_batch_entries[] = array(
  1028. 'session_id' => $session_id,
  1029. 'source_type' => $source_type,
  1030. 'source_id' => $source_id,
  1031. 'offset' => $offset,
  1032. 'uri' => $uri,
  1033. 'filesize' => $size
  1034. );
  1035. if (count($this->_index_batch_entries) == 100)
  1036. {
  1037. $this->_execute_index_batch();
  1038. }
  1039. }
  1040. /**
  1041. * Do a multi-row insert of index entries
  1042. */
  1043. protected function _execute_index_batch()
  1044. {
  1045. $query = "INSERT INTO " . $this->EE->db->dbprefix . 'assets_index_data (session_id, source_type, source_id, offset, uri, filesize) VALUES ';
  1046. foreach ($this->_index_batch_entries as $row)
  1047. {
  1048. $row_insert = '(';
  1049. foreach ($row as $value)
  1050. {
  1051. $row_insert .= $this->EE->db->escape($value) . ",";
  1052. }
  1053. $row_insert = rtrim($row_insert, ',') . ')';
  1054. $query .= $row_insert . ',';
  1055. }
  1056. $query = rtrim($query, ',');
  1057. if (!empty($this->_index_batch_entries))
  1058. {
  1059. $this->EE->db->query($query);
  1060. }
  1061. $this->_index_batch_entries = array();
  1062. }
  1063. /**
  1064. * @param $parameters
  1065. * @return bool
  1066. */
  1067. protected function _get_index_entry($parameters)
  1068. {
  1069. $result = $this->EE->db->get_where('assets_index_data', $parameters)->result();
  1070. if (empty($result))
  1071. {
  1072. return FALSE;
  1073. }
  1074. return $result[0];
  1075. }
  1076. }