config.inc 17.2 KB
Newer Older
chx's avatar
chx committed
1
2
<?php

gdd's avatar
gdd committed
3
4
5
6
7
/**
 * @file
 * This is the API for configuration storage.
 */

8
/**
9
 * Gets the randomly generated config directory name.
10
11
12
13
 *
 * @return
 *   The directory name.
 */
14
function config_get_config_directory() {
15
  global $config_directory_name;
16

17
  return conf_path() . '/files/' . $config_directory_name;
18
19
}

20
/**
21
 * Moves the default config supplied by a module to the live config directory.
22
23
24
25
26
27
28
29
30
 *
 * @param
 *   The name of the module we are installing.
 */
function config_install_default_config($module) {
  $module_config_dir = drupal_get_path('module', $module) . '/config';
  $drupal_config_dir = config_get_config_directory();
  if (is_dir(drupal_get_path('module', $module) . '/config')) {
    $files = glob($module_config_dir . '/' . '*.xml');
31
32
    foreach ($files as $key => $file) {
      // Load config data into the active store and write it out to the
33
34
35
      // file system in the drupal config directory. Note the config name
      // needs to be the same as the file name WITHOUT the extension.
      // @todo Make this acknowledge other storage engines rather than having
36
      //   SQL be hardcoded.
37
38
39
40
41
42
43
      $parts = explode('/', $file);
      $file = array_pop($parts);
      $config_name = str_replace('.xml', '', $file);

      $verified_storage = new DrupalVerifiedStorageSQL($config_name);
      $verified_storage->write(file_get_contents($module_config_dir . '/' . $file));
    }
44
  }
45
46
}

47
/**
48
 * Retrieves an iterable array which lists the children under a config 'branch'.
gdd's avatar
gdd committed
49
50
 *
 * Given the following configuration files:
51
52
 * - core.entity.node_type.article.xml
 * - core.entity.node_type.page.xml
gdd's avatar
gdd committed
53
54
 *
 * You can pass a prefix 'core.entity.node_type' and get back an array of the
sun's avatar
sun committed
55
 * filenames that match. This allows you to iterate through all files in a
gdd's avatar
gdd committed
56
57
58
59
60
 * branch. Note that this will only work on the level above the tips, so
 * a prefix of 'core.entity' would return an empty array.
 *
 * @param $prefix
 *   The prefix of the files we are searching for.
61
 *
62
 * @return
gdd's avatar
gdd committed
63
 *   An array of file names under a branch.
64
65
 */
function config_get_signed_file_storage_names_with_prefix($prefix = '') {
66
  $files = glob(config_get_config_directory() . '/' . $prefix . '*.xml');
67
  $clean_name = function ($value) {
68
    return basename($value, '.xml');
69
  };
70
  return array_map($clean_name, $files);
David Strauss's avatar
David Strauss committed
71
72
}

gdd's avatar
gdd committed
73
/**
74
 * Generates a hash of a config file's contents using our encryption key.
sun's avatar
sun committed
75
 *
gdd's avatar
gdd committed
76
77
 * @param $data
 *   The contents of a configuration file.
78
 *
gdd's avatar
gdd committed
79
80
81
 * @return
 *   A hash of the data.
 */
David Strauss's avatar
David Strauss committed
82
function config_sign_data($data) {
83
  // The configuration key is loaded from settings.php and imported into the global namespace
84
  global $config_signature_key;
85

chx's avatar
chx committed
86
  // SHA-512 is both secure and very fast on 64 bit CPUs.
87
  // @todo What about 32-bit CPUs?
88
  return hash_hmac('sha512', $data, $config_signature_key);
David Strauss's avatar
David Strauss committed
89
90
}

91
92
93
94
95
96
97
98
99
/**
 * @todo
 *
 * @param $prefix
 *   @todo
 *
 * @return
 *   @todo
 */
100
101
102
103
function config_get_verified_storage_names_with_prefix($prefix = '') {
  return DrupalVerifiedStorageSQL::getNamesWithPrefix($prefix);
}

104
105
106
107
108
109
110
111
112
/**
 * @todo
 *
 * @param $prefix
 *   @todo
 *
 * @return
 *   @todo
 */
113
114
115
116
function config_get_names_with_prefix($prefix) {
  return config_get_verified_storage_names_with_prefix($prefix);
}

