PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Jackalope/Transport/DoctrineDBAL/Client.php

https://github.com/paschke/jackalope-doctrine-dbal
PHP | 1922 lines | 1334 code | 262 blank | 326 comment | 162 complexity | f5930528a7fd15d18d554d710ed84d15 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. namespace Jackalope\Transport\DoctrineDBAL;
  3. use PHPCR\PropertyType;
  4. use PHPCR\Query\QOM\QueryObjectModelInterface;
  5. use PHPCR\Query\QOM\SelectorInterface;
  6. use PHPCR\Query\QueryInterface;
  7. use PHPCR\RepositoryException;
  8. use PHPCR\NamespaceException;
  9. use PHPCR\NamespaceRegistryInterface;
  10. use PHPCR\RepositoryInterface;
  11. use PHPCR\Util\UUIDHelper;
  12. use PHPCR\Util\QOM\Sql2ToQomQueryConverter;
  13. use PHPCR\NoSuchWorkspaceException;
  14. use PHPCR\ItemExistsException;
  15. use PHPCR\ItemNotFoundException;
  16. use PHPCR\ReferentialIntegrityException;
  17. use PHPCR\ValueFormatException;
  18. use PHPCR\PathNotFoundException;
  19. use PHPCR\Query\InvalidQueryException;
  20. use PHPCR\NodeType\ConstraintViolationException;
  21. use Doctrine\DBAL\Connection;
  22. use Doctrine\DBAL\Driver\PDOConnection;
  23. use Doctrine\DBAL\Platforms\MySqlPlatform;
  24. use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
  25. use Doctrine\DBAL\Platforms\SqlitePlatform;
  26. use Doctrine\DBAL\DBALException;
  27. use Jackalope\Node;
  28. use Jackalope\Property;
  29. use Jackalope\Query\Query;
  30. use Jackalope\Transport\BaseTransport;
  31. use Jackalope\Transport\QueryInterface as QueryTransport;
  32. use Jackalope\Transport\WritingInterface;
  33. use Jackalope\Transport\WorkspaceManagementInterface;
  34. use Jackalope\Transport\NodeTypeManagementInterface;
  35. use Jackalope\Transport\TransactionInterface;
  36. use Jackalope\Transport\StandardNodeTypes;
  37. use Jackalope\Transport\DoctrineDBAL\Query\QOMWalker;
  38. use Jackalope\NodeType\NodeTypeManager;
  39. use Jackalope\NodeType\NodeType;
  40. use Jackalope\NotImplementedException;
  41. use Jackalope\FactoryInterface;
  42. /**
  43. * Class to handle the communication between Jackalope and RDBMS via Doctrine DBAL.
  44. *
  45. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License Version 2.0, January 2004
  46. *
  47. * @author Benjamin Eberlei <kontakt@beberlei.de>
  48. * @author Lukas Kahwe Smith <smith@pooteeweet.org>
  49. */
  50. class Client extends BaseTransport implements QueryTransport, WritingInterface, WorkspaceManagementInterface, NodeTypeManagementInterface, TransactionInterface
  51. {
  52. /**
  53. * @var Doctrine\DBAL\Connection
  54. */
  55. private $conn;
  56. /**
  57. * @var bool
  58. */
  59. private $loggedIn = false;
  60. /**
  61. * @var \PHPCR\SimpleCredentials
  62. */
  63. private $credentials;
  64. /**
  65. * @var string
  66. */
  67. protected $workspaceName;
  68. /**
  69. * @var array
  70. */
  71. private $nodeIdentifiers = array();
  72. /**
  73. * @var NodeTypeManager
  74. */
  75. private $nodeTypeManager;
  76. /**
  77. * @var bool
  78. */
  79. protected $inTransaction = false;
  80. /**
  81. * Check if an initial request on login should be send to check if repository exists
  82. * This is according to the JCR specifications and set to true by default
  83. * @see setCheckLoginOnServer
  84. * @var bool
  85. */
  86. private $checkLoginOnServer = true;
  87. /**
  88. * @var array
  89. */
  90. protected $namespaces = array();
  91. /**
  92. * @var string|null
  93. */
  94. private $sequenceWorkspaceName;
  95. /**
  96. * @var string|null
  97. */
  98. private $sequenceNodeName;
  99. /**
  100. * @var string|null
  101. */
  102. private $sequenceTypeName;
  103. public function __construct(FactoryInterface $factory, Connection $conn)
  104. {
  105. $this->factory = $factory;
  106. $this->conn = $conn;
  107. if ($conn->getDatabasePlatform() instanceof PostgreSqlPlatform) {
  108. $this->sequenceWorkspaceName = 'phpcr_workspaces_id_seq';
  109. $this->sequenceNodeName = 'phpcr_nodes_id_seq';
  110. $this->sequenceTypeName = 'phpcr_type_nodes_node_type_id_seq';
  111. }
  112. // @TODO: move to "SqlitePlatform" and rename to "registerExtraFunctions"?
  113. if ($this->conn->getDatabasePlatform() instanceof SqlitePlatform) {
  114. $this->registerSqliteFunctions($this->conn->getWrappedConnection());
  115. }
  116. }
  117. /**
  118. * @TODO: move to "SqlitePlatform" and rename to "registerExtraFunctions"?
  119. *
  120. * @param PDOConnection $sqliteConnection
  121. *
  122. * @return Client
  123. */
  124. private function registerSqliteFunctions(PDOConnection $sqliteConnection)
  125. {
  126. $sqliteConnection->sqliteCreateFunction('EXTRACTVALUE', function ($string, $expression) {
  127. $dom = new \DOMDocument('1.0', 'UTF-8');
  128. $dom->loadXML($string);
  129. $xpath = new \DOMXPath($dom);
  130. $list = $xpath->evaluate($expression);
  131. if (!is_object($list)) {
  132. return $list;
  133. }
  134. // @TODO: don't know if there are expressions returning more then one row
  135. if ($list->length > 0) {
  136. return $list->item(0)->textContent;
  137. }
  138. // @TODO: don't know if return value is right
  139. return null;
  140. }, 2);
  141. $sqliteConnection->sqliteCreateFunction('CONCAT', function () {
  142. return implode('', func_get_args());
  143. });
  144. return $this;
  145. }
  146. /**
  147. * @return Doctrine\DBAL\Connection
  148. */
  149. public function getConnection()
  150. {
  151. return $this->conn;
  152. }
  153. /**
  154. * {@inheritDoc}
  155. *
  156. */
  157. public function createWorkspace($name, $srcWorkspace = null)
  158. {
  159. if (null !== $srcWorkspace) {
  160. throw new NotImplementedException();
  161. }
  162. try {
  163. $this->conn->insert('phpcr_workspaces', array('name' => $name));
  164. } catch (\Exception $e) {
  165. throw new RepositoryException("Workspace '$name' already exists");
  166. }
  167. $this->conn->insert('phpcr_nodes', array(
  168. 'path' => '/',
  169. 'parent' => '',
  170. 'workspace_name'=> $name,
  171. 'identifier' => UUIDHelper::generateUUID(),
  172. 'type' => 'nt:unstructured',
  173. 'local_name' => '',
  174. 'namespace' => '',
  175. 'props' => '<?xml version="1.0" encoding="UTF-8"?>
  176. <sv:node xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sv="http://www.jcp.org/jcr/sv/1.0" xmlns:rep="internal" />',
  177. // TODO compute proper value
  178. 'depth' => 0,
  179. ));
  180. }
  181. /**
  182. * {@inheritDoc}
  183. */
  184. public function login(\PHPCR\CredentialsInterface $credentials = null, $workspaceName = 'default')
  185. {
  186. $this->credentials = $credentials;
  187. $this->workspaceName = $workspaceName;
  188. if (!$this->checkLoginOnServer) {
  189. return true;
  190. }
  191. if (!$this->workspaceExists($workspaceName)) {
  192. if ('default' !== $workspaceName) {
  193. throw new NoSuchWorkspaceException("Requested workspace: $workspaceName");
  194. }
  195. // create default workspace if it not exists
  196. $this->createWorkspace($workspaceName);
  197. }
  198. $this->loggedIn = true;
  199. return true;
  200. }
  201. /**
  202. * {@inheritDoc}
  203. */
  204. public function logout()
  205. {
  206. if ($this->loggedIn) {
  207. $this->loggedIn = false;
  208. $this->conn->close();
  209. $this->conn = null;
  210. }
  211. }
  212. /**
  213. * {@inheritDoc}
  214. */
  215. public function setCheckLoginOnServer($bool)
  216. {
  217. $this->checkLoginOnServer = $bool;
  218. }
  219. protected function workspaceExists($workspaceName)
  220. {
  221. try {
  222. $query = 'SELECT 1 FROM phpcr_workspaces WHERE name = ?';
  223. $result = $this->conn->fetchColumn($query, array($workspaceName));
  224. } catch (\Exception $e) {
  225. if ($e instanceof DBALException || $e instanceof \PDOException) {
  226. if (1045 == $e->getCode()) {
  227. throw new \PHPCR\LoginException('Access denied with your credentials: '.$e->getMessage());
  228. }
  229. if ('42S02' == $e->getCode()) {
  230. throw new \PHPCR\RepositoryException('You did not properly set up the database for the repository. See README.md for more information. Message from backend: '.$e->getMessage());
  231. }
  232. throw new \PHPCR\RepositoryException('Unexpected error talking to the backend: '.$e->getMessage());
  233. }
  234. throw $e;
  235. }
  236. return $result;
  237. }
  238. protected function assertLoggedIn()
  239. {
  240. if (!$this->loggedIn) {
  241. if (!$this->checkLoginOnServer && $this->workspaceName) {
  242. $this->checkLoginOnServer = true;
  243. if ($this->login($this->credentials, $this->workspaceName)) {
  244. return;
  245. }
  246. }
  247. throw new RepositoryException('You need to be logged in for this operation');
  248. }
  249. }
  250. /**
  251. * {@inheritDoc}
  252. */
  253. public function getRepositoryDescriptors()
  254. {
  255. return array(
  256. RepositoryInterface::IDENTIFIER_STABILITY => RepositoryInterface::IDENTIFIER_STABILITY_INDEFINITE_DURATION,
  257. RepositoryInterface::REP_NAME_DESC => 'jackalope_doctrine_dbal',
  258. RepositoryInterface::REP_VENDOR_DESC => 'Jackalope Community',
  259. RepositoryInterface::REP_VENDOR_URL_DESC => 'http://github.com/jackalope',
  260. RepositoryInterface::REP_VERSION_DESC => '1.0.0-DEV',
  261. RepositoryInterface::SPEC_NAME_DESC => 'Content Repository for PHP',
  262. RepositoryInterface::SPEC_VERSION_DESC => '2.1',
  263. RepositoryInterface::NODE_TYPE_MANAGEMENT_AUTOCREATED_DEFINITIONS_SUPPORTED => true,
  264. RepositoryInterface::NODE_TYPE_MANAGEMENT_INHERITANCE => RepositoryInterface::NODE_TYPE_MANAGEMENT_INHERITANCE_SINGLE,
  265. RepositoryInterface::NODE_TYPE_MANAGEMENT_MULTIPLE_BINARY_PROPERTIES_SUPPORTED => true,
  266. RepositoryInterface::NODE_TYPE_MANAGEMENT_MULTIVALUED_PROPERTIES_SUPPORTED => true,
  267. RepositoryInterface::NODE_TYPE_MANAGEMENT_ORDERABLE_CHILD_NODES_SUPPORTED => true,
  268. RepositoryInterface::NODE_TYPE_MANAGEMENT_OVERRIDES_SUPPORTED => false,
  269. RepositoryInterface::NODE_TYPE_MANAGEMENT_PRIMARY_ITEM_NAME_SUPPORTED => true,
  270. RepositoryInterface::NODE_TYPE_MANAGEMENT_PROPERTY_TYPES => true,
  271. RepositoryInterface::NODE_TYPE_MANAGEMENT_RESIDUAL_DEFINITIONS_SUPPORTED => false,
  272. RepositoryInterface::NODE_TYPE_MANAGEMENT_SAME_NAME_SIBLINGS_SUPPORTED => false,
  273. RepositoryInterface::NODE_TYPE_MANAGEMENT_UPDATE_IN_USE_SUPPORTED => false,
  274. RepositoryInterface::NODE_TYPE_MANAGEMENT_VALUE_CONSTRAINTS_SUPPORTED => false,
  275. RepositoryInterface::OPTION_ACCESS_CONTROL_SUPPORTED => false,
  276. RepositoryInterface::OPTION_ACTIVITIES_SUPPORTED => false,
  277. RepositoryInterface::OPTION_BASELINES_SUPPORTED => false,
  278. RepositoryInterface::OPTION_JOURNALED_OBSERVATION_SUPPORTED => false,
  279. RepositoryInterface::OPTION_LIFECYCLE_SUPPORTED => false,
  280. RepositoryInterface::OPTION_LOCKING_SUPPORTED => false,
  281. RepositoryInterface::OPTION_NODE_AND_PROPERTY_WITH_SAME_NAME_SUPPORTED => true,
  282. RepositoryInterface::OPTION_NODE_TYPE_MANAGEMENT_SUPPORTED => true,
  283. RepositoryInterface::OPTION_OBSERVATION_SUPPORTED => false,
  284. RepositoryInterface::OPTION_RETENTION_SUPPORTED => false,
  285. RepositoryInterface::OPTION_SHAREABLE_NODES_SUPPORTED => false,
  286. RepositoryInterface::OPTION_SIMPLE_VERSIONING_SUPPORTED => false,
  287. RepositoryInterface::OPTION_TRANSACTIONS_SUPPORTED => true,
  288. RepositoryInterface::OPTION_UNFILED_CONTENT_SUPPORTED => true,
  289. RepositoryInterface::OPTION_UPDATE_MIXIN_NODETYPES_SUPPORTED => true,
  290. RepositoryInterface::OPTION_UPDATE_PRIMARY_NODETYPE_SUPPORTED => true,
  291. RepositoryInterface::OPTION_VERSIONING_SUPPORTED => false,
  292. RepositoryInterface::OPTION_WORKSPACE_MANAGEMENT_SUPPORTED => true,
  293. RepositoryInterface::OPTION_XML_EXPORT_SUPPORTED => true,
  294. RepositoryInterface::OPTION_XML_IMPORT_SUPPORTED => true,
  295. RepositoryInterface::QUERY_FULL_TEXT_SEARCH_SUPPORTED => true,
  296. RepositoryInterface::QUERY_JOINS => RepositoryInterface::QUERY_JOINS_NONE,
  297. RepositoryInterface::QUERY_LANGUAGES => array(QueryInterface::JCR_SQL2, QueryInterface::JCR_JQOM),
  298. RepositoryInterface::QUERY_STORED_QUERIES_SUPPORTED => false,
  299. RepositoryInterface::WRITE_SUPPORTED => true,
  300. );
  301. }
  302. /**
  303. * {@inheritDoc}
  304. */
  305. public function getNamespaces()
  306. {
  307. if (empty($this->namespaces)) {
  308. $query = 'SELECT * FROM phpcr_namespaces';
  309. $data = $this->conn->fetchAll($query);
  310. $this->namespaces = array(
  311. NamespaceRegistryInterface::PREFIX_EMPTY => NamespaceRegistryInterface::NAMESPACE_EMPTY,
  312. NamespaceRegistryInterface::PREFIX_JCR => NamespaceRegistryInterface::NAMESPACE_JCR,
  313. NamespaceRegistryInterface::PREFIX_NT => NamespaceRegistryInterface::NAMESPACE_NT,
  314. NamespaceRegistryInterface::PREFIX_MIX => NamespaceRegistryInterface::NAMESPACE_MIX,
  315. NamespaceRegistryInterface::PREFIX_XML => NamespaceRegistryInterface::NAMESPACE_XML,
  316. NamespaceRegistryInterface::PREFIX_SV => NamespaceRegistryInterface::NAMESPACE_SV,
  317. 'phpcr' => 'http://github.com/jackalope/jackalope', // TODO: Namespace?
  318. );
  319. foreach ($data as $row) {
  320. $this->namespaces[$row['prefix']] = $row['uri'];
  321. }
  322. }
  323. return $this->namespaces;
  324. }
  325. /**
  326. * {@inheritDoc}
  327. */
  328. public function copyNode($srcAbsPath, $dstAbsPath, $srcWorkspace = null)
  329. {
  330. $this->assertLoggedIn();
  331. $workspaceName = $this->workspaceName;
  332. if (null !== $srcWorkspace) {
  333. if (!$this->workspaceExists($srcWorkspace)) {
  334. throw new NoSuchWorkspaceException("Source workspace '$srcWorkspace' does not exist.");
  335. }
  336. }
  337. $this->assertValidPath($dstAbsPath, true);
  338. $srcNodeId = $this->pathExists($srcAbsPath);
  339. if (!$srcNodeId) {
  340. throw new PathNotFoundException("Source path '$srcAbsPath' not found");
  341. }
  342. if ($this->pathExists($dstAbsPath)) {
  343. throw new ItemExistsException("Cannot copy to destination path '$dstAbsPath' that already exists.");
  344. }
  345. if (!$this->pathExists($this->getParentPath($dstAbsPath))) {
  346. throw new PathNotFoundException("Parent of the destination path '" . $this->getParentPath($dstAbsPath) . "' has to exist.");
  347. }
  348. // Algorithm:
  349. // 1. Select all nodes with path $srcAbsPath."%" and iterate them
  350. // 2. create a new node with path $dstAbsPath + leftovers, with a new uuid. Save old => new uuid
  351. // 3. copy all properties from old node to new node
  352. // 4. if a reference is in the properties, either update the uuid based on the map if its inside the copied graph or keep it.
  353. // 5. "May drop mixin types"
  354. $query = 'SELECT * FROM phpcr_nodes WHERE path LIKE ? AND workspace_name = ?';
  355. $stmt = $this->conn->executeQuery($query, array($srcAbsPath . '%', $workspaceName));
  356. foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
  357. $newPath = str_replace($srcAbsPath, $dstAbsPath, $row['path']);
  358. $dom = new \DOMDocument('1.0', 'UTF-8');
  359. $dom->loadXML($row['props']);
  360. $propsData = array('dom' => $dom, 'binaryData' => array());
  361. //when copying a node, it is always a new node, then $isNewNode is set to true
  362. $newNodeId = $this->syncNode(null, $newPath, $this->getParentPath($newPath), $row['type'], true, array(), $propsData);
  363. $query = 'INSERT INTO phpcr_binarydata (node_id, property_name, workspace_name, idx, data)'.
  364. ' SELECT ?, b.property_name, ?, b.idx, b.data FROM phpcr_binarydata b WHERE b.node_id = ?';
  365. $this->conn->executeUpdate($query, array($newNodeId, $this->workspaceName, $srcNodeId));
  366. }
  367. }
  368. /**
  369. * @param string $path
  370. * @return array
  371. */
  372. private function getJcrName($path)
  373. {
  374. $name = implode('', array_slice(explode('/', $path), -1, 1));
  375. if (strpos($name, ':') === false) {
  376. $alias = '';
  377. } else {
  378. list($alias, $name) = explode(':', $name);
  379. }
  380. $namespaces = $this->getNamespaces();
  381. if (!isset($namespaces[$alias])) {
  382. throw new NamespaceException('the namespace ' . $alias . ' was not registered.');
  383. }
  384. return array($namespaces[$alias], $name);
  385. }
  386. /**
  387. * Actually write the node into the database
  388. *
  389. * @param string $uuid node uuid
  390. * @param string $path absolute path of the node
  391. * @param string $parent absolute path of the parent node
  392. * @param string $type node type
  393. * @param bool $isNewNode new nodes to insert (true) or existing node to update (false)
  394. * @param array $props
  395. * @param array $propsData
  396. *
  397. * @return bool|mixed|string
  398. *
  399. * @throws \Exception|\PHPCR\ItemExistsException|\PHPCR\RepositoryException
  400. */
  401. private function syncNode($uuid, $path, $parent, $type, $isNewNode, $props = array(), $propsData = array())
  402. {
  403. // TODO: Not sure if there are always ALL props in $props, should we grab the online data here?
  404. // TODO: Binary data is handled very inefficiently here, UPSERT will really be necessary here as well as lazy handling
  405. if (!$propsData) {
  406. $propsData = $this->propsToXML($props);
  407. }
  408. if (null === $uuid) {
  409. $uuid = UUIDHelper::generateUUID();
  410. }
  411. if ($isNewNode) {
  412. list($namespace, $localName) = $this->getJcrName($path);
  413. $qb = $this->conn->createQueryBuilder();
  414. $qb->select(':identifier, :type, :path, :local_name, :namespace, :parent, :workspace_name, :props, :depth, COALESCE(MAX(n.sort_order), 0) + 1')
  415. ->from('phpcr_nodes', 'n')
  416. ->where('n.parent = :parent_a');
  417. $sql = $qb->getSql();
  418. try {
  419. $insert = "INSERT INTO phpcr_nodes (identifier, type, path, local_name, namespace, parent, workspace_name, props, depth, sort_order) " . $sql;
  420. $this->conn->executeUpdate($insert, array(
  421. 'identifier' => $uuid,
  422. 'type' => $type,
  423. 'path' => $path,
  424. 'local_name' => $localName,
  425. 'namespace' => $namespace,
  426. 'parent' => $parent,
  427. 'workspace_name' => $this->workspaceName,
  428. 'props' => $propsData['dom']->saveXML(),
  429. // TODO compute proper value
  430. 'depth' => 0,
  431. 'parent_a' => $parent,
  432. ));
  433. } catch (\PDOException $e) {
  434. throw new ItemExistsException('Item ' . $path . ' already exists in the database');
  435. } catch (DBALException $e) {
  436. throw new ItemExistsException('Item ' . $path . ' already exists in the database');
  437. }
  438. $nodeId = $this->conn->lastInsertId($this->sequenceNodeName);
  439. } else {
  440. $nodeId = $this->pathExists($path);
  441. if (!$nodeId) {
  442. throw new RepositoryException();
  443. }
  444. $this->conn->update('phpcr_nodes', array('props' => $propsData['dom']->saveXML()), array('id' => $nodeId));
  445. }
  446. $this->nodeIdentifiers[$path] = $uuid;
  447. if (isset($propsData['binaryData'])) {
  448. $this->syncBinaryData($nodeId, $propsData['binaryData']);
  449. }
  450. $this->syncForeignKeys($nodeId, $path, $props);
  451. return $nodeId;
  452. }
  453. private function syncBinaryData($nodeId, $binaryData)
  454. {
  455. foreach ($binaryData as $propertyName => $binaryValues) {
  456. foreach ($binaryValues as $idx => $data) {
  457. // TODO verify in which cases we can just update
  458. $params = array(
  459. 'node_id' => $nodeId,
  460. 'property_name' => $propertyName,
  461. 'workspace_name' => $this->workspaceName,
  462. );
  463. $this->conn->delete('phpcr_binarydata', $params);
  464. $params['idx'] = $idx;
  465. $params['data'] = $data;
  466. $types = array(
  467. \PDO::PARAM_INT,
  468. \PDO::PARAM_STR,
  469. \PDO::PARAM_STR,
  470. \PDO::PARAM_INT,
  471. \PDO::PARAM_LOB
  472. );
  473. $this->conn->insert('phpcr_binarydata', $params, $types);
  474. }
  475. }
  476. }
  477. private function syncForeignKeys($nodeId, $path, $props)
  478. {
  479. $this->conn->delete('phpcr_nodes_foreignkeys', array('source_id' => $nodeId));
  480. foreach ($props as $property) {
  481. $type = $property->getType();
  482. if (PropertyType::REFERENCE == $type || PropertyType::WEAKREFERENCE == $type) {
  483. $values = array_unique( $property->isMultiple() ? $property->getString() : array($property->getString()) );
  484. foreach ($values as $value) {
  485. try {
  486. $targetId = $this->pathExists(self::getNodePathForIdentifier($value));
  487. $this->conn->insert('phpcr_nodes_foreignkeys', array(
  488. 'source_id' => $nodeId,
  489. 'source_property_name' => $property->getName(),
  490. 'target_id' => $targetId,
  491. 'type' => $type
  492. ));
  493. } catch (ItemNotFoundException $e) {
  494. if (PropertyType::REFERENCE == $type) {
  495. throw new ReferentialIntegrityException(
  496. "Trying to store reference to non-existant node with path '$value' in node $path property " . $property->getName()
  497. );
  498. }
  499. }
  500. }
  501. }
  502. }
  503. }
  504. static public function xmlToProps($xml, $filter = null)
  505. {
  506. $props = array();
  507. $dom = new \DOMDocument('1.0', 'UTF-8');
  508. $dom->loadXML($xml);
  509. foreach ($dom->getElementsByTagNameNS('http://www.jcp.org/jcr/sv/1.0', 'property') as $propertyNode) {
  510. $name = $propertyNode->getAttribute('sv:name');
  511. $values = array();
  512. $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type'));
  513. foreach ($propertyNode->childNodes as $valueNode) {
  514. switch ($type) {
  515. case PropertyType::NAME:
  516. case PropertyType::URI:
  517. case PropertyType::WEAKREFERENCE:
  518. case PropertyType::REFERENCE:
  519. case PropertyType::PATH:
  520. case PropertyType::DECIMAL:
  521. case PropertyType::STRING:
  522. $values[] = $valueNode->nodeValue;
  523. break;
  524. case PropertyType::BOOLEAN:
  525. $values[] = (bool)$valueNode->nodeValue;
  526. break;
  527. case PropertyType::LONG:
  528. $values[] = (int)$valueNode->nodeValue;
  529. break;
  530. case PropertyType::BINARY:
  531. $values[] = (int)$valueNode->nodeValue;
  532. break;
  533. case PropertyType::DATE:
  534. $values[] = $valueNode->nodeValue;
  535. break;
  536. case PropertyType::DOUBLE:
  537. $values[] = (double)$valueNode->nodeValue;
  538. break;
  539. default:
  540. throw new \InvalidArgumentException("Type with constant $type not found.");
  541. }
  542. }
  543. // only return the properties that pass through the filter callback
  544. if (null !== $filter && is_callable($filter)) {
  545. if (false === $filter($name, $values)) {
  546. continue;
  547. }
  548. }
  549. if (PropertyType::BINARY == $type) {
  550. if (1 == $propertyNode->getAttribute('sv:multi-valued')) {
  551. $props[':' . $name] = $values;
  552. } else {
  553. $props[':' . $name] = $values[0];
  554. }
  555. } else {
  556. if (1 == $propertyNode->getAttribute('sv:multi-valued')) {
  557. $props[$name] = $values;
  558. } else {
  559. $props[$name] = $values[0];
  560. }
  561. $props[':' . $name] = $type;
  562. }
  563. }
  564. return $props;
  565. }
  566. /**
  567. * Seperate properties array into an xml and binary data.
  568. *
  569. * @param array $properties
  570. * @param bool $inlineBinaries
  571. * @return array ('dom' => $dom, 'binary' => streams)
  572. */
  573. public function propsToXML($properties, $inlineBinaries = false)
  574. {
  575. $namespaces = array(
  576. 'mix' => "http://www.jcp.org/jcr/mix/1.0",
  577. 'nt' => "http://www.jcp.org/jcr/nt/1.0",
  578. 'xs' => "http://www.w3.org/2001/XMLSchema",
  579. 'jcr' => "http://www.jcp.org/jcr/1.0",
  580. 'sv' => "http://www.jcp.org/jcr/sv/1.0",
  581. 'rep' => "internal"
  582. );
  583. $dom = new \DOMDocument('1.0', 'UTF-8');
  584. $rootNode = $dom->createElement('sv:node');
  585. foreach ($namespaces as $namespace => $uri) {
  586. $rootNode->setAttribute('xmlns:' . $namespace, $uri);
  587. }
  588. $dom->appendChild($rootNode);
  589. $binaryData = null;
  590. foreach ($properties as $property) {
  591. /* @var $property Property */
  592. $propertyNode = $dom->createElement('sv:property');
  593. $propertyNode->setAttribute('sv:name', $property->getName());
  594. $propertyNode->setAttribute('sv:type', PropertyType::nameFromValue($property->getType()));
  595. $propertyNode->setAttribute('sv:multi-valued', $property->isMultiple() ? '1' : '0');
  596. switch ($property->getType()) {
  597. case PropertyType::NAME:
  598. case PropertyType::URI:
  599. case PropertyType::WEAKREFERENCE:
  600. case PropertyType::REFERENCE:
  601. case PropertyType::PATH:
  602. case PropertyType::STRING:
  603. $values = $property->getString();
  604. break;
  605. case PropertyType::DECIMAL:
  606. $values = $property->getDecimal();
  607. break;
  608. case PropertyType::BOOLEAN:
  609. $values = array_map('intval', (array) $property->getBoolean());
  610. break;
  611. case PropertyType::LONG:
  612. $values = $property->getLong();
  613. break;
  614. case PropertyType::BINARY:
  615. if ($property->isNew() || $property->isModified()) {
  616. if ($property->isMultiple()) {
  617. $values = array();
  618. foreach ($property->getValueForStorage() as $stream) {
  619. if (null === $stream) {
  620. $binary = '';
  621. } else {
  622. $binary = stream_get_contents($stream);
  623. fclose($stream);
  624. }
  625. $binaryData[$property->getName()][] = $binary;
  626. $values[] = strlen($binary);
  627. }
  628. } else {
  629. $stream = $property->getValueForStorage();
  630. if (null === $stream) {
  631. $binary = '';
  632. } else {
  633. $binary = stream_get_contents($stream);
  634. fclose($stream);
  635. }
  636. $binaryData[$property->getName()][] = $binary;
  637. $values = strlen($binary);
  638. }
  639. } else {
  640. $values = $property->getLength();
  641. if (!$property->isMultiple() && empty($values)) {
  642. // TODO: not sure why this happens.
  643. $values = array(0);
  644. }
  645. }
  646. break;
  647. case PropertyType::DATE:
  648. $date = $property->getDate();
  649. if (!$date instanceof \DateTime) {
  650. $date = new \DateTime("now");
  651. }
  652. $values = $date->format('r');
  653. break;
  654. case PropertyType::DOUBLE:
  655. $values = $property->getDouble();
  656. break;
  657. default:
  658. throw new RepositoryException('unknown type '.$property->getType());
  659. }
  660. foreach ((array)$values as $value) {
  661. $element = $propertyNode->appendChild($dom->createElement('sv:value'));
  662. $element->appendChild($dom->createTextNode($value));
  663. }
  664. $rootNode->appendChild($propertyNode);
  665. }
  666. return array('dom' => $dom, 'binaryData' => $binaryData);
  667. }
  668. /**
  669. * {@inheritDoc}
  670. */
  671. public function getAccessibleWorkspaceNames()
  672. {
  673. $query = "SELECT DISTINCT name FROM phpcr_workspaces";
  674. $stmt = $this->conn->executeQuery($query);
  675. return $stmt->fetchAll(\PDO::FETCH_COLUMN);
  676. }
  677. /**
  678. * {@inheritDoc}
  679. */
  680. public function getNode($path)
  681. {
  682. $this->assertValidPath($path);
  683. $this->assertLoggedIn();
  684. $query = 'SELECT * FROM phpcr_nodes WHERE path = ? AND workspace_name = ?';
  685. $row = $this->conn->fetchAssoc($query, array($path, $this->workspaceName));
  686. if (!$row) {
  687. throw new ItemNotFoundException("Item $path not found in workspace ".$this->workspaceName);
  688. }
  689. return $this->getNodeData($path, $row);
  690. }
  691. private function getNodeData($path, $row)
  692. {
  693. $data = new \stdClass();
  694. $data->{'jcr:primaryType'} = $row['type'];
  695. $this->nodeIdentifiers[$path] = $row['identifier'];
  696. $query = 'SELECT path FROM phpcr_nodes WHERE parent = ? AND workspace_name = ? ORDER BY sort_order ASC';
  697. $children = $this->conn->fetchAll($query, array($path, $this->workspaceName));
  698. foreach ($children as $child) {
  699. $childName = explode('/', $child['path']);
  700. $childName = end($childName);
  701. $data->{$childName} = new \stdClass();
  702. }
  703. $dom = new \DOMDocument('1.0', 'UTF-8');
  704. $dom->loadXML($row['props']);
  705. foreach ($dom->getElementsByTagNameNS('http://www.jcp.org/jcr/sv/1.0', 'property') as $propertyNode) {
  706. $name = $propertyNode->getAttribute('sv:name');
  707. $values = array();
  708. $type = PropertyType::valueFromName($propertyNode->getAttribute('sv:type'));
  709. foreach ($propertyNode->childNodes as $valueNode) {
  710. switch ($type) {
  711. case PropertyType::NAME:
  712. case PropertyType::URI:
  713. case PropertyType::WEAKREFERENCE:
  714. case PropertyType::REFERENCE:
  715. case PropertyType::PATH:
  716. case PropertyType::DECIMAL:
  717. case PropertyType::STRING:
  718. $values[] = $valueNode->nodeValue;
  719. break;
  720. case PropertyType::BOOLEAN:
  721. $values[] = (bool)$valueNode->nodeValue;
  722. break;
  723. case PropertyType::LONG:
  724. $values[] = (int)$valueNode->nodeValue;
  725. break;
  726. case PropertyType::BINARY:
  727. $values[] = (int)$valueNode->nodeValue;
  728. break;
  729. case PropertyType::DATE:
  730. $values[] = $valueNode->nodeValue;
  731. break;
  732. case PropertyType::DOUBLE:
  733. $values[] = (double)$valueNode->nodeValue;
  734. break;
  735. default:
  736. throw new \InvalidArgumentException("Type with constant " . $type . " not found.");
  737. }
  738. }
  739. if (PropertyType::BINARY == $type) {
  740. if (1 == $propertyNode->getAttribute('sv:multi-valued')) {
  741. $data->{':' . $name} = $values;
  742. } else {
  743. $data->{':' . $name} = $values[0];
  744. }
  745. } else {
  746. if (1 == $propertyNode->getAttribute('sv:multi-valued')) {
  747. $data->{$name} = $values;
  748. } else {
  749. $data->{$name} = $values[0];
  750. }
  751. $data->{':' . $name} = $type;
  752. }
  753. }
  754. // If the node is referenceable, return jcr:uuid.
  755. $is_referenceable = false;
  756. if (isset($data->{"jcr:mixinTypes"})) {
  757. foreach ((array) $data->{"jcr:mixinTypes"} as $mixin) {
  758. if ($this->nodeTypeManager->getNodeType($mixin)->isNodeType('mix:referenceable')) {
  759. $is_referenceable = true;
  760. break;
  761. }
  762. }
  763. }
  764. if ($is_referenceable) {
  765. $data->{'jcr:uuid'} = $row['identifier'];
  766. }
  767. return $data;
  768. }
  769. /**
  770. * {@inheritDoc}
  771. */
  772. public function getNodes($paths)
  773. {
  774. foreach ($paths as $path) {
  775. $this->assertValidPath($path);
  776. }
  777. $this->assertLoggedIn();
  778. $query = 'SELECT path AS arraykey, id, path, parent, local_name, namespace, workspace_name, identifier, type, props, depth, sort_order
  779. FROM phpcr_nodes WHERE workspace_name = ? AND path IN (?)';
  780. $params = array($this->workspaceName, $paths);
  781. $stmt = $this->conn->executeQuery($query, $params, array(\PDO::PARAM_STR, Connection::PARAM_STR_ARRAY));
  782. $all = $stmt->fetchAll(\PDO::FETCH_UNIQUE | \PDO::FETCH_GROUP);
  783. $nodes = array();
  784. foreach ($paths as $key => $path) {
  785. if (isset($all[$path])) {
  786. $nodes[$key] = $this->getNodeData($path, $all[$path]);
  787. }
  788. }
  789. return $nodes;
  790. }
  791. private function pathExists($path)
  792. {
  793. $query = 'SELECT id FROM phpcr_nodes WHERE path = ? AND workspace_name = ?';
  794. if ($nodeId = $this->conn->fetchColumn($query, array($path, $this->workspaceName))) {
  795. return $nodeId;
  796. }
  797. return false;
  798. }
  799. /**
  800. * {@inheritDoc}
  801. */
  802. public function deleteNode($path)
  803. {
  804. $this->assertLoggedIn();
  805. if ('/' == $path) {
  806. throw new ConstraintViolationException('You can not delete the root node of a repository');
  807. }
  808. $nodeId = $this->pathExists($path);
  809. if (!$nodeId) {
  810. throw new ItemNotFoundException("No node found at ".$path);
  811. }
  812. $params = array($path, $path."/%", $this->workspaceName);
  813. $query =
  814. 'SELECT COUNT(*)
  815. FROM phpcr_nodes_foreignkeys fk
  816. INNER JOIN phpcr_nodes n ON n.id = fk.target_id
  817. WHERE (n.path = ? OR n.path LIKE ?)
  818. AND workspace_name = ?
  819. AND fk.type = ' . PropertyType::REFERENCE;
  820. $fkReferences = $this->conn->fetchColumn($query, $params);
  821. if ($fkReferences > 0) {
  822. /*
  823. TODO: if we had logging, we could report which nodes
  824. $query =
  825. 'SELECT fk.source_id
  826. FROM phpcr_nodes_foreignkeys fk
  827. INNER JOIN phpcr_nodes n ON n.id = fk.target_id
  828. INNER JOIN phpcr_nodes f ON f.id = fk.source_id
  829. WHERE (n.path = ? OR n.path LIKE ?)
  830. AND n.workspace_name = ?
  831. AND fk.type = ' . PropertyType::REFERENCE;
  832. $paths = $this->conn->fetchAssoc($query, $params);
  833. */
  834. throw new ReferentialIntegrityException("Cannot delete $path: A reference points to this node or a subnode");
  835. }
  836. $query =
  837. 'DELETE FROM phpcr_nodes
  838. WHERE (path = ? OR path LIKE ?)
  839. AND workspace_name = ?';
  840. $this->conn->executeUpdate($query, $params);
  841. }
  842. /**
  843. * {@inheritDoc}
  844. */
  845. public function deleteProperty($path)
  846. {
  847. $this->assertLoggedIn();
  848. $nodePath = $this->getParentPath($path);
  849. $nodeId = $this->pathExists($nodePath);
  850. if (!$nodeId) {
  851. // no we really don't know that path
  852. throw new ItemNotFoundException("No item found at ".$path);
  853. }
  854. if ('/' == $nodePath) {
  855. // root node is a special case
  856. $propertyName = substr($path, 1);
  857. } else {
  858. $propertyName = str_replace($nodePath . '/', '', $path);
  859. }
  860. $query = 'SELECT props FROM phpcr_nodes WHERE id = ?';
  861. $xml = $this->conn->fetchColumn($query, array($nodeId));
  862. $dom = new \DOMDocument('1.0', 'UTF-8');
  863. $dom->loadXml($xml);
  864. $found = false;
  865. foreach ($dom->getElementsByTagNameNS('http://www.jcp.org/jcr/sv/1.0', 'property') as $propertyNode) {
  866. if ($propertyName == $propertyNode->getAttribute('sv:name')) {
  867. $found = true;
  868. // would be nice to have the property object to ask for type
  869. // but its in state deleted, would mean lots of refactoring
  870. if ($propertyNode->hasAttribute('sv:type') &&
  871. ('reference' == $propertyNode->getAttribute('sv:type')
  872. || 'weakreference' == $propertyNode->getAttribute('sv:type')
  873. )
  874. ) {
  875. $query = 'DELETE FROM phpcr_nodes_foreignkeys
  876. WHERE source_id = ?
  877. AND source_property_name = ?';
  878. $this->conn->executeUpdate($query, array($nodeId, $propertyName));
  879. }
  880. $propertyNode->parentNode->removeChild($propertyNode);
  881. break;
  882. }
  883. }
  884. if (! $found) {
  885. throw new ItemNotFoundException("Node $nodePath has no property $propertyName");
  886. }
  887. $xml = $dom->saveXML();
  888. $query = 'UPDATE phpcr_nodes SET props = ? WHERE id = ?';
  889. $params = array($xml, $nodeId);
  890. $this->conn->executeUpdate($query, $params);
  891. }
  892. /**
  893. * {@inheritDoc}
  894. */
  895. public function moveNode($srcAbsPath, $dstAbsPath)
  896. {
  897. $this->assertLoggedIn();
  898. $this->assertValidPath($dstAbsPath, true);
  899. $srcNodeId = $this->pathExists($srcAbsPath);
  900. if (!$srcNodeId) {
  901. throw new PathNotFoundException("Source path '$srcAbsPath' not found");
  902. }
  903. if ($this->pathExists($dstAbsPath)) {
  904. throw new ItemExistsException("Cannot move '$srcAbsPath' to '$dstAbsPath' because destination node already exists.");
  905. }
  906. if (!$this->pathExists($this->getParentPath($dstAbsPath))) {
  907. throw new PathNotFoundException("Parent of the destination path '" . $this->getParentPath($dstAbsPath) . "' has to exist.");
  908. }
  909. $query = 'SELECT path, id FROM phpcr_nodes WHERE path LIKE ? OR path = ? AND workspace_name = ? ' . $this->conn->getDatabasePlatform()->getForUpdateSQL();
  910. $stmt = $this->conn->executeQuery($query, array($srcAbsPath . '/%', $srcAbsPath, $this->workspaceName));
  911. /*
  912. * TODO: https://github.com/jackalope/jackalope-doctrine-dbal/pull/26/files#L0R1057
  913. * the other thing i wonder: can't you do the replacement inside sql instead of loading and then storing
  914. * the node? this will be extremly slow for a large set of nodes. i think you should use query builder here
  915. * rather than raw sql, to make it work on a maximum of platforms.
  916. *
  917. * can you try to do this please? if we don't figure out how to do it, at least fix the where criteria, and
  918. * we can ask the doctrine community how to do the substring operation.
  919. * http://stackoverflow.com/questions/8619421/correct-syntax-for-doctrine2s-query-builder-substring-helper-method
  920. */
  921. $ids = '';
  922. $query = "UPDATE phpcr_nodes SET ";
  923. $updatePathCase = "path = CASE ";
  924. $updateParentCase = "parent = CASE ";
  925. $updateLocalNameCase = "local_name = CASE ";
  926. $updateSortOrderCase = "sort_order = CASE ";
  927. $i = 0;
  928. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
  929. $values[':id' . $i] = $row['id'];
  930. $values[':path' . $i] = str_replace($srcAbsPath, $dstAbsPath, $row['path']);
  931. $values[':parent' . $i] = dirname($values[':path' . $i]);
  932. $updatePathCase .= "WHEN id = :id" . $i . " THEN :path" . $i . " ";
  933. $updateParentCase .= "WHEN id = :id" . $i . " THEN :parent" . $i . " ";
  934. if ($srcAbsPath === $row['path']) {
  935. $values[':localname' . $i] = basename($values[':path' . $i]);
  936. $updateLocalNameCase .= "WHEN id = :id" . $i . " THEN :localname" . $i . " ";
  937. $updateSortOrderCase .= "WHEN id = :id" . $i . " THEN (SELECT * FROM ( SELECT MAX(x.sort_order) + 1 FROM phpcr_nodes x WHERE x.parent = :parent" . $i . ") y) ";
  938. }
  939. $ids .= $row['id'] . ',';
  940. $i ++;
  941. }
  942. $ids = rtrim($ids, ',');
  943. $updateLocalNameCase .= "ELSE local_name END, ";
  944. $updateSortOrderCase .= "ELSE sort_order END ";
  945. $query .= $updatePathCase . "END, " . $updateParentCase . "END, " . $updateLocalNameCase . $updateSortOrderCase;
  946. $query .= "WHERE id IN (" . $ids . ")";
  947. $this->conn->executeUpdate($query, $values);
  948. }
  949. /**
  950. * {@inheritDoc}
  951. */
  952. public function reorderNodes($absPath, $reorders)
  953. {
  954. $this->assertLoggedIn();
  955. /* Solution:
  956. * - Determine the current order (from DB query).
  957. * - Use the $reorders to calculate the new order.
  958. * - Compare the old and new sequences to generate the required update statements.
  959. * We cant just use the $reorders to get UPDATE statements directly as even a simple single move, from being the
  960. * last sibling to being the first, could result in the need to update the sort_order of every sibling.
  961. */
  962. // Retrieve an array of siblings names in the original order.
  963. $qb = $this->conn->createQueryBuilder();
  964. $qb->select("CONCAT(n.namespace,(CASE namespace WHEN '' THEN '' ELSE ':' END), n.local_name)")
  965. ->from('phpcr_nodes', 'n')
  966. ->where('n.parent = :absPath')
  967. ->orderBy('n.sort_order', 'ASC');
  968. $query = $qb->getSql() . ' ' . $this->conn->getDatabasePlatform()->getForUpdateSQL();
  969. $stmnt = $this->conn->executeQuery($query, array('absPath' => $absPath));
  970. while ($row = $stmnt->fetchColumn()) {
  971. $original[] = $row;
  972. }
  973. // Flip to access via the name.
  974. $modified = array_flip($original);
  975. foreach ($reorders as $reorder) {
  976. if (null === $reorder[1]) {
  977. // Case: need to move node to the end of the array.
  978. // Remove from old position and append to end.
  979. unset($modified[$reorder[0]]);
  980. $modified = array_flip($modified);
  981. $modified[] = $reorder[0];
  982. // Resequence keys and flip back so we can access via name again.
  983. $modified = array_values($modified);
  984. $modified = array_flip($modified);
  985. } else {
  986. // Case: need to move node to before the specified target.
  987. // Remove from old position, resequence the keys and flip back so we can access by name again.
  988. unset($modified[$reorder[0]]);
  989. $modified = array_keys($modified);
  990. $modified = array_flip($modified);
  991. // Get target position and splice in.
  992. $targetPos = $modified[$reorder[1]];
  993. $modified = array_flip($modified);
  994. array_splice($modified, $targetPos, 0, $reorder[0]);
  995. $modified = array_flip($modified);
  996. }
  997. }
  998. $values[':absPath'] = $absPath;
  999. $sql = "UPDATE phpcr_nodes SET sort_order = CASE CONCAT(
  1000. namespace,
  1001. (CASE namespace WHEN '' THEN '' ELSE ':' END),
  1002. local_name
  1003. )";
  1004. $i = 0;
  1005. foreach ($modified as $name => $order) {
  1006. $values[':name' . $i] = $name;
  1007. $values[':order' . $i] = $order;
  1008. $sql .= " WHEN :name" . $i . " THEN :order" . $i;
  1009. $i++;
  1010. }
  1011. $sql .= " ELSE sort_order END WHERE parent = :absPath";
  1012. $this->conn->executeUpdate($sql, $values);
  1013. }
  1014. /**
  1015. * Get parent path of a path.
  1016. *
  1017. * @param string $path
  1018. * @return string
  1019. */
  1020. private function getParentPath($path)
  1021. {
  1022. $parent = implode('/', array_slice(explode('/', $path), 0, -1));
  1023. if (!$parent) {
  1024. return '/';
  1025. }
  1026. return $parent;
  1027. }
  1028. /**
  1029. * TODO: we should move that into the common Jackalope BaseTransport or as new method of NodeType
  1030. * it will be helpful for other implementations.
  1031. *
  1032. * Validate this node with the nodetype and generate not yet existing
  1033. * autogenerated properties as necessary.
  1034. *
  1035. * @param Node $node
  1036. * @param NodeType $def
  1037. */
  1038. private function validateNode(Node $node, NodeType $def)
  1039. {
  1040. foreach ($def->getDeclaredChildNodeDefinitions() as $childDef) {
  1041. /* @var $childDef \PHPCR\NodeType\NodeDefinitionInterface */
  1042. if (!$node->hasNode($childDef->getName())) {
  1043. if ('*' === $childDef->getName()) {
  1044. continue;
  1045. }
  1046. if ($childDef->isMandatory() && !$childDef->isAutoCreated()) {
  1047. throw new RepositoryException(
  1048. "Child " . $childDef->getName() . " is mandatory, but is not present while ".
  1049. "saving " . $def->getName() . " at " . $node->getPath()
  1050. );
  1051. }
  1052. if ($childDef->isAutoCreated()) {
  1053. throw new NotImplementedException("Auto-creation of child node '".$def->getName()."#".$childDef->getName()."' is not yet supported in DoctrineDBAL transport.");
  1054. }
  1055. }
  1056. }
  1057. foreach ($def->getDeclaredPropertyDefinitions() as $propertyDef) {
  1058. /* @var $propertyDef \PHPCR\NodeType\PropertyDefinitionInterface */
  1059. if ('*' == $propertyDef->getName()) {
  1060. continue;
  1061. }
  1062. if (!$node->hasProperty($propertyDef->getName())) {
  1063. if ($propertyDef->isMandatory() && !$propertyDef->isAutoCreated()) {
  1064. throw new RepositoryException(
  1065. "Property " . $propertyDef->getName() . " is mandatory, but is not present while ".
  1066. "saving " . $def->getName() . " at " . $node->getPath()
  1067. );
  1068. }
  1069. if ($propertyDef->isAutoCreated()) {
  1070. switch ($propertyDef->getName()) {
  1071. case 'jcr:uuid':
  1072. $value = UUIDHelper::generateUUID();
  1073. break;
  1074. case 'jcr:createdBy':
  1075. case 'jcr:lastModifiedBy':
  1076. $value = $this->credentials->getUserID();
  1077. break;
  1078. case 'jcr:created':
  1079. case 'jcr:lastModified':
  1080. $value = new \DateTime();
  1081. break;
  1082. case 'jcr:etag':
  1083. // TODO: http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.7.12.1%20mix:etag
  1084. $value = 'TODO: generate from binary properties of this node';
  1085. break;
  1086. default:
  1087. $defaultValues = $propertyDef->getDefaultValues();
  1088. if ($propertyDef->isMultiple()) {
  1089. $value = $defaultValues;
  1090. } elseif (isset($defaultValues[0])) {
  1091. $value = $defaultValues[0];
  1092. } else {
  1093. // When implementing versionable or activity, we need to handle more properties explicitly
  1094. throw new RepositoryException('No default value for autocreated property '.
  1095. $propertyDef->getName(). ' at '.$node->getPath());
  1096. }
  1097. }
  1098. $node->setProperty(
  1099. $propertyDef->getName(),
  1100. $value,
  1101. $propertyDef->getRequiredType()
  1102. );
  1103. }
  1104. }
  1105. }
  1106. foreach ($node->getProperties() as $property) {
  1107. $this->assertValidProperty($property);
  1108. }
  1109. }
  1110. private function getResponsibleN

Large files files are truncated, but you can click here to view the full file