commit b29997cf8d17312ed37eb59112093d24fc766b43
Author: Bart Feenstra <bart@mynameisbart.com>
Date:   Tue Apr 29 15:50:40 2014 +0200

    foo

diff --git a/payment/payment.local_tasks.yml b/payment/payment.local_tasks.yml
index 21c5417..38b1f4c 100644
--- a/payment/payment.local_tasks.yml
+++ b/payment/payment.local_tasks.yml
@@ -8,6 +8,11 @@ payment.payment.edit:
   route_name: payment.payment.edit
   title: 'Edit'
 
+payment.payment.update_status:
+  base_route: payment.payment.view
+  route_name: payment.payment.update_status
+  title: 'Update status'
+
 payment.payment_method.list:
   base_route: payment.payment_method.list
   route_name: payment.payment_method.list
diff --git a/payment/payment.routing.yml b/payment/payment.routing.yml
index 3a30536..3a2f5fb 100644
--- a/payment/payment.routing.yml
+++ b/payment/payment.routing.yml
@@ -30,6 +30,16 @@ payment.payment.edit:
   options:
     _admin_route: TRUE
 
+payment.payment.update_status:
+  path: '/payment/{payment}/update-status'
+  defaults:
+    _entity_form: 'payment.update_status'
+    _title: Update payment status
+  requirements:
+    _entity_access: 'payment.update_status'
+  options:
+    _admin_route: TRUE
+
 payment.payment.delete:
   path: '/payment/{payment}/delete'
   defaults:
diff --git a/payment/src/Entity/Payment.php b/payment/src/Entity/Payment.php
index a69c886..9c09c8a 100644
--- a/payment/src/Entity/Payment.php
+++ b/payment/src/Entity/Payment.php
@@ -30,7 +30,8 @@ use Drupal\user\UserInterface;
  *     "access" = "Drupal\payment\Entity\Payment\PaymentAccess",
  *     "form" = {
  *       "delete" = "Drupal\payment\Entity\Payment\PaymentDeleteForm",
- *       "edit" = "Drupal\payment\Entity\Payment\PaymentEditForm"
+ *       "edit" = "Drupal\payment\Entity\Payment\PaymentEditForm",
+ *       "update_status" = "Drupal\payment\Entity\Payment\PaymentStatusForm"
  *     },
  *     "list_builder" = "Drupal\payment\Entity\Payment\PaymentListBuilder",
  *     "view_builder" = "Drupal\payment\Entity\Payment\PaymentViewBuilder",
@@ -48,7 +49,8 @@ use Drupal\user\UserInterface;
  *     "admin-form" = "payment.payment_type",
  *     "canonical" = "payment.payment.view",
  *     "edit-form" = "payment.payment.edit",
- *     "delete-form" = "payment.payment.delete"
+ *     "delete-form" = "payment.payment.delete",
+ *     "update-status-form" = "payment.payment.update_status"
  *   }
  * )
  */
diff --git a/payment/src/Entity/Payment/PaymentListBuilder.php b/payment/src/Entity/Payment/PaymentListBuilder.php
index bab734a..66c09d2 100644
--- a/payment/src/Entity/Payment/PaymentListBuilder.php
+++ b/payment/src/Entity/Payment/PaymentListBuilder.php
@@ -68,15 +68,38 @@ class PaymentListBuilder extends EntityListBuilder {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
 
     if ($entity->access('view')) {
       $operations['view'] = array(
         'title' => $this->t('View'),
       ) + $entity->urlInfo()->toArray();
     }
+    if ($entity->access('update_status')) {
+      $operations['update_status'] = array(
+        'title' => $this->t('Update status'),
+        'attributes' => array(
+          'class' => array('use-ajax'),
+          'data-accepts' => 'application/vnd.drupal-modal',
+        ),
+        'query' => drupal_get_destination(),
+      ) + $entity->urlInfo('update-status-form')->toArray();
+    }
 
     return $operations;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOperations(EntityInterface $entity) {
+    $build = parent::buildOperations($entity);
+    // @todo Remove this when https://drupal.org/node/2253257 is fixed.
+    $build['#attached'] = array(
+      'library' => array('core/drupal.ajax'),
+    );
+
+    return $build;
+  }
 }
