diff --git a/core/lib/Drupal/Core/TempStore/SessionTempStore.php b/core/lib/Drupal/Core/TempStore/SessionTempStore.php new file mode 100644 index 0000000..83475c2 --- /dev/null +++ b/core/lib/Drupal/Core/TempStore/SessionTempStore.php @@ -0,0 +1,56 @@ + $this->subsystem, + ':temp_key' => $key, + ))->fetchObject(); + + // If the current user owns the lock and is excluded, report that the object + // is not locked. + if ($exclude_owner && isset($lock_owner->uid) && $lock_owner->uid == $GLOBALS['user']->uid) { + return; + } + + return $lock_owner; + } + +} diff --git a/core/lib/Drupal/Core/TempStore/SpecialTempStore.php b/core/lib/Drupal/Core/TempStore/SpecialTempStore.php new file mode 100644 index 0000000..b0fb8ac --- /dev/null +++ b/core/lib/Drupal/Core/TempStore/SpecialTempStore.php @@ -0,0 +1,38 @@ + $this->subsystem, + ':temp_key' => $key, + ))->fetchField(); + } + +} diff --git a/core/lib/Drupal/Core/TempStore/TempStoreBase.php b/core/lib/Drupal/Core/TempStore/TempStoreBase.php new file mode 100644 index 0000000..ccb848b --- /dev/null +++ b/core/lib/Drupal/Core/TempStore/TempStoreBase.php @@ -0,0 +1,231 @@ +subsystem = $subsystem; + $this->ownerId = $ownerId; + } + + /** + * Fetches the data from the store. + * + * @param string $key + * The key to the stored object. See TempStoreBase::set() for details. + * + * @return object + * The stored data. + */ + function get($key) { + // @todo We may wish to add static caching here in the future, either + // as a static cache on the object or using drupal_static(). + $data = db_query( + 'SELECT * FROM {temp_store} WHERE ownerId = :session_id AND subsystem = :subsystem AND temp_key = :temp_key', + array( + ':session_id' => $this->ownerId, + ':subsystem' => $this->subsystem, + ':temp_key' => $key, + ) + ) + ->fetchObject(); + if ($data) { + return unserialize($data->data); + } + } + + /** + * Writes the data to the store. + * + * @param string $key + * The key to the object being stored. For objects that already exist in + * the database somewhere else, this is typically the primary key of that + * object. For objects that do not already exist, this is typically 'new' + * or some special key that indicates that the object does not yet exist. + * @param mixed $data + * The data to be cached. It will be serialized. + * + * @todo + * Using 'new' as a key might result in collisions if the same user tries + * to create multiple new objects simultaneously. Document a workaround? + */ + function set($key, $data) { + // Clear any existing data for this key. + $this->delete($key); + + // Store the new data. + db_insert('temp_store') + ->fields(array( + 'ownerId' => $this->ownerId, + 'subsystem' => $this->subsystem, + 'temp_key' => $key, + 'data' => serialize($data), + 'updated' => REQUEST_TIME, + )) + ->execute(); + } + + /** + * Removes one or more objects from this store for this session. + * + * @param string|array $key + * The key to the stored object, or an array of keys. See + * TempStoreBase::set() for details. + */ + function delete($key) { + $this->deleteRecords($key); + } + + /** + * Removes one or more objects from this store for all sessions. + * + * @param string|array $key + * The key to the stored object, or an array of keys. See + * TempStoreBase::set() for details. + */ + function deleteAll($key) { + $this->deleteRecords($key, TRUE); + } + + /** + * Deletes database records for objects. + * + * @param string|array $key + * The key to the stored object, or an array of keys. See + * TempStoreBase::set() for details. + * @param bool $all + * Whether to delete all records for this key (TRUE) or just the current + * owner's (FALSE). Defaults to FALSE. + */ + protected function deleteRecords($key, $all = FALSE) { + // The query builder will automatically use an IN condition when an array + // is passed. + $query = db_delete('temp_store') + ->condition('temp_key', $key) + ->condition('subsystem', $this->subsystem); + + if (!$all) { + $query->condition('ownerId', $this->ownerId); + } + + $query->execute(); + + // @todo If we add static caching to get(), we will need to invalidate + // that cache here as appropriate. + } + + /** + * Determines if the object is in use by another store for locking purposes. + * + * @param string $key + * The key to the stored object. See TempStoreBase::set() for details. + * @param bool $exclude_owner + * (optional) Whether or not to disregard the current user when determining + * the lock owner. Defaults to FALSE. + * + * @return stdClass|string|null + * An object with the user ID and updated date if found, otherwise NULL. + */ + public function getLockOwner($key, $exclude_owner = FALSE) { + return; + } + + /** + * Gets the temporary object status of multiple items. + * + * @param string $subsystem + * The module or subsystem. Possible values might include 'entity', + * 'form', 'views', etc. + * @param array $keys + * An array of keys of stored objects. See + * TempStoreBase::set() for details. + */ + public static function testStoredObjects($subsystem, $keys) { + return db_query("SELECT t.temp_key, s.uid, t.updated FROM {temp_store} t INNER JOIN {sessions} s ON t.ownerId = s.ownerId WHERE t.subsystem = :subsystem AND t.temp_key IN (:keys) ORDER BY t.updated ASC", array(':subsystem' => $subsystem, ':temp_keys' => $keys)) + ->fetchAllAssoc('temp_key'); + } + + /** + * Truncates all temporary objects with certain names in the subsystem. + * + * @param string $subsystem + * The module or subsystem. Possible values might include 'entity', + * 'form', 'views', etc. + * @param array $key + * The key to the stored object. See TempStoreBase::set() for details. + */ + public static function clearAll($subsystem, $key) { + $query = db_delete('temp_store') + ->condition('temp_key', $key) + ->condition('subsystem', $subsystem); + + $query->execute(); + + // @todo If we add static caching to get(), we will need to invalidate + // that cache here as appropriate. + } + + /** + * Truncates all temporary objects older than a certain age. + * + * @param int $age + * The minimum age of objects to remove, in seconds. For example, 86400 is + * one day. Defaults to 7 days. + */ + public static function clearOldObjects($age = NULL) { + if (!isset($age)) { + // 7 days. + $age = 86400 * 7; + } + db_delete('temp_store') + ->condition('updated', REQUEST_TIME - $age, '<') + ->execute(); + } + +} diff --git a/core/lib/Drupal/Core/TempStore/TempStoreException.php b/core/lib/Drupal/Core/TempStore/TempStoreException.php new file mode 100644 index 0000000..fe65c56 --- /dev/null +++ b/core/lib/Drupal/Core/TempStore/TempStoreException.php @@ -0,0 +1,16 @@ + 'TempStore', + 'description' => 'Tests the temporary object storage system.', + 'group' => 'TempStore', + ); + } + + protected function setUp() { + parent::setUp(); + // Create some users for testing. + $this->actingUser = $this->drupalCreateUser(); + $this->anotherUser = $this->drupalCreateUser(); + } + + /** + * Generates a random php object. + * + * @param int $size + * The amount of keys which is used for the object. + * @return stdClass + */ + public function randomObject($size = 4) { + $object = new stdClass(); + for ($i = 0; $i < $size; $i++) { + $random_key = $this->randomName(); + $random_value = $this->randomString(); + $object->{$random_key} = $random_value; + } + return $object; + } + + public function testApi() { + $this->drupalLogin($this->actingUser); + $subsystem = $this->randomName(); + $temp_store = new SessionTempStore($subsystem, $this->session_id); + + // An empty storage should return nothing. + $this->assertFalse($temp_store->get($this->randomName())); + + $random_key = $this->randomName(); + $random_object = $this->randomObject(); + $temp_store->set($random_key, $random_object); + + // Take sure that the value we put in is returned. + $result_data = $temp_store->get($random_key); + $this->assertEqual($random_object, $result_data); + + // Remove the data from the tempstore to be sure it is removed. + $temp_store->delete($random_key); + $this->assertFalse($temp_store->get($random_key)); + + // Create multiple keys to test the deleteAll functionality. + $keys = array(); + for ($i = 0; $i < 4; $i++) { + $key = $this->randomName(); + $keys[] = $key; + $temp_store->set($key, $this->randomObject()); + $stored_data = $temp_store->get($key); + $this->assertTrue(!empty($stored_data)); + } + $temp_store->deleteAll($keys); + foreach ($keys as $key) { + $this->assertFalse($temp_store->get($key)); + } + + // Test the clearOldObjects method, therefore create one entry older than + // one week and one newer than one week. + $old_key = $this->randomName(); + $new_key = $this->randomName(); + $temp_store->set($old_key, $this->randomObject()); + $temp_store->set($new_key, $this->randomObject()); + // Manually change the updated data as the api doesn't allow that. + foreach (array($old_key, $new_key) as $key) { + db_update('temp_store') + ->fields(array( + 'updated' => $key == $old_key ? REQUEST_TIME - (10 * 86400) : REQUEST_TIME - (4 * 86400), + )) + ->condition('temp_key', $key) + ->execute(); + } + SessionTempStore::clearOldObjects(7 * 86400); + $this->assertFalse($temp_store->get($old_key)); + $this->assertTrue($temp_store->get($new_key)); + + // Test the getLockOwner method. + $tempStore_one = new SessionTempStore($subsystem, $this->session_id); + + // Allow two users to be logged in at once. + $this->curlClose(); + $this->loggedInUser = FALSE; + + $this->drupalLogin($this->anotherUser); + $tempStore_two = new SessionTempStore($subsystem, $this->session_id); + + $key_user_one = $this->randomName(); + $key_user_two = $this->randomName(); + $tempStore_one->set($key_user_one, $this->randomObject()); + $tempStore_two->set($key_user_two, $this->randomObject()); + + $this->assertEqual($tempStore_one->getLockOwner($key_user_one)->uid, $this->actingUser->uid); + $this->assertEqual($tempStore_two->getLockOwner($key_user_two)->uid, $this->anotherUser->uid); + } +} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 9ba7c4c..4a62a53 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1373,6 +1373,45 @@ function system_schema() { 'primary key' => array('mlid'), ); + $schema['temp_store'] = array( + 'description' => t('A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.'), + 'fields' => array( + 'ownerId' => array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'The session ID this object belongs to.', + ), + 'subsystem' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.', + ), + 'temp_key' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The key of the object this cache is attached to.', + ), + 'updated' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The time this cache was created or updated.', + ), + 'data' => array( + 'type' => 'text', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('ownerId', 'subsystem', 'temp_key'), + 'indexes' => array('updated' => array('updated')), + ); + $schema['queue'] = array( 'description' => 'Stores items in queues.', 'fields' => array( @@ -1924,6 +1963,52 @@ function system_update_8010() { )); } +/* + * Create the 'temp_store' table. + */ +function system_update_8011() { + $table = array( + 'description' => t('A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.'), + 'fields' => array( + 'ownerId' => array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'The session ID this object belongs to.', + ), + 'subsystem' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.', + ), + 'temp_key' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'description' => 'The key of the object this cache is attached to.', + ), + 'updated' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The time this cache was created or updated.', + ), + 'data' => array( + 'type' => 'text', + 'size' => 'big', + 'description' => 'Serialized data being stored.', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('ownerId', 'subsystem', 'temp_key'), + 'indexes' => array('updated' => array('updated')), + ); + + db_create_table('temp_store', $table); +} + /** * @} End of "defgroup updates-7.x-to-8.x". * The next series of updates should start at 9000. diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css index 0531e49..72f776d 100644 --- a/core/themes/bartik/css/style.css +++ b/core/themes/bartik/css/style.css @@ -52,8 +52,6 @@ img { * The generic monospace font family is listed before Courier new to avoid a * a bug in font-size rendering: * http://meyerweb.com/eric/thoughts/2010/02/12/fixed-monospace-sizing - * - * @todo Remove when IE8 is no longer supported. */ code, pre,