Parent Directory
|
Revision Log
|
Revision Graph
Initial commit of the Configuration Framework. This framework will be used as the core of the Patterns module and provide a programming interface to configure sites.
| 1 | <?php |
| 2 | // $Id: $ |
| 3 | |
| 4 | /** |
| 5 | * @file |
| 6 | * Provide a unified method for defining site configurations abstracted from their data format. |
| 7 | * Various data formats should be supported via a plugin architecture such as XML, YAML, JSON, PHP |
| 8 | */ |
| 9 | |
| 10 | /** |
| 11 | * The first execution phase: Apply the base map to $context |
| 12 | */ |
| 13 | define('CONFIGURATION_INITIALIZE', 1); |
| 14 | |
| 15 | /** |
| 16 | * The second execution phase: Apply the base map to $context |
| 17 | */ |
| 18 | define('CONFIGURATION_DEFAULT_MAP', 2); |
| 19 | |
| 20 | /** |
| 21 | * The third execution phase: Enable all modules needed for the configuration |
| 22 | */ |
| 23 | define('CONFIGURATION_ENABLE_MODULES', 3); |
| 24 | |
| 25 | /** |
| 26 | * The fourth execution phase: Apply all modules configuration maps |
| 27 | */ |
| 28 | define('CONFIGURATION_MODULE_MAPS', 4); |
| 29 | |
| 30 | /** |
| 31 | * The fifth execution phase: Execute the built up actions, one action per execution pass. |
| 32 | */ |
| 33 | define('CONFIGURATION_EXECUTE_ACTIONS', 5); |
| 34 | |
| 35 | /** |
| 36 | * The six execution phase: Disable any modules marked for disabling |
| 37 | */ |
| 38 | define('CONFIGURATION_DISABLE_MODULES', 6); |
| 39 | |
| 40 | /** |
| 41 | * The seventh and last execution phase: Any needed cleanup operations |
| 42 | */ |
| 43 | define('CONFIGURATION_FINALIZE', 7); |
| 44 | |
| 45 | /** |
| 46 | * An execution phase to mark the success and completion of the configuration |
| 47 | */ |
| 48 | define('CONFIGURATION_SUCCESS', -1); |
| 49 | |
| 50 | /** |
| 51 | * An execution phase to mark an error in the execution somewhere |
| 52 | */ |
| 53 | define('CONFIGURATION_ERROR', 0); |
| 54 | |
| 55 | /** |
| 56 | * The key that signals an attribute in the end-result parsed data |
| 57 | */ |
| 58 | define('CONFIGURATION_ATTRIBUTE_KEY', '$'); |
| 59 | |
| 60 | /** |
| 61 | * Implementation of hook_perm() |
| 62 | */ |
| 63 | function configuration_perm() { |
| 64 | return array('execute configurations'); |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Execute a configuration diven a data object of actions. The supplied |
| 69 | * data should be a php array in the correct format. |
| 70 | * |
| 71 | * Here is an example configuration to setup two blocks: |
| 72 | * |
| 73 | * array( |
| 74 | * array( |
| 75 | * tag => block |
| 76 | * config => array( |
| 77 | * description => My Custom Block |
| 78 | * region => right |
| 79 | * weight => -5 |
| 80 | * ) |
| 81 | * ) |
| 82 | * array( |
| 83 | * tag => block |
| 84 | * config => array( |
| 85 | * id => locale-0 |
| 86 | * region => left |
| 87 | * weight => 5 |
| 88 | * ) |
| 89 | * ) |
| 90 | * ) |
| 91 | * |
| 92 | * @param $data |
| 93 | * The dataset containing the configuration to execute |
| 94 | * @param $mode |
| 95 | * A string that will form a callback function used to dictate |
| 96 | * execution of the configuration. Two default modes are: |
| 97 | * - batch: Run the configuration on a batch/progress bar page |
| 98 | * - xmlrpc: Run the configuration through services without a |
| 99 | * break in the pages php execution |
| 100 | * |
| 101 | * The job of the callback function is to ensure that data |
| 102 | * set in the configuration_set_data static cache is maintained |
| 103 | * through any possible page re-loads or however the execution |
| 104 | * is processed so that each pass of configuration_execute_pass |
| 105 | * has the right data available to it. |
| 106 | * @return |
| 107 | * TRUE or FALSE for successful execution. The exact error in case |
| 108 | * of an unsuccessful run can be retrieved with configuration_get_error() |
| 109 | */ |
| 110 | function configuration_execute($data, $mode = 'batch', $current_id = null) { |
| 111 | $args = func_get_args(); |
| 112 | array_splice($args, 0, 3); |
| 113 | |
| 114 | $function = 'configuration_execute_'. $mode; |
| 115 | |
| 116 | if (!function_exists($function)) { |
| 117 | return FALSE; |
| 118 | } |
| 119 | |
| 120 | // Create and set a unique id for this execution |
| 121 | if (!$current_id) { |
| 122 | $current_id = md5(mt_rand()); |
| 123 | } |
| 124 | |
| 125 | configuration_set_current_id($current_id); |
| 126 | |
| 127 | // Set the current data so it can be used with this execution |
| 128 | configuration_set_data('data', $data); |
| 129 | |
| 130 | // Set to the first phase to get things started |
| 131 | configuration_set_data('phase', CONFIGURATION_INTIALIZE); |
| 132 | |
| 133 | return call_user_func_array($function, $args); |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Run through a pass of the configuration process |
| 138 | * |
| 139 | * @return |
| 140 | * Return the current phase |
| 141 | */ |
| 142 | function configuration_run_pass() { |
| 143 | $phases = array(CONFIGURATION_INTIALIZE, CONFIGURATION_DEFAULT_MAP, CONFIGURATION_ENABLE_MODULES, CONFIGURATION_MODULE_MAPS, CONFIGURATION_EXECUTE_ACTIONS, CONFIGURATION_DISABLE_MODULES, CONFIGURATION_FINALIZE); |
| 144 | |
| 145 | $phase =& configuration_get_data('phase'); |
| 146 | $context =& configuration_get_data('context'); |
| 147 | $data =& configuration_get_data('data'); |
| 148 | |
| 149 | // The context object is required for every phase except the first |
| 150 | // Also, the original data object should always be a reference to the |
| 151 | // data in the context object which we can't guarantee unless we do it |
| 152 | // ourselves here. |
| 153 | if (is_object($context)) { |
| 154 | $data = &$context->item; |
| 155 | } |
| 156 | else if ($phase > CONFIGURATION_INTIALIZE) { |
| 157 | configuration_set_error('no_context'); |
| 158 | $phase = CONFIGURATION_ERROR; |
| 159 | } |
| 160 | |
| 161 | // Ensure our phase is valid |
| 162 | if (!in_array($phase, $phases)) { |
| 163 | configuration_set_error('invalid_phase', array('phase' => $phase)); |
| 164 | } |
| 165 | |
| 166 | // Make sure necessary include files are loaded |
| 167 | configuration_load_includes(); |
| 168 | |
| 169 | switch($phase) { |
| 170 | // TODO Decide if maybe we should run the first few phases all at once |
| 171 | case CONFIGURATION_INTIALIZE: |
| 172 | configuration_initiate(); |
| 173 | $phase++; |
| 174 | break; |
| 175 | case CONFIGURATION_DEFAULT_MAP: |
| 176 | // First apply the base configuration map. This primary purpose |
| 177 | // of this map is to set appropriate attributes in the context |
| 178 | // object, make it look a bit nicer, and build the module list |
| 179 | configuration_apply_map($context, configuration_default_map()); |
| 180 | |
| 181 | if (!configuration_get_error()) { |
| 182 | $phase++; |
| 183 | } |
| 184 | break; |
| 185 | case CONFIGURATION_ENABLE_MODULES: |
| 186 | // Enable all modules set in the configuration |
| 187 | $modules =& configuration_get_data('enable modules'); |
| 188 | if (!empty($modules)) { |
| 189 | configuration_enable_modules($modules); |
| 190 | } |
| 191 | |
| 192 | if (!configuration_get_error()) { |
| 193 | $phase++; |
| 194 | } |
| 195 | break; |
| 196 | case CONFIGURATION_MODULE_MAPS: |
| 197 | // Apply all module configuration maps to the object to prepare |
| 198 | // the data, apply some basic validations, and setup data needed |
| 199 | // for the next configuration phases |
| 200 | configuration_apply_maps($context); |
| 201 | |
| 202 | if (!configuration_get_error()) { |
| 203 | $phase++; |
| 204 | } |
| 205 | break; |
| 206 | case CONFIGURATION_EXECUTE_ACTIONS: |
| 207 | // Go through the built list of actions and build/run them one at a time |
| 208 | configuration_process_actions(); |
| 209 | |
| 210 | $left = count(configuration_get_data('actions')); |
| 211 | if ($phase != CONFIGURATION_ERROR && !$left) { |
| 212 | $phase++; |
| 213 | } |
| 214 | break; |
| 215 | case CONFIGURATION_DISABLE_MODULES: |
| 216 | // Disable modules |
| 217 | $modules =& configuration_get_data('disable modules'); |
| 218 | if (!empty($modules)) { |
| 219 | configuration_disable_modules($modules); |
| 220 | } |
| 221 | |
| 222 | if (!configuration_get_error()) { |
| 223 | $phase++; |
| 224 | } |
| 225 | break; |
| 226 | case CONFIGURATION_FINALIZE: |
| 227 | // The configuration succeed so do any necessary cleanup |
| 228 | configuration_finalize(); |
| 229 | $phases++; |
| 230 | break; |
| 231 | case CONFIGURATION_SUCCESS: |
| 232 | // We are done. Nothing to do here. |
| 233 | break; |
| 234 | case CONFIGURATION_ERROR: |
| 235 | default: |
| 236 | // TODO Error handling |
| 237 | break; |
| 238 | } |
| 239 | |
| 240 | // Any data can be retreived through configuration_get_data, but this can be |
| 241 | // a quick way of determining if there was an error |
| 242 | return $phase; |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * The "batch" method of executing a configuration |
| 247 | * |
| 248 | * The job of this method is to save and set the configuration data |
| 249 | * upon the start of each batch operation while passing it through |
| 250 | * the batch sandbox |
| 251 | */ |
| 252 | function configuration_execute_batch() { |
| 253 | $batch = array( |
| 254 | 'init_message' => t('Running action @current out of @total', array('@current' => 1, '@total' => 7)), |
| 255 | 'progress_message' => t('Running action @current out of @total.'), |
| 256 | 'operations' => array(), |
| 257 | ); |
| 258 | |
| 259 | for($i=1;$i<=7;$i++) { |
| 260 | $batch['operations'][] = array('_configuration_execute_batch', array()); |
| 261 | } |
| 262 | |
| 263 | batch_set($batch); |
| 264 | |
| 265 | // Get the current batch so we can add the current configuration data |
| 266 | $batch =& batch_get(); |
| 267 | $batch['sets'][0]['results']['configuration_data'] = configuration_get_data(null, true); |
| 268 | |
| 269 | batch_process(); |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Batch callback function for batch execution |
| 274 | */ |
| 275 | function _configuration_execute_batch(&$batch_context) { |
| 276 | // Set the configuration data |
| 277 | configuration_set_data(null, null, $batch_context['results']['configuration_data']); |
| 278 | |
| 279 | // Bail on an error |
| 280 | if (configuration_get_error()) { |
| 281 | configuration_debug_batch(configuration_set_error()); |
| 282 | } |
| 283 | |
| 284 | // Start a timer. Since we want each action to be its own http request, we need |
| 285 | // to ensure the batch api will decide to do it like that by making each action |
| 286 | // take at least a second to execute |
| 287 | timer_start('configuration_action'); |
| 288 | |
| 289 | // Run the current pass |
| 290 | $phase = configuration_run_pass(); |
| 291 | |
| 292 | // Deal with custom stuff for the error and action phases |
| 293 | if ($phase == CONFIGURATION_EXECUTE_ACTIONS) { |
| 294 | $done = configuration_get_data('actions done'); |
| 295 | $left = configuration_get_data('actions'); |
| 296 | $done = count($done); |
| 297 | $left = count($left); |
| 298 | |
| 299 | if ($done != $left) { |
| 300 | // Set the progress and message of the actions |
| 301 | $batch_context['finished'] = $done / ($done + $left); |
| 302 | $batch_context['message'] = t('@current out of @total', array('@current' => $done, '@total' => $done + $left)); |
| 303 | |
| 304 | // Sleep till 1000 (a second) if needed so we know we'll get a new page load |
| 305 | if (timer_read('configuration_action') < 1000) { |
| 306 | //@usleep(1000 - timer_read('configuration_action')); |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | // Make sure batch is updated with the updated configuration data store |
| 312 | $batch_context['results']['configuration_data'] = configuration_set_data(); |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * Print debug data during a batch operation |
| 317 | */ |
| 318 | function configuration_debug_batch($var = null) { |
| 319 | $var = print_r($var, true); |
| 320 | $var = str_replace(" ", ' ', $var); |
| 321 | $var = str_replace("\n", "<br />", $var); |
| 322 | print drupal_to_js(array('status' => 0, 'data' => $var)); |
| 323 | exit; |
| 324 | } |
| 325 | |
| 326 | /** |
| 327 | * Move through the actions list processing one per call |
| 328 | */ |
| 329 | function configuration_process_actions() { |
| 330 | $actions =& configuration_get_data('actions'); |
| 331 | $done =& configuration_get_data('actions done'); |
| 332 | |
| 333 | if (empty($actions) || !isset($actions[0])) { |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | // Get the next action |
| 338 | $action = $actions[0]; |
| 339 | array_shift($actions); |
| 340 | |
| 341 | // If the action here is not set, call the callback to set them |
| 342 | if (!isset($action[0]) && ($callback = $action[1]->action_callback) && function_exists($callback)) { |
| 343 | $value = $callback($action[1]->item, $action[1]); |
| 344 | |
| 345 | if (!configuration_get_error() && !isset($value)) { |
| 346 | $done[] = $action; |
| 347 | return true; |
| 348 | } |
| 349 | |
| 350 | if (is_scalar($value)) { |
| 351 | $value = array($value); |
| 352 | } |
| 353 | |
| 354 | // Insert the action(s) here and move out so the next calls can handle them |
| 355 | $context = &$action[1]; |
| 356 | for($i=count($value)-1;isset($value[$i]);$i--) { |
| 357 | $new = array($value[$i], &$context); |
| 358 | array_unshift($actions, $new); |
| 359 | } |
| 360 | |
| 361 | $done[] = $action; |
| 362 | return true; |
| 363 | } |
| 364 | else if (!isset($action[0])){ |
| 365 | configuration_set_error('missing action', array('!context' => $action[1])); |
| 366 | } |
| 367 | |
| 368 | // Process this action |
| 369 | if (!configuration_get_error() && $action[0]) { |
| 370 | configuration_process_action($action); |
| 371 | } |
| 372 | |
| 373 | // Mark this action as complete if no errors |
| 374 | if (!configuration_get_error()) { |
| 375 | $done[] = $action; |
| 376 | } |
| 377 | else { |
| 378 | // Put the action back on if there was an error. Maybe the error |
| 379 | // can be corrected and tried again |
| 380 | array_unshift($actions, $action); |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | /** |
| 385 | * Execute a single action |
| 386 | */ |
| 387 | function configuration_process_action($action) { |
| 388 | // Get the module list of actions and identifiers |
| 389 | $actions =& module_invoke_all('configuration_actions'); |
| 390 | $identifiers =& configuration_get_data('identifiers'); |
| 391 | |
| 392 | $action_id = $action[0]; |
| 393 | $action_info = $actions[$action_id]; |
| 394 | $action_context = &$action[1]; |
| 395 | |
| 396 | if (!isset($action_info)) { |
| 397 | configuration_set_error('invalid action id', array('!action' => $action_id)); |
| 398 | return false; |
| 399 | } |
| 400 | |
| 401 | // Replace identifiers and tokens in the action data |
| 402 | configuration_replace_tokens($action_context, $identifiers); |
| 403 | |
| 404 | // Unlikely, but if there is an error replacing tokens |
| 405 | if (configuration_get_error()) { |
| 406 | return false; |
| 407 | } |
| 408 | |
| 409 | // With proper data and values replaced, apply the actions build map. This is a great |
| 410 | // place to do any validation as well (using a #validate callback property or directly) |
| 411 | // TODO Implement #validate property... |
| 412 | if ($action_info['#build map']) { |
| 413 | configuration_apply_map($action_context, $action_info['#build map']); |
| 414 | } |
| 415 | |
| 416 | if (configuration_get_error()) { |
| 417 | configuration_set_error('action build map', array('!action' => $action_id)); |
| 418 | } |
| 419 | |
| 420 | // Build the execute $args array. Starting with the actual form data |
| 421 | //if ($action_context->build_data) { |
| 422 | if ($action_info['#build data'] && ($matches = configuration_fetch($action_info['#build data'], $action_context))) { |
| 423 | $action_data = $matches[0]->item; |
| 424 | } |
| 425 | else { |
| 426 | $action_data = $action_context->item; |
| 427 | } |
| 428 | |
| 429 | // Then the form state |
| 430 | if ($action_context->form_state) { |
| 431 | $action_state = &$action_context->form_state; |
| 432 | $action_state['values'] = &$action_data; |
| 433 | } |
| 434 | else { |
| 435 | $action_state = array( |
| 436 | 'storage' => null, |
| 437 | 'submitted' => false, |
| 438 | 'values' => &$action_data |
| 439 | ); |
| 440 | } |
| 441 | // Then the params |
| 442 | for($i=0;isset($action_info['#parameters'][$i]);$i++) { |
| 443 | $matches = configuration_fetch($action_info['#parameters'][$i], $action_context); |
| 444 | // Ensure that the parameter is at least set |
| 445 | $params[$i] = null; |
| 446 | if (isset($matches[0])) { |
| 447 | $params[$i] = $matches[0]->item; |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | // Drupal Execute |
| 452 | configuration_drupal_execute($action_id, $action_state, $params); |
| 453 | |
| 454 | // Store any form errors |
| 455 | if ($form_errors = form_set_error()) { |
| 456 | configuration_set_error('form error', $form_errors); |
| 457 | } |
| 458 | // Run an on-success map and save any identifiers |
| 459 | else { |
| 460 | if ($action_info['#success map']) { |
| 461 | configuration_apply_map($action_context, $action_info['#success map']); |
| 462 | } |
| 463 | // TODO Replace identifiers before running action |
| 464 | if (!empty($action_info['#identifiers'])) { |
| 465 | for($i=0;isset($action_info['#identifiers'][$i]);$i++) { |
| 466 | $matches = configuration_fetch($action_info['#identifiers'][$i], $action_context); |
| 467 | |
| 468 | // TODO Should we set an error if count($matches) > 1 ? |
| 469 | if (!empty($matches)) { |
| 470 | $action_context->identifiers[$i] = $matches[0]->item; |
| 471 | |
| 472 | // Make an id=>identifier map for easier access |
| 473 | if ($action_context->id) { |
| 474 | $identifiers[$action_context->id] = &$action_context->identifiers[$i]; |
| 475 | } |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | // Cleanup items |
| 482 | unset($action_context->build_data, $action_context->form_state); |
| 483 | |
| 484 | if ($action_info['#cleanup map']) { |
| 485 | configuration_apply_map($action_context, $action_info['#cleanup map']); |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | /** |
| 490 | * Apply all module configuration maps to the data while |
| 491 | * building the action queue at the same time |
| 492 | * |
| 493 | * @param $context |
| 494 | * The context array needing the config maps applied to |
| 495 | * @return |
| 496 | * An array of actions referencing points in the $context object for |
| 497 | * later use in action execution |
| 498 | */ |
| 499 | function configuration_apply_maps(&$context) { |
| 500 | // Get all the module maps and apply each one to the context object |
| 501 | foreach(module_implements('configuration_maps') as $module) { |
| 502 | $map = module_invoke($module, 'configuration_maps'); |
| 503 | configuration_apply_map($context, $map); |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | /** |
| 508 | * Apply an individual configuration map to the data set object |
| 509 | * |
| 510 | * @param $context |
| 511 | * The context array for which to apply the map |
| 512 | * @param $map |
| 513 | * The configuration map to apply to the context array |
| 514 | * @return |
| 515 | * An array of referenced actions that resulted from this configuration map |
| 516 | */ |
| 517 | function configuration_apply_map(&$context, $map) { |
| 518 | foreach($map as $path => &$config) { |
| 519 | // TODO Make $path available in sub-functions for error reporting |
| 520 | // Get the matches for this path |
| 521 | $matches = configuration_fetch($path, $context, TRUE); |
| 522 | |
| 523 | while(!empty($matches)) { |
| 524 | // Get and drop off the first match from the matches array |
| 525 | $match = &$matches[0]; |
| 526 | array_shift($matches); |
| 527 | |
| 528 | // If this match is null (was unset somewhere) skip it |
| 529 | if (!isset($match)) { |
| 530 | continue; |
| 531 | } |
| 532 | |
| 533 | // Setup a list of properties to process in the right order |
| 534 | $properties = array_keys(array_intersect_key(configuration_map_properties(), $config)); |
| 535 | |
| 536 | // Loop through the properties list processing one at a time |
| 537 | foreach($properties as $property) { |
| 538 | $value = $config[$property]; |
| 539 | |
| 540 | // Process this property |
| 541 | configuration_context_process_property($property, $value, $match, $config); |
| 542 | |
| 543 | // Unset this property as we are done with it |
| 544 | unset($properties[$property]); |
| 545 | } |
| 546 | } |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | /** |
| 551 | * The default configuration map that is applied to all configuration |
| 552 | * context objects. For the time being all it does is make it easier |
| 553 | * to visualize the incoming configuration data and set up the module |
| 554 | * list. |
| 555 | * |
| 556 | * The original format of the incoming data is like: array( |
| 557 | * array( type => block, config => array( // config data ) ) |
| 558 | * array( type => block, config => array( // config data ) ) |
| 559 | * array( type => menu, config => array( // config data ) ) |
| 560 | * ) |
| 561 | * |
| 562 | * Applying a config path against an object like that would look like: |
| 563 | * /*[type=block]/config |
| 564 | * |
| 565 | * When in realitiy all that should be needed is: |
| 566 | * /block |
| 567 | * |
| 568 | * This is accomplished here by moving the config data a level above it |
| 569 | * and getting rid of the tag key from the data object completely. The |
| 570 | * tag key is set in the context object and is made to act as a secondary |
| 571 | * array key (the original is just an indexed number). |
| 572 | * |
| 573 | * The new configuration looks like: array( |
| 574 | * array( array( // config data ) ) // secondary_key=block |
| 575 | * array( array( // config data ) ) // secondary_key=block |
| 576 | * array( array( // config data ) ) // secondary_key=menu |
| 577 | * ) |
| 578 | */ |
| 579 | function configuration_default_map() { |
| 580 | return array( |
| 581 | '//*' => array('#value callback' => 'configuration_find_attributes'), // Convert tags starting with a # to attributes |
| 582 | '/*/tag' => array('#required' => true, '#context key' => true), |
| 583 | '/*/config' => array('#move' => '..'), |
| 584 | '/module' => array('#array' => true), // TODO This is temporary since the next item can't catch these matches after the #alias yet |
| 585 | '/modules' => array('#alias' => array('module'), '#array' => true), |
| 586 | '/modules/*' => array('#enable module' => true) |
| 587 | ); |
| 588 | } |
| 589 | |
| 590 | /** |
| 591 | * Return a list of supported config properties in the order they |
| 592 | * should be processed with their descriptions |
| 593 | * |
| 594 | * TODO Remove descriptions here as they could just be in documentation somewhere. |
| 595 | * TODO Allow modules to specify custom properties as well that would get sent to custom callbacks. Or add a #utility type property that handles a variety of functionality including module custom functionality. Such as a 'split' utitlity or in block modules case a custom function to split a module ID (block-3) into module=block and delta=3. |
| 596 | */ |
| 597 | function configuration_map_properties() { |
| 598 | return array( |
| 599 | '#include' => t('Include/require a file during the build process.'), |
| 600 | '#alias' => t('Allow a tag to have aliases for better readability.'), |
| 601 | '#default callback' => t('Set the default value if the tag is missing via a callback'), |
| 602 | '#default php' => t('Set the default value if the tag is missing via a php snippet'), |
| 603 | '#default path' => t('Set the default value if the tag is missing via a path to another item.'), |
| 604 | '#default value' => t('Set the default value if the tag is missing.'), |
| 605 | '#default' => t('Same as #default value'), |
| 606 | '#create' => t('Create the tag under this tag if it does not exist. Either the name of the tag can be set or both the name and the value like: array( name => value )'), |
| 607 | '#key' => t('The name of the tag will change to this when processed.'), |
| 608 | '#key callback' => t('The tag will change when processed to the result of the callback'), |
| 609 | '#key path' => t('The tag will change when processed to the value of the tag at the specified path in the data object. In case the path does not exist a default value can be specified by setting an array like: array( path => default )'), |
| 610 | '#key php' => t('The tag will change when processed to the value of the evaluted php.'), |
| 611 | '#array' => t('Mark that this data must be an array and make it so if it is not.'), |
| 612 | '#assoc' => t('Mark that this data must be an associative array. We cannot make it one if it is not.'), |
| 613 | '#move' => t('Move the tag to a different location specified by a path here. The end of the path will replace the current key.'), |
| 614 | '#copy' => t('Copy this tag to another location specified by a path here.'), |
| 615 | '#required' => t('Specify that this tag is required and must be specified.'), |
| 616 | '#context key' => t('Act as a secondary_key for that data object and will be matched against config paths.'), |
| 617 | '#value alias' => t('A key=>list array that determines possible aliases of certain values'), |
| 618 | '#value callback' => t('Apply a callback on a value. The returned value will become the new value.'), |
| 619 | '#value php' => t('Run a php snippet on the value here. The returned value will become the new value.'), |
| 620 | '#value path' => t('Set the value of this item to the item specified with the path.'), |
| 621 | '#value' => t('Set the value to what is specified here.'), |
| 622 | '#id callback' => t('Run a callback that returns an identifier for the action.'), |
| 623 | '#id php' => t('Evaluate php that returns an identifier for the action.'), |
| 624 | '#options' => t('An array of possible values for the tag. It must be one of them.'), |
| 625 | '#options callback' => t('An array of possible values for the tag as returned from the callback.'), |
| 626 | '#build callback' => t('The return of this callback will be used as the input data for the execution and will not replace the data in the $context object.'), |
| 627 | '#build php' => t('Use a php snippet to get the build data instead of the callback.'), |
| 628 | '#action' => t('Specify an action to be called on the corresponding data. This will be saved and run at the end of the execution process.'), |
| 629 | '#action callback' => t('A callback that will specify the action to be run here. This callback is run after previous actions are executed.'), |
| 630 | '#attribute' => t('A boolean value specifying wether this tag/value pair is an attribute. Child tags are not allowed with attributes. Attributes stay in the context object, but not in the actual data set.'), |
| 631 | '#enable module' => t('Add the module here to the enable modules list.'), |
| 632 | '#disable module' => t('Add the module here to the disable modules list.'), |
| 633 | '#delete' => t('Remove the key=>value from the object completely.') |
| 634 | ); |
| 635 | } |
| 636 | |
| 637 | /** |
| 638 | * Process a single mapping config property. Various properties will mark static data |
| 639 | * that will be processed later on in the configuration phases. |
| 640 | * |
| 641 | * @param $property |
| 642 | * The property needing processed |
| 643 | * @param $value |
| 644 | * The value of the property |
| 645 | * @param $context |
| 646 | * The context area of the data to process with this property |
| 647 | * @param $config |
| 648 | * The config this property=>value pair is a part of |
| 649 | */ |
| 650 | function configuration_context_process_property($property, $value, &$context, &$config = array()) { |
| 651 | $empty = $context->empty; |
| 652 | |
| 653 | // This is a list of properties that can take an empty match |
| 654 | $empty_properties = array('#alias', '#required', '#create', '#default', '#default callback', '#default php', '#default path', '#default value'); |
| 655 | |
| 656 | // This is a list of properties that do not modify the context object |
| 657 | // which will save any integrity checks later on |
| 658 | $no_modify_propertiers = array('#include', '#action', '#action callback', '#required', '#options', '#options callback'); |
| 659 | |
| 660 | |
| 661 | // If this is an empty context and not in the empty property list, return immediately |
| 662 | if ($empty && !in_array($property, $empty_properties)) { |
| 663 | return; |
| 664 | } |
| 665 | |
| 666 | switch ($property) { |
| 667 | case '#include': |
| 668 | $file = './'. $value; |
| 669 | if (!is_file($file)) { |
| 670 | configuration_set_error('pre-validate: include', array('!file' => $file)); |
| 671 | } |
| 672 | else { |
| 673 | @require_once $file; |
| 674 | $context->include = $file; |
| 675 | } |
| 676 | break; |
| 677 | case '#alias': |
| 678 | // TODO Figure out how to deal with the $matches array syncing with new matches here |
| 679 | if ($empty) { |
| 680 | if (is_scalar($value)) { |
| 681 | $value = array($value); |
| 682 | } |
| 683 | |
| 684 | foreach($value as $alias) { |
| 685 | // Find the alias, if it exists rename it |
| 686 | if (array_key_exists($alias, $context->parent->item)) { |
| 687 | // Find the alias context |
| 688 | for($i=0;isset($context->parent->children[$i]);$i++) { |
| 689 | $change = &$context->parent->children[$i]; |
| 690 | if ($change->key == $alias) { |
| 691 | configuration_context_process_property('#key', $context->key, $change, $config); |
| 692 | break(2); |
| 693 | } |
| 694 | } |
| 695 | } |
| 696 | } |
| 697 | } |
| 698 | |
| 699 | // Deal with secondary keys here. No need to check if it already exists |
| 700 | // since they can exist multiple times |
| 701 | for($i=0;isset($context->parent->children[$i]);$i++) { |
| 702 | $check=&$context->parent->children[$i]; |
| 703 | if (($pos = in_array($check->secondary_key, $value)) !== false) { |
| 704 | $check->secondary_key = $context->secondary_key; |
| 705 | } |
| 706 | } |
| 707 | break; |
| 708 | case '#default callback': |
| 709 | if ($empty) { |
| 710 | if (is_array($value)) { |
| 711 | $args = reset($value); |
| 712 | $value = key($value); |
| 713 | } |
| 714 | if (function_exists($value)) { |
| 715 | if (isset($args)) { |
| 716 | $context->parent->item[$context->key] = call_user_func_array($value, $args); |
| 717 | } |
| 718 | else { |
| 719 | $context->parent->item[$context->key] = $value($context); |
| 720 | } |
| 721 | $context->empty = false; |
| 722 | } |
| 723 | } |
| 724 | break; |
| 725 | case '#default php': |
| 726 | if ($empty) { |
| 727 | // Make sure it is set and then get the default and reset it to that |
| 728 | $context->parent->item[$context->key] = null; |
| 729 | $php = $value; |
| 730 | $value = &$context->parent->item[$context->key]; |
| 731 | $new = eval($php); |
| 732 | if (isset($new)) { |
| 733 | $context->parent->item[$context->key] = $new; |
| 734 | } |
| 735 | $context->empty = false; |
| 736 | } |
| 737 | break; |
| 738 | case '#default path': |
| 739 | if ($empty && ($matches = configuration_fetch($value, $context)) && count($matches) == 1) { |
| 740 | $context->parent->item[$context->key] = $matches[0]->item; |
| 741 | $context->empty = false; |
| 742 | } |
| 743 | break; |
| 744 | case '#default': |
| 745 | case '#default value': |
| 746 | case '#create': |
| 747 | if ($empty) { |
| 748 | // TODO Think about converting all other context keys to ->_ like ->_parent ->_item |
| 749 | if ($context->_attribute) { |
| 750 | $context->item = $value; |
| 751 | } |
| 752 | else { |
| 753 | $context->parent->item[$context->key] = $value; |
| 754 | $context->item = &$context->parent->item[$context->key]; |
| 755 | } |
| 756 | $context->empty = false; |
| 757 | } |
| 758 | break; |
| 759 | case '#key callback': |
| 760 | if (function_exists($value)) { |
| 761 | $value = $value($context->item); |
| 762 | } |
| 763 | else { |
| 764 | configuration_set_error(t('An invalid callback was specified: !callback', array('!callback' => $value))); |
| 765 | break; |
| 766 | } |
| 767 | case '#key path': |
| 768 | if ($property == '#key path') { |
| 769 | if (is_array($value)) { |
| 770 | $path = key($value); |
| 771 | $default = current($value); |
| 772 | } |
| 773 | else { |
| 774 | $path = $value; |
| 775 | } |
| 776 | $match = configuration_fetch($path, $context); |
| 777 | |
| 778 | if (count($match) == 1 && is_scalar($match[0]->item)) { |
| 779 | $value = $match[0]->item; |
| 780 | } |
| 781 | else if ($config['#key default']) { |
| 782 | $value = $config['#key default']; |
| 783 | } |
| 784 | else { |
| 785 | break; |
| 786 | } |
| 787 | } |
| 788 | case '#key php': |
| 789 | if ($property == '#key php') { |
| 790 | $value = eval($value); |
| 791 | } |
| 792 | break; |
| 793 | case '#key': |
| 794 | if (array_key_exists($value, $context->parent->item)) { |
| 795 | // Key exists already |
| 796 | // TODO Is it a problem that the key exists already? Quite possibly.... |
| 797 | break; |
| 798 | } |
| 799 | |
| 800 | $key = $context->key; |
| 801 | $context->parent->item[$value] = &$context->item; |
| 802 | |
| 803 | // We need to keep our current context object. check_context should fix the trace paths below |
| 804 | $context->key = $value; |
| 805 | |
| 806 | // A plain unset is sufficient even with indexed arrays because check_context will pick it up and fix |
| 807 | unset($context->parent->item[$key]); |
| 808 | break; |
| 809 | case '#array': |
| 810 | // TODO Figure out how to deal with associative vs indexed arrays |
| 811 | if (!is_array($context->item) || !array_key_exists(0, $context->item)) { |
| 812 | if (is_null($context->item) || (is_array($context->item && empty($context->item)))) { |
| 813 | $context->item = array(); |
| 814 | } |
| 815 | else { |
| 816 | $context->item = array($context->item); |
| 817 | } |
| 818 | } |
| 819 | break; |
| 820 | case '#assoc': |
| 821 | if (!is_array($value) || array_key_exists(0, $context->item)) { |
| 822 | configuration_set_error('assoc array'); |
| 823 | } |
| 824 | break; |
| 825 | // TODO Test both copy and move |
| 826 | case '#copy': |
| 827 | $destinations = configuration_fetch($value, $context); |
| 828 | |
| 829 | foreach($destinations as &$dest) { |
| 830 | // TODO We are making it impossible to overwrite an existing item. Maybe we should make it possible? This applies to #move too |
| 831 | if ($dest->empty) { |
| 832 | $dest->item = $context->item; |
| 833 | } |
| 834 | } |
| 835 | break; |
| 836 | case '#move': |
| 837 | // TODO Moving/Copying to the same array level as the current item can cause problems with the current matches set. Maybe make it impossible to do so or think of a solution. |
| 838 | $dest = configuration_fetch($value, $context); |
| 839 | |
| 840 | if (count($dest) == 1 && $dest = &$dest[0]) { |
| 841 | $parent = &$context->parent; |
| 842 | $key = $context->key; |
| 843 | |
| 844 | // Move the necessary dest info over here |
| 845 | foreach(array_diff(array_keys((array) $dest), array('children', 'item')) as $key) { |
| 846 | // TODO Is there room to do better memory cleanup here or does it not matter? |
| 847 | $context->$key = &$dest->$key; |
| 848 | } |
| 849 | |
| 850 | // Make sure the old child pointer points to the new context |
| 851 | for($i=0;isset($context->parent->children[$i]);$i++) { |
| 852 | $check = &$context->parent->children[$i]; |
| 853 | if ($check === $dest) { |
| 854 | $context->parent->children[$i] = &$context; |
| 855 | break; |
| 856 | } |
| 857 | } |
| 858 | |
| 859 | // Make sure the moved data exists in the right place of the original array |
| 860 | $context->parent->item[$context->key] = &$context->item; |
| 861 | |
| 862 | // Remove the item here. check_context will make sure contexts are removed |
| 863 | // We can't remove if we moved to this items parent |
| 864 | // TODO Make it work so you can move two parents up |
| 865 | if ($parent !== $dest) { |
| 866 | unset($parent->item[$key]); |
| 867 | } |
| 868 | |
| 869 | // Fix all the children trace paths |
| 870 | configuration_fix_context_trace($context); |
| 871 | } |
| 872 | case '#required': |
| 873 | if ($context->empty) { |
| 874 | $path = implode('/', array_merge($context->trace, array($context->key))); |
| 875 | configuration_set_error('pre-validate: required', array('!tag' => $path)); |
| 876 | } |
| 877 | else { |
| 878 | $context->required = true; |
| 879 | } |
| 880 | break; |
| 881 | case '#context key': |
| 882 | if (is_scalar($context->item)) { |
| 883 | $context->parent->secondary_key = $context->item; |
| 884 | } |
| 885 | break; |
| 886 | case '#attribute': |
| 887 | if (!is_scalar($context->item)) { |
| 888 | $path = implode('/', $context->trace); |
| 889 | configuration_set_error('pre-validate: attribute', array('!tag' => $path)); |
| 890 | } |
| 891 | else { |
| 892 | if ($context->key[0] == CONFIGURATION_ATTRIBUTE_KEY) { |
| 893 | $key = substr($context->key, 1); |
| 894 | } |
| 895 | else { |
| 896 | $key = $context->key; |
| 897 | } |
| 898 | if (!configuration_reserved_attribute($key)) { |
| 899 | $context->parent->{$key} = $context->item; |
| 900 | unset($context->parent->item[$context->key]); |
| 901 | } |
| 902 | } |
| 903 | break; |
| 904 | case '#options callback': |
| 905 | if (function_exists($value)) { |
| 906 | $value = $value($context); |
| 907 | } |
| 908 | case '#options': |
| 909 | if (is_scalar($value)) { |
| 910 | $value = array($value); |
| 911 | } |
| 912 | |
| 913 | if (!in_array($context->item, $value)) { |
| 914 | if (!($context->item === '' && !$context->required)) { |
| 915 | $path = implode('/', $context->trace); |
| 916 | configuration_set_error('pre-validate: options', array('!path' => $path, '!options' => implode(', ', $value))); |
| 917 | break; |
| 918 | } |
| 919 | } |
| 920 | case '#value alias': |
| 921 | foreach($value as $real => $aliases) { |
| 922 | foreach($aliases as $alias) { |
| 923 | if ($context->item == $alias) { |
| 924 | $context->item = $real; |
| 925 | break(2); |
| 926 | } |
| 927 | } |
| 928 | } |
| 929 | break; |
| 930 | case '#value callback': |
| 931 | if (function_exists($value)) { |
| 932 | $value($context->item, $context); |
| 933 | } |
| 934 | else { |
| 935 | configuration_set_error('value callback', array('@callback' => $value)); |
| 936 | } |
| 937 | break; |
| 938 | case '#value php': |
| 939 | $php = $value; |
| 940 | $value = &$context->item; |
| 941 | $new = eval($php); |
| 942 | if (isset($new)) { |
| 943 | $context->item = $new; |
| 944 | } |
| 945 | break; |
| 946 | case '#value path': |
| 947 | if (($matches = configuration_fetch($value, $context)) && count($matches) == 1) { |
| 948 | $context->item = $matches[0]->item; |
| 949 | } |
| 950 | break; |
| 951 | case '#value': |
| 952 | $context->item = $value; |
| 953 | break; |
| 954 | case '#id callback': |
| 955 | if (function_exists($value)) { |
| 956 | $identifiers =& configuration_get_data('identifiers'); |
| 957 | } |
| 958 | break; |
| 959 | case '#id php': |
| 960 | |
| 961 | break; |
| 962 | case '#id path': |
| 963 | |
| 964 | break; |
| 965 | // Not used/supported. |
| 966 | // TODO Remove? |
| 967 | case '#build callback': |
| 968 | if (function_exists($value)) { |
| 969 | $context->build_data = $value($context); |
| 970 | } |
| 971 | break; |
| 972 | case '#build php': |
| 973 | $context->build_data = eval($value); |
| 974 | break; |
| 975 | case '#action': |
| 976 | if (!is_array($value)) { |
| 977 | $value = array($value); |
| 978 | } |
| 979 | |
| 980 | $actions =& configuration_get_data('actions'); |
| 981 | foreach($value as $action) { |
| 982 | $action[] = array($action, null, &$context); |
| 983 | } |
| 984 | break; |
| 985 | case '#action callback': |
| 986 | if (function_exists($value)) { |
| 987 | $actions =& configuration_get_data('actions'); |
| 988 | $context->action_callback = $value; |
| 989 | $actions[] = array(null, &$context); |
| 990 | } |
| 991 | else { |
| 992 | configuration_set_error('missing action callback', array('@callback' => $value)); |
| 993 | } |
| 994 | break; |
| 995 | case '#enable module': |
| 996 | if (is_scalar($context->item)) { |
| 997 | $enable =& configuration_get_data('enable modules'); |
| 998 | $enable[] = $context->item; |
| 999 | } |
| 1000 | break; |
| 1001 | case '#disable module': |
| 1002 | if (is_scalar($context->item)) { |
| 1003 | $disable =& configuration_get_data('disable modules'); |
| 1004 | $disable[] = $context->item; |
| 1005 | } |
| 1006 | break; |
| 1007 | case '#delete': |
| 1008 | unset($context->parent->item[$context->key]); |
| 1009 | break; |
| 1010 | } |
| 1011 | |
| 1012 | // Update the context object to reflect any possible new changes |
| 1013 | if (!in_array($property, $no_modify_propertiers)) { |
| 1014 | configuration_check_context($context); |
| 1015 | } |
| 1016 | } |
| 1017 | |
| 1018 | /** |
| 1019 | * Do the actual drupal execute on an action |
| 1020 | */ |
| 1021 | function configuration_drupal_execute($form_id, &$form_state, $params) { |
| 1022 | // Make sure we always have a clear cache for everything |
| 1023 | $result = db_query('SHOW TABLES LIKE "cache_%"'); |
| 1024 | |
| 1025 | while ($table = db_fetch_array($result)) { |
| 1026 | $table = current($table); |
| 1027 | cache_clear_all(null, $table); |
| 1028 | } |
| 1029 | |
| 1030 | // Perform direct and automatic manipulation of the configuration data |
| 1031 | // from the default fapi form object |
| 1032 | configuration_sync_data_form($form_state['values'], $form_id, $form_state, $params); |
| 1033 | |
| 1034 | $args = array($form_id, &$form_state); |
| 1035 | |
| 1036 | if (is_array($params)) { |
| 1037 | $args = array_merge($args, $params); |
| 1038 | } |
| 1039 | |
| 1040 | configuration_set_data('executing', true); |
| 1041 | |
| 1042 | // If we are in batch mode, trick the form api to think |
| 1043 | // otherwise to avoid potential problems |
| 1044 | $batch =& batch_get(); |
| 1045 | $batch_clone = $batch; |
| 1046 | $batch = null; |
| 1047 | |
| 1048 | // drupal_execute fails to keep $form_state in-sync through the |
| 1049 | // whole FAPI process. Issue http://drupal.org/node/346244 |
| 1050 | //$return = call_user_func_array('drupal_execute', $args); |
| 1051 | |
| 1052 | // Copy of drupal_execute until above issue is fixed |
| 1053 | $form = call_user_func_array('drupal_retrieve_form', $args); |
| 1054 | |
| 1055 | $form['#post'] = $form_state['values']; |
| 1056 | drupal_prepare_form($form_id, $form, $form_state); |
| 1057 | |
| 1058 | // If you call drupal_validate_form() on the same form more |
| 1059 | // than once per page request, validation is not performed |
| 1060 | // on any but the first call. |
| 1061 | // see issue: http://drupal.org/node/260934 |
| 1062 | // drupal_process_form($form_id, $form, $form_state); |
| 1063 | |
| 1064 | // Until above issue is fixed we use our own implementation |
| 1065 | // of drupal_process_form() and drupal_validate_form(). |
| 1066 | _configuration_process_form($form_id, $form, $form_state); |
| 1067 | |
| 1068 | configuration_set_data('executing', true); |
| 1069 | $batch = $batch_clone; |
| 1070 | } |
| 1071 | |
| 1072 | /** |
| 1073 | * Based on a form api array, make automatic modifications |
| 1074 | * to the form submission data. |
| 1075 | * |
| 1076 | * What this can do: |
| 1077 | * |
| 1078 | * 1. Check all #options and try and match the data to them. Example of why this is needed: |
| 1079 | * |
| 1080 | * XML syntax |
| 1081 | * <types>page</types> |
| 1082 | * OR |
| 1083 | * <types> |
| 1084 | * <type>page</type> |
| 1085 | * <type>story</type> |
| 1086 | * </types> |
| 1087 | * |
| 1088 | * Original Results |
| 1089 | * array( |
| 1090 | * 'types' => 'page' |
| 1091 | * ) |
| 1092 | * OR |
| 1093 | * array( |
| 1094 | * 'types' => array( |
| 1095 | * 'type' => array('page', 'store') |
| 1096 | * ) |
| 1097 | * ) |
| 1098 | * |
| 1099 | * Desired Results |
| 1100 | * array( |
| 1101 | * 'types' => array( |
| 1102 | * 'page' => 'page' |
| 1103 | * ) |
| 1104 | * ) |
| 1105 | * OR |
| 1106 | * array( |
| 1107 | * 'types' => array( |
| 1108 | * 'page' => 'page', |
| 1109 | * 'story' => 'story' |
| 1110 | * ) |
| 1111 | * ) |
| 1112 | * |
| 1113 | * However in some cases the first example (<types>page</types>) would be in the form <type>page</type> without |
| 1114 | * the <types> parent and no need for the associative array. It would just be array('type' => 'page'). |
| 1115 | * |
| 1116 | * Based off of the form api #options, #tree, and #multiple values, it should be possible to convert |
| 1117 | * any of the above xml objects to the right form api object. |
| 1118 | */ |
| 1119 | function configuration_sync_data_form(&$data, $form_id, $form_state, $params = null) { |
| 1120 | unset($form_state['values']); |
| 1121 | $args = array($form_id, &$form_state); |
| 1122 | if (is_array($params)) { |
| 1123 | $args = array_merge($args, $params); |
| 1124 | } |
| 1125 | |
| 1126 | // Get the fully built fapi object |
| 1127 | $form = call_user_func_array('drupal_retrieve_form', $args); |
| 1128 | drupal_prepare_form($form_id, $form, $form_state); |
| 1129 | $form = form_builder($form_id, $form, $form_state); |
| 1130 | |
| 1131 | $context = configuration_build_context($data); |
| 1132 | $form = configuration_build_context($form); |
| 1133 | |
| 1134 | // TODO Should we remove build data that does not exist in the form object? |
| 1135 | if ($form_matches = configuration_fetch('//#options', $form)) { |
| 1136 | for($i=0;isset($form_matches[$i]);$i++) { |
| 1137 | $form_match = &$form_matches[$i]; |
| 1138 | |
| 1139 | // Check if we have this data |
| 1140 | $path = '/'. implode('/', $form_matches[$i]->parent->item['#parents']); |
| 1141 | if ($data_matches = configuration_fetch($path, $context)) { |
| 1142 | // Possibly manipulate our data to match these #options |
| 1143 | for($j=0;isset($data_matches[$j]);$j++) { |
| 1144 | $data_match = &$data_matches[$j]; |
| 1145 | |
| 1146 | // Determine if this is a singular/scalar or arrayed element |
| 1147 | $scalar = false; |
| 1148 | if (!$form_match->parent->item['#tree'] || (isset($form_match->parent->item['#multiple']) && !$form_match->parent->item['#multiple'])) { |
| 1149 | $scalar = true; |
| 1150 | } |
| 1151 | // Setup the new value |
| 1152 | // TODO Actually implement the update vs overwrite functionality as opposed to the 100% update (true) |
| 1153 | if (true && !$scalar && !empty($form_match->parent->item['#default_value'])) { |
| 1154 | // Default values are not key=>key arrays like the post values. They are |
| 1155 | // indexed arrays. The below should catch an indexed array as well as a singular |
| 1156 | // scalar default value and turn it into a key=>key array |
| 1157 | $values = (array)$form_match->parent->item['#default_value']; |
| 1158 | $values = array_combine($values, $values); |
| 1159 | } |
| 1160 | else { |
| 1161 | $values = array(); |
| 1162 | } |
| 1163 | foreach((array)$data_match->item as $value) { |
| 1164 | // If the supplied value matches an options label, use that option |
| 1165 | if ($key = array_search($value, $form_match->item)) { |
| 1166 | $values[$key] = $key; |
| 1167 | } |
| 1168 | // Or if the supplied value is an actual options value (not the label) use that |
| 1169 | else if (array_key_exists($value, $form_match->item)) { |
| 1170 | $values[$value] = $value; |
| 1171 | } |
| 1172 | } |
| 1173 | |
| 1174 | // If we are not on a multiple/tree form, the value should be singular/not an array |
| 1175 | if ($scalar) { |
| 1176 | $data_match->item = reset($values); |
| 1177 | } |
| 1178 | else { |
| 1179 | $data_match->item = $values; |
| 1180 | } |
| 1181 | } |
| 1182 | } |
| 1183 | } |
| 1184 | } |
| 1185 | } |
| 1186 | |
| 1187 | /** |
| 1188 | * Set the unique execution identifier for this configuration |
| 1189 | * |
| 1190 | * @param $id |
| 1191 | * The unique identifier for this execution run |
| 1192 | * @return |
| 1193 | * The currently set identifier |
| 1194 | */ |
| 1195 | function configuration_set_current_id($id = null) { |
| 1196 | static $current_id; |
| 1197 | |
| 1198 | if ($id) { |
| 1199 | $current_id = $id; |
| 1200 | } |
| 1201 | |
| 1202 | return $current_id; |
| 1203 | } |
| 1204 | |
| 1205 | /** |
| 1206 | * Get the current configuration execution identifier |
| 1207 | */ |
| 1208 | function configuration_get_current_id() { |
| 1209 | return configuration_set_current_id(); |
| 1210 | } |
| 1211 | |
| 1212 | /** |
| 1213 | * Set an error during configuration setup |
| 1214 | * |
| 1215 | * @param $type |
| 1216 | * The type of error being set. |
| 1217 | * @param $vars |
| 1218 | * Any variables that are associated with the particular error |
| 1219 | * @param $cache |
| 1220 | * This will set the static $errors cache |
| 1221 | * @return |
| 1222 | * If $type is set, the error(s) for that type are returned. If not the entire |
| 1223 | * static cache is returned. |
| 1224 | * //TODO Add a param to mark critical vs warning errors? |
| 1225 | */ |
| 1226 | function configuration_set_error($type = null, $vars = array()) { |
| 1227 | // Use the configuration data store instead of our own static cache here |
| 1228 | $errors =& configuration_set_data('errors'); |
| 1229 | |
| 1230 | if ($type) { |
| 1231 | $errors[$type][] = $vars; |
| 1232 | } |
| 1233 | |
| 1234 | if ($type) { |
| 1235 | return $errors[$type]; |
| 1236 | } |
| 1237 | else { |
| 1238 | return $errors; |
| 1239 | } |
| 1240 | } |
| 1241 | |
| 1242 | /** |
| 1243 | * Get any errors that have happened during configuration |
| 1244 | * |
| 1245 | * @param $type |
| 1246 | * The type of error being checked |
| 1247 | * @return |
| 1248 | * The variable data stored for that particular error, if there is an error |
| 1249 | */ |
| 1250 | function configuration_get_error($type = null, &$vars = null) { |
| 1251 | $vars = configuration_set_error($type); |
| 1252 | |
| 1253 | // TODO Problem here |
| 1254 | if (isset($vars)) { |
| 1255 | return true; |
| 1256 | } |
| 1257 | else { |
| 1258 | return false; |
| 1259 | } |
| 1260 | } |
| 1261 | |
| 1262 | /** |
| 1263 | * Set static data to be retrieved during later phases |
| 1264 | * |
| 1265 | * @param $type |
| 1266 | * The type of data being stored, such as 'phase', 'build', 'action', 'cleanup' etc... |
| 1267 | * @param $data |
| 1268 | * The actual data needing stored |
| 1269 | * @param $cache |
| 1270 | * Set the full static $store of data with this $cache |