diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f99486..9c449747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # alpha2 / dev-master -- Added `--pretty` option to `session:export:view` command to output formatted XML -- Ask confirmation before overwriting file in `session:export:view` +## Features + +- New command: `file:import`: - import files into the repository. + +## Improvements + +- `session:export:view`: Added `--pretty` option to `session:export:view` command to output formatted XML. +- `session:export:view`: Ask confirmation before overwriting file. diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index d5b859d1..b84942a1 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -471,6 +471,15 @@ public function thereExistsAPropertyAt($arg1) $session->getProperty($arg1); } + /** + * @Given /^there should exist a property at "([^"]*)"$/ + */ + public function thereShouldExistAPropertyAt($arg1) + { + $session = $this->getSession(); + $session->getProperty($arg1); + } + /** * @Given /^there should not exist a property at "([^"]*)"$/ */ diff --git a/features/fixtures/files/phpcrlogos.png b/features/fixtures/files/phpcrlogos.png new file mode 100644 index 00000000..dc6facc0 Binary files /dev/null and b/features/fixtures/files/phpcrlogos.png differ diff --git a/features/phpcr_file_import.feature b/features/phpcr_file_import.feature new file mode 100644 index 00000000..f07062d7 --- /dev/null +++ b/features/phpcr_file_import.feature @@ -0,0 +1,68 @@ +Feature: Import an external file as to a node + In order to import an external file into the system + As a user that is logged into the shell + I should be able to run a command which does that + + Background: + Given that I am logged in as "testuser" + And the "cms.xml" fixtures are loaded + And the file "phpcr.png" contains the contents of "files/phpcrlogos.png" + And the current node is "/" + + Scenario: Import a file + Given I execute the "file:import . phpcr.png" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a node at "/phpcr.png" + And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "image/png" + + Scenario: Import a file onto existing file, no overwrite specified + Given I execute the "file:import . phpcr.png" command + And I execute the "file:import . phpcr.png" command + Then the command should fail + + Scenario: Import a file onto existing file, force not specified + Given I execute the "file:import . phpcr.png --no-interaction" command + And I execute the "file:import . phpcr.png" command + Then the command should fail + + Scenario: Import a file onto existing file, force specified + Given I execute the "file:import . phpcr.png" command + And I execute the "file:import . phpcr.png --force" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a node at "/phpcr.png" + And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "image/png" + + Scenario: Import a file, override mime type + Given I execute the "file:import . phpcr.png --mime-type=application/data" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a node at "/phpcr.png" + And the node at "/phpcr.png/jcr:content" should have the property "jcr:mimeType" with value "application/data" + + Scenario: Import a file, specify a name + Given I execute the "file:import ./foobar.png phpcr.png --mime-type=application/data" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a node at "/foobar.png" + + Scenario: Import a file to a specified property + Given I execute the "file:import ./ phpcr.png --no-container" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a property at "/phpcr.png" + + Scenario: Import overwrite a specified property + Given I execute the "file:import ./ phpcr.png --no-container" command + And I save the session + And I execute the "file:import ./ phpcr.png --no-container" command + Then the command should not fail + And I save the session + Then the command should not fail + And there should exist a property at "/phpcr.png" diff --git a/src/PHPCR/Shell/Console/Application/ShellApplication.php b/src/PHPCR/Shell/Console/Application/ShellApplication.php index 3f7ffd6d..0febc116 100644 --- a/src/PHPCR/Shell/Console/Application/ShellApplication.php +++ b/src/PHPCR/Shell/Console/Application/ShellApplication.php @@ -189,6 +189,7 @@ private function registerCommands() $this->add(new CommandPhpcr\NodeCreateCommand()); $this->add(new CommandPhpcr\NodeCorrespondingCommand()); $this->add(new CommandPhpcr\NodeDefinitionCommand()); + $this->add(new CommandPhpcr\NodeFileImportCommand()); $this->add(new CommandPhpcr\NodePropertySetCommand()); $this->add(new CommandPhpcr\NodeSetPrimaryTypeCommand()); $this->add(new CommandPhpcr\NodeRenameCommand()); diff --git a/src/PHPCR/Shell/Console/Command/Phpcr/NodeFileImportCommand.php b/src/PHPCR/Shell/Console/Command/Phpcr/NodeFileImportCommand.php new file mode 100644 index 00000000..36f28988 --- /dev/null +++ b/src/PHPCR/Shell/Console/Command/Phpcr/NodeFileImportCommand.php @@ -0,0 +1,126 @@ +setName('file:import'); + $this->setDescription('Import a file at the given path'); + $this->addArgument('path', InputArgument::REQUIRED, 'Path to import file to'); + $this->addArgument('file', InputArgument::REQUIRED, 'Path to file to import'); + $this->addOption('mime-type', null, InputOption::VALUE_REQUIRED, 'Mime type (optional, auto-detected)'); + $this->addOption('force', null, InputOption::VALUE_NONE, 'Force overwriting any existing node'); + $this->addOption('no-container', null, InputOption::VALUE_NONE, 'Do not wrap in a JCR nt:file, but write directly to the specified property'); + $this->setHelp(<<nt:file. + +If a Node is specified as path then the filename of the imported file will be used +as the new node, otherwise, if the target path does not exist, then it is assumed +that the path is the target path for the new file, including the filename. + + PHPCRSH> file:import ./ foobar.png + PHPCRSH> file:import ./barfoo.png foobar.png + +In the first example above will create /foobar.png, whereas the second will create +./barfoo.png. + +By default the file will be imported in a container, i.e. a node with type nt:file. In +addition to the file data, the node will contain metadata. + +Alternatively you can specify the --no-container option to import directly to a single property. + +The mime-type of the file (in the case where a container is used) will be automatically determined unless +specified with --mime-type. +HERE + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->session = $this->getHelper('phpcr')->getSession(); + + $filePath = $input->getArgument('file'); + $force = $input->getOption('force'); + $noContainer = $input->getOption('no-container'); + + $path = $this->session->getAbsPath($input->getArgument('path')); + $mimeType = $input->getOption('mime-type'); + $filename = basename($filePath); + + if (!file_exists($filePath)) { + throw new \InvalidArgumentException(sprintf( + 'File "%s" does not exist.', + $filePath + )); + } + + try { + // first assume the user specified the path to the parent node + $parentNode = $this->session->getNode($path); + } catch (PathNotFoundException $e) { + // if the given path does not exist, assume that the basename is the target + // filename and the dirname the path to the parent node + $parentPath = dirname($path); + $parentNode = $this->session->getNode($parentPath); + $filename = basename($path); + } + + $fhandle = fopen($filePath, 'r'); + + if ($noContainer) { + $this->importToProperty($fhandle, $filePath, $filename, $parentNode, $force); + } else { + $this->importToContainer($fhandle, $mimeType, $filePath, $filename, $parentNode, $force); + } + } + + private function importToProperty($fhandle, $filePath, $filename, $parentNode, $force) + { + $parentNode->setProperty($filename, $fhandle, PropertyType::BINARY); + } + + private function importToContainer($fhandle, $mimeType, $file, $filename, $parentNode, $force) + { + // if no mime-type specified, guess it. + if (!$mimeType) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $file); + } + + // handle existing node + if ($parentNode->hasNode($filename)) { + if (true === $force) { + $fileNode = $parentNode->getNode($filename); + $this->session->removeItem($fileNode->getPath()); + } else { + throw new \InvalidArgumentException(sprintf( + 'Node "%s" already has child "%s". Use --force to overwrite.', + $parentNode->getPath(), + $filename + )); + } + } + + $fileNode = $parentNode->addNode($filename, 'nt:file'); + $contentNode = $fileNode->addNode('jcr:content', 'nt:unstructured'); + $contentNode->setProperty('jcr:data', $fhandle, PropertyType::BINARY); + $contentNode->setProperty('jcr:mimeType', $mimeType); + } +}