forked from LionsAd/drafty
-
Notifications
You must be signed in to change notification settings - Fork 0
/
drafty.module
237 lines (216 loc) · 7.83 KB
/
drafty.module
1
2
3
4
5
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
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<?php
/**
* @file
* Hook implementations and API functions for drafty module.
*/
/**
* Implements hook_module_implements_alter().
*/
function drafty_module_implements_alter(&$implementations) {
if (isset($implementations['drafty'])) {
$group = $implementations['drafty'];
unset($implementations['drafty']);
$implementations['drafty'] = $group;
}
}
/**
* Implements hook_entity_presave().
*/
function drafty_entity_presave($entity, $type) {
$recursion_level = &drupal_static('drafty_recursion_level', 0);
if (!$recursion_level && empty($entity->is_new) && !empty($entity->is_draft_revision)) {
// Since this is a draft revision, after saving we want the current,
// published revision to remain in place in the base entity table and
// field_data_*() tables. Set the revision to publish once the draft entity
// has been written to the database.
list($id) = entity_extract_ids($type, $entity);
$vid = drafty()->getPublishedRevisionId($type, $id);
drafty()->setRevisionToBePublished($type, $id, $vid);
}
$recursion_level++;
}
/**
* Implements hook_entity_update().
*/
function drafty_entity_update($entity, $type) {
$recursion_level = &drupal_static('drafty_recursion_level', 0);
if ($recursion_level == 1) {
// Doing this in hook_entity_update() so that the entire process is
// completed within entity saving. However this results in two entity saves
// within entity insert. The other option is hook_exit(), which is not
// better since for example that would happen outside the transaction.
drafty()->restorePublishedRevisions();
}
$recursion_level--;
}
/**
* Implements hook_entity_insert().
*/
function drafty_entity_insert($entity, $type) {
$recursion_level = &drupal_static('drafty_recursion_level', 0);
$recursion_level--;
}
/**
* Implements hook_field_attach_load().
*/
function drafty_field_attach_load($entity_type, $entities, $age, $options) {
// Entity API provides the function entity_revision_is_default() to determine
// whether an entity was loaded with the default revision or not. However this
// is not sufficient for two reasons.
// - It relies on entity_load() for core entities, which makes it unsafe to
// call within hook_entity_load() implementations. This can be useful when
// allowing drafts to be previewed in context such as listings.
// - The entity API implementation only tells you whether the entity was
// loaded with that revision or not, but not whether it was requested
// with the ID or with the revision explicitly specified.
// Note that hook_field_attach_load() is the only hook in core where it is
// possible to determine whether calling code requested a revision or not,
// this information is not available to hook_entity_load(). Also note that
// hook_field_attach_load() is cached when entities are loaded only be ID, but
// since revision loads don't use the field cache it works fine for our
// purposes.
foreach ($entities as $entity) {
$entity->_drafty_revision_requested = $age;
}
}
/**
* Factory function for the DraftyTracker class.
*/
function drafty() {
$tracker = &drupal_static(__FUNCTION__);
if (!isset($tracker)) {
$tracker = new Drafty();
}
return $tracker;
}
/**
* Handles tracking, selecting and publishing revisions.
*/
class Drafty {
/**
* A list of entity types, ids and version IDs to be published.
*/
protected $revisionsToPublish = array();
public function wasRevisionRequested($entity) {
return isset($entity->_drafty_revision_requested) && $entity->_drafty_revision_requested === FIELD_LOAD_REVISION;
}
public function isDraftRevision($entity) {
return !empty($entity->is_draft_revision);
}
/**
* Get the current published revision for an entity.
*
* @param $type
* The entity type.
* @param $id
* The entity ID.
*
* @return A version ID.
*/
public function getPublishedRevisionId($type, $id) {
$info = entity_get_info();
// Get the version ID of the published revision directly from the database.
// It is not possible to rely on $entity->original here since that does not
// guarantee being the published revision. Also avoid loading the entity
// because we may be in the process of saving it.
$query = db_select($info[$type]['base table'], 'b');
$query->addField('b', $info[$type]['entity keys']['revision']);
$query->condition($info[$type]['entity keys']['id'], $id);
$vid = $query->execute()->fetchField();
return $vid;
}
/**
* Add a revision to be published to the tracker.
*
* @param $type
* The entity type.
* @param $id
* The entity ID.
* @param $vid
* The entity version ID.
*
* @return $this
*/
public function setRevisionToBePublished($type, $id, $vid) {
// Only one revision can be published during a request, so just overwrite
// and for now last one wins.
$this->revisionsToPublish[$type][$id] = $vid;
return $this;
}
/**
* Publish a revision.
*
* @param $type
* The entity type.
* @param $vid
* The entity version ID.
*
* @return The newly published revision.
*/
function publishRevision($type, $id, $vid) {
// Title module assumes that the current content language is used when
// saving an entity. This is OK for the new draft revision, but it does not
// work when publishing a revision. Therefore, ensure that
// title_active_language() reflects the original language of the entity.
// Without this, title may overwrite {$title}_field in the original language
// with the contents of the legacy field.
// @todo: this might not be necessary after one or both of these patches
// lands:
// https://www.drupal.org/node/2267251
// https://www.drupal.org/node/2098097
if (module_exists('title')) {
$entity = entity_load_single($type, $id);
$langcode = entity_language($type, $entity);
title_active_language($langcode);
}
$revision = entity_revision_load($type, $vid);
// Publishing a revision sometimes happens within hook_entity_update(). When
// we do that, set $entity->original to the entity we're in the process of
// saving. i.e. the draft we're in the process of creating and need to
// replace with the published version again.
$revision->is_draft_revision = FALSE;
return $this->saveRevisionAsNew($type, $revision);
}
/**
* Save a revision as new.
*
* @param $type
* The entity type.
* @param $revision
* An entity object.
*
* @return The newly saved revision.
*/
public function saveRevisionAsNew($type, $revision) {
list($id) = entity_extract_ids($type, $revision);
entity_get_controller($type)->resetCache();
$original = entity_load_single($type, $id);
$revision->original = $original;
// @todo: entity API function?
$revision->revision = TRUE;
$revision->is_new_revision = TRUE;
$revision->default_revision = TRUE;
entity_save($type, $revision);
return $revision;
}
/**
* Publish revisions previously set with setRevisionToBePublished().
*/
public function restorePublishedRevisions() {
foreach ($this->revisionsToPublish as $type => $value) {
foreach ($value as $id => $vid) {
unset($this->revisionsToPublish[$type][$id]);
$this->publishRevision($type, $id, $vid);
// Now that the revision is deleted, there are two identical copies of
// the revision in the system. The original 'draft' revision and the
// newly saved published revision. Delete the draft revision now since
// it's not needed.
// @todo: make this configurable?
// @todo: when restoring a published revision, should the revision
// timestamp be set to the old value?
// @todo: move this to a queue since the deletion doesn't strictly have
// to happen inline.
}
}
}
}