├── .gitignore ├── Resources ├── doc │ ├── images │ │ └── welcome.png │ ├── index.rst │ └── reference │ │ └── twig.rst ├── config │ ├── twig.xml │ ├── session.xml │ ├── doctrine │ │ ├── User.orm.xml │ │ ├── FosUser.orm.xml │ │ └── DrupalUser.orm.xml │ ├── user_hook.xml │ └── services.xml └── meta │ └── LICENSE ├── .travis.yml ├── Tests ├── Drupal │ ├── mock.php │ └── DrupalRequestListenerTest.php ├── tests │ ├── bootstrap.php │ └── autoload.php.dist ├── Stub │ ├── FooEntityRepository.php │ └── EntityFieldQueryInterface.php ├── Event │ ├── DrupalEventTest.php │ └── Listener │ │ └── UserRegistrationHookListenerTest.php ├── Delivery │ ├── FullDrupalDeliveryStrategyTest.php │ ├── BackgroundDeliveryStrategyTest.php │ └── FullSymfonyDeliveryStrategyTest.php ├── Security │ └── Voter │ │ └── RoleVoterTest.php ├── Twig │ └── ToolbarExtensionTest.php ├── Entity │ ├── EntityRegistryTest.php │ ├── UserTest.php │ └── EntityRepositoryTest.php └── Port │ └── DrupalSessionStorageTest.php ├── Drupal ├── InvalidStateMethodCallException.php ├── DrupalRequestListener.php ├── DrupalInterface.php └── Drupal.php ├── Entity ├── User.php ├── EntityRegistry.php ├── DrupalUser.php ├── EntityRepository.php ├── HybridUser.php └── FosUser.php ├── phpunit.xml.dist ├── Delivery ├── DeliveryStrategyInterface.php ├── BackgroundDeliveryStrategy.php ├── FullDrupalDeliveryStrategy.php └── FullSymfonyDeliveryStrategy.php ├── CHANGELOG.md ├── Event ├── DrupalEntityEvent.php ├── DrupalEntitiesEvent.php ├── DrupalEvent.php ├── Subscriber │ ├── TablePrefixSubscriber.php │ └── UserSubscriber.php └── Listener │ ├── UserRegistrationHookListener.php │ └── UserEntityHookListener.php ├── EkinoDrupalBundle.php ├── composer.json ├── Twig └── ToolbarExtension.php ├── Listener └── ExceptionListener.php ├── Security └── Voter │ └── RoleVoter.php ├── DependencyInjection ├── Configuration.php └── EkinoDrupalExtension.php ├── Port ├── DrupalAttributeBag.php ├── DrupalFlashBag.php └── DrupalSessionStorage.php ├── Log └── LoggerWatchdog.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor -------------------------------------------------------------------------------- /Resources/doc/images/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekino/EkinoDrupalBundle/HEAD/Resources/doc/images/welcome.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_script: composer install --dev --prefer-source 9 | -------------------------------------------------------------------------------- /Resources/doc/index.rst: -------------------------------------------------------------------------------- 1 | Ekino Drupal Bundle 2 | =================== 3 | 4 | The ``EkinoDrupalBundle`` tries to deeply integrate Symfony2 with Drupal and Drupal with Symfony2. Of course this is done without 5 | altering the Drupal's core. 6 | 7 | Reference Guide 8 | --------------- 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | :numbered: 13 | 14 | reference/twig -------------------------------------------------------------------------------- /Tests/Drupal/mock.php: -------------------------------------------------------------------------------- 1 | permissions = array(); 5 | 6 | /** 7 | * Drupal user_access Mock 8 | * 9 | * @param string $string 10 | * @param mixed $user 11 | * 12 | * @return bool 13 | */ 14 | function user_access($string, $user) 15 | { 16 | global $user; 17 | 18 | foreach ($user->permissions as $permission) { 19 | if ($string === $permission) { 20 | return true; 21 | } 22 | } 23 | 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /Drupal/InvalidStateMethodCallException.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class InvalidStateMethodCallException extends \RuntimeException 18 | { 19 | 20 | } -------------------------------------------------------------------------------- /Tests/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * for the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | if (file_exists($file = __DIR__.'/autoload.php')) { 13 | require_once $file; 14 | } elseif (file_exists($file = __DIR__.'/autoload.php.dist')) { 15 | require_once $file; 16 | } 17 | 18 | require_once __DIR__.'/../Drupal/mock.php'; 19 | -------------------------------------------------------------------------------- /Resources/config/twig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Tests/Stub/FooEntityRepository.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class FooEntityRepository extends EntityRepository 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /Entity/User.php: -------------------------------------------------------------------------------- 1 | uid; 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function setSalt($salt) 27 | { 28 | $this->salt = $salt; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./Tests/ 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Delivery/DeliveryStrategyInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface DeliveryStrategyInterface 20 | { 21 | /** 22 | * @abstract 23 | * @param \Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface $drupal 24 | */ 25 | function buildResponse(DrupalInterface $drupal); 26 | } -------------------------------------------------------------------------------- /Tests/tests/autoload.php.dist: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class BackgroundDeliveryStrategy implements DeliveryStrategyInterface 22 | { 23 | /** 24 | * @param DrupalInterface $drupal 25 | */ 26 | public function buildResponse(DrupalInterface $drupal) 27 | { 28 | $drupal->disableResponse(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Delivery/FullDrupalDeliveryStrategy.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class FullDrupalDeliveryStrategy implements DeliveryStrategyInterface 23 | { 24 | /** 25 | * @param Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface $drupal 26 | */ 27 | public function buildResponse(DrupalInterface $drupal) 28 | { 29 | if ($drupal->isFound()) { 30 | $drupal->buildContent(); 31 | } 32 | 33 | $drupal->render(); 34 | } 35 | } -------------------------------------------------------------------------------- /Tests/Event/DrupalEventTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class DrupalEventTest extends \PHPUnit_Framework_TestCase 20 | { 21 | public function testReference() 22 | { 23 | $value = array( 24 | 'name' => 'Foo' 25 | ); 26 | 27 | $event = new DrupalEvent(); 28 | $event->addParameter($value); 29 | 30 | $v = &$event->getParameter(0); 31 | 32 | $v['name'] = 'Bar'; 33 | 34 | $this->assertEquals('Bar', $value['name']); 35 | 36 | $value['name'] = 'Foo'; 37 | 38 | $this->assertEquals('Foo', $v['name']); 39 | } 40 | } -------------------------------------------------------------------------------- /Resources/config/session.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Ekino\Bundle\DrupalBundle\Port\DrupalSessionStorage 9 | Ekino\Bundle\DrupalBundle\Port\DrupalAttributeBag 10 | Ekino\Bundle\DrupalBundle\Port\DrupalFlashBag 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tests/Stub/EntityFieldQueryInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface EntityFieldQueryInterface 20 | { 21 | /** 22 | * @see \EntityFieldQuery 23 | */ 24 | public function entityCondition(); 25 | 26 | /** 27 | * @see \EntityFieldQuery 28 | */ 29 | public function propertyCondition(); 30 | 31 | /** 32 | * @see \EntityFieldQuery 33 | */ 34 | public function fieldCondition(); 35 | 36 | /** 37 | * @see \EntityFieldQuery 38 | */ 39 | public function range(); 40 | 41 | /** 42 | * @see \EntityFieldQuery 43 | */ 44 | public function execute(); 45 | } 46 | -------------------------------------------------------------------------------- /Delivery/FullSymfonyDeliveryStrategy.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class FullSymfonyDeliveryStrategy implements DeliveryStrategyInterface 22 | { 23 | /** 24 | * @param DrupalInterface $drupal 25 | */ 26 | public function buildResponse(DrupalInterface $drupal) 27 | { 28 | if ($drupal->is404()) { 29 | $drupal->disableResponse(); 30 | 31 | return; 32 | } 33 | 34 | if ($drupal->isFound()) { 35 | $drupal->buildContent(); 36 | } 37 | 38 | $drupal->render(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Event/DrupalEntityEvent.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class DrupalEntityEvent extends Event 21 | { 22 | protected $type; 23 | 24 | protected $entity; 25 | 26 | /** 27 | * @param array $parameters 28 | */ 29 | public function __construct($type, $entity) 30 | { 31 | $this->type = $type; 32 | $this->entity = $entity; 33 | } 34 | 35 | public function getEntity() 36 | { 37 | return $this->entity; 38 | } 39 | 40 | public function getType() 41 | { 42 | return $this->type; 43 | } 44 | 45 | public function isType($name) 46 | { 47 | return $this->type === $name; 48 | } 49 | } -------------------------------------------------------------------------------- /Tests/Delivery/FullDrupalDeliveryStrategyTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FullDrupalDeliveryStrategyTest extends \PHPUnit_Framework_TestCase 20 | { 21 | 22 | public function testIsFound() 23 | { 24 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 25 | 26 | $drupal->expects($this->once())->method('isFound')->will($this->returnValue(true)); 27 | $drupal->expects($this->once())->method('buildContent'); 28 | $drupal->expects($this->once())->method('render'); 29 | 30 | $strategy = new FullDrupalDeliveryStrategy; 31 | $strategy->buildResponse($drupal); 32 | } 33 | } -------------------------------------------------------------------------------- /EkinoDrupalBundle.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class EkinoDrupalBundle extends Bundle 20 | { 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function boot() 25 | { 26 | if (php_sapi_name() === 'cli' && !defined('EKINO_DRUSH_FROM')) { 27 | 28 | global $container; 29 | 30 | $container = $this->container; 31 | 32 | if (!defined('DRUSH_BASE_PATH')) { 33 | define('EKINO_DRUSH_FROM', 'symfony'); 34 | 35 | $this->container->get('ekino.drupal')->initializeDrush(); 36 | } else { 37 | define('EKINO_DRUSH_FROM', 'drupal'); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Event/DrupalEntitiesEvent.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class DrupalEntitiesEvent extends Event 21 | { 22 | protected $type; 23 | 24 | protected $entities; 25 | 26 | /** 27 | * @param array $parameters 28 | */ 29 | public function __construct($type, &$entities) 30 | { 31 | $this->type = $type; 32 | $this->entities =& $entities; 33 | } 34 | 35 | public function &getEntities() 36 | { 37 | return $this->entities; 38 | } 39 | 40 | public function getType() 41 | { 42 | return $this->type; 43 | } 44 | 45 | public function isType($name) 46 | { 47 | return $this->type === $name; 48 | } 49 | } -------------------------------------------------------------------------------- /Tests/Delivery/BackgroundDeliveryStrategyTest.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class BackgroundDeliveryStrategyTest extends \PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * Tests the buildResponse() method. 25 | * 26 | * The disableContent() method on the Drupal instance should be called in any case. 27 | */ 28 | public function testIsFound() 29 | { 30 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 31 | $drupal->expects($this->once())->method('disableResponse'); 32 | 33 | $strategy = new BackgroundDeliveryStrategy(); 34 | $strategy->buildResponse($drupal); 35 | } 36 | } -------------------------------------------------------------------------------- /Resources/config/doctrine/User.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ekino/drupal-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Integrate Symfony2 with Drupal", 5 | "keywords": ["components", "drupal"], 6 | "homepage": "https://github.com/ekino/EkinoDrupalBundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Thomas Rabaix", 11 | "email": "thomas.rabaix@ekino.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.5.9", 16 | "doctrine/common": "^2.6", 17 | "doctrine/orm": "^2.5", 18 | "friendsofsymfony/user-bundle": "^2.1", 19 | "psr/log": "^1.1", 20 | "symfony/config": "^3.4", 21 | "symfony/dependency-injection": "^3.4", 22 | "symfony/event-dispatcher": "^3.4", 23 | "symfony/http-foundation": "^3.4", 24 | "symfony/http-kernel": "^3.4", 25 | "symfony/security-core": "^3.4", 26 | "twig/twig": "^1.42" 27 | }, 28 | "autoload": { 29 | "psr-0": { "Ekino\\Bundle\\DrupalBundle": "" } 30 | }, 31 | "target-dir": "Ekino/Bundle/DrupalBundle", 32 | "require-dev": { 33 | "phpunit/phpunit": "^4.8", 34 | "symfony/phpunit-bridge": "^4.3" 35 | }, 36 | "config": { 37 | "sort-packages": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Resources/doc/reference/twig.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Extensions Twig 3 | 4 | Extensions Twig 5 | =============== 6 | 7 | We provide a Twig extension to help rendering Drupal parts into your Symfony Twig templates. 8 | 9 | ToolbarExtension 10 | ---------------- 11 | 12 | You can render the administration Drupal toolbar parts by adding the following HTML to your Twig template: 13 | 14 | .. code-block:: html 15 | 16 | {% if is_granted('PERMISSION_DRUPAL_ACCESS_TOOLBAR') %} 17 |
18 |
19 | {{ ekino_drupal_toolbar_render_item('toolbar_home') }} 20 | {{ ekino_drupal_toolbar_render_item('toolbar_user') }} 21 | {{ ekino_drupal_toolbar_render_item('toolbar_menu') }} 22 | {{ ekino_drupal_toolbar_render_item('toolbar_toggle') }} 23 |
24 |
25 | {% endif %} 26 | 27 | Please ensure that your user has the correct role to "access toolbar" in Drupal administration. 28 | 29 | As an additional note, you will also need to load the following stylesheets: 30 | 31 | .. code-block:: html 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Twig/ToolbarExtension.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ToolbarExtension extends \Twig_Extension 20 | { 21 | /** 22 | * @var array 23 | */ 24 | protected $toolbar; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function getFunctions() 30 | { 31 | return array( 32 | new \Twig_SimpleFunction('ekino_drupal_toolbar_render_item', array($this, 'renderToolbarItem'), array( 33 | 'is_safe' => array('html') 34 | )), 35 | ); 36 | } 37 | 38 | /** 39 | * Renders HTML for a Drupal administration toolbar item 40 | * 41 | * @param $item 42 | * 43 | * @return string 44 | */ 45 | public function renderToolbarItem($item) 46 | { 47 | if (null === $this->toolbar) { 48 | $this->toolbar = toolbar_view(); 49 | } 50 | 51 | return render($this->toolbar[$item]); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getName() 58 | { 59 | return 'ekino_drupal_toolbar_extension'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Event/DrupalEvent.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class DrupalEvent extends Event 22 | { 23 | protected $parameters = array(); 24 | 25 | /** 26 | * @param array $parameters 27 | */ 28 | public function __construct(array $parameters = array()) 29 | { 30 | $this->parameters = $parameters; 31 | } 32 | 33 | /** 34 | * @throws \RuntimeException 35 | * @param $position 36 | * @return 37 | */ 38 | public function &getParameter($position) 39 | { 40 | if (!$this->hasParameter($position)) { 41 | throw new \RuntimeException('Invalid parameter'); 42 | } 43 | 44 | return $this->parameters[$position]; 45 | } 46 | 47 | /** 48 | * @param $position 49 | * @return bool 50 | */ 51 | public function hasParameter($position) 52 | { 53 | return isset($this->parameters[$position]); 54 | } 55 | 56 | /** 57 | * @param $reference 58 | * @return DrupalEvent 59 | */ 60 | public function addParameter(&$reference) 61 | { 62 | $this->parameters[] =& $reference; 63 | 64 | return $this; 65 | } 66 | } -------------------------------------------------------------------------------- /Tests/Delivery/FullSymfonyDeliveryStrategyTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FullSymfonyDeliveryStrategyTest extends \PHPUnit_Framework_TestCase 20 | { 21 | 22 | public function testIs404() 23 | { 24 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 25 | 26 | $drupal->expects($this->once())->method('is404')->will($this->returnValue(true)); 27 | $drupal->expects($this->once())->method('disableResponse'); 28 | 29 | $strategy = new FullSymfonyDeliveryStrategy; 30 | $strategy->buildResponse($drupal); 31 | } 32 | 33 | public function testIsFound() 34 | { 35 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 36 | 37 | $drupal->expects($this->once())->method('is404')->will($this->returnValue(false)); 38 | $drupal->expects($this->once())->method('isFound')->will($this->returnValue(true)); 39 | $drupal->expects($this->once())->method('buildContent'); 40 | $drupal->expects($this->once())->method('render'); 41 | 42 | $strategy = new FullSymfonyDeliveryStrategy; 43 | $strategy->buildResponse($drupal); 44 | } 45 | } -------------------------------------------------------------------------------- /Resources/config/user_hook.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/Security/Voter/RoleVoterTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($voter->supportsClass('Foo')); 16 | } 17 | 18 | /** 19 | * @dataProvider getVoteTests 20 | */ 21 | public function testVote($permissions, $attributes, $expected) 22 | { 23 | global $user; 24 | 25 | $user->permissions = $permissions; 26 | 27 | $voter = new RoleVoter(); 28 | 29 | $this->assertSame($expected, $voter->vote($this->getTokenMock(), null, $attributes)); 30 | } 31 | 32 | public function getVoteTests() 33 | { 34 | return array( 35 | array(array(), array(), VoterInterface::ACCESS_ABSTAIN), 36 | array(array(), array('FOO'), VoterInterface::ACCESS_ABSTAIN), 37 | array(array(), array('PERMISSION_DRUPAL_FOO'), VoterInterface::ACCESS_DENIED), 38 | array(array('foo'), array('PERMISSION_DRUPAL_FOO'), VoterInterface::ACCESS_GRANTED), 39 | array(array('foo'), array('FOO', 'PERMISSION_DRUPAL_FOO'), VoterInterface::ACCESS_GRANTED), 40 | array(array('bar', 'foo'), array('PERMISSION_DRUPAL_FOO'), VoterInterface::ACCESS_GRANTED), 41 | ); 42 | } 43 | 44 | protected function getTokenMock() 45 | { 46 | return $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Listener/ExceptionListener.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ExceptionListener 17 | { 18 | /** 19 | * @var DrupalInterface 20 | */ 21 | protected $drupal; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param DrupalInterface $drupal 27 | */ 28 | public function __construct(DrupalInterface $drupal) 29 | { 30 | $this->drupal = $drupal; 31 | } 32 | 33 | /** 34 | * @param GetResponseForExceptionEvent $event 35 | */ 36 | public function onKernelException(GetResponseForExceptionEvent $event) 37 | { 38 | $is404 = false; 39 | 40 | try { 41 | $is404 = $this->drupal->is404(); 42 | } 43 | catch (InvalidStateMethodCallException $e) {} 44 | 45 | if (!$is404) { 46 | return; 47 | } 48 | 49 | $exception = $event->getException(); 50 | 51 | if ($exception instanceof NotFoundHttpException) { 52 | $this->drupalRender(); 53 | 54 | $event->setResponse($this->drupal->getResponse()); 55 | } 56 | } 57 | 58 | /** 59 | * Render drupal. 60 | */ 61 | protected function drupalRender() 62 | { 63 | if ($this->drupal->isFound()) { 64 | $this->drupal->buildContent(); 65 | } 66 | 67 | $this->drupal->render(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Security/Voter/RoleVoter.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class RoleVoter implements VoterInterface 14 | { 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function supportsAttribute($attribute) 19 | { 20 | return 0 === strpos($attribute, 'PERMISSION_DRUPAL_'); 21 | } 22 | 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function supportsClass($class) 27 | { 28 | return true; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function vote(TokenInterface $token, $object, array $attributes) 35 | { 36 | $user = self::retrieveCurrentUser(); 37 | 38 | foreach ($attributes as $attribute) { 39 | if (!$this->supportsAttribute($attribute)) { 40 | continue; 41 | } 42 | 43 | if (user_access(self::camelize($attribute), $user)) { 44 | return VoterInterface::ACCESS_GRANTED; 45 | } 46 | 47 | return VoterInterface::ACCESS_DENIED; 48 | } 49 | 50 | return VoterInterface::ACCESS_ABSTAIN; 51 | } 52 | 53 | /** 54 | * Camelizes a string. 55 | * 56 | * @param string $id A string to camelize for drupal 57 | * 58 | * @return string The camelized string 59 | */ 60 | public static function camelize($id) 61 | { 62 | return strtolower(str_replace('_', ' ', substr($id, 18))); 63 | } 64 | 65 | /** 66 | * Returns current drupal user. 67 | * 68 | * @return \stdClass 69 | */ 70 | protected static function retrieveCurrentUser() 71 | { 72 | global $user; 73 | 74 | return $user; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Drupal/DrupalRequestListener.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class DrupalRequestListener 27 | { 28 | /** 29 | * @var DrupalInterface 30 | */ 31 | protected $drupal; 32 | 33 | /** 34 | * @var DeliveryStrategyInterface 35 | */ 36 | protected $strategy; 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @param DrupalInterface $drupal A Drupal instance 42 | * @param DeliveryStrategyInterface $strategy A delivery strategy instance 43 | */ 44 | public function __construct(DrupalInterface $drupal, DeliveryStrategyInterface $strategy) 45 | { 46 | $this->drupal = $drupal; 47 | $this->strategy = $strategy; 48 | } 49 | 50 | /** 51 | * @param GetResponseEvent $event 52 | * 53 | * @return mixed 54 | */ 55 | public function onKernelRequest(GetResponseEvent $event) 56 | { 57 | if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { 58 | return false; 59 | } 60 | 61 | $this->drupal->defineState($event->getRequest()); 62 | 63 | $this->strategy->buildResponse($this->drupal); 64 | 65 | $response = $this->drupal->getResponse(); 66 | 67 | if ($this->drupal->hasResponse()) { 68 | $event->setResponse($response); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Resources/config/doctrine/FosUser.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/config/doctrine/DrupalUser.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Event/Subscriber/TablePrefixSubscriber.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class TablePrefixSubscriber implements EventSubscriber 24 | { 25 | /** 26 | * @var string 27 | */ 28 | private $prefix; 29 | 30 | /** 31 | * @var array 32 | */ 33 | private $exclude; 34 | 35 | /** 36 | * Constructor 37 | * 38 | * @param string $prefix The prefix of Symfony tables 39 | * @param array $exclude An array of tables to exclude 40 | */ 41 | public function __construct($prefix, array $exclude = array()) 42 | { 43 | $this->prefix = $prefix; 44 | $this->exclude = $exclude; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getSubscribedEvents() 51 | { 52 | return array('loadClassMetadata'); 53 | } 54 | 55 | /** 56 | * Alters table name and associations 57 | * 58 | * @param LoadClassMetadataEventArgs $args 59 | */ 60 | public function loadClassMetadata(LoadClassMetadataEventArgs $args) 61 | { 62 | $classMetadata = $args->getClassMetadata(); 63 | 64 | if (in_array($classMetadata->getTableName(), $this->exclude)) { 65 | return; 66 | } 67 | 68 | $classMetadata->setPrimaryTable(array( 69 | 'name' => $this->prefix . $classMetadata->getTableName() 70 | )); 71 | 72 | foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { 73 | if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY) { 74 | $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; 75 | $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/Twig/ToolbarExtensionTest.php: -------------------------------------------------------------------------------- 1 | 'value'); 21 | } 22 | 23 | /** 24 | * Fake Drupal render() function 25 | * 26 | * @param string $item 27 | * 28 | * @return string 29 | */ 30 | function render($item) 31 | { 32 | return sprintf('

%s

', $item); 33 | } 34 | } 35 | 36 | namespace Ekino\Bundle\DrupalBundle\Tests\Twig { 37 | use Ekino\Bundle\DrupalBundle\Twig\ToolbarExtension; 38 | 39 | /** 40 | * Class ToolbarExtensionTest 41 | * 42 | * @author Vincent Composieux 43 | */ 44 | class ToolbarExtensionTest extends \PHPUnit_Framework_TestCase 45 | { 46 | /** 47 | * @var ToolbarExtension 48 | */ 49 | protected $extension; 50 | 51 | /** 52 | * Sets up Twig toolbar extension 53 | */ 54 | public function setUp() 55 | { 56 | $this->extension = new ToolbarExtension(); 57 | } 58 | 59 | /** 60 | * Tests the getFunctions() method 61 | */ 62 | public function testGetFunctions() 63 | { 64 | $functions = $this->extension->getFunctions(); 65 | 66 | $this->assertTrue(is_array($functions), 'Should return an array of functions'); 67 | 68 | foreach ($functions as $function) { 69 | $this->assertInstanceOf('\Twig_SimpleFunction', $function); 70 | } 71 | } 72 | 73 | /** 74 | * Tests the renderToolbarItem() method 75 | */ 76 | public function testRenderToolbarItem() 77 | { 78 | // When 79 | $result = $this->extension->renderToolbarItem('test'); 80 | 81 | // Then 82 | $this->assertEquals('

value

', $result, 'Should return render() function'); 83 | } 84 | 85 | /** 86 | * Tests the getName() method 87 | */ 88 | public function testGetName() 89 | { 90 | // When 91 | $name = $this->extension->getName(); 92 | 93 | // Then 94 | $this->assertEquals('ekino_drupal_toolbar_extension', $name, 'Should return correct Twig extension name'); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Configuration implements ConfigurationInterface 21 | { 22 | /** 23 | * Generates the configuration tree. 24 | * 25 | * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder 26 | */ 27 | public function getConfigTreeBuilder() 28 | { 29 | $treeBuilder = new TreeBuilder(); 30 | $rootNode = $treeBuilder->root('ekino_drupal', 'array'); 31 | 32 | $rootNode 33 | ->addDefaultsIfNotSet() 34 | ->children() 35 | ->scalarNode('root')->isRequired()->end() 36 | ->scalarNode('logger')->defaultValue('ekino.drupal.logger.watchdog')->end() 37 | ->scalarNode('strategy_id')->defaultValue('ekino.drupal.delivery_strategy.symfony')->end() 38 | ->arrayNode('provider_keys') 39 | ->prototype('scalar')->cannotBeEmpty()->end() 40 | ->end() 41 | ->arrayNode('entity_repositories') 42 | ->prototype('array') 43 | ->children() 44 | ->scalarNode('type')->cannotBeEmpty()->defaultValue('node')->end() 45 | ->scalarNode('bundle')->end() 46 | ->scalarNode('class')->cannotBeEmpty()->defaultValue('Ekino\Bundle\DrupalBundle\Entity\EntityRepository')->end() 47 | ->end() 48 | ->end() 49 | ->end() 50 | ->arrayNode('table_prefix') 51 | ->addDefaultsIfNotSet() 52 | ->children() 53 | ->booleanNode('enabled')->defaultFalse()->end() 54 | ->scalarNode('prefix')->defaultValue('symfony__')->end() 55 | ->arrayNode('exclude') 56 | ->prototype('scalar')->end() 57 | ->defaultValue(array('users')) 58 | ->end() 59 | ->end() 60 | ->end() 61 | ->arrayNode('session') 62 | ->addDefaultsIfNotSet() 63 | ->children() 64 | ->booleanNode('refresh_cookie_lifetime')->defaultFalse()->end() 65 | ->end() 66 | ->end() 67 | ->end(); 68 | 69 | return $treeBuilder; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Event/Listener/UserRegistrationHookListener.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 50 | $this->requestStack = $requestStack; 51 | $this->providerKeys = $providerKeys; 52 | } 53 | 54 | /** 55 | * http://api.drupal.org/api/drupal/modules--user--user.api.php/function/hook_user_login/7 56 | * 57 | * @param DrupalEvent $event 58 | */ 59 | public function onLogin(DrupalEvent $event) 60 | { 61 | $user = $event->getParameter(1); 62 | 63 | if (!$user instanceof UserInterface) { 64 | throw new \RuntimeException('An instance of UserInterface is expected'); 65 | } 66 | 67 | $request = $this->requestStack->getMasterRequest(); 68 | 69 | // The ContextListener from the Security component is hijacked to insert a valid token into session 70 | // so next time the user go to a valid symfony2 url with a proper security context, then the following token 71 | // will be used 72 | foreach ($this->providerKeys as $providerKey) { 73 | $token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()); 74 | $request->getSession()->set('_security_'.$providerKey, serialize($token)); 75 | } 76 | } 77 | 78 | /** 79 | * @param DrupalEvent $event 80 | */ 81 | public function onLogout(DrupalEvent $event) 82 | { 83 | $request = $this->requestStack->getMasterRequest(); 84 | 85 | foreach ($this->providerKeys as $providerKey) { 86 | $request->getSession()->set('_security_'.$providerKey, null); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Ekino\Bundle\DrupalBundle\Drupal\Drupal 9 | Ekino\Bundle\DrupalBundle\Listener\ExceptionListener 10 | Ekino\Bundle\DrupalBundle\Security\Voter\RoleVoter 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Entity/EntityRegistry.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class EntityRegistry 22 | { 23 | /** 24 | * @var DrupalInterface 25 | */ 26 | private $drupal; 27 | 28 | /** 29 | * @var EntityRepository[] 30 | */ 31 | private $repositories = array(); 32 | 33 | /** 34 | * @var array 35 | */ 36 | private $repositoriesMetadata = array(); 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @param DrupalInterface $drupal A Drupal instance 42 | */ 43 | public function __construct(DrupalInterface $drupal) 44 | { 45 | $this->drupal = $drupal; 46 | } 47 | 48 | /** 49 | * Gets the entity controller 50 | * 51 | * @param string $entityType An entity type 52 | * 53 | * @return \DrupalDefaultEntityController 54 | */ 55 | public function getController($entityType) 56 | { 57 | return $this->drupal->getEntityController($entityType); 58 | } 59 | 60 | /** 61 | * Adds metadata of a repository 62 | * 63 | * @param string $class A namespace of repository class 64 | * @param string $entityType An entity type 65 | * @param string|null $bundle A bundle name 66 | */ 67 | public function addRepositoryMetadata($class, $entityType, $bundle) 68 | { 69 | $this->repositoriesMetadata[$this->computeKey($entityType, $bundle)] = array( 70 | 'class' => $class, 71 | 'entityType' => $entityType, 72 | 'bundle' => $bundle, 73 | ); 74 | } 75 | 76 | /** 77 | * Gets the entity repository 78 | * 79 | * @param string $entityType An entity type 80 | * @param string|null $bundle A bundle name 81 | * 82 | * @return EntityRepository 83 | */ 84 | public function getRepository($entityType, $bundle = null) 85 | { 86 | $key = $this->computeKey($entityType, $bundle); 87 | 88 | if (!isset($this->repositories[$key])) { 89 | $class = isset($this->repositoriesMetadata[$key]) ? $this->repositoriesMetadata[$key]['class'] : 'Ekino\Bundle\DrupalBundle\Entity\EntityRepository'; 90 | 91 | $this->repositories[$key] = new $class($entityType, $bundle); 92 | } 93 | 94 | return $this->repositories[$key]; 95 | } 96 | 97 | /** 98 | * Computes the key 99 | * 100 | * @param string $entityType An entity type 101 | * @param string $bundle A bundle name 102 | * 103 | * @return string 104 | */ 105 | private function computeKey($entityType, $bundle) 106 | { 107 | // use @ separator to avoid collision 108 | return sprintf('%s@%s', $entityType, $bundle); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/Drupal/DrupalRequestListenerTest.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class DrupalRequestListenerTest extends \PHPUnit_Framework_TestCase 25 | { 26 | public function testNonMasterRequest() 27 | { 28 | $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); 29 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 30 | $strategy = $this->getMock('Ekino\Bundle\DrupalBundle\Delivery\DeliveryStrategyInterface'); 31 | 32 | $request = new Request; 33 | $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); 34 | $listener = new DrupalRequestListener($drupal, $strategy); 35 | 36 | $this->assertFalse($listener->onKernelRequest($event)); 37 | } 38 | 39 | public function testWithoutResponse() 40 | { 41 | $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); 42 | 43 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 44 | $drupal->expects($this->once())->method('defineState'); 45 | $drupal->expects($this->once())->method('hasResponse')->will($this->returnValue(false)); 46 | 47 | $strategy = $this->getMock('Ekino\Bundle\DrupalBundle\Delivery\DeliveryStrategyInterface'); 48 | $strategy->expects($this->once())->method('buildResponse'); 49 | 50 | $request = new Request; 51 | $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); 52 | $listener = new DrupalRequestListener($drupal, $strategy); 53 | 54 | $listener->onKernelRequest($event); 55 | 56 | $this->assertFalse($event->hasResponse()); 57 | } 58 | 59 | public function testWithResponse() 60 | { 61 | $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); 62 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 63 | $drupal->expects($this->once())->method('defineState'); 64 | $drupal->expects($this->once())->method('hasResponse')->will($this->returnValue(true)); 65 | $drupal->expects($this->once())->method('getResponse')->will($this->returnValue(new Response)); 66 | 67 | $strategy = $this->getMock('Ekino\Bundle\DrupalBundle\Delivery\DeliveryStrategyInterface'); 68 | $strategy->expects($this->once())->method('buildResponse'); 69 | 70 | $request = new Request; 71 | $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); 72 | $listener = new DrupalRequestListener($drupal, $strategy); 73 | 74 | $listener->onKernelRequest($event); 75 | 76 | $this->assertTrue($event->hasResponse()); 77 | } 78 | } -------------------------------------------------------------------------------- /Event/Listener/UserEntityHookListener.php: -------------------------------------------------------------------------------- 1 | userManager = $userManager; 37 | $this->logger = $logger; 38 | } 39 | 40 | /** 41 | * http://api.drupal.org/api/drupal/modules--user--user.api.php/function/hook_user_load/7 42 | * 43 | * @param \Ekino\Bundle\DrupalBundle\Event\DrupalEvent $event 44 | * @return void 45 | */ 46 | public function onLoad(DrupalEntitiesEvent $event) 47 | { 48 | if (!$event->isType('user')) { 49 | return; 50 | } 51 | 52 | $users =& $event->getEntities(); 53 | 54 | foreach ($users as $pos => $drupalUser) { 55 | $users[$pos] = $this->getSymfonyUser($drupalUser); 56 | } 57 | } 58 | 59 | /** 60 | * @param \stdClass $drupalUser 61 | * @return \FOS\UserBundle\Model\UserInterface 62 | */ 63 | public function getSymfonyUser(\stdClass $drupalUser) 64 | { 65 | $user = $this->userManager->createUser(); 66 | 67 | $user->fromDrupalUser($drupalUser); 68 | 69 | return $user; 70 | } 71 | 72 | /** 73 | * http://api.drupal.org/api/drupal/modules--user--user.api.php/function/hook_user_insert/7 74 | * 75 | * @param \Ekino\Bundle\DrupalBundle\Event\DrupalEvent $event 76 | * @return void 77 | */ 78 | public function onInsert(DrupalEntityEvent $event) 79 | { 80 | 81 | } 82 | 83 | /** 84 | * @param \Ekino\Bundle\DrupalBundle\Event\DrupalEvent $event 85 | * @return void 86 | */ 87 | public function onUpdate(DrupalEntityEvent $event) 88 | { 89 | 90 | } 91 | 92 | /** 93 | * http://api.drupal.org/api/drupal/modules--user--user.api.php/function/hook_user_presave/7 94 | * 95 | * @param \Ekino\Bundle\DrupalBundle\Event\DrupalEvent $event 96 | * @return void 97 | */ 98 | public function onPreSave(DrupalEntityEvent $event) 99 | { 100 | if (!$event->isType('user')) { 101 | return; 102 | } 103 | 104 | $account = $event->getEntity(); 105 | 106 | if (property_exists($account, 'email')) { 107 | $account->emailCanonical = $account->email; 108 | } 109 | 110 | if (property_exists($account, 'name')) { 111 | $account->usernameCanonical = $account->name; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Port/DrupalAttributeBag.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class DrupalAttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable 20 | { 21 | /** 22 | * @var string 23 | */ 24 | private $storageKey; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param string $storageKey The key used to store flashes in the session. 30 | */ 31 | public function __construct($storageKey = '_sf2_attributes') 32 | { 33 | $this->storageKey = $storageKey; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getName() 40 | { 41 | return 'attributes'; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function initialize(array &$attributes) 48 | { 49 | $_SESSION[$this->storageKey] = &$attributes; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function getStorageKey() 56 | { 57 | return $this->storageKey; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function has($name) 64 | { 65 | return array_key_exists($name, $_SESSION[$this->storageKey]); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function get($name, $default = null) 72 | { 73 | return array_key_exists($name, $_SESSION[$this->storageKey]) ? $_SESSION[$this->storageKey][$name] : $default; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function set($name, $value) 80 | { 81 | $_SESSION[$this->storageKey][$name] = $value; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function all() 88 | { 89 | return $_SESSION[$this->storageKey]; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function replace(array $attributes) 96 | { 97 | $_SESSION[$this->storageKey] = array(); 98 | foreach ($attributes as $key => $value) { 99 | $this->set($key, $value); 100 | } 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function remove($name) 107 | { 108 | $retval = null; 109 | if (array_key_exists($name, $_SESSION[$this->storageKey])) { 110 | $retval = $_SESSION[$this->storageKey][$name]; 111 | unset($_SESSION[$this->storageKey][$name]); 112 | } 113 | 114 | return $retval; 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function clear() 121 | { 122 | $return = $_SESSION[$this->storageKey]; 123 | $_SESSION[$this->storageKey] = array(); 124 | 125 | return $return; 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function getIterator() 132 | { 133 | return new \ArrayIterator($_SESSION[$this->storageKey]); 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function count() 140 | { 141 | return count($_SESSION[$this->storageKey]); 142 | } 143 | } -------------------------------------------------------------------------------- /Tests/Event/Listener/UserRegistrationHookListenerTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class UserRegistrationHookListenerTest extends \PHPUnit_Framework_TestCase 20 | { 21 | /** 22 | * Asserts the onLogin method throws an exception if user not instance of UserInterface 23 | * 24 | * @expectedException \RuntimeException 25 | * @expectedExceptionMessage An instance of UserInterface is expected 26 | */ 27 | public function testOnLoginThrowsException() 28 | { 29 | $logger = $this->getMock('Psr\Log\LoggerInterface'); 30 | $requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); 31 | $listener = new UserRegistrationHookListener($logger, $requestStack, array()); 32 | 33 | $event = $this->getMock('Ekino\Bundle\DrupalBundle\Event\DrupalEvent'); 34 | $event->expects($this->once())->method('getParameter')->with($this->equalTo(1))->willReturn(null); 35 | 36 | $listener->onLogin($event); 37 | } 38 | 39 | /** 40 | * Test onLogin method with provider keys 41 | */ 42 | public function testOnLoginUserWithProviderKeys() 43 | { 44 | $logger = $this->getMock('Psr\Log\LoggerInterface'); 45 | $session = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'); 46 | 47 | $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); 48 | $request->expects($this->exactly(3))->method('getSession')->willReturn($session); 49 | 50 | $requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); 51 | $requestStack->expects($this->once())->method('getMasterRequest')->willReturn($request); 52 | 53 | $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); 54 | $user->expects($this->any())->method('getRoles')->willReturn(array('ROLE_USER')); 55 | 56 | $event = $this->getMock('Ekino\Bundle\DrupalBundle\Event\DrupalEvent'); 57 | $event->expects($this->once())->method('getParameter')->with($this->equalTo(1))->willReturn($user); 58 | 59 | $listener = new UserRegistrationHookListener($logger, $requestStack, array('1', '2', '3')); 60 | $listener->onLogin($event); 61 | } 62 | 63 | /** 64 | * Test onLogin method with no provider keys 65 | */ 66 | public function testOnLoginUserWithoutProviderKeys() 67 | { 68 | $logger = $this->getMock('Psr\Log\LoggerInterface'); 69 | 70 | $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); 71 | $request->expects($this->never())->method('getSession'); 72 | 73 | $requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); 74 | $requestStack->expects($this->once())->method('getMasterRequest')->willReturn($request); 75 | 76 | $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); 77 | 78 | $event = $this->getMock('Ekino\Bundle\DrupalBundle\Event\DrupalEvent'); 79 | $event->expects($this->once())->method('getParameter')->with($this->equalTo(1))->willReturn($user); 80 | 81 | $listener = new UserRegistrationHookListener($logger, $requestStack, array()); 82 | $listener->onLogin($event); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/Entity/EntityRegistryTest.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class EntityRegistryTest extends \PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * @var EntityRegistry 25 | */ 26 | private $entityRegistry; 27 | 28 | /** 29 | * Tests to add a repository metadata 30 | */ 31 | public function testAddRepositoryMetadata() 32 | { 33 | $property = new \ReflectionProperty($this->entityRegistry, 'repositoriesMetadata'); 34 | $property->setAccessible(true); 35 | 36 | $this->assertTrue(is_array($property->getValue($this->entityRegistry))); 37 | $this->assertCount(0, $property->getValue($this->entityRegistry)); 38 | 39 | $this->entityRegistry->addRepositoryMetadata('Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', 'node', 'page'); 40 | 41 | $this->assertCount(1, $property->getValue($this->entityRegistry)); 42 | $expected = array('node@page' => array( 43 | 'class' => 'Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', 44 | 'entityType' => 'node', 45 | 'bundle' => 'page', 46 | )); 47 | $this->assertEquals($expected, $property->getValue($this->entityRegistry)); 48 | } 49 | 50 | /** 51 | * Tests whether the default entity repository is returned 52 | */ 53 | public function testGetDefaultEntityRepository() 54 | { 55 | $this->assertInstanceOf('Ekino\Bundle\DrupalBundle\Entity\EntityRepository', $this->entityRegistry->getRepository('node', 'page')); 56 | 57 | $this->entityRegistry->addRepositoryMetadata('Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', 'node', 'page'); 58 | 59 | $this->assertNotInstanceOf('Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', $this->entityRegistry->getRepository('node', 'page')); 60 | } 61 | 62 | /** 63 | * Tests whether the configured entity repository is returned 64 | */ 65 | public function testGetConfiguredEntityRepository() 66 | { 67 | $this->entityRegistry->addRepositoryMetadata('Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', 'node', 'page'); 68 | 69 | $this->assertInstanceOf('Ekino\Bundle\DrupalBundle\Tests\Stub\FooEntityRepository', $this->entityRegistry->getRepository('node', 'page')); 70 | } 71 | 72 | /** 73 | * Tests the computed key 74 | */ 75 | public function testComputeKey() 76 | { 77 | $method = new \ReflectionMethod($this->entityRegistry, 'computeKey'); 78 | $method->setAccessible(true); 79 | 80 | $this->assertEquals('node@page', $method->invoke($this->entityRegistry, 'node', 'page')); 81 | } 82 | 83 | /** 84 | * Initializes the entity registry 85 | */ 86 | protected function setUp() 87 | { 88 | $drupal = $this->getMock('Ekino\Bundle\DrupalBundle\Drupal\DrupalInterface'); 89 | 90 | $this->entityRegistry = new EntityRegistry($drupal); 91 | } 92 | 93 | /** 94 | * Cleanups the entity registry 95 | */ 96 | protected function tearDown() 97 | { 98 | unset($this->entityRegistry); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /DependencyInjection/EkinoDrupalExtension.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class EkinoDrupalExtension extends Extension 25 | { 26 | /** 27 | * @param array $config 28 | * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container 29 | */ 30 | public function load(array $config, ContainerBuilder $container) 31 | { 32 | $configuration = new Configuration(); 33 | $processor = new Processor(); 34 | $config = $processor->processConfiguration($configuration, $config); 35 | 36 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 37 | $loader->load('services.xml'); 38 | $loader->load('session.xml'); 39 | $loader->load('user_hook.xml'); 40 | $loader->load('twig.xml'); 41 | 42 | $this->configureEntityRepositories($container, $config); 43 | $this->configureTablesPrefix($container, $config); 44 | 45 | $container->getDefinition('ekino.drupal') 46 | ->replaceArgument(0, $config['root']); 47 | 48 | $container->getDefinition('ekino.drupal.request_listener') 49 | ->replaceArgument(1, new Reference($config['strategy_id'])); 50 | 51 | $container->getDefinition('ekino.drupal.user_registration_hook') 52 | ->replaceArgument(2, $config['provider_keys']); 53 | 54 | $container->setAlias('logger', $config['logger']); 55 | 56 | $container->getDefinition('ekino.drupal.session.storage') 57 | ->replaceArgument(1, $config['session']['refresh_cookie_lifetime']); 58 | } 59 | 60 | /** 61 | * Configures the entity repositories 62 | * 63 | * @param ContainerBuilder $container A container builder instance 64 | * @param array $config An array of configuration 65 | */ 66 | private function configureEntityRepositories(ContainerBuilder $container, array $config) 67 | { 68 | $registry = $container->getDefinition('ekino.drupal.entity_registry'); 69 | 70 | foreach ($config['entity_repositories'] as $repository) { 71 | $registry->addMethodCall('addRepositoryMetadata', array($repository['class'], $repository['type'], $repository['bundle'])); 72 | } 73 | } 74 | 75 | /** 76 | * Configures the tables prefix 77 | * 78 | * @param ContainerBuilder $container A container builder instance 79 | * @param array $config An array of configuration 80 | */ 81 | private function configureTablesPrefix(ContainerBuilder $container, array $config) 82 | { 83 | $id = 'ekino.drupal.subscriber.table_prefix'; 84 | 85 | if (false === $config['table_prefix']['enabled']) { 86 | $container->removeDefinition($id); 87 | 88 | return; 89 | } 90 | 91 | $definition = $container->getDefinition($id); 92 | $definition->replaceArgument(0, $config['table_prefix']['prefix']); 93 | $definition->replaceArgument(1, $config['table_prefix']['exclude']); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Log/LoggerWatchdog.php: -------------------------------------------------------------------------------- 1 | 20 | * @author Florent Denis 21 | */ 22 | class LoggerWatchdog implements LoggerInterface 23 | { 24 | const LOGGER_EMERGENCY = 0; // WATCHDOG_EMERGENCY 25 | const LOGGER_ALERT = 1; // WATCHDOG_ALERT 26 | const LOGGER_CRITICAL = 2; // WATCHDOG_CRITICAL 27 | const LOGGER_ERROR = 3; // WATCHDOG_ERROR 28 | const LOGGER_WARNING = 4; // WATCHDOG_WARNING 29 | const LOGGER_NOTICE = 5; // WATCHDOG_NOTICE 30 | const LOGGER_INFO = 6; // WATCHDOG_INFO 31 | const LOGGER_DEBUG = 7; // WATCHDOG_DEBUG 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function emerg($message, array $context = array()) 37 | { 38 | $this->emergency($message, $context); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function crit($message, array $context = array()) 45 | { 46 | $this->critical($message, $context); 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function err($message, array $context = array()) 53 | { 54 | $this->error($message, $context); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function warn($message, array $context = array()) 61 | { 62 | $this->warning($message, $context); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function emergency($message, array $context = array()) 69 | { 70 | $this->log(self::LOGGER_EMERGENCY, $message, $context); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function alert($message, array $context = array()) 77 | { 78 | $this->log(self::LOGGER_ALERT, $message, $context); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function critical($message, array $context = array()) 85 | { 86 | $this->log(self::LOGGER_CRITICAL, $message, $context); 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function error($message, array $context = array()) 93 | { 94 | $this->log(self::LOGGER_ERROR, $message, $context); 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function warning($message, array $context = array()) 101 | { 102 | $this->log(self::LOGGER_WARNING, $message, $context); 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public function notice($message, array $context = array()) 109 | { 110 | $this->log(self::LOGGER_NOTICE, $message, $context); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function info($message, array $context = array()) 117 | { 118 | $this->log(self::LOGGER_INFO, $message, $context); 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | public function debug($message, array $context = array()) 125 | { 126 | $this->log(self::LOGGER_DEBUG, $message, $context); 127 | } 128 | 129 | /** 130 | * {@inheritdoc} 131 | */ 132 | public function log($level, $message, array $context = array()) 133 | { 134 | if (function_exists('watchdog')) { 135 | watchdog('Symfony2', $message, $context, $level); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Tests/Port/DrupalSessionStorageTest.php: -------------------------------------------------------------------------------- 1 | 72 | */ 73 | class DrupalSessionStorageTest extends \PHPUnit_Framework_TestCase 74 | { 75 | /** 76 | * Tests Drupal session storage service 77 | */ 78 | public function testSessionStorage() 79 | { 80 | // Given 81 | global $time; 82 | 83 | $session = $this->getDrupalSessionStorageMock(false); 84 | 85 | // When 86 | $session->start(); 87 | 88 | // Then 89 | $this->assertEquals(1, $time, 'Time should be equal to 1 because 1 iteration is done'); 90 | } 91 | 92 | /** 93 | * Tests Drupal session storage service with cookie session refresh lifetime 94 | */ 95 | public function testSessionStorageWithRefreshLifetime() 96 | { 97 | // Given 98 | global $started, $time; 99 | 100 | $session = $this->getDrupalSessionStorageMock(true); 101 | 102 | $started = true; 103 | 104 | // When 105 | $session->start(); 106 | 107 | // Then 108 | $this->assertEquals(2, $time, 'Time should be equal to 2 because 2 iteration is done'); 109 | } 110 | 111 | /** 112 | * Returns DrupalSessionStorage mock 113 | * 114 | * @param bool $refreshCookieLifetime 115 | * 116 | * @return DrupalSessionStorage 117 | */ 118 | protected function getDrupalSessionStorageMock($refreshCookieLifetime) 119 | { 120 | $drupal = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Drupal\Drupal') 121 | ->disableOriginalConstructor() 122 | ->getMock(); 123 | 124 | $bag = $this->getMock('Symfony\Component\HttpFoundation\Session\SessionBagInterface'); 125 | $bag->expects($this->any())->method('getName')->will($this->returnValue('test')); 126 | 127 | $session = new DrupalSessionStorage($drupal, $refreshCookieLifetime); 128 | $session->registerBag($bag); 129 | 130 | return $session; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /Drupal/DrupalInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface DrupalInterface 20 | { 21 | /** 22 | * Initializes the Drupal core 23 | */ 24 | public function initialize(); 25 | 26 | /** 27 | * The shutdown method only catches exit instruction from the Drupal code to rebuild the correct response 28 | * 29 | * @param integer $level 30 | * 31 | * @return mixed 32 | */ 33 | public function shutdown($level); 34 | 35 | /** 36 | * Disables the response 37 | */ 38 | public function disableResponse(); 39 | 40 | /** 41 | * Return true if the current Drupal object contains a valid Response object 42 | * 43 | * @return boolean 44 | */ 45 | public function hasResponse(); 46 | 47 | /** 48 | * @return boolean 49 | * 50 | * @throws InvalidStateMethodCallException 51 | */ 52 | public function is403(); 53 | 54 | /** 55 | * @return boolean 56 | * 57 | * @throws InvalidStateMethodCallException 58 | */ 59 | public function is404(); 60 | 61 | /** 62 | * @return boolean 63 | * 64 | * @throws InvalidStateMethodCallException 65 | */ 66 | public function isOffline(); 67 | 68 | /** 69 | * @return boolean 70 | * 71 | * @throws InvalidStateMethodCallException 72 | */ 73 | public function isOnline(); 74 | 75 | /** 76 | * @return boolean 77 | * 78 | * @throws InvalidStateMethodCallException 79 | */ 80 | public function isFound(); 81 | 82 | /** 83 | * Return true if the Drupal is correctly installed 84 | * 85 | * @return boolean 86 | */ 87 | public function isInstalled(); 88 | 89 | /** 90 | * This method builds the state of the current Drupal instance 91 | * @see menu_execute_active_handler function for more information 92 | * 93 | * @param Request $request 94 | */ 95 | public function defineState(Request $request); 96 | 97 | /** 98 | * Decorates the inner content and renders the page 99 | * 100 | * @throws InvalidStateMethodCallException 101 | */ 102 | public function render(); 103 | 104 | /** 105 | * Builds the content 106 | */ 107 | public function buildContent(); 108 | 109 | /** 110 | * @return \Symfony\Component\HttpFoundation\Response 111 | */ 112 | public function getResponse(); 113 | 114 | /** 115 | * @param integer $pageResultCallback 116 | */ 117 | public function setPageResultCallback($pageResultCallback); 118 | 119 | /** 120 | * @return integer 121 | */ 122 | public function getPageResultCallback(); 123 | 124 | /** 125 | * @param array $routerItem 126 | */ 127 | public function setRouterItem($routerItem); 128 | 129 | /** 130 | * @return array 131 | */ 132 | public function getRouterItem(); 133 | 134 | /** 135 | * Gets info of entity type if given, all entities otherwise 136 | * 137 | * @param string $entityType An entity type (like node) 138 | * 139 | * @return array 140 | */ 141 | public function getEntityInfo($entityType = null); 142 | 143 | /** 144 | * Gets the entity controller 145 | * 146 | * @param string $entityType An entity type (like node) 147 | * 148 | * @return \DrupalDefaultEntityController 149 | */ 150 | public function getEntityController($entityType); 151 | } 152 | -------------------------------------------------------------------------------- /Event/Subscriber/UserSubscriber.php: -------------------------------------------------------------------------------- 1 | getClassMetadata(); 70 | 71 | if ($metadata->name !== 'Ekino\Bundle\DrupalBundle\Entity\User') { 72 | return; 73 | } 74 | 75 | // column definition for id field, this declaration does not work with an 'id' field 76 | $metadata->fieldMappings['uid']['columnDefinition'] = 'int(10) unsigned NOT NULL DEFAULT \'0\' COMMENT \'Primary Key: Unique user ID.\''; 77 | 78 | // force value to be nullable 79 | $metadata->fieldMappings['usernameCanonical']['nullable'] = true; 80 | $metadata->fieldMappings['emailCanonical']['nullable'] = true; 81 | $metadata->fieldMappings['enabled']['nullable'] = true; 82 | $metadata->fieldMappings['algorithm']['nullable'] = true; 83 | $metadata->fieldMappings['salt']['nullable'] = true; 84 | $metadata->fieldMappings['locked']['nullable'] = true; 85 | $metadata->fieldMappings['expired']['nullable'] = true; 86 | $metadata->fieldMappings['roles']['nullable'] = true; 87 | $metadata->fieldMappings['credentialsExpired']['nullable'] = true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Port/DrupalFlashBag.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class DrupalFlashBag implements FlashBagInterface, \IteratorAggregate, \Countable 20 | { 21 | /** 22 | * The storage key for flashes in the session 23 | * 24 | * @var string 25 | */ 26 | private $storageKey; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param string $storageKey The key used to store flashes in the session. 32 | */ 33 | public function __construct($storageKey = '_sf2_flashes') 34 | { 35 | $this->storageKey = $storageKey; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getName() 42 | { 43 | return 'flashes'; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function initialize(array &$flashes) 50 | { 51 | $_SESSION[$this->storageKey] = &$flashes; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function add($type, $message) 58 | { 59 | $_SESSION[$this->storageKey][$type][] = $message; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function peek($type, array $default =array()) 66 | { 67 | return $this->has($type) ? $_SESSION[$this->storageKey][$type] : $default; 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function peekAll() 74 | { 75 | return $_SESSION[$this->storageKey]; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function get($type, array $default = array()) 82 | { 83 | if (!$this->has($type)) { 84 | return $default; 85 | } 86 | 87 | $return = $_SESSION[$this->storageKey][$type]; 88 | 89 | unset($_SESSION[$this->storageKey][$type]); 90 | 91 | return $return; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function all() 98 | { 99 | $return = $this->peekAll(); 100 | $_SESSION[$this->storageKey] = array(); 101 | 102 | return $return; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public function set($type, $messages) 109 | { 110 | $_SESSION[$this->storageKey][$type] = (array) $messages; 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function setAll(array $messages) 117 | { 118 | $_SESSION[$this->storageKey] = $messages; 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | public function has($type) 125 | { 126 | return array_key_exists($type, $_SESSION[$this->storageKey]) && $_SESSION[$this->storageKey][$type]; 127 | } 128 | 129 | /** 130 | * {@inheritdoc} 131 | */ 132 | public function keys() 133 | { 134 | return array_keys($_SESSION[$this->storageKey]); 135 | } 136 | 137 | /** 138 | * {@inheritdoc} 139 | */ 140 | public function getStorageKey() 141 | { 142 | return $this->storageKey; 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public function clear() 149 | { 150 | return $this->all(); 151 | } 152 | 153 | /** 154 | * Returns an iterator for flashes. 155 | * 156 | * @return \ArrayIterator An \ArrayIterator instance 157 | */ 158 | public function getIterator() 159 | { 160 | return new \ArrayIterator($this->all()); 161 | } 162 | 163 | /** 164 | * Returns the number of flashes. 165 | * 166 | * This method does not work. 167 | * 168 | * @deprecated in 2.2, removed in 2.3 169 | * @see https://github.com/symfony/symfony/issues/6408 170 | * 171 | * @return int The number of flashes 172 | */ 173 | public function count() 174 | { 175 | trigger_error(sprintf('%s() is deprecated since 2.2 and will be removed in 2.3', __METHOD__), E_USER_DEPRECATED); 176 | 177 | return count($_SESSION[$this->storageKey]); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Tests/Entity/UserTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class UserTest extends \PHPUnit_Framework_TestCase 20 | { 21 | public function getDrupalUser() 22 | { 23 | $user = new \stdClass(); 24 | $user->mail = 'foo@bar.com'; 25 | $user->name = 'foobar'; 26 | $user->uid = 42; 27 | 28 | return $user; 29 | } 30 | 31 | public function testFromDrupalUser() 32 | { 33 | $drupalUser = $this->getDrupalUser(); 34 | $drupalUser->status = 2; 35 | 36 | $user = new User; 37 | $user->fromDrupalUser($drupalUser); 38 | 39 | $this->assertEquals('foo@bar.com', $user->getMail()); 40 | // $this->assertEquals(true, $user->isLocked()); 41 | $this->assertEquals('foobar', $user->getUsername()); 42 | } 43 | 44 | public function testSetterGetter() 45 | { 46 | $object = new \stdClass(); 47 | $object->name = 'haha'; 48 | 49 | $drupalUser = $this->getDrupalUser(); 50 | $drupalUser->randomProperty = 'foobar'; 51 | 52 | $drupalUser->randomObject = $object; 53 | $drupalUser->name = 'Thomas'; 54 | 55 | 56 | $user = new User; 57 | $user->fromDrupalUser($drupalUser); 58 | 59 | $user->foo = array(); 60 | $user->foo += array('salut'); 61 | 62 | $this->assertEquals(array('salut'), $user->foo); 63 | 64 | $user->foo['bar'] = 'foobar'; 65 | 66 | $this->assertEquals('Thomas', $user->name); 67 | $this->assertEquals('foo@bar.com', $user->mail); 68 | 69 | $this->assertEquals('foobar', $user->randomProperty); 70 | 71 | $object->name = 'reference'; 72 | 73 | $this->assertEquals('reference', $user->randomObject->name); 74 | } 75 | 76 | public function testSerialize() 77 | { 78 | $drupalUser = $this->getDrupalUser(); 79 | 80 | $user = new User; 81 | $user->fromDrupalUser($drupalUser); 82 | $user->salt = 'salt'; 83 | $user->setConfirmationToken('token'); 84 | 85 | $expected = array( 86 | 'username' => NULL, 87 | 'usernameCanonical' => NULL, 88 | 'email' => NULL, 89 | 'emailCanonical' => NULL, 90 | 'enabled' => false, 91 | 'algorithm' => NULL, 92 | 'salt' => 'salt', 93 | 'password' => NULL, 94 | 'plainPassword' => NULL, 95 | 'lastLogin' => NULL, 96 | 'confirmationToken' => 'token', 97 | 'passwordRequestedAt' => NULL, 98 | 'groups' => NULL, 99 | 'locked' => false, 100 | 'expired' => false, 101 | 'expiresAt' => NULL, 102 | 'roles' => array (), 103 | 'credentialsExpired' => false, 104 | 'credentialsExpireAt' => NULL, 105 | 'uid' => 42, 106 | 'pass' => NULL, 107 | 'name' => 'foobar', 108 | 'mail' => 'foo@bar.com', 109 | 'theme' => NULL, 110 | 'signature' => NULL, 111 | 'signature_format' => NULL, 112 | 'created' => NULL, 113 | 'access' => NULL, 114 | 'login' => NULL, 115 | 'status' => NULL, 116 | 'timezone' => NULL, 117 | 'language' => NULL, 118 | 'picture' => NULL, 119 | 'init' => NULL, 120 | 'data' => NULL, 121 | 'path' => NULL 122 | ); 123 | 124 | $this->assertEquals($expected, unserialize($user->serialize())); 125 | } 126 | 127 | public function testUnserialize() 128 | { 129 | $s = serialize($expected = array( 130 | 'uid' => 42, 131 | 'pass' => NULL, 132 | 'name' => 'foobar', 133 | 'mail' => 'foo@bar.com', 134 | 'theme' => NULL, 135 | 'signature' => NULL, 136 | 'signature_format' => NULL, 137 | 'created' => NULL, 138 | 'access' => NULL, 139 | 'login' => NULL, 140 | 'status' => NULL, 141 | 'timezone' => NULL, 142 | 'language' => NULL, 143 | 'picture' => NULL, 144 | 'init' => NULL, 145 | 'data' => NULL, 146 | 'roles' => NULL, 147 | )); 148 | 149 | $user = new User; 150 | 151 | $values = $user->unserialize($s); 152 | 153 | $this->assertEquals($expected, $values); 154 | $this->assertEquals(42, $user->getUid()); 155 | } 156 | } -------------------------------------------------------------------------------- /Entity/DrupalUser.php: -------------------------------------------------------------------------------- 1 | $name; 50 | } 51 | 52 | public function setAccess($access) 53 | { 54 | $this->access = $access; 55 | } 56 | 57 | public function getAccess() 58 | { 59 | return $this->access; 60 | } 61 | 62 | public function setCreated($created) 63 | { 64 | $this->created = $created; 65 | } 66 | 67 | public function getCreated() 68 | { 69 | return $this->created; 70 | } 71 | 72 | public function setData($data) 73 | { 74 | $this->data = $data; 75 | } 76 | 77 | public function getData() 78 | { 79 | return $this->data; 80 | } 81 | 82 | public function setInit($init) 83 | { 84 | $this->init = $init; 85 | } 86 | 87 | public function getInit() 88 | { 89 | return $this->init; 90 | } 91 | 92 | public function setLanguage($language) 93 | { 94 | $this->language = $language; 95 | } 96 | 97 | public function getLanguage() 98 | { 99 | return $this->language; 100 | } 101 | 102 | public function setLogin($login) 103 | { 104 | $this->login = $login; 105 | } 106 | 107 | public function getLogin() 108 | { 109 | return $this->login; 110 | } 111 | 112 | public function setMail($mail) 113 | { 114 | $this->mail = $mail; 115 | } 116 | 117 | public function getMail() 118 | { 119 | return $this->mail; 120 | } 121 | 122 | public function setName($name) 123 | { 124 | $this->name = $name; 125 | } 126 | 127 | public function getName() 128 | { 129 | return $this->name; 130 | } 131 | 132 | public function setPass($pass) 133 | { 134 | $this->pass = $pass; 135 | } 136 | 137 | public function getPass() 138 | { 139 | return $this->pass; 140 | } 141 | 142 | public function setPicture($picture) 143 | { 144 | $this->picture = $picture; 145 | } 146 | 147 | public function getPicture() 148 | { 149 | return $this->picture; 150 | } 151 | 152 | public function setSignature($signature) 153 | { 154 | $this->signature = $signature; 155 | } 156 | 157 | public function getSignature() 158 | { 159 | return $this->signature; 160 | } 161 | 162 | public function setSignatureFormat($signatureFormat) 163 | { 164 | $this->signatureFormat = $signatureFormat; 165 | } 166 | 167 | public function getSignatureFormat() 168 | { 169 | return $this->signatureFormat; 170 | } 171 | 172 | public function setStatus($status) 173 | { 174 | $this->status = $status; 175 | } 176 | 177 | public function getStatus() 178 | { 179 | return $this->status; 180 | } 181 | 182 | public function setTheme($theme) 183 | { 184 | $this->theme = $theme; 185 | } 186 | 187 | public function getTheme() 188 | { 189 | return $this->theme; 190 | } 191 | 192 | public function setTimezone($timezone) 193 | { 194 | $this->timezone = $timezone; 195 | } 196 | 197 | public function getTimezone() 198 | { 199 | return $this->timezone; 200 | } 201 | 202 | public function setUid($uid) 203 | { 204 | $this->uid = $uid; 205 | } 206 | 207 | public function getUid() 208 | { 209 | return $this->uid; 210 | } 211 | 212 | /** 213 | * (PHP 5 >= 5.1.0)
214 | * String representation of object 215 | * @link http://php.net/manual/en/serializable.serialize.php 216 | * @return string the string representation of the object or &null; 217 | */ 218 | public function serialize() 219 | { 220 | $values = array(); 221 | 222 | foreach ($this as $name => $value) { 223 | $values[$name] = $value; 224 | } 225 | 226 | return serialize($values); 227 | } 228 | 229 | /** 230 | * (PHP 5 >= 5.1.0)
231 | * Constructs the object 232 | * @link http://php.net/manual/en/serializable.unserialize.php 233 | * @param string $serialized

234 | * The string representation of the object. 235 | *

236 | * @return mixed the original value unserialized. 237 | */ 238 | public function unserialize($serialized) 239 | { 240 | $values = unserialize($serialized); 241 | 242 | foreach ($values as $name => $value) { 243 | $this->$name = $value; 244 | } 245 | 246 | return $values; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Entity/EntityRepository.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class EntityRepository 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $type; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $bundle; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param string $type An entity type (like node) 35 | * @param string $bundle A bundle name (like page, article, ...) 36 | */ 37 | public function __construct($type, $bundle = null) 38 | { 39 | $this->type = $type; 40 | $this->bundle = $bundle; 41 | } 42 | 43 | /** 44 | * Finds all entities 45 | * 46 | * @return array 47 | */ 48 | public function findAll() 49 | { 50 | return $this->findBy(array()); 51 | } 52 | 53 | /** 54 | * Finds all published entities 55 | * 56 | * @return array 57 | */ 58 | public function findAllPublished() 59 | { 60 | return $this->findBy(array(), array(array( 61 | 'column' => 'status', 62 | 'value' => 1, 63 | ))); 64 | } 65 | 66 | /** 67 | * Finds entities by given criteria 68 | * 69 | * @param array $entityConditions An array of entity conditions to filter results 70 | * @param array $propertyConditions An array of property conditions to filter results 71 | * @param array $fieldConditions An array of field conditions to filter results 72 | * @param integer|null $offset Offset used in query 73 | * @param integer|null $limit Limit used in query 74 | * 75 | * @return array 76 | */ 77 | public function findBy(array $entityConditions, array $propertyConditions = array(), array $fieldConditions = array(), $offset = null, $limit = null) 78 | { 79 | $query = $this->createQuery(); 80 | 81 | foreach ($entityConditions as $crit) { 82 | $query->entityCondition($crit['name'], $crit['value'], array_key_exists('operator', $crit) ? $crit['operator'] : null); 83 | } 84 | 85 | foreach ($propertyConditions as $crit) { 86 | $query->propertyCondition($crit['column'], $crit['value'], array_key_exists('operator', $crit) ? $crit['operator'] : null); 87 | } 88 | 89 | foreach ($fieldConditions as $crit) { 90 | $query->fieldCondition($crit['field'], $crit['column'], $crit['value'], array_key_exists('operator', $crit) ? $crit['operator'] : null); 91 | } 92 | 93 | if (null !== $offset) { 94 | $query->range($offset); 95 | } 96 | 97 | if (null !== $limit) { 98 | $query->range($offset, $limit); 99 | } 100 | 101 | $result = $query->execute(); 102 | 103 | return isset($result[$this->type]) ? entity_load($this->type, array_keys($result[$this->type])) : array(); 104 | } 105 | 106 | /** 107 | * Finds one entity by a given identifier 108 | * 109 | * @param integer $id An entity identifier 110 | * 111 | * @return \stdClass|false 112 | */ 113 | public function find($id) 114 | { 115 | return $this->findOneBy(array(array( 116 | 'name' => 'entity_id', 117 | 'value' => $id, 118 | ))); 119 | } 120 | 121 | /** 122 | * Finds one published entity by a given identifier 123 | * 124 | * @param integer $id An entity identifier 125 | * 126 | * @return \stdClass|false 127 | */ 128 | public function findPublished($id) 129 | { 130 | return $this->findOnePublishedBy(array(array( 131 | 'name' => 'entity_id', 132 | 'value' => $id, 133 | ))); 134 | } 135 | 136 | /** 137 | * Finds one entity by given criteria 138 | * 139 | * @param array $entityConditions An array of entity conditions to filter results 140 | * @param array $propertyConditions An array of property conditions to filter results 141 | * @param array $fieldConditions An array of field conditions to filter results 142 | * 143 | * @return \stdClass|false 144 | */ 145 | public function findOneBy(array $entityConditions, array $propertyConditions = array(), array $fieldConditions = array()) 146 | { 147 | $entities = $this->findBy($entityConditions, $propertyConditions, $fieldConditions, null, 1); 148 | 149 | return reset($entities); 150 | } 151 | 152 | /** 153 | * Finds one published entity by given criteria 154 | * 155 | * @param array $entityConditions An array of entity conditions to filter results 156 | * @param array $propertyConditions An array of property conditions to filter results 157 | * @param array $fieldConditions An array of field conditions to filter results 158 | * 159 | * @return \stdClass|false 160 | */ 161 | public function findOnePublishedBy(array $entityConditions, array $propertyConditions = array(), array $fieldConditions = array()) 162 | { 163 | $propertyConditions = array_filter($propertyConditions, function ($item) { 164 | return isset($item['column']) && 'status' != $item['column']; 165 | }); 166 | 167 | $propertyConditions[] = array( 168 | 'column' => 'status', 169 | 'value' => 1, 170 | ); 171 | 172 | return $this->findOneBy($entityConditions, $propertyConditions, $fieldConditions); 173 | } 174 | 175 | /** 176 | * Creates a base entity field query 177 | * 178 | * @param string $bundle A bundle name 179 | * 180 | * @return \EntityFieldQuery 181 | */ 182 | public function createQuery($bundle = null) 183 | { 184 | $query = new \EntityFieldQuery; 185 | $query->entityCondition('entity_type', $this->type); 186 | 187 | $bundle = $bundle ?: $this->bundle; 188 | 189 | if ($bundle) { 190 | $query->entityCondition('bundle', $bundle); 191 | } 192 | 193 | return $query; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Drupal 7 Bundle by Ekino 2 | ======================== 3 | 4 | [![Build Status](https://secure.travis-ci.org/ekino/EkinoDrupalBundle.png?branch=master)](http://travis-ci.org/ekino/EkinoDrupalBundle) 5 | 6 | **Requires** at least *Drush 5.0* for compatibility with Symfony console. 7 | 8 | The bundle tries to deeply integrate Symfony2 with Drupal and Drupal with Symfony2. Of course this is done without 9 | altering the Drupal's core. 10 | 11 | When this bundle is activated, the Symfony2 console will have the Drupal libraries autoloaded. So, it makes possible 12 | the use of Drupal libraries from your Symfony2 command. 13 | 14 | Install 15 | ------- 16 | 17 | ### Download the symfony2 sandbox and the Drupal code 18 | 19 | ### Install the files to have the following structure 20 | 21 | Symfony Sandbox Root 22 | - app 23 | - vendor 24 | - src 25 | - web (Drupal source code) 26 | 27 | The ``web`` directory must be the document root and contains the Drupal source code. 28 | 29 | ### Update the ``index.php`` file 30 | 31 | This file "share" the container with Drupal so it is possible to reuse Symfony2's services from within Drupal. The 32 | initialization process is always handled by Symfony2. 33 | 34 | ``` php 35 | loadClassCache(); 45 | $kernel->boot(); 46 | 47 | // make the Symfony container available from Drupal file 48 | global $container; 49 | 50 | $container = $kernel->getContainer(); 51 | 52 | $request = Request::createFromGlobals(); 53 | 54 | $response = $kernel->handle($request); 55 | $response->send(); 56 | 57 | $kernel->terminate($request, $response); 58 | ``` 59 | ### Install the related Drupal module 60 | 61 | The module can be downloaded from the following url: https://github.com/ekino/ekino_drupal_symfony2 62 | 63 | ### Configuration 64 | 65 | Edit the Symfony ``config.yml`` file and add the following lines: 66 | 67 | parameters: 68 | session.flashbag.class: Ekino\Bundle\DrupalBundle\Port\DrupalFlashBag 69 | session.attribute_bag.class: Ekino\Bundle\DrupalBundle\Port\DrupalAttributeBag 70 | 71 | framework: 72 | # ... configuration options 73 | session: 74 | # ... configuration options 75 | storage_id: ekino.drupal.session.storage 76 | 77 | ekino_drupal: 78 | root: %kernel.root_dir%/../web 79 | logger: ekino.drupal.logger.watchdog 80 | strategy_id: ekino.drupal.delivery_strategy.symfony 81 | # attach a security token to the following provider keys 82 | provider_keys: [main, admin] 83 | 84 | # not required 85 | entity_repositories: 86 | # 3 equivalent examples of configuration: 87 | - { bundle: page } 88 | - { type: node, bundle: page } 89 | - { type: node, bundle: page, class: Ekino\Bundle\DrupalBundle\Entity\EntityRepository } 90 | # you can also define an entity repository: 91 | - { type: node, class: Application\Ekino\Bundle\DrupalBundle\Entity\Node\NodeRepository } 92 | 93 | # switch to true if you want to prefix the name of Symfony tables 94 | table_prefix: 95 | enabled: false 96 | prefix: symfony__ 97 | exclude: [users] 98 | 99 | # optional 100 | session: 101 | refresh_cookie_lifetime: true # default value: false 102 | 103 | # declare 2 required mapping definition used by Drupal 104 | doctrine: 105 | dbal: 106 | driver: %database_driver% 107 | dbname: %database_name% 108 | user: %database_user% 109 | host: %database_host% 110 | port: %database_port% 111 | password: %database_password% 112 | charset: UTF8 113 | 114 | mapping_types: 115 | longblob: object 116 | blob: object 117 | 118 | # Tips: this allows Doctrine to consider only tables starting with 119 | # "symfony__" during a migration generation. 120 | # Think to add Doctrine migrations table here or configure it in 121 | # the doctrine_migrations section (table_name) 122 | schema_filter: ~^(symfony__|migration_versions)~ 123 | 124 | The bundle comes with 3 delivery strategies: 125 | 126 | * ekino.drupal.delivery_strategy.background: Drupal never returns the response, Symfony does 127 | * ekino.drupal.delivery_strategy.drupal: Drupal always returns the response, even if the page is 404 128 | * ekino.drupal.delivery_strategy.symfony: Drupal returns the response only if the page is not 404 129 | 130 | The (optional) section ``entity_repositories`` allows you to easy interact with 131 | Drupal API to retrieve contents and handle it from Symfony code. 132 | The configuration offers default values: 133 | 134 | * default entity type is ``node`` 135 | * default repository class is ``Ekino\Bundle\DrupalBundle\Entity\EntityRepository``, feel free to configure yours 136 | 137 | Update Queries 138 | -------------- 139 | 140 | ``` sql 141 | UPDATE users SET `emailCanonical` = `mail`, `usernameCanonical` = `name`, `roles` = 'b:0;'; 142 | ``` 143 | 144 | Usage 145 | ----- 146 | 147 | Symfony components can be used from within Drupal: 148 | 149 | ``` php 150 | foo(); 153 | 154 | // do some stuff with $result 155 | } 156 | ``` 157 | 158 | Security 159 | -------- 160 | 161 | You can secure a Symfony route with a Drupal permission, with prefix PERMISSION_DRUPAL_. 162 | Like it: 163 | 164 | ``` yml 165 | security: 166 | role_hierarchy: 167 | # ... 168 | 169 | firewalls: 170 | # ... 171 | 172 | access_control: 173 | - { path: ^/symfony/admin, role: PERMISSION_DRUPAL_ACCESS_ADMINISTRATION_PAGES } 174 | 175 | ``` 176 | 177 | The PERMISSION_DRUPAL_ACCESS_ADMINISTRATION_PAGES is translate in "access administration pages" 178 | and used with user_access and global Drupal user. 179 | 180 | If you want use you "personal access" permission, use role PERMISSION_DRUPAL_PERSONAL_ACCESS for example. 181 | 182 | 183 | Limitations 184 | ----------- 185 | 186 | * It is not possible to use Symfony native class to manage session as Drupal initializes its own session handler 187 | and there is no way to change this. 188 | * requests must be served through the index.php as it is the default value in the .htaccess file and there is no 189 | way to change the default script in Drupal 190 | 191 | Preview 192 | ------- 193 | 194 | If installation is completed successfully, the welcome page looks like: 195 | 196 | ![Screenshot](https://raw.github.com/ekino/EkinoDrupalBundle/2.2/Resources/doc/images/welcome.png) 197 | 198 | You can note the Web Debug Toolbar of Symfony at the bottom ;-). 199 | -------------------------------------------------------------------------------- /Entity/HybridUser.php: -------------------------------------------------------------------------------- 1 | getUid(); 25 | } 26 | 27 | /** 28 | * Returns the password used to authenticate the user. 29 | * 30 | * @return string The password 31 | */ 32 | public function getPassword() 33 | { 34 | return $this->pass; 35 | } 36 | 37 | /** 38 | * Returns the username used to authenticate the user. 39 | * 40 | * @return string The username 41 | */ 42 | public function getUsername() 43 | { 44 | return $this->getName(); 45 | } 46 | 47 | /** 48 | * @param $name 49 | * @return mixed 50 | */ 51 | public function __get($name) 52 | { 53 | if (method_exists($this, 'get'.$name)) { 54 | return call_user_func(array($this, 'get'.$name)); 55 | } 56 | 57 | return $this->$name; 58 | } 59 | 60 | /** 61 | * @param \stdClass $user 62 | * @return \Symfony\Component\Security\Core\User\UserInterface 63 | */ 64 | public function fromDrupalUser(\stdClass $user) 65 | { 66 | foreach ($user as $property => $value) { 67 | $this->$property = $value; 68 | } 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Checks whether the user's account has expired. 75 | * 76 | * @return Boolean true if the user's account is non expired, false otherwise 77 | */ 78 | public function isAccountNonExpired() 79 | { 80 | return $this->status == 1; 81 | } 82 | 83 | /** 84 | * Checks whether the user is locked. 85 | * 86 | * @return Boolean true if the user is not locked, false otherwise 87 | */ 88 | public function isAccountNonLocked() 89 | { 90 | return $this->status != self::STATUS_BLOCKED; 91 | } 92 | 93 | /** 94 | * Checks whether the user's credentials (password) has expired. 95 | * 96 | * @return Boolean true if the user's credentials are non expired, false otherwise 97 | */ 98 | public function isCredentialsNonExpired() 99 | { 100 | return $this->status != self::STATUS_BLOCKED; 101 | } 102 | 103 | /** 104 | * Checks whether the user is enabled. 105 | * 106 | * @return Boolean true if the user is enabled, false otherwise 107 | */ 108 | public function isEnabled() 109 | { 110 | return $this->status === self::STATUS_ENABLED; 111 | } 112 | 113 | /** 114 | * (PHP 5 >= 5.1.0)
115 | * String representation of object 116 | * @link http://php.net/manual/en/serializable.serialize.php 117 | * @return string the string representation of the object or &null; 118 | */ 119 | public function serialize() 120 | { 121 | $values = array(); 122 | 123 | foreach ($this as $name => $value) { 124 | $values[$name] = $value; 125 | } 126 | 127 | return serialize($values); 128 | } 129 | 130 | /** 131 | * (PHP 5 >= 5.1.0)
132 | * Constructs the object 133 | * @link http://php.net/manual/en/serializable.unserialize.php 134 | * @param string $serialized

135 | * The string representation of the object. 136 | *

137 | * @return mixed the original value unserialized. 138 | */ 139 | public function unserialize($serialized) 140 | { 141 | $values = unserialize($serialized); 142 | 143 | foreach ($values as $name => $value) { 144 | $this->$name = $value; 145 | } 146 | 147 | return $values; 148 | } 149 | 150 | /** 151 | * Gets the algorithm used to encode the password. 152 | * 153 | * @return string 154 | */ 155 | public function getAlgorithm() 156 | { 157 | return 'drupal'; 158 | } 159 | 160 | /** 161 | * Sets the username. 162 | * 163 | * @param string $username 164 | */ 165 | public function setUsername($username) 166 | { 167 | $this->name = $username; 168 | } 169 | 170 | /** 171 | * Gets the canonical username in search and sort queries. 172 | * 173 | * @return string 174 | */ 175 | public function getUsernameCanonical() 176 | { 177 | return $this->name; 178 | } 179 | 180 | /** 181 | * Sets the canonical username. 182 | * 183 | * @param string $usernameCanonical 184 | */ 185 | public function setUsernameCanonical($usernameCanonical) 186 | { 187 | $this->setName($usernameCanonical); 188 | } 189 | 190 | /** 191 | * Gets email. 192 | * 193 | * @return string 194 | */ 195 | public function getEmail() 196 | { 197 | return $this->getMail(); 198 | } 199 | 200 | /** 201 | * Sets the email. 202 | * 203 | * @param string $email 204 | */ 205 | public function setEmail($email) 206 | { 207 | $this->mail = $email; 208 | } 209 | 210 | /** 211 | * Gets the canonical email in search and sort queries. 212 | * 213 | * @return string 214 | */ 215 | public function getEmailCanonical() 216 | { 217 | return $this->getMail(); 218 | } 219 | 220 | /** 221 | * Set the canonical email. 222 | * 223 | * @param string $emailCanonical 224 | */ 225 | public function setEmailCanonical($emailCanonical) 226 | { 227 | $this->setEmail($emailCanonical); 228 | } 229 | 230 | /** 231 | * Gets the plain password. 232 | * 233 | * @return string 234 | */ 235 | public function getPlainPassword() 236 | { 237 | return null; 238 | } 239 | 240 | /** 241 | * Never use this to check if this user has access to anything! 242 | * 243 | * Use the SecurityContext, or an implementation of AccessDecisionManager 244 | * instead, e.g. 245 | * 246 | * $securityContext->isGranted('ROLE_USER'); 247 | * 248 | * @param string $role 249 | * @return Boolean 250 | */ 251 | public function hasRole($role) 252 | { 253 | return in_array($role, $this->roles); 254 | } 255 | 256 | /** 257 | * Adds a role to the user. 258 | * 259 | * @param string $role 260 | */ 261 | public function addRole($role) 262 | { 263 | if ($role === static::ROLE_DEFAULT) { 264 | return; 265 | } 266 | 267 | if (!in_array($role, $this->roles, true)) { 268 | $this->roles[] = $role; 269 | } 270 | } 271 | 272 | /** 273 | * Tells if the the given user is this user. 274 | * 275 | * Useful when not hydrating all fields. 276 | * 277 | * @param UserInterface $user 278 | * @return Boolean 279 | */ 280 | public function isUser(FOSUserInterface $user = null) 281 | { 282 | return $user instanceof self && $user->getId() == $this->getId(); 283 | } 284 | 285 | /** 286 | * Removes a role to the user. 287 | * 288 | * @param string $role 289 | */ 290 | public function removeRole($role) 291 | { 292 | if (false !== $key = array_search($role, $this->roles, true)) { 293 | unset($this->roles[$key]); 294 | $this->roles = array_values($this->roles); 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /Port/DrupalSessionStorage.php: -------------------------------------------------------------------------------- 1 | 22 | * @author Florent Denis 23 | */ 24 | class DrupalSessionStorage implements SessionStorageInterface 25 | { 26 | /** 27 | * Array of SessionBagInterface 28 | * 29 | * @var SessionBagInterface[] 30 | */ 31 | protected $bags; 32 | 33 | /** 34 | * @var Boolean 35 | */ 36 | protected $started = false; 37 | 38 | /** 39 | * @var Boolean 40 | */ 41 | protected $closed = false; 42 | 43 | /** 44 | * @var MetadataBag 45 | */ 46 | protected $metadataBag; 47 | 48 | /** 49 | * @var Drupal 50 | */ 51 | protected $drupal; 52 | 53 | /** 54 | * @var bool 55 | */ 56 | protected $refreshCookieLifetime; 57 | 58 | /** 59 | * Constructor. 60 | * 61 | * @param Drupal $drupal A Drupal instance 62 | * @param bool $refreshCookieLifetime Do we need to refresh session cookie lifetime? 63 | * @param null|MetadataBag $metaBag A metadata bag (optional) 64 | */ 65 | public function __construct(Drupal $drupal, $refreshCookieLifetime, MetadataBag $metaBag = null) 66 | { 67 | $this->drupal = $drupal; 68 | $this->refreshCookieLifetime = $refreshCookieLifetime; 69 | 70 | $this->setMetadataBag($metaBag); 71 | } 72 | 73 | /** 74 | * Starts the session. 75 | * 76 | * @api 77 | */ 78 | public function start() 79 | { 80 | if ($this->started && !$this->closed) { 81 | return true; 82 | } 83 | 84 | $this->drupal->initialize(); 85 | 86 | $this->started = drupal_session_started(); 87 | 88 | // refresh cookie lifetime if enabled in configuration 89 | if ($this->started && $this->refreshCookieLifetime) { 90 | $params = session_get_cookie_params(); 91 | $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; 92 | 93 | setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); 94 | } 95 | 96 | // force start session 97 | if (!$this->started) { 98 | drupal_session_start(); 99 | 100 | $this->started = drupal_session_started(); 101 | } 102 | 103 | $this->loadSession(); 104 | 105 | return true; 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function getId() 112 | { 113 | if (!$this->started) { 114 | $this->start(); 115 | } 116 | 117 | return session_id(); 118 | } 119 | 120 | /** 121 | * {@InheritDoc} 122 | */ 123 | public function setId($id) 124 | { 125 | throw new \LogicException('It\'s Drupal who set session id.'); 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function getName() 132 | { 133 | $this->start(); 134 | 135 | return session_name(); 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function setName($name) 142 | { 143 | throw new \LogicException('It\'s Drupal who set session name.'); 144 | } 145 | 146 | /** 147 | * {@InheritDoc} 148 | */ 149 | public function regenerate($destroy = false, $lifetime = null) 150 | { 151 | $this->start(); 152 | 153 | drupal_session_regenerate(); 154 | } 155 | 156 | /** 157 | * {@InheritDoc} 158 | */ 159 | public function save() 160 | { 161 | $this->closed = true; 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function clear() 168 | { 169 | // clear out the bags 170 | foreach ($this->bags as $bag) { 171 | $bag->clear(); 172 | } 173 | 174 | // clear out the session 175 | $_SESSION = array(); 176 | 177 | // reconnect the bags to the session 178 | $this->loadSession(); 179 | } 180 | 181 | /** 182 | * {@InheritDoc} 183 | */ 184 | public function registerBag(SessionBagInterface $bag) 185 | { 186 | $this->bags[$bag->getName()] = $bag; 187 | } 188 | 189 | /** 190 | * {@inheritdoc} 191 | */ 192 | public function getBag($name) 193 | { 194 | if (!isset($this->bags[$name])) { 195 | throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); 196 | } 197 | 198 | $this->start(); 199 | 200 | return $this->bags[$name]; 201 | } 202 | 203 | /** 204 | * Sets the MetadataBag. 205 | * 206 | * @param MetadataBag $metaBag 207 | */ 208 | public function setMetadataBag(MetadataBag $metaBag = null) 209 | { 210 | if (null === $metaBag) { 211 | $metaBag = new MetadataBag(); 212 | } 213 | 214 | $this->metadataBag = $metaBag; 215 | } 216 | 217 | /** 218 | * {@InheritDoc} 219 | */ 220 | public function getMetadataBag() 221 | { 222 | return $this->metadataBag; 223 | } 224 | 225 | /** 226 | * {@InheritDoc} 227 | */ 228 | public function isStarted() 229 | { 230 | return $this->started; 231 | } 232 | 233 | /** 234 | * Sets session.* ini variables. 235 | * 236 | * For convenience we omit 'session.' from the beginning of the keys. 237 | * Explicitly ignores other ini keys. 238 | * 239 | * @param array $options Session ini directives array(key => value). 240 | * 241 | * @see http://php.net/session.configuration 242 | */ 243 | public function setOptions(array $options) 244 | { 245 | $validOptions = array_flip(array( 246 | 'cache_limiter', 'cookie_domain', 'cookie_httponly', 247 | 'cookie_lifetime', 'cookie_path', 'cookie_secure', 248 | 'entropy_file', 'entropy_length', 'gc_divisor', 249 | 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', 250 | 'hash_function', 'name', 'referer_check', 251 | 'serialize_handler', 'use_cookies', 252 | 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 253 | 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', 254 | 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', 255 | )); 256 | 257 | foreach ($options as $key => $value) { 258 | if (isset($validOptions[$key])) { 259 | ini_set('session.'.$key, $value); 260 | } 261 | } 262 | } 263 | 264 | /** 265 | * Load the session with attributes. 266 | * 267 | * After starting the session, PHP retrieves the session from whatever handlers 268 | * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). 269 | * PHP takes the return value from the read() handler, unserializes it 270 | * and populates $_SESSION with the result automatically. 271 | */ 272 | protected function loadSession() 273 | { 274 | foreach ($this->bags as $bag) { 275 | $key = $bag->getStorageKey(); 276 | $_SESSION[$key] = isset($_SESSION[$key]) ? $_SESSION[$key] : array(); 277 | $bag->initialize($_SESSION[$key]); 278 | } 279 | 280 | $this->started = true; 281 | $this->closed = false; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Tests/Entity/EntityRepositoryTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class EntityRepositoryTest extends \PHPUnit_Framework_TestCase 20 | { 21 | /** 22 | * Tests findAll method 23 | */ 24 | public function testFindAll() 25 | { 26 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 27 | ->setConstructorArgs(array('node')) 28 | ->setMethods(array('findBy')) 29 | ->getMock(); 30 | 31 | $repository 32 | ->expects($this->once()) 33 | ->method('findBy') 34 | ->with($this->equalTo(array())); 35 | 36 | $repository->findAll(); 37 | } 38 | 39 | /** 40 | * Tests findAllPublished method 41 | */ 42 | public function testFindAllPublished() 43 | { 44 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 45 | ->setConstructorArgs(array('node')) 46 | ->setMethods(array('findBy')) 47 | ->getMock(); 48 | 49 | $repository 50 | ->expects($this->once()) 51 | ->method('findBy') 52 | ->with($this->equalTo(array()), $this->equalTo(array(array( 53 | 'column' => 'status', 54 | 'value' => 1, 55 | )))); 56 | 57 | $repository->findAllPublished(); 58 | } 59 | 60 | /** 61 | * Tests a very simple call to findBy method 62 | */ 63 | public function testSimpleFindBy() 64 | { 65 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 66 | ->setConstructorArgs(array('node')) 67 | ->setMethods(array('createQuery')) 68 | ->getMock(); 69 | 70 | $query = $this->getMockEntityFieldQuery(); 71 | 72 | $repository 73 | ->expects($this->once()) 74 | ->method('createQuery') 75 | ->will($this->returnValue($query)); 76 | 77 | foreach (array('entityCondition', 'propertyCondition', 'range') as $method) { 78 | $query 79 | ->expects($this->never()) 80 | ->method($method); 81 | } 82 | 83 | $query 84 | ->expects($this->once()) 85 | ->method('execute'); 86 | 87 | $repository->findBy(array()); 88 | } 89 | 90 | /** 91 | * Tests the findBy method with criteria 92 | */ 93 | public function testFindByWithCriteria() 94 | { 95 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 96 | ->setConstructorArgs(array('node')) 97 | ->setMethods(array('createQuery')) 98 | ->getMock(); 99 | 100 | $query = $this->getMockEntityFieldQuery(); 101 | 102 | $repository 103 | ->expects($this->once()) 104 | ->method('createQuery') 105 | ->will($this->returnValue($query)); 106 | 107 | $query 108 | ->expects($this->at(0)) 109 | ->method('entityCondition') 110 | ->with($this->equalTo('entity_type'), $this->equalTo('node'), $this->equalTo(null)); 111 | 112 | $query 113 | ->expects($this->at(1)) 114 | ->method('entityCondition') 115 | ->with($this->equalTo('bundle'), $this->equalTo('page'), $this->equalTo('=')); 116 | 117 | $query 118 | ->expects($this->at(2)) 119 | ->method('propertyCondition') 120 | ->with($this->equalTo('status'), $this->equalTo(1), $this->equalTo(null)); 121 | 122 | $query 123 | ->expects($this->at(3)) 124 | ->method('propertyCondition') 125 | ->with($this->equalTo('title'), $this->equalTo('test title'), $this->equalTo('=')); 126 | 127 | $query 128 | ->expects($this->at(4)) 129 | ->method('fieldCondition') 130 | ->with($this->equalTo('field_name_1'), $this->equalTo('column_name_1'), $this->equalTo(1), $this->equalTo(null)); 131 | 132 | $query 133 | ->expects($this->at(5)) 134 | ->method('fieldCondition') 135 | ->with($this->equalTo('field_name_2'), $this->equalTo('column_name_2'), $this->equalTo('value_2'), $this->equalTo('=')); 136 | 137 | $repository->findBy(array( 138 | array('name' => 'entity_type', 'value' => 'node'), 139 | array('name' => 'bundle', 'value' => 'page', 'operator' => '='), 140 | ), array( 141 | array('column' => 'status', 'value' => 1), 142 | array('column' => 'title', 'value' => 'test title', 'operator' => '='), 143 | ), array( 144 | array('field' => 'field_name_1', 'column' => 'column_name_1', 'value' => 1), 145 | array('field' => 'field_name_2', 'column' => 'column_name_2', 'value' => 'value_2', 'operator' => '='), 146 | )); 147 | } 148 | 149 | /** 150 | * Tests the findBy method with offset 151 | */ 152 | public function testFindByWithOffset() 153 | { 154 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 155 | ->setConstructorArgs(array('node')) 156 | ->setMethods(array('createQuery')) 157 | ->getMock(); 158 | 159 | $query = $this->getMockEntityFieldQuery(); 160 | 161 | $repository 162 | ->expects($this->once()) 163 | ->method('createQuery') 164 | ->will($this->returnValue($query)); 165 | 166 | $query 167 | ->expects($this->once()) 168 | ->method('range') 169 | ->with($this->equalTo(0)); 170 | 171 | $repository->findBy(array(), array(), array(), 0); 172 | } 173 | 174 | /** 175 | * Tests the findBy method with limit 176 | */ 177 | public function testFindByWithLimit() 178 | { 179 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 180 | ->setConstructorArgs(array('node')) 181 | ->setMethods(array('createQuery')) 182 | ->getMock(); 183 | 184 | $query = $this->getMockEntityFieldQuery(); 185 | 186 | $repository 187 | ->expects($this->once()) 188 | ->method('createQuery') 189 | ->will($this->returnValue($query)); 190 | 191 | $query 192 | ->expects($this->once()) 193 | ->method('range') 194 | ->with($this->equalTo(null), $this->equalTo(10)); 195 | 196 | $repository->findBy(array(), array(), array(), null, 10); 197 | } 198 | 199 | /** 200 | * Tests the findBy method with range 201 | */ 202 | public function testFindByWithRange() 203 | { 204 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 205 | ->setConstructorArgs(array('node')) 206 | ->setMethods(array('createQuery')) 207 | ->getMock(); 208 | 209 | $query = $this->getMockEntityFieldQuery(); 210 | 211 | $repository 212 | ->expects($this->once()) 213 | ->method('createQuery') 214 | ->will($this->returnValue($query)); 215 | 216 | $query 217 | ->expects($this->at(0)) 218 | ->method('range') 219 | ->with($this->equalTo(0)); 220 | 221 | $query 222 | ->expects($this->at(1)) 223 | ->method('range') 224 | ->with($this->equalTo(0), $this->equalTo(10)); 225 | 226 | $repository->findBy(array(), array(), array(), 0, 10); 227 | } 228 | 229 | /** 230 | * Tests the find method 231 | */ 232 | public function testFind() 233 | { 234 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 235 | ->setConstructorArgs(array('node')) 236 | ->setMethods(array('findOneBy')) 237 | ->getMock(); 238 | 239 | $repository 240 | ->expects($this->once()) 241 | ->method('findOneBy') 242 | ->with($this->equalTo(array(array( 243 | 'name' => 'entity_id', 244 | 'value' => 10, 245 | )))); 246 | 247 | $repository->find(10); 248 | } 249 | 250 | /** 251 | * Tests the findPublished method 252 | */ 253 | public function testFindPublished() 254 | { 255 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 256 | ->setConstructorArgs(array('node')) 257 | ->setMethods(array('findOnePublishedBy')) 258 | ->getMock(); 259 | 260 | $repository 261 | ->expects($this->once()) 262 | ->method('findOnePublishedBy') 263 | ->with($this->equalTo(array(array( 264 | 'name' => 'entity_id', 265 | 'value' => 10, 266 | )))); 267 | 268 | $repository->findPublished(10); 269 | } 270 | 271 | /** 272 | * Tests the findOneBy method 273 | */ 274 | public function testFindOneBy() 275 | { 276 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 277 | ->setConstructorArgs(array('node')) 278 | ->setMethods(array('findBy')) 279 | ->getMock(); 280 | 281 | $criteria = array( 282 | array('name' => 'entity_type', 'value' => 'node'), 283 | array('name' => 'bundle', 'value' => 'page'), 284 | ); 285 | 286 | $repository 287 | ->expects($this->once()) 288 | ->method('findBy') 289 | ->with($this->equalTo($criteria), $this->equalTo(array()), $this->equalTo(array()), $this->equalTo(null), $this->equalTo(1)) 290 | ->will($this->returnValue(array())); 291 | 292 | $repository->findOneBy($criteria); 293 | } 294 | 295 | /** 296 | * Tests the findOnePublishedBy method 297 | */ 298 | public function testFindOnePublishedBy() 299 | { 300 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 301 | ->setConstructorArgs(array('node')) 302 | ->setMethods(array('findOneBy')) 303 | ->getMock(); 304 | 305 | $criteria = array( 306 | array('name' => 'entity_type', 'value' => 'node'), 307 | array('name' => 'bundle', 'value' => 'page'), 308 | ); 309 | 310 | $repository 311 | ->expects($this->once()) 312 | ->method('findOneBy') 313 | ->with($this->equalTo($criteria), $this->equalTo(array( 314 | array('column' => 'language', 'value' => 'fr'), 315 | array('column' => 'status', 'value' => 1), 316 | ))) 317 | ->will($this->returnValue(array())); 318 | 319 | $repository->findOnePublishedBy($criteria, array( 320 | array('column' => 'language', 'value' => 'fr') 321 | )); 322 | } 323 | 324 | /** 325 | * Tests the findOnePublishedBy method when trying to override status 326 | */ 327 | public function testFindOnePublishedByTryingToOverrideStatus() 328 | { 329 | $repository = $this->getMockBuilder('Ekino\Bundle\DrupalBundle\Entity\EntityRepository') 330 | ->setConstructorArgs(array('node')) 331 | ->setMethods(array('findOneBy')) 332 | ->getMock(); 333 | 334 | $criteria = array( 335 | array('name' => 'entity_type', 'value' => 'node'), 336 | array('name' => 'bundle', 'value' => 'page'), 337 | ); 338 | 339 | $repository 340 | ->expects($this->once()) 341 | ->method('findOneBy') 342 | ->with($this->equalTo($criteria), $this->equalTo(array( 343 | array('column' => 'language', 'value' => 'fr'), 344 | array('column' => 'status', 'value' => 1), 345 | ))) 346 | ->will($this->returnValue(array())); 347 | 348 | $repository->findOnePublishedBy($criteria, array( 349 | array('column' => 'language', 'value' => 'fr'), 350 | array('column' => 'status', 'value' => 0) 351 | )); 352 | } 353 | 354 | /** 355 | * Gets a mock of entity field query 356 | * 357 | * @return object 358 | */ 359 | private function getMockEntityFieldQuery() 360 | { 361 | return $this->getMock('Ekino\Bundle\DrupalBundle\Tests\Stub\EntityFieldQueryInterface'); 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /Drupal/Drupal.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class Drupal implements DrupalInterface 28 | { 29 | const STATE_FRESH = 0; // the Drupal instance is not initialized 30 | const STATE_INIT = 1; // the Drupal instance has been initialized 31 | const STATE_STATUS_DEFINED = 2; // the response status is known 32 | const STATE_INNER_CONTENT = 3; // Drupal has generated the inner content 33 | const STATE_RESPONSE = 4; // Drupal has generated the Response object 34 | 35 | /** 36 | * @var boolean 37 | */ 38 | protected $initialized = false; 39 | 40 | /** 41 | * @var string 42 | */ 43 | protected $root; 44 | 45 | /** 46 | * @var integer 47 | */ 48 | protected $status; 49 | 50 | /** 51 | * @var integer 52 | */ 53 | protected $state; 54 | 55 | /** 56 | * @var Response 57 | */ 58 | protected $response; 59 | 60 | /** 61 | * @var array 62 | */ 63 | protected $routerItem; 64 | 65 | /** 66 | * @var boolean 67 | */ 68 | protected $encapsulated; 69 | 70 | /** 71 | * @var integer 72 | */ 73 | protected $pageResultCallback; 74 | 75 | /** 76 | * @var boolean 77 | */ 78 | protected $disableResponse; 79 | 80 | /** 81 | * @var UserManagerInterface 82 | */ 83 | protected $userManager; 84 | 85 | /** 86 | * Constructor 87 | * 88 | * @param string $root The path of Drupal core 89 | * @param UserManagerInterface $userManager A user manager instance 90 | */ 91 | public function __construct($root, UserManagerInterface $userManager) 92 | { 93 | $this->root = $root; 94 | $this->state = self::STATE_FRESH; 95 | $this->response = new Response(); 96 | $this->encapsulated = false; 97 | $this->disableResponse = false; 98 | $this->userManager = $userManager; 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function initialize() 105 | { 106 | if ($this->initialized) { 107 | return; 108 | } 109 | 110 | $this->initialized = true; 111 | $currentLevel = ob_get_level(); 112 | 113 | register_shutdown_function(array($this, 'shutdown'), $currentLevel); 114 | 115 | $this->encapsulate(function($path) { 116 | // start the Drupal bootstrap 117 | define('DRUPAL_ROOT', $path); 118 | 119 | // make sure the default path point to the correct instance 120 | chdir($path); 121 | 122 | require_once $path . '/includes/bootstrap.inc'; 123 | drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); 124 | 125 | // restore the symfony error handle 126 | restore_error_handler(); 127 | restore_exception_handler(); 128 | 129 | }, $this->root); 130 | 131 | $this->restoreBufferLevel($currentLevel); 132 | 133 | $this->fixAnonymousUser(); 134 | $this->state = self::STATE_INIT; 135 | } 136 | 137 | /** 138 | * State of initilize. 139 | * 140 | * @return boolean 141 | */ 142 | public function isInitialized() 143 | { 144 | return $this->initialized; 145 | } 146 | 147 | /** 148 | * Initializes Drush which boostraps Drupal core 149 | */ 150 | public function initializeDrush() 151 | { 152 | define('DRUSH_BASE_PATH', sprintf('%s/../drush', $this->root)); 153 | 154 | define('DRUSH_REQUEST_TIME', microtime(TRUE)); 155 | 156 | require_once DRUSH_BASE_PATH . '/includes/bootstrap.inc'; 157 | require_once DRUSH_BASE_PATH . '/includes/environment.inc'; 158 | require_once DRUSH_BASE_PATH . '/includes/command.inc'; 159 | require_once DRUSH_BASE_PATH . '/includes/drush.inc'; 160 | require_once DRUSH_BASE_PATH . '/includes/backend.inc'; 161 | require_once DRUSH_BASE_PATH . '/includes/batch.inc'; 162 | require_once DRUSH_BASE_PATH . '/includes/context.inc'; 163 | require_once DRUSH_BASE_PATH . '/includes/sitealias.inc'; 164 | require_once DRUSH_BASE_PATH . '/includes/exec.inc'; 165 | require_once DRUSH_BASE_PATH . '/includes/drupal.inc'; 166 | require_once DRUSH_BASE_PATH . '/includes/output.inc'; 167 | require_once DRUSH_BASE_PATH . '/includes/cache.inc'; 168 | require_once DRUSH_BASE_PATH . '/includes/filesystem.inc'; 169 | require_once DRUSH_BASE_PATH . '/includes/dbtng.inc'; 170 | 171 | $drush_info = drush_read_drush_info(); 172 | define('DRUSH_VERSION', $drush_info['drush_version']); 173 | 174 | $version_parts = explode('.', DRUSH_VERSION); 175 | define('DRUSH_MAJOR_VERSION', $version_parts[0]); 176 | define('DRUSH_MINOR_VERSION', $version_parts[1]); 177 | 178 | $GLOBALS['argv'][0] = 'default'; 179 | 180 | drush_set_context('arguments', array('default', 'help')); 181 | drush_set_context('argc', $GLOBALS['argc']); 182 | drush_set_context('argv', $GLOBALS['argv']); 183 | 184 | drush_set_option('root', $this->root); 185 | 186 | // make sure the default path point to the correct instance 187 | $currentDirectory = getcwd(); 188 | chdir($this->root); 189 | 190 | $phases = _drush_bootstrap_phases(FALSE, TRUE); 191 | drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE); 192 | 193 | // We need some global options processed at this early stage. Namely --debug. 194 | _drush_bootstrap_global_options(); 195 | 196 | $return = ''; 197 | $command_found = FALSE; 198 | 199 | foreach ($phases as $phase) { 200 | drush_bootstrap_to_phase($phase); 201 | } 202 | 203 | chdir($currentDirectory); 204 | } 205 | 206 | /** 207 | * Fixes the user, Drupal does not provide a hook for anonymous user 208 | */ 209 | public function fixAnonymousUser() 210 | { 211 | global $user; 212 | 213 | if (!$user || $user->uid != 0) { 214 | return; 215 | } 216 | 217 | $user = $this->userManager->createUser()->fromDrupalUser($user); 218 | } 219 | 220 | /** 221 | * {@inheritdoc} 222 | */ 223 | public function shutdown($level) 224 | { 225 | if (!$this->encapsulated) { 226 | return; 227 | } 228 | 229 | $headers = $this->cleanHeaders(); 230 | 231 | foreach ($headers as $name => $value) { 232 | $this->response->headers->set($name, $value); 233 | } 234 | 235 | $statusCode = http_response_code(); 236 | 237 | if ($statusCode !== false) { 238 | $this->response->setStatusCode($statusCode); 239 | } 240 | 241 | $content = ob_get_contents(); 242 | 243 | $this->response->setContent($content); 244 | 245 | $this->restoreBufferLevel($level); 246 | 247 | $this->response->send(); 248 | } 249 | 250 | /** 251 | * {@inheritdoc} 252 | */ 253 | public function disableResponse() 254 | { 255 | $this->disableResponse = true; 256 | } 257 | 258 | /** 259 | * {@inheritdoc} 260 | */ 261 | public function hasResponse() 262 | { 263 | return !$this->disableResponse; 264 | } 265 | 266 | /** 267 | * {@inheritdoc} 268 | */ 269 | public function is403() 270 | { 271 | if ($this->state < self::STATE_STATUS_DEFINED) { 272 | throw new InvalidStateMethodCallException; 273 | } 274 | 275 | return $this->status == MENU_ACCESS_DENIED; 276 | } 277 | 278 | /** 279 | * {@inheritdoc} 280 | */ 281 | public function is404() 282 | { 283 | if ($this->state < self::STATE_STATUS_DEFINED) { 284 | throw new InvalidStateMethodCallException; 285 | } 286 | 287 | return $this->status == MENU_NOT_FOUND; 288 | } 289 | 290 | /** 291 | * {@inheritdoc} 292 | */ 293 | public function isOffline() 294 | { 295 | if ($this->state < self::STATE_STATUS_DEFINED) { 296 | throw new InvalidStateMethodCallException; 297 | } 298 | 299 | return $this->status == MENU_SITE_OFFLINE; 300 | } 301 | 302 | /** 303 | * {@inheritdoc} 304 | */ 305 | public function isOnline() 306 | { 307 | if ($this->state < self::STATE_STATUS_DEFINED) { 308 | throw new InvalidStateMethodCallException; 309 | } 310 | 311 | return $this->status != MENU_SITE_OFFLINE; 312 | } 313 | 314 | /** 315 | * {@inheritdoc} 316 | */ 317 | public function isFound() 318 | { 319 | if ($this->state < self::STATE_STATUS_DEFINED) { 320 | throw new InvalidStateMethodCallException; 321 | } 322 | 323 | return $this->status == MENU_FOUND; 324 | } 325 | 326 | /** 327 | * {@inheritdoc} 328 | */ 329 | public function isInstalled() 330 | { 331 | if (!$this->response->isRedirect()) { 332 | return true; 333 | } 334 | 335 | if (stripos($this->response->headers->get('Location'), 'install.php')) { 336 | return false; 337 | } 338 | 339 | return true; 340 | } 341 | 342 | /** 343 | * {@inheritdoc} 344 | */ 345 | public function defineState(Request $request) 346 | { 347 | $this->initialize(); 348 | 349 | // Check if site is offline. 350 | $this->status = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; 351 | 352 | // Allow other modules to change the site status but not the path because that 353 | // would not change the global variable. hook_url_inbound_alter() can be used 354 | // to change the path. Code later will not use the $read_only_path variable. 355 | $read_only_path = $_GET['q']; 356 | $path = null; 357 | 358 | drupal_alter('menu_site_status', $this->status, $read_only_path); 359 | 360 | $this->state = self::STATE_STATUS_DEFINED; 361 | 362 | $this->pageResultCallback = $this->status; 363 | 364 | // Only continue if the site status is not set. 365 | if (!$this->isOnline()) { 366 | return; 367 | } 368 | 369 | if (!($this->routerItem = menu_get_item($path))) { 370 | $this->state = self::STATE_INNER_CONTENT; 371 | $this->pageResultCallback = MENU_NOT_FOUND; 372 | $this->status = MENU_NOT_FOUND; 373 | 374 | return; 375 | } 376 | 377 | if (!$this->routerItem['access']) { 378 | $this->state = self::STATE_INNER_CONTENT; 379 | $this->status = MENU_ACCESS_DENIED; 380 | $this->pageResultCallback = MENU_ACCESS_DENIED; 381 | 382 | return; 383 | } 384 | 385 | $this->status = MENU_FOUND; 386 | } 387 | 388 | /** 389 | * {@inheritdoc} 390 | */ 391 | public function render() 392 | { 393 | if ($this->state < self::STATE_INNER_CONTENT) { 394 | throw new InvalidStateMethodCallException; 395 | } 396 | 397 | if ($this->state == self::STATE_RESPONSE) { 398 | return; 399 | } 400 | 401 | // Deliver the result of the page callback to the browser, or if requested, 402 | // return it raw, so calling code can do more processing. 403 | $content = $this->encapsulate(function(DrupalInterface $drupal) { 404 | $routerItem = $drupal->getRouterItem(); 405 | $defaultDeliveryCallback = $routerItem ? $routerItem['delivery_callback'] : NULL; 406 | 407 | $pageResultCallback = $drupal->getPageResultCallback(); 408 | 409 | drupal_deliver_page($pageResultCallback, $defaultDeliveryCallback); 410 | $drupal->setPageResultCallback($pageResultCallback); 411 | }, $this); 412 | 413 | $this->response->setContent($content); 414 | 415 | $statusCode = http_response_code(); 416 | 417 | if ($statusCode !== false) { 418 | $this->response->setStatusCode($statusCode); 419 | } 420 | 421 | $this->state = self::STATE_RESPONSE; 422 | } 423 | 424 | /** 425 | * {@inheritdoc} 426 | */ 427 | public function buildContent() 428 | { 429 | if ($this->state > self::STATE_INNER_CONTENT) { 430 | throw new InvalidStateMethodCallException; 431 | } 432 | 433 | if ($this->routerItem['include_file']) { 434 | require_once $this->root . '/' . $this->routerItem['include_file']; 435 | } 436 | 437 | $this->pageResultCallback = call_user_func_array($this->routerItem['page_callback'], $this->routerItem['page_arguments']); 438 | 439 | $this->state = self::STATE_INNER_CONTENT; 440 | } 441 | 442 | /** 443 | * {@inheritdoc} 444 | */ 445 | public function getResponse() 446 | { 447 | return $this->response; 448 | } 449 | 450 | /** 451 | * {@inheritdoc} 452 | */ 453 | public function setPageResultCallback($pageResultCallback) 454 | { 455 | $this->pageResultCallback = $pageResultCallback; 456 | } 457 | 458 | /** 459 | * {@inheritdoc} 460 | */ 461 | public function getPageResultCallback() 462 | { 463 | return $this->pageResultCallback; 464 | } 465 | 466 | /** 467 | * {@inheritdoc} 468 | */ 469 | public function setRouterItem($routerItem) 470 | { 471 | $this->routerItem = $routerItem; 472 | } 473 | 474 | /** 475 | * {@inheritdoc} 476 | */ 477 | public function getRouterItem() 478 | { 479 | return $this->routerItem; 480 | } 481 | 482 | /** 483 | * {@inheritdoc} 484 | */ 485 | public function getEntityInfo($entityType = null) 486 | { 487 | $this->initialize(); 488 | 489 | return entity_get_info($entityType); 490 | } 491 | 492 | /** 493 | * {@inheritdoc} 494 | */ 495 | public function getEntityController($entityType) 496 | { 497 | $this->initialize(); 498 | 499 | return entity_get_controller($entityType); 500 | } 501 | 502 | /** 503 | * This method executes code related to the Drupal code, and builds a correct response if required 504 | * 505 | * @return string 506 | */ 507 | protected function encapsulate() 508 | { 509 | $this->encapsulated = true; 510 | $args = func_get_args(); 511 | $function = array_shift($args); 512 | 513 | $content = ''; 514 | 515 | ob_start(function($buffer) use (&$content) { 516 | $content .= $buffer; 517 | 518 | return ''; 519 | }); 520 | 521 | try { 522 | call_user_func_array($function, $args); 523 | } catch (\Exception $e) { 524 | // @todo: log error message 525 | } 526 | 527 | ob_end_flush(); 528 | 529 | $headers = $this->cleanHeaders(); 530 | 531 | foreach ($headers as $name => $value) { 532 | $this->response->headers->set($name, $value); 533 | } 534 | 535 | $this->encapsulated = false; 536 | 537 | return $content; 538 | } 539 | 540 | /** 541 | * @return array 542 | */ 543 | protected function cleanHeaders() 544 | { 545 | $headers = array(); 546 | 547 | foreach (headers_list() as $header) { 548 | list($name, $value) = explode(':', $header, 2); 549 | $headers[$name] = trim($value); 550 | 551 | header_remove($name); 552 | } 553 | 554 | return $headers; 555 | } 556 | 557 | /** 558 | * Restores the buffer level by the given one 559 | * 560 | * @param integer $level 561 | */ 562 | protected function restoreBufferLevel($level) 563 | { 564 | if (!is_numeric($level)) { 565 | return; 566 | } 567 | 568 | while (ob_get_level() > $level) { 569 | ob_end_flush(); 570 | } 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /Entity/FosUser.php: -------------------------------------------------------------------------------- 1 | salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36); 127 | $this->generateConfirmationToken(); 128 | $this->enabled = false; 129 | $this->locked = false; 130 | $this->expired = false; 131 | $this->roles = array(); 132 | $this->credentialsExpired = false; 133 | } 134 | 135 | /** 136 | * Adds a role to the user. 137 | * 138 | * @param string $role 139 | */ 140 | public function addRole($role) 141 | { 142 | $role = strtoupper($role); 143 | if ($role === static::ROLE_DEFAULT) { 144 | return; 145 | } 146 | 147 | if (!in_array($role, $this->roles, true)) { 148 | $this->roles[] = $role; 149 | } 150 | } 151 | 152 | /** 153 | * Implementation of SecurityUserInterface. 154 | * 155 | * @param \Symfony\Component\Security\Core\User\UserInterface $user 156 | * @return Boolean 157 | */ 158 | public function equals(SecurityUserInterface $user) 159 | { 160 | if (!$user instanceof User) { 161 | return false; 162 | } 163 | 164 | if ($this->password !== $user->getPassword()) { 165 | return false; 166 | } 167 | if ($this->getSalt() !== $user->getSalt()) { 168 | return false; 169 | } 170 | if ($this->usernameCanonical !== $user->getUsernameCanonical()) { 171 | return false; 172 | } 173 | if ($this->isAccountNonExpired() !== $user->isAccountNonExpired()) { 174 | return false; 175 | } 176 | if (!$this->locked !== $user->isAccountNonLocked()) { 177 | return false; 178 | } 179 | if ($this->isCredentialsNonExpired() !== $user->isCredentialsNonExpired()) { 180 | return false; 181 | } 182 | if ($this->enabled !== $user->isEnabled()) { 183 | return false; 184 | } 185 | 186 | return true; 187 | } 188 | 189 | /** 190 | * Serializes the user. 191 | * 192 | * The serialized data have to contain the fields used by the equals method and the username. 193 | * 194 | * @return string 195 | */ 196 | public function serialize() 197 | { 198 | return serialize(array( 199 | $this->password, 200 | $this->salt, 201 | $this->usernameCanonical, 202 | $this->username, 203 | $this->expired, 204 | $this->locked, 205 | $this->credentialsExpired, 206 | $this->enabled, 207 | )); 208 | } 209 | 210 | /** 211 | * Unserializes the user. 212 | * 213 | * @param string $serialized 214 | */ 215 | public function unserialize($serialized) 216 | { 217 | list( 218 | $this->password, 219 | $this->salt, 220 | $this->usernameCanonical, 221 | $this->username, 222 | $this->expired, 223 | $this->locked, 224 | $this->credentialsExpired, 225 | $this->enabled 226 | ) = unserialize($serialized); 227 | } 228 | 229 | /** 230 | * Removes sensitive data from the user. 231 | * 232 | * Implements SecurityUserInterface 233 | */ 234 | public function eraseCredentials() 235 | { 236 | $this->plainPassword = null; 237 | } 238 | 239 | /** 240 | * @return string 241 | */ 242 | public function getUsername() 243 | { 244 | return $this->username; 245 | } 246 | 247 | /** 248 | * Gets the canonical username in search and sort queries. 249 | * 250 | * @return string 251 | */ 252 | public function getUsernameCanonical() 253 | { 254 | return $this->usernameCanonical; 255 | } 256 | 257 | /** 258 | * Implementation of SecurityUserInterface 259 | * 260 | * @return string 261 | */ 262 | public function getSalt() 263 | { 264 | return $this->salt; 265 | } 266 | 267 | /** 268 | * Gets the algorithm used to encode the password. 269 | * 270 | * @return string 271 | */ 272 | public function getAlgorithm() 273 | { 274 | return $this->algorithm; 275 | } 276 | 277 | /** 278 | * Gets email. 279 | * 280 | * @return string 281 | */ 282 | public function getEmail() 283 | { 284 | return $this->email; 285 | } 286 | 287 | /** 288 | * Gets the canonical email in search and sort queries. 289 | * 290 | * @return string 291 | */ 292 | public function getEmailCanonical() 293 | { 294 | return $this->emailCanonical; 295 | } 296 | 297 | /** 298 | * Gets the encrypted password. 299 | * 300 | * Implements SecurityUserInterface 301 | * @return string 302 | */ 303 | public function getPassword() 304 | { 305 | return $this->password; 306 | } 307 | 308 | /** 309 | * Gets the plain password. 310 | * 311 | * @return string 312 | */ 313 | public function getPlainPassword() 314 | { 315 | return $this->plainPassword; 316 | } 317 | 318 | /** 319 | * Gets the last login time. 320 | * 321 | * @return \DateTime 322 | */ 323 | public function getLastLogin() 324 | { 325 | return $this->lastLogin; 326 | } 327 | 328 | /** 329 | * Gets the confirmation token. 330 | * 331 | * @return string 332 | */ 333 | public function getConfirmationToken() 334 | { 335 | return $this->confirmationToken; 336 | } 337 | 338 | /** 339 | * Returns the user roles 340 | * 341 | * Implements SecurityUserInterface 342 | * 343 | * @return array The roles 344 | */ 345 | public function getRoles() 346 | { 347 | $roles = $this->roles; 348 | 349 | // foreach ($this->getGroups() as $group) { 350 | // $roles = array_merge($roles, $group->getRoles()); 351 | // } 352 | 353 | // we need to make sure to have at least one role 354 | $roles[] = static::ROLE_DEFAULT; 355 | 356 | return array_unique($roles); 357 | } 358 | 359 | /** 360 | * Never use this to check if this user has access to anything! 361 | * 362 | * Use the SecurityContext, or an implementation of AccessDecisionManager 363 | * instead, e.g. 364 | * 365 | * $securityContext->isGranted('ROLE_USER'); 366 | * 367 | * @param string $role 368 | * @return Boolean 369 | */ 370 | public function hasRole($role) 371 | { 372 | return in_array(strtoupper($role), $this->getRoles(), true); 373 | } 374 | 375 | /** 376 | * Checks whether the user's account has expired. 377 | * 378 | * Implements AdvancedUserInterface 379 | * 380 | * @return Boolean true if the user's account is non expired, false otherwise 381 | */ 382 | public function isAccountNonExpired() 383 | { 384 | if (true === $this->expired) { 385 | return false; 386 | } 387 | 388 | if (null !== $this->expiresAt && $this->expiresAt->getTimestamp() < time()) { 389 | return false; 390 | } 391 | 392 | return true; 393 | } 394 | 395 | /** 396 | * Checks whether the user is locked. 397 | * 398 | * Implements AdvancedUserInterface 399 | * 400 | * @return Boolean true if the user is not locked, false otherwise 401 | */ 402 | public function isAccountNonLocked() 403 | { 404 | return !$this->locked; 405 | } 406 | 407 | /** 408 | * Checks whether the user's credentials (password) has expired. 409 | * 410 | * Implements AdvancedUserInterface 411 | * 412 | * @return Boolean true if the user's credentials are non expired, false otherwise 413 | */ 414 | public function isCredentialsNonExpired() 415 | { 416 | if (true === $this->credentialsExpired) { 417 | return false; 418 | } 419 | 420 | if (null !== $this->credentialsExpireAt && $this->credentialsExpireAt->getTimestamp() < time()) { 421 | return false; 422 | } 423 | 424 | return true; 425 | } 426 | 427 | public function isCredentialsExpired() 428 | { 429 | return !$this->isCredentialsNonExpired(); 430 | } 431 | 432 | /** 433 | * Checks whether the user is enabled. 434 | * 435 | * Implements AdvancedUserInterface 436 | * 437 | * @return Boolean true if the user is enabled, false otherwise 438 | */ 439 | public function isEnabled() 440 | { 441 | return $this->enabled; 442 | } 443 | 444 | public function isExpired() 445 | { 446 | return !$this->isAccountNonExpired(); 447 | } 448 | 449 | public function isLocked() 450 | { 451 | return $this->locked; 452 | } 453 | 454 | /** 455 | * Tells if the the given user has the super admin role. 456 | * 457 | * @return Boolean 458 | */ 459 | public function isSuperAdmin() 460 | { 461 | return $this->hasRole(static::ROLE_SUPER_ADMIN); 462 | } 463 | 464 | /** 465 | * Tells if the the given user is this user. 466 | * 467 | * Useful when not hydrating all fields. 468 | * 469 | * @param UserInterface $user 470 | * @return Boolean 471 | */ 472 | public function isUser(FOSUserInterface $user = null) 473 | { 474 | return null !== $user && $this->getId() === $user->getId(); 475 | } 476 | 477 | /** 478 | * Removes a role to the user. 479 | * 480 | * @param string $role 481 | */ 482 | public function removeRole($role) 483 | { 484 | if (false !== $key = array_search(strtoupper($role), $this->roles, true)) { 485 | unset($this->roles[$key]); 486 | $this->roles = array_values($this->roles); 487 | } 488 | } 489 | 490 | /** 491 | * Sets the username. 492 | * 493 | * @param string $username 494 | */ 495 | public function setUsername($username) 496 | { 497 | $this->username = $username; 498 | } 499 | 500 | /** 501 | * Sets the canonical username. 502 | * 503 | * @param string $usernameCanonical 504 | */ 505 | public function setUsernameCanonical($usernameCanonical) 506 | { 507 | $this->usernameCanonical = $usernameCanonical; 508 | } 509 | 510 | /** 511 | * Sets the algorithm 512 | * 513 | * @param string $algorithm 514 | */ 515 | public function setAlgorithm($algorithm) 516 | { 517 | $this->algorithm = $algorithm; 518 | } 519 | 520 | public function setCredentialsExpireAt(\DateTime $date) 521 | { 522 | $this->credentialsExpireAt = $date; 523 | } 524 | 525 | public function setCredentialsExpired($boolean) 526 | { 527 | $this->credentialsExpired = $boolean; 528 | } 529 | 530 | /** 531 | * Sets the email. 532 | * 533 | * @param string $email 534 | */ 535 | public function setEmail($email) 536 | { 537 | $this->email = $email; 538 | } 539 | 540 | /** 541 | * Set the canonical email. 542 | * 543 | * @param string $emailCanonical 544 | */ 545 | public function setEmailCanonical($emailCanonical) 546 | { 547 | $this->emailCanonical = $emailCanonical; 548 | } 549 | 550 | /** 551 | * @param Boolean $boolean 552 | */ 553 | public function setEnabled($boolean) 554 | { 555 | $this->enabled = (Boolean) $boolean; 556 | } 557 | 558 | /** 559 | * Sets this user to expired. 560 | * 561 | * @param Boolean $boolean 562 | */ 563 | public function setExpired($boolean) 564 | { 565 | $this->expired = (Boolean) $boolean; 566 | } 567 | 568 | public function setExpiresAt(\DateTime $date) 569 | { 570 | $this->expiresAt = $date; 571 | } 572 | 573 | /** 574 | * Sets the hashed password. 575 | * 576 | * @param string $password 577 | */ 578 | public function setPassword($password) 579 | { 580 | $this->password = $password; 581 | } 582 | 583 | /** 584 | * Sets the super admin status 585 | * 586 | * @param Boolean $boolean 587 | */ 588 | public function setSuperAdmin($boolean) 589 | { 590 | if (true === $boolean) { 591 | $this->addRole(static::ROLE_SUPER_ADMIN); 592 | } else { 593 | $this->removeRole(static::ROLE_SUPER_ADMIN); 594 | } 595 | } 596 | 597 | /** 598 | * Sets the plain password. 599 | * 600 | * @param string $password 601 | */ 602 | public function setPlainPassword($password) 603 | { 604 | $this->plainPassword = $password; 605 | } 606 | 607 | /** 608 | * Sets the last login time 609 | * 610 | * @param \DateTime|null $time 611 | */ 612 | public function setLastLogin(\DateTime $time = null) 613 | { 614 | $this->lastLogin = $time; 615 | } 616 | 617 | /** 618 | * Sets the locking status of the user. 619 | * 620 | * @param Boolean $boolean 621 | */ 622 | public function setLocked($boolean) 623 | { 624 | $this->locked = $boolean; 625 | } 626 | 627 | /** 628 | * Sets the confirmation token 629 | * 630 | * @param string $confirmationToken 631 | */ 632 | public function setConfirmationToken($confirmationToken) 633 | { 634 | $this->confirmationToken = $confirmationToken; 635 | } 636 | 637 | /** 638 | * Sets the timestamp that the user requested a password reset. 639 | * 640 | * @param \DateTime $date 641 | */ 642 | public function setPasswordRequestedAt(\DateTime $date = null) 643 | { 644 | $this->passwordRequestedAt = $date; 645 | } 646 | 647 | /** 648 | * Gets the timestamp that the user requested a password reset. 649 | * 650 | * @return \DateTime 651 | */ 652 | public function getPasswordRequestedAt() 653 | { 654 | return $this->passwordRequestedAt; 655 | } 656 | 657 | /** 658 | * Checks whether the password reset request has expired. 659 | * 660 | * @param integer $ttl Requests older than this many seconds will be considered expired 661 | * @return Boolean true if the user's password request is non expired, false otherwise 662 | */ 663 | public function isPasswordRequestNonExpired($ttl) 664 | { 665 | return $this->passwordRequestedAt instanceof \DateTime && 666 | $this->passwordRequestedAt->getTimestamp() + $ttl > time(); 667 | } 668 | 669 | /** 670 | * Generates the confirmation token if it is not set. 671 | */ 672 | public function generateConfirmationToken() 673 | { 674 | if (null === $this->confirmationToken) { 675 | $this->confirmationToken = $this->generateToken(); 676 | } 677 | } 678 | 679 | /** 680 | * Sets the roles of the user. 681 | * 682 | * This overwrites any previous roles. 683 | * 684 | * @param array $roles 685 | */ 686 | public function setRoles(array $roles) 687 | { 688 | $this->roles = array(); 689 | 690 | foreach ($roles as $role) { 691 | $this->addRole($role); 692 | } 693 | } 694 | 695 | public function __toString() 696 | { 697 | return (string) $this->getUsername(); 698 | } 699 | 700 | /** 701 | * Generates a token. 702 | * 703 | * @return string 704 | */ 705 | protected function generateToken() 706 | { 707 | $bytes = false; 708 | if (function_exists('openssl_random_pseudo_bytes') && 0 !== stripos(PHP_OS, 'win')) { 709 | $bytes = openssl_random_pseudo_bytes(32, $strong); 710 | 711 | if (true !== $strong) { 712 | $bytes = false; 713 | } 714 | } 715 | 716 | // let's just hope we got a good seed 717 | if (false === $bytes) { 718 | $bytes = hash('sha256', uniqid(mt_rand(), true), true); 719 | } 720 | 721 | return base_convert(bin2hex($bytes), 16, 36); 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------