/** * copy() follows the rules unlike move_uploaded_file(): * http://www.php.net/manual/en/function.move-uploaded-file.php#85149 * * As for the security issue, validation should be used to check if a file * is, in fact, an uploaded file. Custom validation for this is provided * in app/config/bootstrap/validators.php * * @param [type] $source [description] * @param [type] $destination [description] * @param [type] $options [description] * @return [type] [description] */ public function save($source, $destination, array $options = []) { return $this->_filter(__METHOD__, compact('source', 'destination', 'options'), function ($self, $params) { $source = $params['source']; $destination = $params['destination']; $options = $params['options']; return function ($self, $params) use(&$source, &$destination, $options) { $copied = copy($source, $destination); $results = []; // @todo check if $this->_config['processors'] exists to avoid undefined index notice foreach ($this->_config['processors'] as $method => $arguments) { if (!in_array($method, $this->_processors)) { throw new InvalidArgumentException("The processor `{$name}` is not supported."); } foreach ($arguments as $index => $arg) { $arguments[$index] = Uploadable::interpolate($arg, $options); } $results[$method] = Uploadable::applyStrategies($method, $options['name'], $destination, $arguments); } if ($copied && !in_array(false, $results, true)) { return $results; } return false; }; }); }
/** * @todo If `styles` is not defined, just copy the original to `$destination` * @todo If width and height are not defined, don't resize * @todo http://www.imagemagick.org/Usage/resize/ * @param [type] $path [description] * @param [type] $options [description] * @return [type] [description] */ public function save($destination, array $options = []) { $options['quality'] = ['quality' => 75]; if (isset($this->_config['quality'])) { $options['quality'] = ['quality' => $this->_config['quality']]; } foreach ($this->interpolate($destination) as $dimension => $resolved) { list($options['width'], $options['height']) = explode('x', $dimension); $source = $options['source']; $this->_resize($source, $resolved, $options); } /** * We return `true` instead of the chain since we don't want * File::save() to do anything because Imagine::save() does * all the writing. */ $adapter = Uploadable::adapter($options['name']); $adapter->applyFilter(__FUNCTION__, function ($self, $params, $chain) { return function ($self, $params) { return true; }; }); }
/** * @todo We may also need to filter `remove()`. */ protected function _init() { parent::_init(); if (PHP_SAPI === 'cli') { return true; } if ($model = $this->_model) { $behavior = $this; $model::applyFilter('save', function ($self, $params, $chain) use($behavior) { $options = ['placeholders' => []]; $configs = $behavior->_config; $fields = $behavior::_formattedFields($configs['fields']); $source = $destination = $configName = []; $data = $behavior::_modified($params['data'], $fields); $dataKeys = array_keys($data); if (empty($dataKeys)) { return $chain->next($self, $params, $chain); } $queried = $params['entity']->export()['data']; if (0 === count(array_intersect_key($queried, $fields))) { return $chain->next($self, $params, $chain); } $params['data'] = []; $params['entity']->set($data); $entity = $params['entity']; $entityData['model'] = $entity->model(); $entityData['data'] = $entity->data(); if (!isset($configs['placeholders'])) { $configs['placeholders'] = []; } $defaults = ['save' => null, 'remove' => null]; $skip = []; $fieldCount = 0; foreach ($fields as $field => $name) { if (array_key_exists($field, $data)) { $configName[$field] = $name; $source[$field] = $_FILES[$field]['tmp_name']; $settings = UploadableStorage::config($name); $settings += $defaults; $path = $settings['save']; $newFile = $_FILES[$field]['name']; if ($entity->exists()) { if (!$entity->modified($field)) { $skip[$field] = true; } else { // We delete the old file $oldFile = $entity->export()['data'][$field]; $removeOptions['placeholders'] = UploadableStorage::placeholders($oldFile, $configs['placeholders'] + ['field' => $field], $entityData); $removePath = UploadableStorage::interpolate($settings['remove'], $removeOptions); UploadableStorage::remove($removePath, $removeOptions + ['name' => $name]); if (null === $data[$field]) { $skip[$field] = true; continue; } $options['placeholders'] = UploadableStorage::placeholders($newFile, $configs['placeholders'] + ['field' => $field], $entityData); $destination[$field] = UploadableStorage::interpolate($path, $options); /** * The field has been modified to now contain 'null' so we * remove the field from the config so that, later on, we don't * try to upload a non-existent file later. */ if (null === $entity->{$field}) { $params['entity']->{$field} = null; unset($configs['fields'][$field]); } else { /** * the field has been modified but it contains a value * so we must upload the new one. */ $fieldValue = static::fieldValue($path, $options['placeholders']); $params['entity']->{$field} = $fieldValue; } } } else { $options['placeholders'] = UploadableStorage::placeholders($newFile, $configs['placeholders'] + ['field' => $field], $entityData); $fieldValue = static::fieldValue($path, $options['placeholders']); $destination[$field] = UploadableStorage::interpolate($path, $options); $params['entity']->{$field} = $fieldValue; } } else { $fieldCount++; } } if (!$params['entity']->validates()) { return false; } $saved = $chain->next($self, $params, $chain); if ($fieldCount == count($fields)) { return $saved; } $options['placeholders'] += $params['entity']->data(); $uploaded = []; foreach ($fields as $field => $name) { if (!isset($skip[$field]) || $skip[$field] !== true) { $options['name'] = $configName[$field]; $uploaded[] = UploadableStorage::save($source[$field], $destination[$field], $options); } } return $saved && !in_array(false, $uploaded, true); }); $model::applyFilter('find', function ($self, $params, $chain) use($behavior) { $data = $chain->next($self, $params, $chain); switch ($params['type']) { case 'first': $entity = $behavior->invokeMethod('_assignAccessors', [$data]); return $entity; break; case 'all': foreach ($data as $datum) { $entity = $behavior->invokeMethod('_assignAccessors', [$datum]); } return $data; break; default: return $data; break; } }); $model::applyFilter('delete', function ($self, $params, $chain) use($behavior) { $entity = $params['entity']; $entityData['model'] = $entity->model(); $entityData['data'] = $entity->data(); $configs = $behavior->_config; $fields = $behavior::_formattedFields($configs['fields']); if (!isset($configs['placeholders'])) { $configs['placeholders'] = []; } $results = []; foreach ($fields as $field => $name) { $path = UploadableStorage::config($name)['remove']; $existingFile = $entity->export()['data'][$field]; $options['placeholders'] = UploadableStorage::placeholders($existingFile, $configs['placeholders'] + ['field' => $field], $entityData); $destination = UploadableStorage::interpolate($path, $options); $results[] = UploadableStorage::remove($destination, $options + ['name' => $name]); } $deleted = $chain->next($self, $params, $chain); return $deleted && !in_array(false, $results, true); }); } }