vendor/jackalope/jackalope/src/Jackalope/Session.php line 271

Open in your IDE?
  1. <?php
  2. namespace Jackalope;
  3. use PHPCR\Util\PathHelper;
  4. use Exception;
  5. use PHPCR\RepositoryInterface;
  6. use PHPCR\SessionInterface;
  7. use PHPCR\SimpleCredentials;
  8. use PHPCR\CredentialsInterface;
  9. use PHPCR\PathNotFoundException;
  10. use PHPCR\ItemNotFoundException;
  11. use PHPCR\ItemExistsException;
  12. use PHPCR\RepositoryException;
  13. use PHPCR\UnsupportedRepositoryOperationException;
  14. use InvalidArgumentException;
  15. use PHPCR\Security\AccessControlException;
  16. use Jackalope\ImportExport\ImportExport;
  17. use Jackalope\Transport\TransportInterface;
  18. use Jackalope\Transport\TransactionInterface;
  19. use Traversable;
  20. /**
  21.  * {@inheritDoc}
  22.  *
  23.  * Jackalope adds the SessionOption concept to handle session specific tweaking
  24.  * and optimization. We distinguish between options that are purely
  25.  * optimization but do not affect the behaviour and those that are change the
  26.  * behaviour.
  27.  *
  28.  * @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
  29.  * @license http://opensource.org/licenses/MIT MIT License
  30.  *
  31.  * @api
  32.  */
  33. class Session implements SessionInterface
  34. {
  35.     /**
  36.      * Constant for setSessionOption to manage the fetch depth.
  37.      *
  38.      * This option is used to set the depth with which nodes should be fetched from the backend to optimize
  39.      * performance when you know you will need the child nodes.
  40.      */
  41.     const OPTION_FETCH_DEPTH 'jackalope.fetch_depth';
  42.     /**
  43.      * Constant for setSessionOption to manage whether nodes having mix:lastModified should automatically be updated.
  44.      *
  45.      * Disable if you want to manually control this information, e.g. in a PHPCR-ODM listener.
  46.      */
  47.     const OPTION_AUTO_LASTMODIFIED 'jackalope.auto_lastmodified';
  48.     /**
  49.      * A registry for all created sessions to be able to reference them by id in
  50.      * the stream wrapper for lazy loading binary properties.
  51.      *
  52.      * Keys are spl_object_hash'es for the sessions which are the values
  53.      *
  54.      * @var array
  55.      */
  56.     protected static $sessionRegistry = [];
  57.     /**
  58.      * The factory to instantiate objects
  59.      *
  60.      * @var FactoryInterface
  61.      */
  62.     protected $factory;
  63.     /**
  64.      * @var Repository
  65.      */
  66.     protected $repository;
  67.     /**
  68.      * @var Workspace
  69.      */
  70.     protected $workspace;
  71.     /**
  72.      * @var ObjectManager
  73.      */
  74.     protected $objectManager;
  75.     /**
  76.      * @var SimpleCredentials
  77.      */
  78.     protected $credentials;
  79.     /**
  80.      * Whether this session is in logged out state and can not be used anymore
  81.      *
  82.      * @var bool
  83.      */
  84.     protected $logout false;
  85.     /**
  86.      * The namespace registry.
  87.      *
  88.      * It is only used to check prefixes and at setup. Session namespace remapping must be handled locally.
  89.      *
  90.      * @var NamespaceRegistry
  91.      */
  92.     protected $namespaceRegistry;
  93.     /**
  94.      * List of local namespaces
  95.      *
  96.      * TODO: implement local namespace rewriting
  97.      * see jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java and friends
  98.      * for how this is done in jackrabbit
  99.      */
  100.     //protected $localNamespaces;
  101.     /** Creates a session
  102.      *
  103.      * Builds the corresponding workspace instance
  104.      *
  105.      * @param FactoryInterface  $factory       the object factory
  106.      * @param Repository        $repository
  107.      * @param string            $workspaceName the workspace name that is used
  108.      * @param SimpleCredentials $credentials   the credentials that where
  109.      *      used to log in, in order to implement Session::getUserID()
  110.      *      if they are null, getUserID returns null
  111.      * @param TransportInterface $transport the transport implementation
  112.      */
  113.     public function __construct(FactoryInterface $factoryRepository $repository$workspaceNameSimpleCredentials $credentials nullTransportInterface $transport)
  114.     {
  115.         $this->factory $factory;
  116.         $this->repository $repository;
  117.         $this->objectManager $this->factory->get(ObjectManager::class, [$transport$this]);
  118.         $this->workspace $this->factory->get(Workspace::class, [$this$this->objectManager$workspaceName]);
  119.         $this->credentials $credentials;
  120.         $this->namespaceRegistry $this->workspace->getNamespaceRegistry();
  121.         self::registerSession($this);
  122.         $transport->setNodeTypeManager($this->workspace->getNodeTypeManager());
  123.     }
  124.     /**
  125.      * {@inheritDoc}
  126.      *
  127.      * @api
  128.      */
  129.     public function getRepository()
  130.     {
  131.         return $this->repository;
  132.     }
  133.     /**
  134.      * {@inheritDoc}
  135.      *
  136.      * @api
  137.      */
  138.     public function getUserID()
  139.     {
  140.         if (null === $this->credentials) {
  141.             return null;
  142.         }
  143.         return $this->credentials->getUserID(); //TODO: what if its not simple credentials? what about anonymous login?
  144.     }
  145.     /**
  146.      * {@inheritDoc}
  147.      *
  148.      * @api
  149.      */
  150.     public function getAttributeNames()
  151.     {
  152.         if (null === $this->credentials) {
  153.             return [];
  154.         }
  155.         return $this->credentials->getAttributeNames();
  156.     }
  157.     /**
  158.      * {@inheritDoc}
  159.      *
  160.      * @api
  161.      */
  162.     public function getAttribute($name)
  163.     {
  164.         if (null === $this->credentials) {
  165.             return null;
  166.         }
  167.         return $this->credentials->getAttribute($name);
  168.     }
  169.     /**
  170.      * {@inheritDoc}
  171.      *
  172.      * @api
  173.      */
  174.     public function getWorkspace()
  175.     {
  176.         return $this->workspace;
  177.     }
  178.     /**
  179.      * {@inheritDoc}
  180.      *
  181.      * @api
  182.      */
  183.     public function getRootNode()
  184.     {
  185.         return $this->getNode('/');
  186.     }
  187.     /**
  188.      * {@inheritDoc}
  189.      *
  190.      * @api
  191.      */
  192.     public function impersonate(CredentialsInterface $credentials)
  193.     {
  194.         throw new UnsupportedRepositoryOperationException('Not supported');
  195.     }
  196.     /**
  197.      * {@inheritDoc}
  198.      *
  199.      * @api
  200.      */
  201.     public function getNodeByIdentifier($id)
  202.     {
  203.         return $this->objectManager->getNodeByIdentifier($id);
  204.     }
  205.     /**
  206.      * {@inheritDoc}
  207.      *
  208.      * @api
  209.      */
  210.     public function getNodesByIdentifier($ids)
  211.     {
  212.         if (! is_array($ids) && ! $ids instanceof Traversable) {
  213.             $hint is_object($ids) ? get_class($ids) : gettype($ids);
  214.             throw new InvalidArgumentException("Not a valid array or Traversable: $hint");
  215.         }
  216.         return $this->objectManager->getNodesByIdentifier($ids);
  217.     }
  218.     /**
  219.      * {@inheritDoc}
  220.      *
  221.      * @api
  222.      */
  223.     public function getItem($absPath)
  224.     {
  225.         if (! is_string($absPath) || strlen($absPath) === || '/' !== $absPath[0]) {
  226.             throw new PathNotFoundException('It is forbidden to call getItem on session with a relative path');
  227.         }
  228.         if ($this->nodeExists($absPath)) {
  229.             return $this->getNode($absPath);
  230.         }
  231.         return $this->getProperty($absPath);
  232.     }
  233.     /**
  234.      * {@inheritDoc}
  235.      *
  236.      * @api
  237.      */
  238.     public function getNode($absPath$depthHint = -1)
  239.     {
  240.         if (-!== $depthHint) {
  241.             $depth $this->getSessionOption(self::OPTION_FETCH_DEPTH);
  242.             $this->setSessionOption(self::OPTION_FETCH_DEPTH$depthHint);
  243.         }
  244.         try {
  245.             $node $this->objectManager->getNodeByPath($absPath);
  246.             if (isset($depth)) {
  247.                 $this->setSessionOption(self::OPTION_FETCH_DEPTH$depth);
  248.             }
  249.             return $node;
  250.         } catch (ItemNotFoundException $e) {
  251.             if (isset($depth)) {
  252.                 $this->setSessionOption(self::OPTION_FETCH_DEPTH$depth);
  253.             }
  254.             throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  255.         }
  256.     }
  257.     /**
  258.      * {@inheritDoc}
  259.      *
  260.      * @api
  261.      */
  262.     public function getNodes($absPaths)
  263.     {
  264.         if (! is_array($absPaths) && ! $absPaths instanceof Traversable) {
  265.             $hint is_object($absPaths) ? get_class($absPaths) : gettype($absPaths);
  266.             throw new InvalidArgumentException("Not a valid array or Traversable: $hint");
  267.         }
  268.         return $this->objectManager->getNodesByPath($absPaths);
  269.     }
  270.     /**
  271.      * {@inheritDoc}
  272.      *
  273.      * @api
  274.      */
  275.     public function getProperty($absPath)
  276.     {
  277.         try {
  278.             return $this->objectManager->getPropertyByPath($absPath);
  279.         } catch (ItemNotFoundException $e) {
  280.             throw new PathNotFoundException($e->getMessage(), $e->getCode(), $e);
  281.         }
  282.     }
  283.     public function getProperties($absPaths)
  284.     {
  285.         if (! is_array($absPaths) && ! $absPaths instanceof Traversable) {
  286.             $hint is_object($absPaths) ? get_class($absPaths) : gettype($absPaths);
  287.             throw new InvalidArgumentException("Not a valid array or Traversable: $hint");
  288.         }
  289.         return $this->objectManager->getPropertiesByPath($absPaths);
  290.     }
  291.     /**
  292.      * {@inheritDoc}
  293.      *
  294.      * @api
  295.      */
  296.     public function itemExists($absPath)
  297.     {
  298.         if ($absPath === '/') {
  299.             return true;
  300.         }
  301.         return $this->nodeExists($absPath) || $this->propertyExists($absPath);
  302.     }
  303.     /**
  304.      * {@inheritDoc}
  305.      *
  306.      * @api
  307.      */
  308.     public function nodeExists($absPath)
  309.     {
  310.         if ($absPath === '/') {
  311.             return true;
  312.         }
  313.         try {
  314.             //OPTIMIZE: avoid throwing and catching errors would improve performance if many node exists calls are made
  315.             //would need to communicate to the lower layer that we do not want exceptions
  316.             $this->objectManager->getNodeByPath($absPath);
  317.         } catch (ItemNotFoundException $e) {
  318.             return false;
  319.         }
  320.         return true;
  321.     }
  322.     /**
  323.      * {@inheritDoc}
  324.      *
  325.      * @api
  326.      */
  327.     public function propertyExists($absPath)
  328.     {
  329.         try {
  330.             //OPTIMIZE: avoid throwing and catching errors would improve performance if many node exists calls are made
  331.             //would need to communicate to the lower layer that we do not want exceptions
  332.             $this->getProperty($absPath);
  333.         } catch (PathNotFoundException $e) {
  334.             return false;
  335.         }
  336.         return true;
  337.     }
  338.     /**
  339.      * {@inheritDoc}
  340.      *
  341.      * @api
  342.      */
  343.     public function move($srcAbsPath$destAbsPath)
  344.     {
  345.         try {
  346.             $parent $this->objectManager->getNodeByPath(PathHelper::getParentPath($destAbsPath));
  347.         } catch (ItemNotFoundException $e) {
  348.             throw new PathNotFoundException("Target path can not be found: $destAbsPath"$e->getCode(), $e);
  349.         }
  350.         if ($parent->hasNode(PathHelper::getNodeName($destAbsPath))) {
  351.             // TODO same-name siblings
  352.             throw new ItemExistsException('Target node already exists at '.$destAbsPath);
  353.         }
  354.         if ($parent->hasProperty(PathHelper::getNodeName($destAbsPath))) {
  355.             throw new ItemExistsException('Target property already exists at '.$destAbsPath);
  356.         }
  357.         $this->objectManager->moveNode($srcAbsPath$destAbsPath);
  358.     }
  359.     /**
  360.      * {@inheritDoc}
  361.      *
  362.      * @api
  363.      */
  364.     public function removeItem($absPath)
  365.     {
  366.         $item $this->getItem($absPath);
  367.         $item->remove();
  368.     }
  369.     /**
  370.      * {@inheritDoc}
  371.      *
  372.      * Wraps the save operation into a transaction if transactions are enabled
  373.      * but we are not currently inside a transaction and rolls back on error.
  374.      *
  375.      * If transactions are disabled, errors on save can lead to partial saves
  376.      * and inconsistent data.
  377.      *
  378.      * @api
  379.      */
  380.     public function save()
  381.     {
  382.         if ($this->getTransport() instanceof TransactionInterface) {
  383.             try {
  384.                 $utx $this->workspace->getTransactionManager();
  385.             } catch (UnsupportedRepositoryOperationException $e) {
  386.                 // user transactions where disabled for this session, do no automatic transaction.
  387.             }
  388.         }
  389.         if (isset($utx) && !$utx->inTransaction()) {
  390.             // do the operation in a short transaction
  391.             $utx->begin();
  392.             try {
  393.                 $this->objectManager->save();
  394.                 $utx->commit();
  395.             } catch (Exception $e) {
  396.                 // if anything goes wrong, rollback this mess
  397.                 try {
  398.                     $utx->rollback();
  399.                 } catch (Exception $rollbackException) {
  400.                     // ignore this exception
  401.                 }
  402.                 // but do not eat this exception
  403.                 throw $e;
  404.             }
  405.         } else {
  406.             $this->objectManager->save();
  407.         }
  408.     }
  409.     /**
  410.      * {@inheritDoc}
  411.      *
  412.      * @api
  413.      */
  414.     public function refresh($keepChanges)
  415.     {
  416.         $this->objectManager->refresh($keepChanges);
  417.     }
  418.     /**
  419.      * Jackalope specific hack to drop the state of the current session
  420.      *
  421.      * Removes all cached objects, planned changes etc without making the
  422.      * objects aware of it. Was done as a cheap replacement for refresh
  423.      * in testing.
  424.      *
  425.      * @deprecated: this will screw up major, as the user of the api can still have references to nodes. USE refresh instead!
  426.      */
  427.     public function clear()
  428.     {
  429.         trigger_error('Use Session::refresh instead, this method is extremely unsafe'E_USER_DEPRECATED);
  430.         $this->objectManager->clear();
  431.     }
  432.     /**
  433.      * {@inheritDoc}
  434.      *
  435.      * @api
  436.      */
  437.     public function hasPendingChanges()
  438.     {
  439.         return $this->objectManager->hasPendingChanges();
  440.     }
  441.     /**
  442.      * {@inheritDoc}
  443.      *
  444.      * @api
  445.      */
  446.     public function hasPermission($absPath$actions)
  447.     {
  448.         $actualPermissions $this->objectManager->getPermissions($absPath);
  449.         $requestedPermissions explode(','$actions);
  450.         foreach ($requestedPermissions as $perm) {
  451.             if (! in_array(strtolower(trim($perm)), $actualPermissions)) {
  452.                 return false;
  453.             }
  454.         }
  455.         return true;
  456.     }
  457.     /**
  458.      * {@inheritDoc}
  459.      *
  460.      * @api
  461.      */
  462.     public function checkPermission($absPath$actions)
  463.     {
  464.         if (! $this->hasPermission($absPath$actions)) {
  465.             throw new AccessControlException($absPath);
  466.         }
  467.     }
  468.     /**
  469.      * {@inheritDoc}
  470.      *
  471.      * Jackalope does currently not check anything and always return true.
  472.      *
  473.      * @api
  474.      */
  475.     public function hasCapability($methodName$target, array $arguments)
  476.     {
  477.         //we never determine whether operation can be performed as it is optional ;-)
  478.         //TODO: could implement some
  479.         return true;
  480.     }
  481.     /**
  482.      * {@inheritDoc}
  483.      *
  484.      * @api
  485.      */
  486.     public function importXML($parentAbsPath$uri$uuidBehavior)
  487.     {
  488.         ImportExport::importXML(
  489.             $this->getNode($parentAbsPath),
  490.             $this->workspace->getNamespaceRegistry(),
  491.             $uri,
  492.             $uuidBehavior
  493.         );
  494.     }
  495.     /**
  496.      * {@inheritDoc}
  497.      *
  498.      * @api
  499.      */
  500.     public function exportSystemView($absPath$stream$skipBinary$noRecurse)
  501.     {
  502.         ImportExport::exportSystemView(
  503.             $this->getNode($absPath),
  504.             $this->workspace->getNamespaceRegistry(),
  505.             $stream,
  506.             $skipBinary,
  507.             $noRecurse
  508.         );
  509.     }
  510.     /**
  511.      * {@inheritDoc}
  512.      *
  513.      * @api
  514.      */
  515.     public function exportDocumentView($absPath$stream$skipBinary$noRecurse)
  516.     {
  517.         ImportExport::exportDocumentView(
  518.             $this->getNode($absPath),
  519.             $this->workspace->getNamespaceRegistry(),
  520.             $stream,
  521.             $skipBinary,
  522.             $noRecurse
  523.         );
  524.     }
  525.     /**
  526.      * {@inheritDoc}
  527.      *
  528.      * @api
  529.      */
  530.     public function setNamespacePrefix($prefix$uri)
  531.     {
  532.         $this->namespaceRegistry->checkPrefix($prefix);
  533.         throw new NotImplementedException('TODO: implement session scope remapping of namespaces');
  534.         //this will lead to rewrite all names and paths in requests and replies. part of this can be done in ObjectManager::normalizePath
  535.     }
  536.     /**
  537.      * {@inheritDoc}
  538.      *
  539.      * @api
  540.      */
  541.     public function getNamespacePrefixes()
  542.     {
  543.         //TODO: once setNamespacePrefix is implemented, must take session remaps into account
  544.         return $this->namespaceRegistry->getPrefixes();
  545.     }
  546.     /**
  547.      * {@inheritDoc}
  548.      *
  549.      * @api
  550.      */
  551.     public function getNamespaceURI($prefix)
  552.     {
  553.         //TODO: once setNamespacePrefix is implemented, must take session remaps into account
  554.         return $this->namespaceRegistry->getURI($prefix);
  555.     }
  556.     /**
  557.      * {@inheritDoc}
  558.      *
  559.      * @api
  560.      */
  561.     public function getNamespacePrefix($uri)
  562.     {
  563.         //TODO: once setNamespacePrefix is implemented, must take session remaps into account
  564.         return $this->namespaceRegistry->getPrefix($uri);
  565.     }
  566.     /**
  567.      * {@inheritDoc}
  568.      *
  569.      * @api
  570.      */
  571.     public function logout()
  572.     {
  573.         //OPTIMIZATION: flush object manager to help garbage collector
  574.         $this->logout true;
  575.         if ($this->getRepository()->getDescriptor(RepositoryInterface::OPTION_LOCKING_SUPPORTED)) {
  576.             $this->getWorkspace()->getLockManager()->logout();
  577.         }
  578.         self::unregisterSession($this);
  579.         $this->getTransport()->logout();
  580.     }
  581.     /**
  582.      * {@inheritDoc}
  583.      *
  584.      * @api
  585.      */
  586.     public function isLive()
  587.     {
  588.         return ! $this->logout;
  589.     }
  590.     /**
  591.      * {@inheritDoc}
  592.      *
  593.      * @api
  594.      */
  595.     public function getAccessControlManager()
  596.     {
  597.         throw new UnsupportedRepositoryOperationException();
  598.     }
  599.     /**
  600.      * {@inheritDoc}
  601.      *
  602.      * @api
  603.      */
  604.     public function getRetentionManager()
  605.     {
  606.         throw new UnsupportedRepositoryOperationException();
  607.     }
  608.     /**
  609.      * Implementation specific: The object manager is also used by other components, i.e. the QueryManager.
  610.      *
  611.      * @return ObjectManager the object manager associated with this session
  612.      *
  613.      * @private
  614.      */
  615.     public function getObjectManager()
  616.     {
  617.         return $this->objectManager;
  618.     }
  619.     /**
  620.      * Implementation specific: The transport implementation is also used by other components,
  621.      * i.e. the NamespaceRegistry
  622.      *
  623.      * @return TransportInterface the transport implementation associated with
  624.      *      this session.
  625.      *
  626.      * @private
  627.      */
  628.     public function getTransport()
  629.     {
  630.         return $this->objectManager->getTransport();
  631.     }
  632.     /**
  633.      * Implementation specific: register session in session registry for the stream wrapper.
  634.      *
  635.      * @param Session $session the session to register
  636.      *
  637.      * @private
  638.      */
  639.     protected static function registerSession(Session $session)
  640.     {
  641.         $key $session->getRegistryKey();
  642.         self::$sessionRegistry[$key] = $session;
  643.     }
  644.     /**
  645.      * Implementation specific: unregister session in session registry on logout.
  646.      *
  647.      * @param Session $session the session to unregister
  648.      *
  649.      * @private
  650.      */
  651.     protected static function unregisterSession(Session $session)
  652.     {
  653.         $key $session->getRegistryKey();
  654.         unset(self::$sessionRegistry[$key]);
  655.     }
  656.     /**
  657.      * Implementation specific: create an id for the session registry so that the stream wrapper can identify it.
  658.      *
  659.      * @private
  660.      *
  661.      * @return string an id for this session
  662.      */
  663.     public function getRegistryKey()
  664.     {
  665.         return spl_object_hash($this);
  666.     }
  667.     /**
  668.      * Implementation specific: get a session from the session registry for the stream wrapper.
  669.      *
  670.      * @param string $key key for the session
  671.      *
  672.      * @return Session|null the session or null if none is registered with the given key
  673.      *
  674.      * @private
  675.      */
  676.     public static function getSessionFromRegistry($key)
  677.     {
  678.         if (isset(self::$sessionRegistry[$key])) {
  679.             return self::$sessionRegistry[$key];
  680.         }
  681.         return null;
  682.     }
  683.     /**
  684.      * Sets a session specific option.
  685.      *
  686.      * @param string $key   the key to be set
  687.      * @param mixed  $value the value to be set
  688.      *
  689.      * @throws InvalidArgumentException if the option is unknown
  690.      * @throws RepositoryException      if this option is not supported and is
  691.      *      a behaviour relevant option
  692.      *
  693.      * @see BaseTransport::setFetchDepth($value);
  694.      */
  695.     public function setSessionOption($key$value)
  696.     {
  697.         switch ($key) {
  698.             case self::OPTION_FETCH_DEPTH:
  699.                 $this->getTransport()->setFetchDepth($value);
  700.                 break;
  701.             case self::OPTION_AUTO_LASTMODIFIED:
  702.                 $this->getTransport()->setAutoLastModified($value);
  703.                 break;
  704.             default:
  705.                 throw new InvalidArgumentException("Unknown option: $key");
  706.         }
  707.     }
  708.     /**
  709.      * Gets a session specific option.
  710.      *
  711.      * @param string $key the key to be gotten
  712.      *
  713.      * @return bool
  714.      *
  715.      * @throws InvalidArgumentException if the option is unknown
  716.      *
  717.      * @see setSessionOption($key, $value);
  718.      */
  719.     public function getSessionOption($key)
  720.     {
  721.         switch ($key) {
  722.             case self::OPTION_FETCH_DEPTH:
  723.                 return $this->getTransport()->getFetchDepth();
  724.             case self::OPTION_AUTO_LASTMODIFIED:
  725.                 return $this->getTransport()->getAutoLastModified();
  726.         }
  727.         throw new InvalidArgumentException("Unknown option: $key");
  728.     }
  729. }