Katherine Senzee's avatar
Katherine Senzee committed
117
/**
118
 * Retrieves a configuration object.
Katherine Senzee's avatar
Katherine Senzee committed
119
120
121
122
123
124
125
126
127
128
129
130
 *
 * This is the main entry point to the configuration API. Calling
 * @code config(book.admin) @endcode will return a configuration object in which
 * the book module can store its administrative settings.
 *
 * @param $name
 *   The name of the configuration object to retrieve. The name corresponds to
 *   an XML configuration file. For @code config(book.admin) @endcode, the
 *   config object returned will contain the contents of book.admin.xml.
 * @param $class
 *   The class name of the config object to be returned. Defaults to
 *   DrupalConfig.
131
 *
Katherine Senzee's avatar
Katherine Senzee committed
132
133
134
135
 * @return
 *   An instance of the class specified in the $class parameter.
 *
 */
136
function config($name, $class = 'DrupalConfig') {
137
  // @todo Replace this with an appropriate factory.
138
  return new $class(new DrupalVerifiedStorageSQL($name));
139
140
141
}

/**
142
 * Decodes configuration data from its native format to an associative array.
143
144
 *
 * @param $data
145
146
 *   Configuration data.
 *
147
148
149
150
151
152
153
154
155
 * @return
 *   An associative array representation of the data.
 */
function config_decode($data) {
  if (empty($data)) {
    return array();
  }
  $xml = new SimpleXMLElement($data);
  $json = json_encode($xml);
156
  return json_decode($json, TRUE);
157
158
159
}

/**
160
 * Standardizes SimpleXML object output into simple arrays for easier use.
gdd's avatar
gdd committed
161
162
 *
 * @param $xmlObject
163
 *   A valid XML string.
164
165
 *
 * @return
166
 *   An array representation of A SimpleXML object.
167
 */
gdd's avatar
gdd committed
168
169
170
function config_xml_to_array($data) {
  $out = array();
  $xmlObject = simplexml_load_string($data);
171

gdd's avatar
gdd committed
172
  if (is_object($xmlObject)) {
173
    $attributes = (array) $xmlObject->attributes();
gdd's avatar
gdd committed
174
175
176
177
    if (isset($attributes['@attributes'])) {
      $out['#attributes'] = $attributes['@attributes'];
    }
  }
178
179
  if (trim((string) $xmlObject)) {
    return trim((string) $xmlObject);
gdd's avatar
gdd committed
180
181
182
183
184
185
186
187
  }
  foreach ($xmlObject as $index => $content) {
    if (is_object($content)) {
      $out[$index] = config_xml2array($content);
    }
  }

  return $out;
188
189
190
}

/**
191
 * Encodes an array into the native configuration format.
192
193
194
 *
 * @param $data
 *   An associative array or an object
195
 *
196
197
 * @return
 *   A representation of this array or object in the native configuration
sun's avatar
sun committed
198
199
200
 *   format.
 *
 * @todo This needs to work for objects as well and currently doesn't.
201
 */
gdd's avatar
gdd committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
function config_encode($data) {
  // creating object of SimpleXMLElement
  $xml_object = new SimpleXMLElement("<?xml version=\"1.0\"?><config></config>");

  // function call to convert array to xml
  config_array_to_xml($data, $xml_object);

  // Pretty print the result
  $dom = new DOMDocument('1.0');
  $dom->preserveWhiteSpace = false;
  $dom->formatOutput = true;
  $dom->loadXML($xml_object->asXML());
  return $dom->saveXML();
}
gdd's avatar
gdd committed
216

gdd's avatar
gdd committed
217
/**
218
 * Encodes an array into XML
gdd's avatar
gdd committed
219
220
221
 *
 * @param $data
 *   An associative array or an object
222
 *
gdd's avatar
gdd committed
223
224
 * @return
 *   A representation of this array or object in the native configuration
sun's avatar
sun committed
225
226
227
 *   format.
 *
 * @todo This needs to work for objects as well and currently doesn't.
gdd's avatar
gdd committed
228
229
230
231
232
233
234
235
236
237
238
 */
function config_array_to_xml($array, &$xml_object) {
  foreach ($array as $key => $value) {
    if (is_array($value)) {
      if (!is_numeric($key)){
        $subnode = $xml_object->addChild("$key");
        config_array_to_xml($value, $subnode);
      }
      else {
        config_array_to_xml($value, $xml_object);
      }
Katherine Senzee's avatar
Katherine Senzee committed
239
    }
gdd's avatar
gdd committed
240
    else {
241
      $xml_object->addChild("$key", "$value");
gdd's avatar
gdd committed
242
243
    }
  }
244
}
gdd's avatar
gdd committed
245