diff --git a/payment/src/Entity/Payment/PaymentStatusForm.php b/payment/src/Entity/Payment/PaymentStatusForm.php
new file mode 100644
index 0000000..40584ba
--- /dev/null
+++ b/payment/src/Entity/Payment/PaymentStatusForm.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Entity\Payment\PaymentStatusForm.
+ */
+
+namespace Drupal\payment\Entity\Payment;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides the payment edit form.
+ */
+class PaymentStatusForm extends EntityForm {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The default datetime.
+   *
+   * @var \Drupal\Core\Datetime\DrupalDateTime
+   */
+  protected $defaultDateTime;
+
+  /**
+   * The payment status plugin manager.
+   *
+   * @var \Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface
+   */
+  protected $paymentStatusManager;
+
+  /**
+   * Constructs a new class instance.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Session\AccountInterface
+   *   The current user.
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The URL generator.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
+   *    The translation manager.
+   * @param \Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface $payment_status_manager
+   *   The payment status plugin manager.
+   * @param \Drupal\Core\Datetime\DrupalDateTime $default_datetime
+   *   The default datetime of the new status.
+   */
+  function __construct(ModuleHandlerInterface $module_handler, AccountInterface $current_user, UrlGeneratorInterface $url_generator, TranslationInterface $translation_manager, PaymentStatusManagerInterface $payment_status_manager, DrupalDateTime $default_datetime) {
+    $this->currentUser = $current_user;
+    $this->defaultDateTime = $default_datetime;
+    $this->moduleHandler = $module_handler;
+    $this->paymentStatusManager = $payment_status_manager;
+    $this->translationManager = $translation_manager;
+    $this->urlGenerator = $url_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('module_handler'), $container->get('current_user'), $container->get('url_generator'), $container->get('string_translation'), $container->get('plugin.manager.payment.status'), new DrupalDateTime());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+    $form['plugin_id'] = array(
+      '#options' => $this->paymentStatusManager->options(),
+      '#required' => TRUE,
+      '#title' => $this->t('New status'),
+      '#type' => 'select',
+    );
+    if ($this->moduleHandler->moduleExists('datetime')) {
+      $form['created'] = array(
+        '#default_value' => $this->defaultDateTime,
+        '#required' => TRUE,
+        '#title' => $this->t('Date and time'),
+        '#type' => 'datetime',
+      );
+    }
+    else {
+      $form['created'] = array(
+        '#default_value' => $this->defaultDateTime,
+        '#type' => 'value',
+      );
+      if ($this->currentUser->hasPermission('administer modules')) {
+        $form['created_message'] = array(
+          '#type' => 'markup',
+          '#markup' => $this->t('Enable the <a href="@url">Datetime</a> module to set the date and time of the new payment status.', array(
+              '@url' => $this->urlGenerator->generateFromRoute('system.modules_list', array(), array(
+                  'fragment' => 'module-datetime',
+                ))
+            )),
+        );
+      }
+    }
+
+    return parent::form($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions = array($actions['submit']);
+
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    $payment_status = $this->paymentStatusManager->createInstance($form_state['values']['plugin_id']);
+    /** @var \Drupal\Core\Datetime\DrupalDateTime $created */
+    $created = $form_state['values']['created'];
+    $payment_status->setCreated($created->getTimestamp());
+
+    /** @var \Drupal\payment\Entity\PaymentInterface $payment */
+    $payment = $this->getEntity();
+    $payment->setStatus($payment_status);
+    $payment->save();
+
+    $form_state['redirect_route'] = array(
+      'route_name' => 'payment.payment.view',
+      'route_parameters' => array(
+        'payment' => $payment->id(),
+      ),
+    );
+  }
+}
diff --git a/payment/src/Hook/Permission.php b/payment/src/Hook/Permission.php
index 3f895fe..6a1ac0a 100644
--- a/payment/src/Hook/Permission.php
+++ b/payment/src/Hook/Permission.php
@@ -59,6 +59,12 @@ class Permission {
       'payment.payment.update.own' => array(
         'title' => $this->t('Update own payments'),
       ),
+      'payment.payment.update_status.any' => array(
+        'title' => $this->t("Update any payment's status"),
+      ),
+      'payment.payment.update_status.own' => array(
+        'title' => $this->t("Update own payments' statuses"),
+      ),
       'payment.payment.delete.any' => array(
         'title' => $this->t('Delete any payment'),
       ),
diff --git a/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php b/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php
new file mode 100644
index 0000000..42071f9
--- /dev/null
+++ b/payment/tests/src/Entity/Payment/PaymentStatusFormUnitTest.php
@@ -0,0 +1,261 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\payment\Tests\Entity\Payment\PaymentStatusFormUnitTest.
+ */
+
+namespace Drupal\payment\Tests\Entity\Payment;
+
+  use Drupal\Core\Language\Language;
+  use Drupal\payment\Entity\Payment\PaymentStatusForm;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\payment\Entity\Payment\PaymentStatusForm
+ */
+class PaymentStatusFormUnitTest extends UnitTestCase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * The default datetime.
+   *
+   * @var \Drupal\Core\Datetime\DrupalDateTime|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $defaultDateTime;
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\payment\Entity\Payment\PaymentStatusForm
+   */
+  protected $form;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The payment.
+   *
+   * @var \Drupal\payment\Entity\Payment|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $payment;
+
+  /**
+   * The payment status plugin manager.
+   *
+   * @var \Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $paymentStatusManager;
+
+  /**
+   * The translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translationManager;
+
+  /**
+   * The URL generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $urlGenerator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'Payment',
+      'name' => '\Drupal\payment\Entity\Payment\PaymentStatusForm unit test',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  public function setUp() {
+    $this->currentUser = $this->getMock('\Drupal\Core\Session\AccountInterface');
+
+    $this->defaultDateTime = $this->getMockBuilder('\Drupal\Core\Datetime\DrupalDateTime')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->moduleHandler = $this->getmock('\Drupal\Core\Extension\ModuleHandlerInterface');
+
+    $this->paymentStatusManager = $this->getmock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusManagerInterface');
+
+    $this->urlGenerator = $this->getmock('\Drupal\Core\Routing\UrlGeneratorInterface');
+
+    $this->translationManager = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface');
+
+    $this->payment = $this->getMockBuilder('\Drupal\payment\Entity\Payment')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->form = new PaymentStatusForm($this->moduleHandler, $this->currentUser, $this->urlGenerator, $this->translationManager, $this->paymentStatusManager, $this->defaultDateTime);
+    $this->form->setEntity($this->payment);
+  }
+
+  /**
+   * @covers ::form
+   */
+  public function testFormWithDateTimeModule() {
+    $this->paymentStatusManager->expects($this->once())
+      ->method('options');
+
+    $this->moduleHandler->expects($this->once())
+      ->method('moduleExists')
+      ->with('datetime')
+      ->will($this->returnValue(TRUE));
+
+    $language = new Language();
+
+    $this->payment->expects($this->any())
+      ->method('language')
+      ->will($this->returnValue($language));
+
+    $form = array();
+    $form_state = array();
+    $form = $this->form->form($form, $form_state);
+    $this->assertInternalType('array', $form);
+    $this->assertArrayHasKey('plugin_id', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('select', $form['plugin_id']['#type']);
+    $this->assertArrayHasKey('created', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('datetime', $form['created']['#type']);
+  }
+
+  /**
+   * @covers ::form
+   */
+  public function testFormWithoutDateTimeModuleWithoutPermission() {
+    $this->paymentStatusManager->expects($this->once())
+      ->method('options');
+
+    $this->moduleHandler->expects($this->once())
+      ->method('moduleExists')
+      ->with('datetime')
+      ->will($this->returnValue(FALSE));
+
+    $this->currentUser->expects($this->once())
+      ->method('hasPermission')
+      ->with('administer modules')
+      ->will($this->returnValue(FALSE));
+
+    $this->urlGenerator->expects($this->never())
+      ->method('generateFromRoute');
+
+    $language = new Language();
+
+    $this->payment->expects($this->any())
+      ->method('language')
+      ->will($this->returnValue($language));
+
+    $form = array();
+    $form_state = array();
+    $form = $this->form->form($form, $form_state);
+    $this->assertInternalType('array', $form);
+    $this->assertArrayHasKey('plugin_id', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('select', $form['plugin_id']['#type']);
+    $this->assertArrayHasKey('created', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('value', $form['created']['#type']);
+  }
+
+  /**
+   * @covers ::form
+   */
+  public function testFormWithoutDateTimeModuleWithPermission() {
+    $this->paymentStatusManager->expects($this->once())
+      ->method('options');
+
+    $this->moduleHandler->expects($this->once())
+      ->method('moduleExists')
+      ->with('datetime')
+      ->will($this->returnValue(FALSE));
+
+    $this->currentUser->expects($this->once())
+      ->method('hasPermission')
+      ->with('administer modules')
+      ->will($this->returnValue(TRUE));
+
+    $this->urlGenerator->expects($this->once())
+      ->method('generateFromRoute')
+      ->with('system.modules_list');
+
+    $language = new Language();
+
+    $this->payment->expects($this->any())
+      ->method('language')
+      ->will($this->returnValue($language));
+
+    $form = array();
+    $form_state = array();
+    $form = $this->form->form($form, $form_state);
+    $this->assertInternalType('array', $form);
+    $this->assertArrayHasKey('plugin_id', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('select', $form['plugin_id']['#type']);
+    $this->assertArrayHasKey('created', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('value', $form['created']['#type']);
+    $this->assertArrayHasKey('created_message', $form);
+    $this->assertInternalType('array', $form['plugin_id']);
+    $this->assertSame('markup', $form['created_message']['#type']);
+  }
+
+  /**
+   * @covers ::submit
+   */
+  public function testSubmit() {
+    $timestamp = $this->randomName();
+    $plugin_id = $this->randomName();
+
+    $payment_status = $this->getMock('\Drupal\payment\Plugin\Payment\Status\PaymentStatusInterface');
+    $payment_status->expects($this->once())
+      ->method('setCreated')
+      ->with($timestamp);
+
+    $this->defaultDateTime->expects($this->any())
+      ->method('getTimestamp')
+      ->will($this->returnValue($timestamp));
+
+    $this->paymentStatusManager->expects($this->once())
+      ->method('createInstance')
+      ->with($plugin_id)
+      ->will($this->returnValue($payment_status));
+
+    $this->payment->expects($this->once())
+      ->method('setStatus')
+      ->with($payment_status);
+    $this->payment->expects($this->once())
+      ->method('save');
+
+    $form = array();
+    $form_state = array(
+      'values' => array(
+        'created' => $this->defaultDateTime,
+        'plugin_id' => $plugin_id,
+      ),
+    );
+    $this->form->submit($form, $form_state);
+  }
+
+}
