Browse Source

First steps to D8.

- Moved files around for PSR0 layout
- Entity(NG) is now Plugin-based
- Entity API UI operations now replaced by (Form|Render|Translation)Controller.
- Info file now in YAML.
- Redone menu
Frederic G. MARAND 11 years ago
parent
commit
3770c059c6

+ 356 - 0
lib/Drupal/wing/CommentFormController.php

@@ -0,0 +1,356 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\comment\CommentFormController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormControllerNG;
+
+/**
+ * Base for controller for comment forms.
+ */
+class CommentFormController extends EntityFormControllerNG {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $comment) {
+    global $user;
+    $node = $comment->nid->entity;
+
+    // Use #comment-form as unique jump target, regardless of node type.
+    $form['#id'] = drupal_html_id('comment_form');
+    $form['#theme'] = array('comment_form__node_' . $node->type, 'comment_form');
+
+    $anonymous_contact = variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT);
+    $is_admin = $comment->id() && user_access('administer comments');
+
+    if (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
+      $form['#attached']['library'][] = array('system', 'jquery.cookie');
+      $form['#attributes']['class'][] = 'user-info-from-cookie';
+    }
+
+    // If not replying to a comment, use our dedicated page callback for new
+    // comments on nodes.
+    if (!$comment->id() && !$comment->pid->target_id) {
+      $form['#action'] = url('comment/reply/' . $comment->nid->target_id);
+    }
+
+    if (isset($form_state['comment_preview'])) {
+      $form += $form_state['comment_preview'];
+    }
+
+    $form['author'] = array(
+      '#weight' => 10,
+    );
+    // Display author information in a details element for comment moderators.
+    if ($is_admin) {
+      $form['author'] += array(
+        '#type' => 'details',
+        '#title' => t('Administration'),
+        '#collapsed' => TRUE,
+      );
+    }
+
+    // Prepare default values for form elements.
+    if ($is_admin) {
+      $author = $comment->name->value;
+      $status = (isset($comment->status->value) ? $comment->status->value : COMMENT_NOT_PUBLISHED);
+      $date = (!empty($comment->date) ? $comment->date : new DrupalDateTime($comment->created->value));
+    }
+    else {
+      if ($user->uid) {
+        $author = $user->name;
+      }
+      else {
+        $author = ($comment->name->value ? $comment->name->value : '');
+      }
+      $status = (user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED);
+      $date = '';
+    }
+
+    // Add the author name field depending on the current user.
+    $form['author']['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Your name'),
+      '#default_value' => $author,
+      '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
+      '#maxlength' => 60,
+      '#size' => 30,
+    );
+    if ($is_admin) {
+      $form['author']['name']['#title'] = t('Authored by');
+      $form['author']['name']['#description'] = t('Leave blank for %anonymous.', array('%anonymous' => config('user.settings')->get('anonymous')));
+      $form['author']['name']['#autocomplete_path'] = 'user/autocomplete';
+    }
+    elseif ($user->uid) {
+      $form['author']['name']['#type'] = 'item';
+      $form['author']['name']['#value'] = $form['author']['name']['#default_value'];
+      $form['author']['name']['#markup'] = theme('username', array('account' => $user));
+    }
+
+    // Add author e-mail and homepage fields depending on the current user.
+    $form['author']['mail'] = array(
+      '#type' => 'email',
+      '#title' => t('E-mail'),
+      '#default_value' => $comment->mail->value,
+      '#required' => (!$user->uid && $anonymous_contact == COMMENT_ANONYMOUS_MUST_CONTACT),
+      '#maxlength' => 64,
+      '#size' => 30,
+      '#description' => t('The content of this field is kept private and will not be shown publicly.'),
+      '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
+    );
+
+    $form['author']['homepage'] = array(
+      '#type' => 'url',
+      '#title' => t('Homepage'),
+      '#default_value' => $comment->homepage->value,
+      '#maxlength' => 255,
+      '#size' => 30,
+      '#access' => $is_admin || (!$user->uid && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT),
+    );
+
+    // Add administrative comment publishing options.
+    $form['author']['date'] = array(
+      '#type' => 'datetime',
+      '#title' => t('Authored on'),
+      '#default_value' => $date,
+      '#size' => 20,
+      '#access' => $is_admin,
+    );
+
+    $form['author']['status'] = array(
+      '#type' => 'radios',
+      '#title' => t('Status'),
+      '#default_value' => $status,
+      '#options' => array(
+        COMMENT_PUBLISHED => t('Published'),
+        COMMENT_NOT_PUBLISHED => t('Not published'),
+      ),
+      '#access' => $is_admin,
+    );
+
+    $form['subject'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Subject'),
+      '#maxlength' => 64,
+      '#default_value' => $comment->subject->value,
+      '#access' => variable_get('comment_subject_field_' . $node->type, 1) == 1,
+    );
+
+    // Used for conditional validation of author fields.
+    $form['is_anonymous'] = array(
+      '#type' => 'value',
+      '#value' => ($comment->id() ? !$comment->uid->target_id : !$user->uid),
+    );
+
+    // Make the comment inherit the current content language unless specifically
+    // set.
+    if ($comment->isNew()) {
+      $language_content = language(LANGUAGE_TYPE_CONTENT);
+      $comment->langcode->value = $language_content->langcode;
+    }
+
+    // Add internal comment properties.
+    foreach (array('cid', 'pid', 'nid', 'uid', 'node_type', 'langcode') as $key) {
+      $key_name = key($comment->$key->offsetGet(0)->getProperties());
+      $form[$key] = array('#type' => 'value', '#value' => $comment->$key->{$key_name});
+    }
+
+    return parent::form($form, $form_state, $comment);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::actions().
+   */
+  protected function actions(array $form, array &$form_state) {
+    $element = parent::actions($form, $form_state);
+    $comment = $this->getEntity($form_state);
+    $node = $comment->nid->entity;
+    $preview_mode = variable_get('comment_preview_' . $node->type, DRUPAL_OPTIONAL);
+
+    // No delete action on the comment form.
+    unset($element['delete']);
+
+    // Mark the submit action as the primary action, when it appears.
+    $element['submit']['#button_type'] = 'primary';
+
+    // Only show the save button if comment previews are optional or if we are
+    // already previewing the submission.
+    $element['submit']['#access'] = ($comment->id() && user_access('administer comments')) || $preview_mode != DRUPAL_REQUIRED || isset($form_state['comment_preview']);
+
+    $element['preview'] = array(
+      '#type' => 'submit',
+      '#value' => t('Preview'),
+      '#access' => $preview_mode != DRUPAL_DISABLED,
+      '#validate' => array(
+        array($this, 'validate'),
+      ),
+      '#submit' => array(
+        array($this, 'submit'),
+        array($this, 'preview'),
+      ),
+    );
+
+    return $element;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::validate().
+   */
+  public function validate(array $form, array &$form_state) {
+    parent::validate($form, $form_state);
+
+    if (!empty($form_state['values']['cid'])) {
+      // Verify the name in case it is being changed from being anonymous.
+      $account = user_load_by_name($form_state['values']['name']);
+      $form_state['values']['uid'] = $account ? $account->uid : 0;
+
+      $date = $form_state['values']['date'];
+      if ($date instanceOf DrupalDateTime && $date->hasErrors()) {
+        form_set_error('date', t('You have to specify a valid date.'));
+      }
+      if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
+        form_set_error('name', t('You have to specify a valid author.'));
+      }
+    }
+    elseif ($form_state['values']['is_anonymous']) {
+      // Validate anonymous comment author fields (if given). If the (original)
+      // author of this comment was an anonymous user, verify that no registered
+      // user with this name exists.
+      if ($form_state['values']['name']) {
+        $query = db_select('users', 'u');
+        $query->addField('u', 'uid', 'uid');
+        $taken = $query
+          ->condition('name', db_like($form_state['values']['name']), 'LIKE')
+          ->countQuery()
+          ->execute()
+          ->fetchField();
+        if ($taken) {
+          form_set_error('name', t('The name you used belongs to a registered user.'));
+        }
+      }
+    }
+  }
+
+  /**
+   * Overrides EntityFormController::buildEntity().
+   */
+  public function buildEntity(array $form, array &$form_state) {
+    $comment = parent::buildEntity($form, $form_state);
+    if (!empty($form_state['values']['date']) && $form_state['values']['date'] instanceOf DrupalDateTime) {
+      $comment->created->value = $form_state['values']['date']->getTimestamp();
+    }
+    else {
+      $comment->created->value = REQUEST_TIME;
+    }
+    $comment->changed->value = REQUEST_TIME;
+    return $comment;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function submit(array $form, array &$form_state) {
+    $comment = parent::submit($form, $form_state);
+
+    // If the comment was posted by a registered user, assign the author's ID.
+    // @todo Too fragile. Should be prepared and stored in comment_form()
+    // already.
+    if (!$comment->is_anonymous && !empty($comment->name->value) && ($account = user_load_by_name($comment->name->value))) {
+      $comment->uid->target_id = $account->uid;
+    }
+    // If the comment was posted by an anonymous user and no author name was
+    // required, use "Anonymous" by default.
+    if ($comment->is_anonymous && (!isset($comment->name->value) || $comment->name->value === '')) {
+      $comment->name->value = config('user.settings')->get('anonymous');
+    }
+
+    // Validate the comment's subject. If not specified, extract from comment
+    // body.
+    if (trim($comment->subject->value) == '') {
+      // The body may be in any format, so:
+      // 1) Filter it into HTML
+      // 2) Strip out all HTML tags
+      // 3) Convert entities back to plain-text.
+      $comment_text = $comment->comment_body->processed;
+      $comment->subject = truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE);
+      // Edge cases where the comment body is populated only by HTML tags will
+      // require a default subject.
+      if ($comment->subject->value == '') {
+        $comment->subject->value = t('(No subject)');
+      }
+    }
+
+    return $comment;
+  }
+
+  /**
+   * Form submission handler for the 'preview' action.
+   *
+   * @param $form
+   *   An associative array containing the structure of the form.
+   * @param $form_state
+   *   A reference to a keyed array containing the current state of the form.
+   */
+  public function preview(array $form, array &$form_state) {
+    $comment = $this->getEntity($form_state);
+    drupal_set_title(t('Preview comment'), PASS_THROUGH);
+    $form_state['comment_preview'] = comment_preview($comment);
+    $form_state['rebuild'] = TRUE;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $node = node_load($form_state['values']['nid']);
+    $comment = $this->getEntity($form_state);
+
+    if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {
+      // Save the anonymous user information to a cookie for reuse.
+      if (user_is_anonymous()) {
+        user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage'))));
+      }
+
+      comment_save($comment);
+      $form_state['values']['cid'] = $comment->id();
+
+      // Add an entry to the watchdog log.
+      watchdog('content', 'Comment posted: %subject.', array('%subject' => $comment->subject->value), WATCHDOG_NOTICE, l(t('view'), 'comment/' . $comment->id(), array('fragment' => 'comment-' . $comment->id())));
+
+      // Explain the approval queue if necessary.
+      if ($comment->status->value == COMMENT_NOT_PUBLISHED) {
+        if (!user_access('administer comments')) {
+          drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
+        }
+      }
+      else {
+        drupal_set_message(t('Your comment has been posted.'));
+      }
+      $query = array();
+      // Find the current display page for this comment.
+      $page = comment_get_display_page($comment->id(), $node->type);
+      if ($page > 0) {
+        $query['page'] = $page;
+      }
+      // Redirect to the newly posted comment.
+      $redirect = array('node/' . $node->nid, array('query' => $query, 'fragment' => 'comment-' . $comment->id()));
+    }
+    else {
+      watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject->value), WATCHDOG_WARNING);
+      drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject->value)), 'error');
+      // Redirect the user to the node they are commenting on.
+      $redirect = 'node/' . $node->nid;
+    }
+    $form_state['redirect'] = $redirect;
+    // Clear the block and page caches so that anonymous users see the comment
+    // they have posted.
+    cache_invalidate_tags(array('content' => TRUE));
+  }
+}