246
247
248
/**
 * @todo
 */
249
250
class ConfigException extends Exception {}

251
252
253
/**
 * @todo
 */
254
class ConfigFileStorageException extends ConfigException {}
255
256
257
258

/**
 * @todo
 */
259
class ConfigFileStorageReadException extends ConfigFileStorageException {}
260
261
262
263

/**
 * @todo
 */
264
265
class ConfigFileStorageSignatureException extends ConfigFileStorageException {}

Katherine Senzee's avatar
Katherine Senzee committed
266
267
268
269
270
271
272
/**
 * Represents the signed file storage interface.
 *
 * Classes implementing this interface allow reading and writing configuration
 * data to and from disk, while automatically managing and verifying
 * cryptographic signatures.
 */
David Strauss's avatar
David Strauss committed
273
class SignedFileStorage {
Katherine Senzee's avatar
Katherine Senzee committed
274

275
  /**
Katherine Senzee's avatar
Katherine Senzee committed
276
   * Constructs a SignedFileStorage object.
Crell's avatar
Crell committed
277
   *
278
   * @param string $name
Katherine Senzee's avatar
Katherine Senzee committed
279
   *   The name for the configuration data. Should be lowercase.
280
   */
David Strauss's avatar
David Strauss committed
281
  public function __construct($name) {
David Strauss's avatar
David Strauss committed
282
283
284
    $this->name = $name;
  }

285
  /**
Katherine Senzee's avatar
Katherine Senzee committed
286
   * Reads and returns a signed file and its signature.
287
288
289
290
291
292
293
   *
   * @return
   *   An array with "signature" and "data" keys.
   *
   * @throws
   *   Exception
   */
294
  protected function readWithSignature() {
295
    // @todo Optimize with explicit offsets?
Crell's avatar
Crell committed
296
    $content = file_get_contents($this->getFilePath());
297
298
    if ($content === FALSE) {
      throw new Exception('Read file is invalid.');
299
    }
gdd's avatar
gdd committed
300
    $signature = file_get_contents($this->getFilePath() . '.sig');
301
302
    if ($signature === FALSE) {
      throw new Exception('Signature file is invalid.');
303
    }
304
    return array('data' => $content, 'signature' => $signature);
David Strauss's avatar
David Strauss committed
305
306
  }

Katherine Senzee's avatar
Katherine Senzee committed
307
308
  /**
   * Checks whether the XML configuration file already exists on disk.
309
310
311
   *
   * @return
   *   @todo
Katherine Senzee's avatar
Katherine Senzee committed
312
   */
313
314
315
  protected function exists() {
    return file_exists($this->getFilePath());
  }
Katherine Senzee's avatar
Katherine Senzee committed
316
317
318

  /**
   * Returns the path to the XML configuration file.
319
320
321
   *
   * @return
   *   @todo
Katherine Senzee's avatar
Katherine Senzee committed
322
   */
323
324
  public function getFilePath() {
    return config_get_config_directory() . '/' . $this->name  . '.xml';
David Strauss's avatar
David Strauss committed
325
326
  }

Katherine Senzee's avatar
Katherine Senzee committed
327
328
329
  /**
   * Recreates the signature for the file.
   */
David Strauss's avatar
David Strauss committed
330
  public function resign() {
331
    if ($this->exists()) {
332
      $parts = $this->readWithSignature();
333
334
      $this->write($parts['data']);
    }
David Strauss's avatar
David Strauss committed
335
336
  }

Katherine Senzee's avatar
Katherine Senzee committed
337
338
339
340
341
  /**
   * Cryptographically verifies the integrity of the configuration file.
   *
   * @param $contentOnSuccess
   *   Whether or not to return the contents of the verified configuration file.
342
   *
Katherine Senzee's avatar
Katherine Senzee committed
343
344
345
346
347
   * @return mixed
   *   If $contentOnSuccess was TRUE, returns the contents of the verified
   *   configuration file; otherwise returns TRUE on success. Always returns
   *   FALSE if the configuration file was not successfully verified.
   */
David Strauss's avatar
David Strauss committed
348
  public function verify($contentOnSuccess = FALSE) {
349
    if ($this->exists()) {
350
      $split = $this->readWithSignature();
351
352
353
354
355
356
      $expected_signature = config_sign_data($split['data']);
      if ($expected_signature === $split['signature']) {
        if ($contentOnSuccess) {
          return $split['data'];
        }
        return TRUE;
David Strauss's avatar
David Strauss committed
357
358
359
360
361
      }
    }
    return FALSE;
  }

Katherine Senzee's avatar
Katherine Senzee committed
362
363
364
365
  /**
   * Writes the contents of the configuration file to disk.
   *
   * @param $data
366
   *   The data to be written to the file.
Katherine Senzee's avatar
Katherine Senzee committed
367
368
369
   *
   * @throws
   *   Exception
370
371
   *
   * @todo What format is $data in?
Katherine Senzee's avatar
Katherine Senzee committed
372
   */
David Strauss's avatar
David Strauss committed
373
374
  public function write($data) {
    $signature = config_sign_data($data);
375
    if (!file_put_contents($this->getFilePath(), $data)) {
376
377
      throw new Exception('Failed to write configuration file.');
    }
gdd's avatar
gdd committed
378
    if (!file_put_contents($this->getFilePath() . '.sig', $signature)) {
379
      throw new Exception('Failed to write signature file.');
David Strauss's avatar
David Strauss committed
380
381
382
    }
  }

Katherine Senzee's avatar
Katherine Senzee committed
383
384
  /**
   * Returns the contents of the configuration file.
385
386
387
   *
   * @return
   *   @todo
Katherine Senzee's avatar
Katherine Senzee committed
388
   */
David Strauss's avatar
David Strauss committed
389
  public function read() {
390
391
392
393
394
395
    if ($this->exists()) {
      $verification = $this->verify(TRUE);
      if ($verification === FALSE) {
        throw new Exception('Invalid signature in file header.');
      }
      return $verification;
David Strauss's avatar
David Strauss committed
396
397
    }
  }
398

399
  /**
400
   * Deletes a configuration file.
401
402
   */
  public function delete() {
403
    // Needs error handling and etc.
404
    @drupal_unlink($this->getFilePath());
gdd's avatar
gdd committed
405
    @drupal_unlink($this->getFilePath() . '.sig');
406
  }
David Strauss's avatar
David Strauss committed
407
408
}

409
410
411
412
413
414
/**
 * Defines an interface for verified storage manipulation.
 *
 * This class allows reading and writing configuration data from/to the
 * verified storage and copying to/from the signed file storing the same data.
 */
415
416
417
interface DrupalConfigVerifiedStorageInterface {

  /**
418
   * Constructs a verified storage manipulation class.
Crell's avatar
Crell committed
419
   *
420
   * @param $name
421
422
   *   Lowercase string, the name for the configuration data.
   */
David Strauss's avatar
David Strauss committed
423
  function __construct($name);
chx's avatar
chx committed
424

425
  /**
426
   * Reads the configuration data from the verified storage.
427
   */
chx's avatar
chx committed
428
429
  function read();

430
  /**
431
   * Copies the configuration data from the verified storage into a file.
432
433
434
435
   */
  function copyToFile();

  /**
436
   * Copies the configuration data from the file into the verified storage.
437
438
439
   */
  function copyFromFile();

440
  /**
441
   * Deletes the configuration data file.
442
443
444
   */
  function deleteFile();

445
  /**
446
   * Checks whether the file and the verified storage is in sync.
Crell's avatar
Crell committed
447
   *
448
449
450
451
   * @return
   *   TRUE if the file and the verified storage contains the same data, FALSE
   *   if not.
   */
chx's avatar
chx committed
452
453
  function isOutOfSync();

454
  /**
455
   * Writes the configuration data into the active storage but not the file.
Crell's avatar
Crell committed
456
   *
457
458
   * Use this function if you need to make temporary changes to your
   * configuration.
459
460
461
   *
   * @param $data
   *   The configuration data to write into active storage.
462
   */
chx's avatar
chx committed
463
  function writeToActive($data);
chx's avatar
chx committed
464

465
  /**
466
467
468
469
   * Writes the configuration data into the active storage and the file.
   *
   * @param $data
   *   The configuration data to write.
470
   */
chx's avatar
chx committed
471
  function write($data);
chx's avatar
chx committed
472

473
  /**
474
475
476
477
   * Gets names starting with this prefix.
   *
   * @param $prefix
   *   @todo
478
   */
chx's avatar
chx committed
479
  static function getNamesWithPrefix($prefix);
chx's avatar
chx committed
480
481
}