+ 104 - 0
lib/Drupal/wing/CommentRenderController.php

@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\comment\CommentRenderController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
+
+/**
+ * Render controller for comments.
+ */
+class CommentRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::buildContent().
+   *
+   * In addition to modifying the content key on entities, this implementation
+   * will also set the node key which all comments carry.
+   */
+  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
+    $return = array();
+    if (empty($entities)) {
+      return $return;
+    }
+
+    // Pre-load associated users into cache to leverage multiple loading.
+    $uids = array();
+    foreach ($entities as $entity) {
+      $uids[] = $entity->uid->target_id;
+    }
+    user_load_multiple(array_unique($uids));
+
+    parent::buildContent($entities, $displays, $view_mode, $langcode);
+
+    // Load all nodes of all comments at once.
+    $nids = array();
+    foreach ($entities as $entity) {
+      $nids[$entity->nid->target_id] = $entity->nid->target_id;
+    }
+    $nodes = node_load_multiple($nids);
+
+    foreach ($entities as $entity) {
+      if (isset($nodes[$entity->nid->target_id])) {
+        $node = $nodes[$entity->nid->target_id];
+      }
+      else {
+        throw new \InvalidArgumentException(t('Invalid node for comment.'));
+      }
+      $entity->content['#node'] = $node;
+      $entity->content['#theme'] = 'comment__node_' . $node->bundle();
+      $entity->content['links'] = array(
+        '#theme' => 'links__comment',
+        '#pre_render' => array('drupal_pre_render_links'),
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+      if (empty($entity->in_preview)) {
+        $entity->content['links'][$this->entityType] = array(
+          '#theme' => 'links__comment__comment',
+          // The "node" property is specified to be present, so no need to check.
+          '#links' => comment_links($entity, $node),
+          '#attributes' => array('class' => array('links', 'inline')),
+        );
+      }
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::alterBuild().
+   */
+  protected function alterBuild(array &$build, EntityInterface $comment, EntityDisplay $display, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $comment, $display, $view_mode, $langcode);
+    if (empty($comment->in_preview)) {
+      $prefix = '';
+      $is_threaded = isset($comment->divs)
+        && variable_get('comment_default_mode_' . $comment->bundle(), COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED;
+
+      // Add 'new' anchor if needed.
+      if (!empty($comment->first_new)) {
+        $prefix .= "<a id=\"new\"></a>\n";
+      }
+
+      // Add indentation div or close open divs as needed.
+      if ($is_threaded) {
+        $build['#attached']['css'][] = drupal_get_path('module', 'comment') . '/comment.theme.css';
+        $prefix .= $comment->divs <= 0 ? str_repeat('</div>', abs($comment->divs)) : "\n" . '<div class="indented">';
+      }
+
+      // Add anchor for each comment.
+      $prefix .= "<a id=\"comment-{$comment->id()}\"></a>\n";
+      $build['#prefix'] = $prefix;
+
+      // Close all open divs.
+      if ($is_threaded && !empty($comment->divs_final)) {
+        $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
+      }
+    }
+  }
+
+}

+ 43 - 0
lib/Drupal/wing/CommentTranslationController.php

@@ -0,0 +1,43 @@
+<?php
+
+
+/**
+ * @file
+ * Definition of Drupal\comment\CommentTranslationController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationControllerNG;
+
+/**
+ * Defines the translation controller class for comments.
+ */
+class CommentTranslationController extends EntityTranslationControllerNG {
+
+  /**
+   * Overrides EntityTranslationController::getAccess().
+   */
+  public function getAccess(EntityInterface $entity, $op) {
+    switch ($op) {
+      case 'view':
+        return user_access('access comments');
+      case 'update':
+        return comment_access('edit', $entity);
+      case 'delete':
+        return user_access('administer comments');
+      case 'create':
+        return user_access('post comments');
+    }
+    return parent::getAccess($entity, $op);
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormTitle().
+   */
+  protected function entityFormTitle(EntityInterface $entity) {
+    return t('Edit comment @subject', array('@subject' => $entity->label()));
+  }
+
+}

+ 122 - 0
lib/Drupal/wing/Plugin/Core/Entity/State.php

@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\wing\Plugin\Core\Entity\State.
+ */
+
+namespace Drupal\wing\Plugin\Core\Entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the Wing State entity class.
+ *
+ * @Plugin(
+ *   id = "wing_state",
+ *   label = @Translation("Wing: State"),
+ *   bundle_label = @Translation("Workflow"),
+ *   module = "wing",
+ *   controller_class = "Drupal\wing\WingStorageController",
+ *   render_controller_class = "Drupal\wing\WingRenderController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\wing\WingFormController"
+ *   },
+ *   translation_controller_class = "Drupal\wing\WingTranslationController",
+ *   base_table = "wing_state",
+ *   uri_callback = "wing_uri",
+ *   fieldable = FALSE,
+ *   static_cache = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "bundle" = "workflow",
+ *     "label" = "title",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class State extends EntityNG implements EntityInterface { // Or ContentEntityInterface ?
+
+  /**
+   * The State ID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $id;
+
+  /**
+   * The State UUID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $uuid;
+
+  /**
+   * The State language code.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $langcode;
+
+  /**
+   * The State title.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $title;
+
+  /**
+   * The time that the State was created.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $created;
+
+  /**
+   * The time that the State was last edited.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $changed;
+
+  /**
+   * The workflow to which the State belongs.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $workflow;
+
+  /**
+   * The plain data values of the contained properties.
+   *
+   * Define default values.
+   *
+   * @var array
+   */
+  protected $values = array(
+    'langcode' => array(LANGUAGE_DEFAULT => array(0 => array('value' => LANGUAGE_NOT_SPECIFIED))),
+  );
+
+  /**
+   * Initialize the object. Invoked upon construction and wake up.
+   */
+  protected function init() {
+    parent::init();
+    // We unset all defined properties, so magic getters apply.
+    $rc = new \ReflectionClass($this);
+    $properties = $rc->getProperties(\ReflectionProperty::IS_PUBLIC);
+    foreach ($properties as $property) {
+      unset($this->{$property->name});
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::id().
+   */
+  public function id() {
+    return $this->get('id')->value;
+  }
+}

+ 0 - 0
WingBase.inc → lib/Drupal/wing/WingBase.inc


+ 0 - 0
WingState.inc → lib/Drupal/wing/WingState.inc


+ 0 - 0
WingStateController.inc → lib/Drupal/wing/WingStateController.inc


+ 0 - 0
WingStateFeaturesController.inc → lib/Drupal/wing/WingStateFeaturesController.inc


+ 0 - 0
WingStateUIController.inc → lib/Drupal/wing/WingStateUIController.inc


+ 0 - 0
WingStateViewsController.inc → lib/Drupal/wing/WingStateViewsController.inc


+ 359 - 0
lib/Drupal/wing/WingStorageController.php

@@ -0,0 +1,359 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\comment\WingStorageController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\DatabaseStorageControllerNG;
+use Drupal\Component\Uuid\Uuid;
+use LogicException;
+
+/**
+ * Defines the controller class for comments.
+ *
+ * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
+ * required special handling for comment entities.
+ */
+class WingStorageController extends DatabaseStorageControllerNG {
+  /**
+   * The thread for which a lock was acquired.
+   */
+  protected $threadLock = '';
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $revision_id);
+    // Specify additional fields from the user and node tables.
+    $query->innerJoin('node', 'n', 'base.nid = n.nid');
+    $query->addField('n', 'type', 'node_type');
+    $query->innerJoin('users', 'u', 'base.uid = u.uid');
+    // @todo: Move to a computed 'name' field instead.
+    $query->addField('u', 'name', 'registered_name');
+    return $query;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
+   */
+  protected function attachLoad(&$records, $load_revision = FALSE) {
+    // Prepare standard comment fields.
+    foreach ($records as $key => $record) {
+      $record->name = $record->uid ? $record->registered_name : $record->name;
+      $record->node_type = 'comment_node_' . $record->node_type;
+      $records[$key] = $record;
+    }
+    parent::attachLoad($records, $load_revision);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::create().
+   */
+  public function create(array $values) {
+    if (empty($values['node_type']) && !empty($values['nid'])) {
+      $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']);
+      $values['node_type'] = 'comment_node_' . $node->type;
+    }
+    return parent::create($values);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
+   *
+   * @see comment_int_to_alphadecimal()
+   * @see comment_alphadecimal_to_int()
+   */
+  protected function preSave(EntityInterface $comment) {
+    global $user;
+
+    if (!isset($comment->status->value)) {
+      $comment->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
+    }
+    // Make sure we have a proper bundle name.
+    if (!isset($comment->node_type->value)) {
+      $comment->node_type->value = 'comment_node_' . $comment->nid->entity->type;
+    }
+    if ($comment->isNew()) {
+      // Add the comment to database. This next section builds the thread field.
+      // Also see the documentation for comment_view().
+      if (!empty($comment->thread->value)) {
+        // Allow calling code to set thread itself.
+        $thread = $comment->thread->value;
+      }
+      else {
+        if ($this->threadLock) {
+          // As preSave() is protected, this can only happen when this class
+          // is extended in a faulty manner.
+          throw new LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
+        }
+        if ($comment->pid->target_id == 0) {
+          // This is a comment with no parent comment (depth 0): we start
+          // by retrieving the maximum thread level.
+          $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField();
+          // Strip the "/" from the end of the thread.
+          $max = rtrim($max, '/');
+          // We need to get the value at the correct depth.
+          $parts = explode('.', $max);
+          $n = comment_alphadecimal_to_int($parts[0]);
+          $prefix = '';
+        }
+        else {
+          // This is a comment with a parent comment, so increase the part of
+          // the thread value at the proper depth.
+
+          // Get the parent comment:
+          $parent = $comment->pid->entity;
+          // Strip the "/" from the end of the parent thread.
+          $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/');
+          $prefix = $parent->thread->value . '.';
+          // Get the max value in *this* thread.
+          $max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
+            ':thread' => $parent->thread->value . '.%',
+            ':nid' => $comment->nid->target_id,
+          ))->fetchField();
+
+          if ($max == '') {
+            // First child of this parent. As the other two cases do an
+            // increment of the thread number before creating the thread
+            // string set this to -1 so it requires an increment too.
+            $n = -1;
+          }
+          else {
+            // Strip the "/" at the end of the thread.
+            $max = rtrim($max, '/');
+            // Get the value at the correct depth.
+            $parts = explode('.', $max);
+            $parent_depth = count(explode('.', $parent->thread->value));
+            $n = comment_alphadecimal_to_int($parts[$parent_depth]);
+          }
+        }
+        // Finally, build the thread field for this new comment. To avoid
+        // race conditions, get a lock on the thread. If aother process already
+        // has the lock, just move to the next integer.
+        do {
+          $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/';
+        } while (!lock()->acquire("comment:{$comment->nid->target_id}:$thread"));
+        $this->threadLock = $thread;
+      }
+      if (empty($comment->created->value)) {
+        $comment->created->value = REQUEST_TIME;
+      }
+      if (empty($comment->changed->value)) {
+        $comment->changed->value = $comment->created->value;
+      }
+      // We test the value with '===' because we need to modify anonymous
+      // users as well.
+      if ($comment->uid->target_id === $user->uid && $user->uid) {
+        $comment->name->value = $user->name;
+      }
+      // Add the values which aren't passed into the function.
+      $comment->thread->value = $thread;
+      $comment->hostname->value = ip_address();
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $comment, $update) {
+    $this->releaseThreadLock();
+    // Update the {node_comment_statistics} table prior to executing the hook.
+    $this->updateNodeStatistics($comment->nid->target_id);
+    if ($comment->status->value == COMMENT_PUBLISHED) {
+      module_invoke_all('comment_publish', $comment);
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   */
+  protected function postDelete($comments) {
+    // Delete the comments' replies.
+    $query = db_select('comment', 'c')
+      ->fields('c', array('cid'))
+      ->condition('pid', array(array_keys($comments)), 'IN');
+    $child_cids = $query->execute()->fetchCol();
+    comment_delete_multiple($child_cids);
+
+    foreach ($comments as $comment) {
+      $this->updateNodeStatistics($comment->nid->target_id);
+    }
+  }
+
+  /**
+   * Updates the comment statistics for a given node.
+   *
+   * The {node_comment_statistics} table has the following fields:
+   * - last_comment_timestamp: The timestamp of the last comment for this node,
+   *   or the node created timestamp if no comments exist for the node.
+   * - last_comment_name: The name of the anonymous poster for the last comment.
+   * - last_comment_uid: The user ID of the poster for the last comment for
+   *   this node, or the node author's user ID if no comments exist for the
+   *   node.
+   * - comment_count: The total number of approved/published comments on this
+   *   node.
+   *
+   * @param $nid
+   *   The node ID.
+   */
+  protected function updateNodeStatistics($nid) {
+    // Allow bulk updates and inserts to temporarily disable the
+    // maintenance of the {node_comment_statistics} table.
+    if (!variable_get('comment_maintain_node_statistics', TRUE)) {
+      return;
+    }
+
+    $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array(
+      ':nid' => $nid,
+      ':status' => COMMENT_PUBLISHED,
+    ))->fetchField();
+
+    if ($count > 0) {
+      // Comments exist.
+      $last_reply = db_query_range('SELECT cid, name, changed, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array(
+        ':nid' => $nid,
+        ':status' => COMMENT_PUBLISHED,
+      ))->fetchObject();
+      db_update('node_comment_statistics')
+        ->fields(array(
+          'cid' => $last_reply->cid,
+          'comment_count' => $count,
+          'last_comment_timestamp' => $last_reply->changed,
+          'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
+          'last_comment_uid' => $last_reply->uid,
+        ))
+        ->condition('nid', $nid)
+        ->execute();
+    }
+    else {
+      // Comments do not exist.
+      $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+      db_update('node_comment_statistics')
+        ->fields(array(
+          'cid' => 0,
+          'comment_count' => 0,
+          'last_comment_timestamp' => $node->created,
+          'last_comment_name' => '',
+          'last_comment_uid' => $node->uid,
+        ))
+        ->condition('nid', $nid)
+        ->execute();
+    }
+  }
+
+  /**
+   * Release the lock acquired for the thread in preSave().
+   */
+  protected function releaseThreadLock() {
+    if ($this->threadLock) {
+      lock()->release($this->threadLock);
+      $this->threadLock = '';
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
+   */
+  public function baseFieldDefinitions() {
+    $properties['cid'] = array(
+      'label' => t('ID'),
+      'description' => t('The comment ID.'),
+      'type' => 'integer_field',
+      'read-only' => TRUE,
+    );
+    $properties['uuid'] = array(
+      'label' => t('UUID'),
+      'description' => t('The comment UUID.'),
+      'type' => 'string_field',
+    );
+    $properties['pid'] = array(
+      'label' => t('Parent ID'),
+      'description' => t('The parent comment ID if this is a reply to a comment.'),
+      'type' => 'entity_reference_field',
+      'settings' => array('target_type' => 'comment'),
+    );
+    $properties['nid'] = array(
+      'label' => t('Node ID'),
+      'description' => t('The ID of the node of which this comment is a reply.'),
+      'type' => 'entity_reference_field',
+      'settings' => array('target_type' => 'node'),
+      'required' => TRUE,
+    );
+    $properties['langcode'] = array(
+      'label' => t('Language code'),
+      'description' => t('The comment language code.'),
+      'type' => 'language_field',
+    );
+    $properties['subject'] = array(
+      'label' => t('Subject'),
+      'description' => t('The comment title or subject.'),
+      'type' => 'string_field',
+    );
+    $properties['uid'] = array(
+      'label' => t('User ID'),
+      'description' => t('The user ID of the comment author.'),
+      'type' => 'entity_reference_field',
+      'settings' => array('target_type' => 'user'),
+    );
+    $properties['name'] = array(
+      'label' => t('Name'),
+      'description' => t("The comment author's name."),
+      'type' => 'string_field',
+    );
+    $properties['mail'] = array(
+      'label' => t('e-mail'),
+      'description' => t("The comment author's e-mail address."),
+      'type' => 'string_field',
+    );
+    $properties['homepage'] = array(
+      'label' => t('Homepage'),
+      'description' => t("The comment author's home page address."),
+      'type' => 'string_field',
+    );
+    $properties['hostname'] = array(
+      'label' => t('Hostname'),
+      'description' => t("The comment author's hostname."),
+      'type' => 'string_field',
+    );
+    $properties['created'] = array(
+      'label' => t('Created'),
+      'description' => t('The time that the comment was created.'),
+      'type' => 'integer_field',
+    );
+    $properties['changed'] = array(
+      'label' => t('Changed'),
+      'description' => t('The time that the comment was last edited.'),
+      'type' => 'integer_field',
+    );
+    $properties['status'] = array(
+      'label' => t('Publishing status'),
+      'description' => t('A boolean indicating whether the comment is published.'),
+      'type' => 'boolean_field',
+    );
+    $properties['thread'] = array(
+      'label' => t('Thread place'),
+      'description' => t("The alphadecimal representation of the comment's place in a thread, consisting of a base 36 string prefixed by an integer indicating its length."),
+      'type' => 'string_field',
+    );
+    $properties['node_type'] = array(
+      // @todo: The bundle property should be stored so it's queryable.
+      'label' => t('Node type'),
+      'description' => t("The comment node type."),
+      'type' => 'string_field',
+      'queryable' => FALSE,
+    );
+    $properties['new'] = array(
+      'label' => t('Comment new marker'),
+      'description' => t("The comment 'new' marker for the current user (0 read, 1 new, 2 updated)."),
+      'type' => 'integer_field',
+      'computed' => TRUE,
+      'class' => '\Drupal\comment\FieldNewItem',
+    );
+    return $properties;
+  }
+}

+ 0 - 0
WingWorkflow.inc → lib/Drupal/wing/WingWorkflow.inc


+ 0 - 0
WingWorkflowController.inc → lib/Drupal/wing/WingWorkflowController.inc


+ 0 - 0
WingWorkflowFeaturesController.inc → lib/Drupal/wing/WingWorkflowFeaturesController.inc


+ 0 - 0
WingWorkflowUIController.inc → lib/Drupal/wing/WingWorkflowUIController.inc


+ 0 - 0
WingWorkflowViewsController.inc → lib/Drupal/wing/WingWorkflowViewsController.inc


+ 5 - 0
wing.admin.inc

@@ -4,6 +4,11 @@
  * Administrative functions for Wing.
  */
 
+function wing_page_info() {
+  dsm(func_get_args(), __FUNCTION__);
+  return 'Info';
+}
+
 /**
  * Form builder for Workflow add/edit.
  *

+ 0 - 19
wing.info

@@ -1,19 +0,0 @@
-name = Wing
-description = Workflow Improved Next Generation
-php = 5.3
-core = 7.x
-
-files[] = WingBase.inc
-
-files[] = WingState.inc
-files[] = WingStateController.inc
-files[] = WingStateFeaturesController.inc
-files[] = WingStateUIController.inc
-files[] = WingStateViewsController.inc
-
-files[] = WingWorkflow.inc
-files[] = WingWorkflowController.inc
-files[] = WingWorkflowFeaturesController.inc
-files[] = WingWorkflowUIController.inc
-files[] = WingWorkflowViewsController.inc
-

+ 4 - 0
wing.info.yml

@@ -0,0 +1,4 @@
+name: Wing
+description: 'Workflow Improved Next Generation'
+php: 5.3
+core: 8.x

+ 3 - 3
wing.install

@@ -62,7 +62,7 @@ function _wing_exportable_schema_fields($entity) {
 }
 
 
-function wing_field_schema($field) {
+function Zwing_field_schema($field) {
   $schema = array(
     'columns' => array(
       'rid' => array(
@@ -102,7 +102,7 @@ function wing_field_schema($field) {
  * - wing_state
  * - wing_workflow
  */
-function wing_schema() {
+function Zwing_schema() {
   $schema = array(
     'wing_state' => array(
       'description' => 'The base table for Wing State entities',
@@ -155,7 +155,7 @@ function wing_schema() {
  *
  * @see entity_test_uninstall() {
  */
-function wing_uninstall() {
+function Zwing_uninstall() {
   // FIXME: provide correct values.
   $wing_state_types = array();
 

+ 225 - 71
wing.module

@@ -1,14 +1,15 @@
 <?php
 
+use Drupal\wing\Plugin\Core\Entity\State;
+
 /**
  * @file
  * Workflow - New Generation
  *
  * - States are entities
- * - Worfklows are the bundle entities of states
  * - Transitions are fields on states, storing a (rid, sid) pair per item
  * - Any entity type can be subject to a workflow: association is represented by
- *   an entityreference field whose widget uses a specific view to list the
+ *   an entity_reference field whose widget uses a specific view to list the
  *   relevant states
  * - Workflow access is handled by field access using the permission associated
  *   with a state, one "view" or "edit" permission per state entity
@@ -30,13 +31,225 @@
  *   state operations form. Main point of interest is the transitions subform
  */
 
+/**
+ * Implements hook_entity_bundle_info().
+ */
+function wing_entity_bundle_info() {
+  $bundles = array();
+  foreach (wing_get_workflows() as $workflow => $title) {
+    $bundles['wing_state'][$type] = array(
+      'label' => $title,
+      'admin' => array(
+        // Place the Field UI paths for comments one level below the
+        // corresponding paths for nodes, so that they appear in the same set
+        // of local tasks. Note that the paths use a different placeholder name
+        // and thus a different menu loader callback, so that Field UI page
+        // callbacks get a comment bundle name from the node type in the URL.
+        // See comment_node_type_load() and comment_menu_alter().
+        'path' => 'admin/content/wing/manage/%wing_workflow',
+        'bundle argument' => 4,
+        'real path' => 'admin/content/wing/manage/' . $workflow. '/comment',
+      ),
+    );
+  }
+  return $bundles;
+}
+
+/**
+ * Implements hook_entity_view_mode().
+ */
+function wing_entity_view_mode_info() {
+  $ret = array(
+    'wing_state' => array(
+      'full' => array(
+        'label' => t('Full'),
+        'custom settings' => FALSE,
+      ),
+      'summary' => array(
+        'label' => t('Summary'),
+        'custom settings' => FALSE,
+      ),
+      'simple' => array(
+        'label' => t('Simple'),
+        'custom settings' => FALSE,
+      ),
+    ),
+  );
+dsm($ret, __FUNCTION__);
+  return $ret;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function wing_field_extra_fields() {
+  $ret = array();
+  foreach (wing_get_workflows() as $workflow => $title) {
+    $ret['wing_state'][$workflow]['workflow'] = array(
+      'display' => array(
+        'label' => t('Workflow name'),
+        'description' => t('The workflow to which the state belongs'),
+        'weight' => -9,
+        'visible' => FALSE,
+      ),
+    );
+  }
+  dsm($ret, __FUNCTION__);
+  return $ret;
+}
+
+/**
+ * Helper to retrive the list or workflows or a specific workflow.
+ *
+ * @param string $workflow
+ */
+function wing_get_workflows($workflow = NULL) {
+  $workflows = config('wing')->get('workflows');
+  if (isset($workflow)) {
+    $ret = isset($workflows[$workflow]) ? $workflows[$workflow] : FALSE;
+  }
+  else {
+    $ret = (array) $workflows;
+  }
+
+  dsm($ret, __FUNCTION__ . "($workflow)");
+  return $ret;
+}
+
+/**
+ * Implements hook_help().
+ */
+function wing_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#wing':
+      $ret = '<h3>' . t('About') . '</h3>'
+        . t('Wing is a new workflow system redesigned from the ground up for Drupal 8.
+States are entities, Workflows are their bundles, and any fieldable entity type
+(not just nodes) can be subjected to a workflow by adding an entity_reference to
+a Wing state field to its field list.');
+      break;
+
+    default:
+      $ret = NULL;
+  }
+
+  return $ret;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function wing_menu() {
+  $items = array();
+  $items['admin/content/wing'] = array(
+    'access arguments' => array('administer wing'),
+    'description' => 'Report general Wing information',
+    'file' => 'wing.admin.inc',
+    'page callback' => 'wing_page_info',
+    'title' => 'Wing',
+    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
+    'weight' => 23, // 'W'
+  );
+
+  // Tabs begin here.
+  $items['admin/content/wing/info'] = array(
+    'title' => 'Info',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/content/wing/overview'] = array(
+    'title' => 'States',
+    'access arguments' => array('administer wing'),
+    'description' => 'List and edit workflow states',
+    'file' => 'wing.pages.inc',
+    'page callback' => 'wing_overview',
+    'title' => 'Wing',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/content/wing/%wing_state'] = array(
+    'access arguments' => array('administer wing'),
+    'page arguments' => array(3),
+    'page callback' => 'wing_state_page',
+    'title' => 'State permalink',
+  );
+  $items['admin/content/wing/%wing_state/view'] = array(
+    'title' => 'View state',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+  );
+  $items['admin/content/wing/%wing_state/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'wing_state_edit_page',
+    'page arguments' => array(3),
+    'access callback' => 'wing_state_access',
+    'access arguments' => array('edit', 3),
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['admin/content/wing/%wing_state/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'wing_state_confirm_delete_page',
+    'page arguments' => array(3),
+    'access arguments' => array('administer wing'),
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'wing.admin.inc',
+    'weight' => 20,
+  );
+
+  $items['admin/content/wing/settings'] = array(
+    'title' => 'Settings',
+    'type' => MENU_LOCAL_TASK,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('wing_settings_form'),
+    'file' => 'wing.admin.inc',
+    'access arguments' => array('administer wing'),
+    'weight' => 99,
+  );
+
+  return $items;
+}
+
+/**
+ * TODO implement
+ *
+ * @param string $op
+ * @param Drupal\wing\Plugin\Core\Entity\State $state
+ *
+ * @return boolean
+ */
+function wing_state_access($op, State $state) {
+
+}
+
+/**
+ * Entity URI callback.
+ */
+function wing_uri(State $state) {
+  return array(
+    'path' => 'admin/content/wing/' . $state->id(),
+  );
+}
+
+
+/**
+ * Menu loader for workflow.
+ *
+ * @param string $workflow
+ *
+ * @return boolean
+ */
+function wing_workflow_load($workflow) {
+  $ret = wing_get_workflows($workflow);
+  dsm($ret, __FUNCTION__);
+  return $ret;
+}
+
+function WIPWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWww() {}
+
 /**
  * Implements hook_entity_info().
  *
  * Workflow is the bundle entity for state, but this concept is obsolete in D8,
  * so we ignore it.
  */
-function wing_entity_info() {
+function Zwing_entity_info() {
   $defaults = array(
     'entity keys' => array(
       'label' => 'title',
@@ -49,20 +262,6 @@ function wing_entity_info() {
     'label callback' => 'entity_class_label',
     'module' => 'wing',
     'uri callback' => 'entity_class_uri',
-    'view modes' => array(
-      'full' => array(
-        'label' => t('Full'),
-        'custom settings' => FALSE,
-      ),
-      'summary' => array(
-        'label' => t('Summary'),
-        'custom settings' => FALSE,
-      ),
-      'simple' => array(
-        'label' => t('Simple'),
-        'custom settings' => FALSE,
-      ),
-    ),
   );
 
   $ret = array();
@@ -108,24 +307,6 @@ function wing_entity_info() {
     'views controller class' => "{$class}ViewsController",
   ), $defaults);
 
-  // Add bundle info but bypass entity_load() as we cannot use it here.
-  $workflows = db_select('wing_workflow', 'ww')
-    ->fields('ww')
-    ->execute()
-    ->fetchAllAssoc('machine_name');
-
-  foreach ($workflows as $workflow_name => $workflow) {
-    $ret[$type]['bundles'][$workflow_name] = array(
-      'label' => $workflow->title,
-//       'admin' => array(
-//         'path' => 'admin/content/wing/workflow/%entity_object',
-//         'real path' => 'admin/content/wing/workflow/' . $workflow_name,
-//         'bundle argument' => 4,
-//         'access arguments' => array('administer wing'),
-//       ),
-    );
-  }
-
   return $ret;
 }
 
@@ -134,7 +315,7 @@ function wing_entity_info() {
  *
  * Sort initial Wing entity info keys for easier debugging.
  */
-function wing_entity_info_alter(&$info) {
+function Zwing_entity_info_alter(&$info) {
   ksort($info['wing_state']);
   ksort($info['wing_workflow']);
 }
@@ -144,7 +325,7 @@ function wing_entity_info_alter(&$info) {
  *
  * Define "wing_transition" as a UI-less field.
  */
-function wing_field_info() {
+function Zwing_field_info() {
   $ret = array(
     'wing_transition' => array(
       'label' => t('Wing: Transition'),
@@ -165,7 +346,7 @@ dsm($ret, __FUNCTION__);
 /**
  * Implements hook_field_widget_info().
  */
-function wing_field_widget_info() {
+function Zwing_field_widget_info() {
   $ret = array(
     'text_textfield' => array(
       'label' => t('Text field'),
@@ -184,43 +365,16 @@ function wing_field_widget_info() {
 /**
  * Implements hook_field_widget_info_alter().
  */
-function wing_field_widget_info_alter(&$info) {
+function Zwing_field_widget_info_alter(&$info) {
   if (module_exists('text')) {
     $info['text_textfield']['field types'][] = 'wing_transition';
   }
 }
 
-/**
- * Implements hook_menu().
- */
-function wing_menu() {
-  $items = array();
-  $items['admin/content/wing'] = array(
-    'title' => 'Wing',
-    'access arguments' => array('administer wing'),
-    'page callback' => 'wing_overview',
-    'file' => 'wing.pages.inc',
-  );
-  $items['admin/content/wing/info'] = array(
-    'title' => 'Info',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-  );
-  $items['admin/content/wing/settings'] = array(
-    'title' => 'Settings',
-    'type' => MENU_LOCAL_TASK,
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('wing_settings_form'),
-    'file' => 'wing.admin.inc',
-    'access arguments' => array('administer wing'),
-    'weight' => 9,
-  );
-  return $items;
-}
-
 /**
  * Implements hook_permission().
  */
-function wing_permission() {
+function Zwing_permission() {
   $ret = array(
     'administer wing' =>  array(
       'title' => t('Administer Wing'),
@@ -242,7 +396,7 @@ function wing_permission() {
  *
  * @return boolean
  */
-function wing_state_access($op, $entity, $account, $entity_type) {
+function Zwing_state_access($op, $entity, $account, $entity_type) {
   // dsm(get_defined_vars(), __FUNCTION__);
   return TRUE;
 }
@@ -250,7 +404,7 @@ function wing_state_access($op, $entity, $account, $entity_type) {
 /**
  * Implements hook_view_api().
  */
-function wing_views_api() {
+function Zwing_views_api() {
   $path = drupal_get_path('module', 'wing');
   $ret = array(
     'api' => 3,
@@ -271,7 +425,7 @@ function wing_views_api() {
  *
  * @return boolean
  */
-function wing_workflow_access($op, $entity, $account, $entity_type) {
+function Zwing_workflow_access($op, $entity, $account, $entity_type) {
   $s_entity = isset($entity) ? "$entity" : 'NULL';
   $s_account = isset($account) ? $account->uid : "'<anonymous>'";
 
@@ -279,7 +433,7 @@ function wing_workflow_access($op, $entity, $account, $entity_type) {
   return TRUE;
 }
 
-function wing_workflow_exists($machine_name) {
+function Zwing_workflow_exists($machine_name) {
   $ret = entity_load_multiple_by_name('wing_workflow', array($machine_name));
   dsm($ret, __FUNCTION__);
   return $ret;