482
483
484
/**
 * @todo
 */
485
abstract class DrupalConfigVerifiedStorage implements DrupalConfigVerifiedStorageInterface {
486
487
488
489

  /**
   * Implements DrupalConfigVerifiedStorageInterface::__construct().
   */
David Strauss's avatar
David Strauss committed
490
  function __construct($name) {
chx's avatar
chx committed
491
492
493
    $this->name = $name;
  }

494
495
496
497
498
499
  /**
   * @todo
   *
   * @return
   *   @todo
   */
chx's avatar
chx committed
500
  protected function signedFileStorage() {
David Strauss's avatar
David Strauss committed
501
    return new SignedFileStorage($this->name);
chx's avatar
chx committed
502
503
  }

504
505
506
  /**
   * Implements DrupalConfigVerifiedStorageInterface::copyToFile().
   */
sun's avatar
sun committed
507
  public function copyToFile() {
508
    return $this->signedFileStorage()->write($this->read());
chx's avatar
chx committed
509
  }
sun's avatar
sun committed
510

511
512
513
  /**
   * Implements DrupalConfigVerifiedStorageInterface::deleteFile().
   */
514
515
516
  public function deleteFile() {
    return $this->signedFileStorage()->delete();
  }
chx's avatar
chx committed
517

518
519
520
  /**
   * Implements DrupalConfigVerifiedStorageInterface::copyFromFile().
   */
Crell's avatar
Crell committed
521
  public function copyFromFile() {
chx's avatar
chx committed
522
523
524
    return $this->writeToActive($this->readFromFile());
  }

525
526
527
528
529
530
  /**
   * @todo
   *
   * @return
   *   @todo
   */
Crell's avatar
Crell committed
531
  public function readFromFile() {
chx's avatar
chx committed
532
    return $this->signedFileStorage()->read($this->name);
chx's avatar
chx committed
533
534
  }

535
536
537
  /**
   * Implements DrupalConfigVerifiedStorageInterface::isOutOfSync().
   */
Crell's avatar
Crell committed
538
  public function isOutOfSync() {
chx's avatar
chx committed
539
    return $this->read() !== $this->readFromFile();
chx's avatar
chx committed
540
541
  }

542
543
544
  /**
   * Implements DrupalConfigVerifiedStorageInterface::write().
   */
545
546
  public function write($data) {
    $this->writeToActive($data);
547
    $this->copyToFile();
548
  }
549

550
551
552
  /**
   * Implements DrupalConfigVerifiedStorageInterface::delete().
   */
553
554
555
556
  public function delete() {
    $this->deleteFromActive();
    $this->deleteFile();
  }
557
558
}

Katherine Senzee's avatar
Katherine Senzee committed
559
560
561
/**
 * Represents an SQL-based configuration storage object.
 */
562
563
class DrupalVerifiedStorageSQL extends DrupalConfigVerifiedStorage {

564
565
566
  /**
   * Overrides DrupalConfigVerifiedStorage::read().
   */
David Strauss's avatar
David Strauss committed
567
  public function read() {
568
569
    // There are situations, like in the installer, where we may attempt a
    // read without actually having the database available. This is a
gdd's avatar
gdd committed
570
571
    // workaround and there is probably a better solution to be had at
    // some point.
572
    if (!empty($GLOBALS['databases']) && db_table_exists('config')) {
gdd's avatar
gdd committed
573
574
      return db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $this->name))->fetchField();
    }
575
576
  }

577
578
579
  /**
   * Implements DrupalConfigVerifiedStorageInterface::writeToActive().
   */
Crell's avatar
Crell committed
580
  public function writeToActive($data) {
chx's avatar
chx committed
581
    return db_merge('config')
David Strauss's avatar
David Strauss committed
582
      ->key(array('name' => $this->name))
chx's avatar
chx committed
583
      ->fields(array('data' => $data))
chx's avatar
chx committed
584
585
586
      ->execute();
  }

587
588
589
  /**
   * @todo
   */
590
591
592
593
594
595
  public function deleteFromActive() {
    db_delete('config')
      ->condition('name', $this->name)
      ->execute();
  }

596
597
598
  /**
   * Implements DrupalConfigVerifiedStorageInterface::getNamesWithPrefix().
   */
David Strauss's avatar
David Strauss committed
599
  static public function getNamesWithPrefix($prefix = '') {
600
    return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol();
chx's avatar
chx committed
601
602
603
  }
}

Katherine Senzee's avatar
Katherine Senzee committed
604
605
606
/**
 * Represents the default configuration storage object.
 */
607
608
class DrupalConfig {

Crell's avatar
Crell committed
609
610
611
612
613
614
  /**
   * The storage engine to save this config object to.
   *
   * @var DrupalConfigVerifiedStorageInterface
   */
  protected $_verifiedStorage;
615

616
617
  protected $data = array();

Katherine Senzee's avatar
Katherine Senzee committed
618
619
620
621
622
623
  /**
   * Constructs a DrupalConfig object.
   *
   * @param DrupalConfigVerifiedStorageInterface $verified_storage
   *   The storage engine where this config object should be saved.
   */
624
  public function __construct(DrupalConfigVerifiedStorageInterface $verified_storage) {
Crell's avatar
Crell committed
625
    $this->_verifiedStorage = $verified_storage;
626
627
    $this->read();
  }
628

629
  /**
sun's avatar
sun committed
630
   * Reads config data from the active store into our object.
631
632
   */
  public function read() {
633
    $active = (array) config_decode($this->_verifiedStorage->read());
634
    foreach ($active as $key => $value) {
635
      $this->set($key, $value);
636
    }
637
638
  }

Katherine Senzee's avatar
Katherine Senzee committed
639
640
  /**
   * Checks whether a particular value is overridden.
641
642
643
644
645
646
   *
   * @param $key
   *   @todo
   *
   * @return
   *   @todo
Katherine Senzee's avatar
Katherine Senzee committed
647
   */
Crell's avatar
Crell committed
648
649
650
651
  public function isOverridden($key) {
    return isset($this->_overrides[$key]);
  }

Justin Randell's avatar
Justin Randell committed
652
  /**
sun's avatar
sun committed
653
   * Gets value in this config object.
654
655
656
657
658
659
   *
   * @param $key
   *   @todo
   *
   * @return
   *   @todo
Justin Randell's avatar
Justin Randell committed
660
   */
sun's avatar
sun committed
661
  public function get($key) {
662
663
    $parts = explode('.', $key);
    if (count($parts) == 1) {
sun's avatar
sun committed
664
      return isset($this->data[$key]) ? $this->data[$key] : NULL;
Crell's avatar
Crell committed
665
    }
666
    else {
sun's avatar
sun committed
667
      return drupal_array_get_nested_value($this->data, $parts);
668
669
670
    }
  }

gdd's avatar
gdd committed
671
  /**
sun's avatar
sun committed
672
   * Sets value in this config object.
673
674
675
676
677
   *
   * @param $key
   *   @todo
   * @param $value
   *   @todo
gdd's avatar
gdd committed
678
   */
sun's avatar
sun committed
679
  public function set($key, $value) {
gdd's avatar
gdd committed
680
681
    $parts = explode('.', $key);
    if (count($parts) == 1) {
sun's avatar
sun committed
682
      $this->data[$key] = $value;
gdd's avatar
gdd committed
683
684
    }
    else {
sun's avatar
sun committed
685
      drupal_array_set_nested_value($this->data, $parts, $value);
gdd's avatar
gdd committed
686
687
688
    }
  }

Justin Randell's avatar
Justin Randell committed
689
  /**
sun's avatar
sun committed
690
   * Unsets value in this config object.
691
692
693
   *
   * @param $key
   *   @todo
Justin Randell's avatar
Justin Randell committed
694
   */
sun's avatar
sun committed
695
  public function clear($key) {
696
697
    $parts = explode('.', $key);
    if (count($parts) == 1) {
sun's avatar
sun committed
698
      unset($this->data[$key]);
699
700
    }
    else {
sun's avatar
sun committed
701
      drupal_array_unset_nested_value($this->data, $parts);
702
703
704
    }
  }

Katherine Senzee's avatar
Katherine Senzee committed
705
706
707
  /**
   * Saves the configuration object to disk as XML.
   */
708
709
  public function save() {
    $this->_verifiedStorage->write(config_encode($this->data));
710
  }
Justin Randell's avatar
Justin Randell committed
711

712
713
714
715
716
717
718
  /**
   * Deletes the configuration object on disk.
   */
  public function delete() {
    $this->_verifiedStorage->delete();
  }
}